##// END OF EJS Templates
merge 1.3 into stable
marcink -
r2031:82a88013 merge default
parent child Browse files
Show More
1 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
@@ -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)
@@ -0,0 +1,449 b''
1 # The code in this module is entirely lifted from the Lamson project
2 # (http://lamsonproject.org/). Its copyright is:
3
4 # Copyright (c) 2008, Zed A. Shaw
5 # All rights reserved.
6
7 # It is provided under this license:
8
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are met:
11
12 # * Redistributions of source code must retain the above copyright notice, this
13 # list of conditions and the following disclaimer.
14
15 # * Redistributions in binary form must reproduce the above copyright notice,
16 # this list of conditions and the following disclaimer in the documentation
17 # and/or other materials provided with the distribution.
18
19 # * Neither the name of the Zed A. Shaw nor the names of its contributors may
20 # be used to endorse or promote products derived from this software without
21 # specific prior written permission.
22
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 # POSSIBILITY OF SUCH DAMAGE.
35
36 import os
37 import mimetypes
38 import string
39 from email import encoders
40 from email.charset import Charset
41 from email.utils import parseaddr
42 from email.mime.base import MIMEBase
43
44 ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc']
45 DEFAULT_ENCODING = "utf-8"
46 VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v
47
48
49 def normalize_header(header):
50 return string.capwords(header.lower(), '-')
51
52
53 class EncodingError(Exception):
54 """Thrown when there is an encoding error."""
55 pass
56
57
58 class MailBase(object):
59 """MailBase is used as the basis of lamson.mail and contains the basics of
60 encoding an email. You actually can do all your email processing with this
61 class, but it's more raw.
62 """
63 def __init__(self, items=()):
64 self.headers = dict(items)
65 self.parts = []
66 self.body = None
67 self.content_encoding = {'Content-Type': (None, {}),
68 'Content-Disposition': (None, {}),
69 'Content-Transfer-Encoding': (None, {})}
70
71 def __getitem__(self, key):
72 return self.headers.get(normalize_header(key), None)
73
74 def __len__(self):
75 return len(self.headers)
76
77 def __iter__(self):
78 return iter(self.headers)
79
80 def __contains__(self, key):
81 return normalize_header(key) in self.headers
82
83 def __setitem__(self, key, value):
84 self.headers[normalize_header(key)] = value
85
86 def __delitem__(self, key):
87 del self.headers[normalize_header(key)]
88
89 def __nonzero__(self):
90 return self.body != None or len(self.headers) > 0 or len(self.parts) > 0
91
92 def keys(self):
93 """Returns the sorted keys."""
94 return sorted(self.headers.keys())
95
96 def attach_file(self, filename, data, ctype, disposition):
97 """
98 A file attachment is a raw attachment with a disposition that
99 indicates the file name.
100 """
101 assert filename, "You can't attach a file without a filename."
102 ctype = ctype.lower()
103
104 part = MailBase()
105 part.body = data
106 part.content_encoding['Content-Type'] = (ctype, {'name': filename})
107 part.content_encoding['Content-Disposition'] = (disposition,
108 {'filename': filename})
109 self.parts.append(part)
110
111 def attach_text(self, data, ctype):
112 """
113 This attaches a simpler text encoded part, which doesn't have a
114 filename.
115 """
116 ctype = ctype.lower()
117
118 part = MailBase()
119 part.body = data
120 part.content_encoding['Content-Type'] = (ctype, {})
121 self.parts.append(part)
122
123 def walk(self):
124 for p in self.parts:
125 yield p
126 for x in p.walk():
127 yield x
128
129
130 class MailResponse(object):
131 """
132 You are given MailResponse objects from the lamson.view methods, and
133 whenever you want to generate an email to send to someone. It has the
134 same basic functionality as MailRequest, but it is designed to be written
135 to, rather than read from (although you can do both).
136
137 You can easily set a Body or Html during creation or after by passing it
138 as __init__ parameters, or by setting those attributes.
139
140 You can initially set the From, To, and Subject, but they are headers so
141 use the dict notation to change them: msg['From'] = 'joe@test.com'.
142
143 The message is not fully crafted until right when you convert it with
144 MailResponse.to_message. This lets you change it and work with it, then
145 send it out when it's ready.
146 """
147 def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None,
148 separator="; "):
149 self.Body = Body
150 self.Html = Html
151 self.base = MailBase([('To', To), ('From', From), ('Subject', Subject)])
152 self.multipart = self.Body and self.Html
153 self.attachments = []
154 self.separator = separator
155
156 def __contains__(self, key):
157 return self.base.__contains__(key)
158
159 def __getitem__(self, key):
160 return self.base.__getitem__(key)
161
162 def __setitem__(self, key, val):
163 return self.base.__setitem__(key, val)
164
165 def __delitem__(self, name):
166 del self.base[name]
167
168 def attach(self, filename=None, content_type=None, data=None,
169 disposition=None):
170 """
171
172 Simplifies attaching files from disk or data as files. To attach
173 simple text simple give data and a content_type. To attach a file,
174 give the data/content_type/filename/disposition combination.
175
176 For convenience, if you don't give data and only a filename, then it
177 will read that file's contents when you call to_message() later. If
178 you give data and filename then it will assume you've filled data
179 with what the file's contents are and filename is just the name to
180 use.
181 """
182
183 assert filename or data, ("You must give a filename or some data to "
184 "attach.")
185 assert data or os.path.exists(filename), ("File doesn't exist, and no "
186 "data given.")
187
188 self.multipart = True
189
190 if filename and not content_type:
191 content_type, encoding = mimetypes.guess_type(filename)
192
193 assert content_type, ("No content type given, and couldn't guess "
194 "from the filename: %r" % filename)
195
196 self.attachments.append({'filename': filename,
197 'content_type': content_type,
198 'data': data,
199 'disposition': disposition,})
200
201 def attach_part(self, part):
202 """
203 Attaches a raw MailBase part from a MailRequest (or anywhere)
204 so that you can copy it over.
205 """
206 self.multipart = True
207
208 self.attachments.append({'filename': None,
209 'content_type': None,
210 'data': None,
211 'disposition': None,
212 'part': part,
213 })
214
215 def attach_all_parts(self, mail_request):
216 """
217 Used for copying the attachment parts of a mail.MailRequest
218 object for mailing lists that need to maintain attachments.
219 """
220 for part in mail_request.all_parts():
221 self.attach_part(part)
222
223 self.base.content_encoding = mail_request.base.content_encoding.copy()
224
225 def clear(self):
226 """
227 Clears out the attachments so you can redo them. Use this to keep the
228 headers for a series of different messages with different attachments.
229 """
230 del self.attachments[:]
231 del self.base.parts[:]
232 self.multipart = False
233
234 def update(self, message):
235 """
236 Used to easily set a bunch of heading from another dict
237 like object.
238 """
239 for k in message.keys():
240 self.base[k] = message[k]
241
242 def __str__(self):
243 """
244 Converts to a string.
245 """
246 return self.to_message().as_string()
247
248 def _encode_attachment(self, filename=None, content_type=None, data=None,
249 disposition=None, part=None):
250 """
251 Used internally to take the attachments mentioned in self.attachments
252 and do the actual encoding in a lazy way when you call to_message.
253 """
254 if part:
255 self.base.parts.append(part)
256 elif filename:
257 if not data:
258 data = open(filename).read()
259
260 self.base.attach_file(filename, data, content_type,
261 disposition or 'attachment')
262 else:
263 self.base.attach_text(data, content_type)
264
265 ctype = self.base.content_encoding['Content-Type'][0]
266
267 if ctype and not ctype.startswith('multipart'):
268 self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
269
270 def to_message(self):
271 """
272 Figures out all the required steps to finally craft the
273 message you need and return it. The resulting message
274 is also available as a self.base attribute.
275
276 What is returned is a Python email API message you can
277 use with those APIs. The self.base attribute is the raw
278 lamson.encoding.MailBase.
279 """
280 del self.base.parts[:]
281
282 if self.Body and self.Html:
283 self.multipart = True
284 self.base.content_encoding['Content-Type'] = (
285 'multipart/alternative', {})
286
287 if self.multipart:
288 self.base.body = None
289 if self.Body:
290 self.base.attach_text(self.Body, 'text/plain')
291
292 if self.Html:
293 self.base.attach_text(self.Html, 'text/html')
294
295 for args in self.attachments:
296 self._encode_attachment(**args)
297
298 elif self.Body:
299 self.base.body = self.Body
300 self.base.content_encoding['Content-Type'] = ('text/plain', {})
301
302 elif self.Html:
303 self.base.body = self.Html
304 self.base.content_encoding['Content-Type'] = ('text/html', {})
305
306 return to_message(self.base, separator=self.separator)
307
308 def all_parts(self):
309 """
310 Returns all the encoded parts. Only useful for debugging
311 or inspecting after calling to_message().
312 """
313 return self.base.parts
314
315 def keys(self):
316 return self.base.keys()
317
318
319 def to_message(mail, separator="; "):
320 """
321 Given a MailBase message, this will construct a MIMEPart
322 that is canonicalized for use with the Python email API.
323 """
324 ctype, params = mail.content_encoding['Content-Type']
325
326 if not ctype:
327 if mail.parts:
328 ctype = 'multipart/mixed'
329 else:
330 ctype = 'text/plain'
331 else:
332 if mail.parts:
333 assert ctype.startswith(("multipart", "message")), \
334 "Content type should be multipart or message, not %r" % ctype
335
336 # adjust the content type according to what it should be now
337 mail.content_encoding['Content-Type'] = (ctype, params)
338
339 try:
340 out = MIMEPart(ctype, **params)
341 except TypeError, exc: # pragma: no cover
342 raise EncodingError("Content-Type malformed, not allowed: %r; "
343 "%r (Python ERROR: %s" %
344 (ctype, params, exc.message))
345
346 for k in mail.keys():
347 if k in ADDRESS_HEADERS_WHITELIST:
348 out[k.encode('ascii')] = header_to_mime_encoding(
349 mail[k],
350 not_email=False,
351 separator=separator
352 )
353 else:
354 out[k.encode('ascii')] = header_to_mime_encoding(
355 mail[k],
356 not_email=True
357 )
358
359 out.extract_payload(mail)
360
361 # go through the children
362 for part in mail.parts:
363 out.attach(to_message(part))
364
365 return out
366
367 class MIMEPart(MIMEBase):
368 """
369 A reimplementation of nearly everything in email.mime to be more useful
370 for actually attaching things. Rather than one class for every type of
371 thing you'd encode, there's just this one, and it figures out how to
372 encode what you ask it.
373 """
374 def __init__(self, type, **params):
375 self.maintype, self.subtype = type.split('/')
376 MIMEBase.__init__(self, self.maintype, self.subtype, **params)
377
378 def add_text(self, content):
379 # this is text, so encode it in canonical form
380 try:
381 encoded = content.encode('ascii')
382 charset = 'ascii'
383 except UnicodeError:
384 encoded = content.encode('utf-8')
385 charset = 'utf-8'
386
387 self.set_payload(encoded, charset=charset)
388
389 def extract_payload(self, mail):
390 if mail.body == None: return # only None, '' is still ok
391
392 ctype, ctype_params = mail.content_encoding['Content-Type']
393 cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
394
395 assert ctype, ("Extract payload requires that mail.content_encoding "
396 "have a valid Content-Type.")
397
398 if ctype.startswith("text/"):
399 self.add_text(mail.body)
400 else:
401 if cdisp:
402 # replicate the content-disposition settings
403 self.add_header('Content-Disposition', cdisp, **cdisp_params)
404
405 self.set_payload(mail.body)
406 encoders.encode_base64(self)
407
408 def __repr__(self):
409 return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
410 self.subtype,
411 self.maintype,
412 self['Content-Type'],
413 self['Content-Disposition'],
414 self.is_multipart())
415
416
417 def header_to_mime_encoding(value, not_email=False, separator=", "):
418 if not value: return ""
419
420 encoder = Charset(DEFAULT_ENCODING)
421 if type(value) == list:
422 return separator.join(properly_encode_header(
423 v, encoder, not_email) for v in value)
424 else:
425 return properly_encode_header(value, encoder, not_email)
426
427 def properly_encode_header(value, encoder, not_email):
428 """
429 The only thing special (weird) about this function is that it tries
430 to do a fast check to see if the header value has an email address in
431 it. Since random headers could have an email address, and email addresses
432 have weird special formatting rules, we have to check for it.
433
434 Normally this works fine, but in Librelist, we need to "obfuscate" email
435 addresses by changing the '@' to '-AT-'. This is where
436 VALUE_IS_EMAIL_ADDRESS exists. It's a simple lambda returning True/False
437 to check if a header value has an email address. If you need to make this
438 check different, then change this.
439 """
440 try:
441 return value.encode("ascii")
442 except UnicodeEncodeError:
443 if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
444 # this could have an email address, make sure we don't screw it up
445 name, address = parseaddr(value)
446 return '"%s" <%s>' % (
447 encoder.header_encode(name.encode("utf-8")), address)
448
449 return encoder.header_encode(value.encode("utf-8"))
@@ -0,0 +1,41 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs
4 ~~~
5
6 Various version Control System (vcs) management abstraction layer for
7 Python.
8
9 :created_on: Apr 8, 2010
10 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
11 """
12
13 VERSION = (0, 2, 3, 'dev')
14
15 __version__ = '.'.join((str(each) for each in VERSION[:4]))
16
17 __all__ = [
18 'get_version', 'get_repo', 'get_backend',
19 'VCSError', 'RepositoryError', 'ChangesetError']
20
21 import sys
22 from rhodecode.lib.vcs.backends import get_repo, get_backend
23 from rhodecode.lib.vcs.exceptions import VCSError, RepositoryError, ChangesetError
24
25
26 def get_version():
27 """
28 Returns shorter version (digit parts only) as string.
29 """
30 return '.'.join((str(each) for each in VERSION[:3]))
31
32 def main(argv=None):
33 if argv is None:
34 argv = sys.argv
35 from rhodecode.lib.vcs.cli import ExecutionManager
36 manager = ExecutionManager(argv)
37 manager.execute()
38 return 0
39
40 if __name__ == '__main__':
41 sys.exit(main(sys.argv))
@@ -0,0 +1,63 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs.backends
4 ~~~~~~~~~~~~
5
6 Main package for scm backends
7
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
11 import os
12 from pprint import pformat
13 from rhodecode.lib.vcs.conf import settings
14 from rhodecode.lib.vcs.exceptions import VCSError
15 from rhodecode.lib.vcs.utils.helpers import get_scm
16 from rhodecode.lib.vcs.utils.paths import abspath
17 from rhodecode.lib.vcs.utils.imports import import_class
18
19
20 def get_repo(path=None, alias=None, create=False):
21 """
22 Returns ``Repository`` object of type linked with given ``alias`` at
23 the specified ``path``. If ``alias`` is not given it will try to guess it
24 using get_scm method
25 """
26 if create:
27 if not (path or alias):
28 raise TypeError("If create is specified, we need path and scm type")
29 return get_backend(alias)(path, create=True)
30 if path is None:
31 path = abspath(os.path.curdir)
32 try:
33 scm, path = get_scm(path, search_recursively=True)
34 path = abspath(path)
35 alias = scm
36 except VCSError:
37 raise VCSError("No scm found at %s" % path)
38 if alias is None:
39 alias = get_scm(path)[0]
40
41 backend = get_backend(alias)
42 repo = backend(path, create=create)
43 return repo
44
45
46 def get_backend(alias):
47 """
48 Returns ``Repository`` class identified by the given alias or raises
49 VCSError if alias is not recognized or backend class cannot be imported.
50 """
51 if alias not in settings.BACKENDS:
52 raise VCSError("Given alias '%s' is not recognized! Allowed aliases:\n"
53 "%s" % (alias, pformat(settings.BACKENDS.keys())))
54 backend_path = settings.BACKENDS[alias]
55 klass = import_class(backend_path)
56 return klass
57
58
59 def get_supported_backends():
60 """
61 Returns list of aliases of supported backends.
62 """
63 return settings.BACKENDS.keys()
This diff has been collapsed as it changes many lines, (911 lines changed) Show them Hide them
@@ -0,0 +1,911 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs.backends.base
4 ~~~~~~~~~~~~~~~~~
5
6 Base for all available scm backends
7
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
11
12
13 from itertools import chain
14 from rhodecode.lib.vcs.utils import author_name, author_email
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
17 from rhodecode.lib.vcs.conf import settings
18
19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
22 RepositoryError
23
24
25 class BaseRepository(object):
26 """
27 Base Repository for final backends
28
29 **Attributes**
30
31 ``DEFAULT_BRANCH_NAME``
32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
33
34 ``scm``
35 alias of scm, i.e. *git* or *hg*
36
37 ``repo``
38 object from external api
39
40 ``revisions``
41 list of all available revisions' ids, in ascending order
42
43 ``changesets``
44 storage dict caching returned changesets
45
46 ``path``
47 absolute path to the repository
48
49 ``branches``
50 branches as list of changesets
51
52 ``tags``
53 tags as list of changesets
54 """
55 scm = None
56 DEFAULT_BRANCH_NAME = None
57 EMPTY_CHANGESET = '0' * 40
58
59 def __init__(self, repo_path, create=False, **kwargs):
60 """
61 Initializes repository. Raises RepositoryError if repository could
62 not be find at the given ``repo_path`` or directory at ``repo_path``
63 exists and ``create`` is set to True.
64
65 :param repo_path: local path of the repository
66 :param create=False: if set to True, would try to craete repository.
67 :param src_url=None: if set, should be proper url from which repository
68 would be cloned; requires ``create`` parameter to be set to True -
69 raises RepositoryError if src_url is set and create evaluates to
70 False
71 """
72 raise NotImplementedError
73
74 def __str__(self):
75 return '<%s at %s>' % (self.__class__.__name__, self.path)
76
77 def __repr__(self):
78 return self.__str__()
79
80 def __len__(self):
81 return self.count()
82
83 @LazyProperty
84 def alias(self):
85 for k, v in settings.BACKENDS.items():
86 if v.split('.')[-1] == str(self.__class__.__name__):
87 return k
88
89 @LazyProperty
90 def name(self):
91 raise NotImplementedError
92
93 @LazyProperty
94 def owner(self):
95 raise NotImplementedError
96
97 @LazyProperty
98 def description(self):
99 raise NotImplementedError
100
101 @LazyProperty
102 def size(self):
103 """
104 Returns combined size in bytes for all repository files
105 """
106
107 size = 0
108 try:
109 tip = self.get_changeset()
110 for topnode, dirs, files in tip.walk('/'):
111 for f in files:
112 size += tip.get_file_size(f.path)
113 for dir in dirs:
114 for f in files:
115 size += tip.get_file_size(f.path)
116
117 except RepositoryError, e:
118 pass
119 return size
120
121 def is_valid(self):
122 """
123 Validates repository.
124 """
125 raise NotImplementedError
126
127 def get_last_change(self):
128 self.get_changesets()
129
130 #==========================================================================
131 # CHANGESETS
132 #==========================================================================
133
134 def get_changeset(self, revision=None):
135 """
136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
137 recent changeset is returned.
138
139 :raises ``EmptyRepositoryError``: if there are no revisions
140 """
141 raise NotImplementedError
142
143 def __iter__(self):
144 """
145 Allows Repository objects to be iterated.
146
147 *Requires* implementation of ``__getitem__`` method.
148 """
149 for revision in self.revisions:
150 yield self.get_changeset(revision)
151
152 def get_changesets(self, start=None, end=None, start_date=None,
153 end_date=None, branch_name=None, reverse=False):
154 """
155 Returns iterator of ``MercurialChangeset`` objects from start to end
156 not inclusive This should behave just like a list, ie. end is not
157 inclusive
158
159 :param start: None or str
160 :param end: None or str
161 :param start_date:
162 :param end_date:
163 :param branch_name:
164 :param reversed:
165 """
166 raise NotImplementedError
167
168 def __getslice__(self, i, j):
169 """
170 Returns a iterator of sliced repository
171 """
172 for rev in self.revisions[i:j]:
173 yield self.get_changeset(rev)
174
175 def __getitem__(self, key):
176 return self.get_changeset(key)
177
178 def count(self):
179 return len(self.revisions)
180
181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
182 """
183 Creates and returns a tag for the given ``revision``.
184
185 :param name: name for new tag
186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
187 :param revision: changeset id for which new tag would be created
188 :param message: message of the tag's commit
189 :param date: date of tag's commit
190
191 :raises TagAlreadyExistError: if tag with same name already exists
192 """
193 raise NotImplementedError
194
195 def remove_tag(self, name, user, message=None, date=None):
196 """
197 Removes tag with the given ``name``.
198
199 :param name: name of the tag to be removed
200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
201 :param message: message of the tag's removal commit
202 :param date: date of tag's removal commit
203
204 :raises TagDoesNotExistError: if tag with given name does not exists
205 """
206 raise NotImplementedError
207
208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
209 context=3):
210 """
211 Returns (git like) *diff*, as plain text. Shows changes introduced by
212 ``rev2`` since ``rev1``.
213
214 :param rev1: Entry point from which diff is shown. Can be
215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
216 the changes since empty state of the repository until ``rev2``
217 :param rev2: Until which revision changes should be shown.
218 :param ignore_whitespace: If set to ``True``, would not show whitespace
219 changes. Defaults to ``False``.
220 :param context: How many lines before/after changed lines should be
221 shown. Defaults to ``3``.
222 """
223 raise NotImplementedError
224
225 # ========== #
226 # COMMIT API #
227 # ========== #
228
229 @LazyProperty
230 def in_memory_changeset(self):
231 """
232 Returns ``InMemoryChangeset`` object for this repository.
233 """
234 raise NotImplementedError
235
236 def add(self, filenode, **kwargs):
237 """
238 Commit api function that will add given ``FileNode`` into this
239 repository.
240
241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
242 already in repository
243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
244 *added*
245 """
246 raise NotImplementedError
247
248 def remove(self, filenode, **kwargs):
249 """
250 Commit api function that will remove given ``FileNode`` into this
251 repository.
252
253 :raises ``EmptyRepositoryError``: if there are no changesets yet
254 :raises ``NodeDoesNotExistError``: if there is no file with given path
255 """
256 raise NotImplementedError
257
258 def commit(self, message, **kwargs):
259 """
260 Persists current changes made on this repository and returns newly
261 created changeset.
262
263 :raises ``NothingChangedError``: if no changes has been made
264 """
265 raise NotImplementedError
266
267 def get_state(self):
268 """
269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
270 containing ``FileNode`` objects.
271 """
272 raise NotImplementedError
273
274 def get_config_value(self, section, name, config_file=None):
275 """
276 Returns configuration value for a given [``section``] and ``name``.
277
278 :param section: Section we want to retrieve value from
279 :param name: Name of configuration we want to retrieve
280 :param config_file: A path to file which should be used to retrieve
281 configuration from (might also be a list of file paths)
282 """
283 raise NotImplementedError
284
285 def get_user_name(self, config_file=None):
286 """
287 Returns user's name from global configuration file.
288
289 :param config_file: A path to file which should be used to retrieve
290 configuration from (might also be a list of file paths)
291 """
292 raise NotImplementedError
293
294 def get_user_email(self, config_file=None):
295 """
296 Returns user's email from global configuration file.
297
298 :param config_file: A path to file which should be used to retrieve
299 configuration from (might also be a list of file paths)
300 """
301 raise NotImplementedError
302
303 # =========== #
304 # WORKDIR API #
305 # =========== #
306
307 @LazyProperty
308 def workdir(self):
309 """
310 Returns ``Workdir`` instance for this repository.
311 """
312 raise NotImplementedError
313
314
315 class BaseChangeset(object):
316 """
317 Each backend should implement it's changeset representation.
318
319 **Attributes**
320
321 ``repository``
322 repository object within which changeset exists
323
324 ``id``
325 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
326
327 ``raw_id``
328 raw changeset representation (i.e. full 40 length sha for git
329 backend)
330
331 ``short_id``
332 shortened (if apply) version of ``raw_id``; it would be simple
333 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
334 as ``raw_id`` for subversion
335
336 ``revision``
337 revision number as integer
338
339 ``files``
340 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
341
342 ``dirs``
343 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
344
345 ``nodes``
346 combined list of ``Node`` objects
347
348 ``author``
349 author of the changeset, as unicode
350
351 ``message``
352 message of the changeset, as unicode
353
354 ``parents``
355 list of parent changesets
356
357 ``last``
358 ``True`` if this is last changeset in repository, ``False``
359 otherwise; trying to access this attribute while there is no
360 changesets would raise ``EmptyRepositoryError``
361 """
362 def __str__(self):
363 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
364 self.short_id)
365
366 def __repr__(self):
367 return self.__str__()
368
369 def __unicode__(self):
370 return u'%s:%s' % (self.revision, self.short_id)
371
372 def __eq__(self, other):
373 return self.raw_id == other.raw_id
374
375 @LazyProperty
376 def last(self):
377 if self.repository is None:
378 raise ChangesetError("Cannot check if it's most recent revision")
379 return self.raw_id == self.repository.revisions[-1]
380
381 @LazyProperty
382 def parents(self):
383 """
384 Returns list of parents changesets.
385 """
386 raise NotImplementedError
387
388 @LazyProperty
389 def id(self):
390 """
391 Returns string identifying this changeset.
392 """
393 raise NotImplementedError
394
395 @LazyProperty
396 def raw_id(self):
397 """
398 Returns raw string identifying this changeset.
399 """
400 raise NotImplementedError
401
402 @LazyProperty
403 def short_id(self):
404 """
405 Returns shortened version of ``raw_id`` attribute, as string,
406 identifying this changeset, useful for web representation.
407 """
408 raise NotImplementedError
409
410 @LazyProperty
411 def revision(self):
412 """
413 Returns integer identifying this changeset.
414
415 """
416 raise NotImplementedError
417
418 @LazyProperty
419 def author(self):
420 """
421 Returns Author for given commit
422 """
423
424 raise NotImplementedError
425
426 @LazyProperty
427 def author_name(self):
428 """
429 Returns Author name for given commit
430 """
431
432 return author_name(self.author)
433
434 @LazyProperty
435 def author_email(self):
436 """
437 Returns Author email address for given commit
438 """
439
440 return author_email(self.author)
441
442 def get_file_mode(self, path):
443 """
444 Returns stat mode of the file at the given ``path``.
445 """
446 raise NotImplementedError
447
448 def get_file_content(self, path):
449 """
450 Returns content of the file at the given ``path``.
451 """
452 raise NotImplementedError
453
454 def get_file_size(self, path):
455 """
456 Returns size of the file at the given ``path``.
457 """
458 raise NotImplementedError
459
460 def get_file_changeset(self, path):
461 """
462 Returns last commit of the file at the given ``path``.
463 """
464 raise NotImplementedError
465
466 def get_file_history(self, path):
467 """
468 Returns history of file as reversed list of ``Changeset`` objects for
469 which file at given ``path`` has been modified.
470 """
471 raise NotImplementedError
472
473 def get_nodes(self, path):
474 """
475 Returns combined ``DirNode`` and ``FileNode`` objects list representing
476 state of changeset at the given ``path``.
477
478 :raises ``ChangesetError``: if node at the given ``path`` is not
479 instance of ``DirNode``
480 """
481 raise NotImplementedError
482
483 def get_node(self, path):
484 """
485 Returns ``Node`` object from the given ``path``.
486
487 :raises ``NodeDoesNotExistError``: if there is no node at the given
488 ``path``
489 """
490 raise NotImplementedError
491
492 def fill_archive(self, stream=None, kind='tgz', prefix=None):
493 """
494 Fills up given stream.
495
496 :param stream: file like object.
497 :param kind: one of following: ``zip``, ``tar``, ``tgz``
498 or ``tbz2``. Default: ``tgz``.
499 :param prefix: name of root directory in archive.
500 Default is repository name and changeset's raw_id joined with dash.
501
502 repo-tip.<kind>
503 """
504
505 raise NotImplementedError
506
507 def get_chunked_archive(self, **kwargs):
508 """
509 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
510
511 :param chunk_size: extra parameter which controls size of returned
512 chunks. Default:8k.
513 """
514
515 chunk_size = kwargs.pop('chunk_size', 8192)
516 stream = kwargs.get('stream')
517 self.fill_archive(**kwargs)
518 while True:
519 data = stream.read(chunk_size)
520 if not data:
521 break
522 yield data
523
524 @LazyProperty
525 def root(self):
526 """
527 Returns ``RootNode`` object for this changeset.
528 """
529 return self.get_node('')
530
531 def next(self, branch=None):
532 """
533 Returns next changeset from current, if branch is gives it will return
534 next changeset belonging to this branch
535
536 :param branch: show changesets within the given named branch
537 """
538 raise NotImplementedError
539
540 def prev(self, branch=None):
541 """
542 Returns previous changeset from current, if branch is gives it will
543 return previous changeset belonging to this branch
544
545 :param branch: show changesets within the given named branch
546 """
547 raise NotImplementedError
548
549 @LazyProperty
550 def added(self):
551 """
552 Returns list of added ``FileNode`` objects.
553 """
554 raise NotImplementedError
555
556 @LazyProperty
557 def changed(self):
558 """
559 Returns list of modified ``FileNode`` objects.
560 """
561 raise NotImplementedError
562
563 @LazyProperty
564 def removed(self):
565 """
566 Returns list of removed ``FileNode`` objects.
567 """
568 raise NotImplementedError
569
570 @LazyProperty
571 def size(self):
572 """
573 Returns total number of bytes from contents of all filenodes.
574 """
575 return sum((node.size for node in self.get_filenodes_generator()))
576
577 def walk(self, topurl=''):
578 """
579 Similar to os.walk method. Insted of filesystem it walks through
580 changeset starting at given ``topurl``. Returns generator of tuples
581 (topnode, dirnodes, filenodes).
582 """
583 topnode = self.get_node(topurl)
584 yield (topnode, topnode.dirs, topnode.files)
585 for dirnode in topnode.dirs:
586 for tup in self.walk(dirnode.path):
587 yield tup
588
589 def get_filenodes_generator(self):
590 """
591 Returns generator that yields *all* file nodes.
592 """
593 for topnode, dirs, files in self.walk():
594 for node in files:
595 yield node
596
597 def as_dict(self):
598 """
599 Returns dictionary with changeset's attributes and their values.
600 """
601 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
602 'revision', 'date', 'message'])
603 data['author'] = {'name': self.author_name, 'email': self.author_email}
604 data['added'] = [node.path for node in self.added]
605 data['changed'] = [node.path for node in self.changed]
606 data['removed'] = [node.path for node in self.removed]
607 return data
608
609
610 class BaseWorkdir(object):
611 """
612 Working directory representation of single repository.
613
614 :attribute: repository: repository object of working directory
615 """
616
617 def __init__(self, repository):
618 self.repository = repository
619
620 def get_branch(self):
621 """
622 Returns name of current branch.
623 """
624 raise NotImplementedError
625
626 def get_changeset(self):
627 """
628 Returns current changeset.
629 """
630 raise NotImplementedError
631
632 def get_added(self):
633 """
634 Returns list of ``FileNode`` objects marked as *new* in working
635 directory.
636 """
637 raise NotImplementedError
638
639 def get_changed(self):
640 """
641 Returns list of ``FileNode`` objects *changed* in working directory.
642 """
643 raise NotImplementedError
644
645 def get_removed(self):
646 """
647 Returns list of ``RemovedFileNode`` objects marked as *removed* in
648 working directory.
649 """
650 raise NotImplementedError
651
652 def get_untracked(self):
653 """
654 Returns list of ``FileNode`` objects which are present within working
655 directory however are not tracked by repository.
656 """
657 raise NotImplementedError
658
659 def get_status(self):
660 """
661 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
662 lists.
663 """
664 raise NotImplementedError
665
666 def commit(self, message, **kwargs):
667 """
668 Commits local (from working directory) changes and returns newly
669 created
670 ``Changeset``. Updates repository's ``revisions`` list.
671
672 :raises ``CommitError``: if any error occurs while committing
673 """
674 raise NotImplementedError
675
676 def update(self, revision=None):
677 """
678 Fetches content of the given revision and populates it within working
679 directory.
680 """
681 raise NotImplementedError
682
683 def checkout_branch(self, branch=None):
684 """
685 Checks out ``branch`` or the backend's default branch.
686
687 Raises ``BranchDoesNotExistError`` if the branch does not exist.
688 """
689 raise NotImplementedError
690
691
692 class BaseInMemoryChangeset(object):
693 """
694 Represents differences between repository's state (most recent head) and
695 changes made *in place*.
696
697 **Attributes**
698
699 ``repository``
700 repository object for this in-memory-changeset
701
702 ``added``
703 list of ``FileNode`` objects marked as *added*
704
705 ``changed``
706 list of ``FileNode`` objects marked as *changed*
707
708 ``removed``
709 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
710 *removed*
711
712 ``parents``
713 list of ``Changeset`` representing parents of in-memory changeset.
714 Should always be 2-element sequence.
715
716 """
717
718 def __init__(self, repository):
719 self.repository = repository
720 self.added = []
721 self.changed = []
722 self.removed = []
723 self.parents = []
724
725 def add(self, *filenodes):
726 """
727 Marks given ``FileNode`` objects as *to be committed*.
728
729 :raises ``NodeAlreadyExistsError``: if node with same path exists at
730 latest changeset
731 :raises ``NodeAlreadyAddedError``: if node with same path is already
732 marked as *added*
733 """
734 # Check if not already marked as *added* first
735 for node in filenodes:
736 if node.path in (n.path for n in self.added):
737 raise NodeAlreadyAddedError("Such FileNode %s is already "
738 "marked for addition" % node.path)
739 for node in filenodes:
740 self.added.append(node)
741
742 def change(self, *filenodes):
743 """
744 Marks given ``FileNode`` objects to be *changed* in next commit.
745
746 :raises ``EmptyRepositoryError``: if there are no changesets yet
747 :raises ``NodeAlreadyExistsError``: if node with same path is already
748 marked to be *changed*
749 :raises ``NodeAlreadyRemovedError``: if node with same path is already
750 marked to be *removed*
751 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
752 changeset
753 :raises ``NodeNotChangedError``: if node hasn't really be changed
754 """
755 for node in filenodes:
756 if node.path in (n.path for n in self.removed):
757 raise NodeAlreadyRemovedError("Node at %s is already marked "
758 "as removed" % node.path)
759 try:
760 self.repository.get_changeset()
761 except EmptyRepositoryError:
762 raise EmptyRepositoryError("Nothing to change - try to *add* new "
763 "nodes rather than changing them")
764 for node in filenodes:
765 if node.path in (n.path for n in self.changed):
766 raise NodeAlreadyChangedError("Node at '%s' is already "
767 "marked as changed" % node.path)
768 self.changed.append(node)
769
770 def remove(self, *filenodes):
771 """
772 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
773 *removed* in next commit.
774
775 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
776 be *removed*
777 :raises ``NodeAlreadyChangedError``: if node has been already marked to
778 be *changed*
779 """
780 for node in filenodes:
781 if node.path in (n.path for n in self.removed):
782 raise NodeAlreadyRemovedError("Node is already marked to "
783 "for removal at %s" % node.path)
784 if node.path in (n.path for n in self.changed):
785 raise NodeAlreadyChangedError("Node is already marked to "
786 "be changed at %s" % node.path)
787 # We only mark node as *removed* - real removal is done by
788 # commit method
789 self.removed.append(node)
790
791 def reset(self):
792 """
793 Resets this instance to initial state (cleans ``added``, ``changed``
794 and ``removed`` lists).
795 """
796 self.added = []
797 self.changed = []
798 self.removed = []
799 self.parents = []
800
801 def get_ipaths(self):
802 """
803 Returns generator of paths from nodes marked as added, changed or
804 removed.
805 """
806 for node in chain(self.added, self.changed, self.removed):
807 yield node.path
808
809 def get_paths(self):
810 """
811 Returns list of paths from nodes marked as added, changed or removed.
812 """
813 return list(self.get_ipaths())
814
815 def check_integrity(self, parents=None):
816 """
817 Checks in-memory changeset's integrity. Also, sets parents if not
818 already set.
819
820 :raises CommitError: if any error occurs (i.e.
821 ``NodeDoesNotExistError``).
822 """
823 if not self.parents:
824 parents = parents or []
825 if len(parents) == 0:
826 try:
827 parents = [self.repository.get_changeset(), None]
828 except EmptyRepositoryError:
829 parents = [None, None]
830 elif len(parents) == 1:
831 parents += [None]
832 self.parents = parents
833
834 # Local parents, only if not None
835 parents = [p for p in self.parents if p]
836
837 # Check nodes marked as added
838 for p in parents:
839 for node in self.added:
840 try:
841 p.get_node(node.path)
842 except NodeDoesNotExistError:
843 pass
844 else:
845 raise NodeAlreadyExistsError("Node at %s already exists "
846 "at %s" % (node.path, p))
847
848 # Check nodes marked as changed
849 missing = set(self.changed)
850 not_changed = set(self.changed)
851 if self.changed and not parents:
852 raise NodeDoesNotExistError(str(self.changed[0].path))
853 for p in parents:
854 for node in self.changed:
855 try:
856 old = p.get_node(node.path)
857 missing.remove(node)
858 if old.content != node.content:
859 not_changed.remove(node)
860 except NodeDoesNotExistError:
861 pass
862 if self.changed and missing:
863 raise NodeDoesNotExistError("Node at %s is missing "
864 "(parents: %s)" % (node.path, parents))
865
866 if self.changed and not_changed:
867 raise NodeNotChangedError("Node at %s wasn't actually changed "
868 "since parents' changesets: %s" % (not_changed.pop().path,
869 parents)
870 )
871
872 # Check nodes marked as removed
873 if self.removed and not parents:
874 raise NodeDoesNotExistError("Cannot remove node at %s as there "
875 "were no parents specified" % self.removed[0].path)
876 really_removed = set()
877 for p in parents:
878 for node in self.removed:
879 try:
880 p.get_node(node.path)
881 really_removed.add(node)
882 except ChangesetError:
883 pass
884 not_removed = set(self.removed) - really_removed
885 if not_removed:
886 raise NodeDoesNotExistError("Cannot remove node at %s from "
887 "following parents: %s" % (not_removed[0], parents))
888
889 def commit(self, message, author, parents=None, branch=None, date=None,
890 **kwargs):
891 """
892 Performs in-memory commit (doesn't check workdir in any way) and
893 returns newly created ``Changeset``. Updates repository's
894 ``revisions``.
895
896 .. note::
897 While overriding this method each backend's should call
898 ``self.check_integrity(parents)`` in the first place.
899
900 :param message: message of the commit
901 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
902 :param parents: single parent or sequence of parents from which commit
903 would be derieved
904 :param date: ``datetime.datetime`` instance. Defaults to
905 ``datetime.datetime.now()``.
906 :param branch: branch name, as string. If none given, default backend's
907 branch would be used.
908
909 :raises ``CommitError``: if any error occurs while committing
910 """
911 raise NotImplementedError
@@ -0,0 +1,9 b''
1 from .repository import GitRepository
2 from .changeset import GitChangeset
3 from .inmemory import GitInMemoryChangeset
4 from .workdir import GitWorkdir
5
6
7 __all__ = [
8 'GitRepository', 'GitChangeset', 'GitInMemoryChangeset', 'GitWorkdir',
9 ]
@@ -0,0 +1,450 b''
1 import re
2 from itertools import chain
3 from dulwich import objects
4 from subprocess import Popen, PIPE
5 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.exceptions import RepositoryError
7 from rhodecode.lib.vcs.exceptions import ChangesetError
8 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
9 from rhodecode.lib.vcs.exceptions import VCSError
10 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
11 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
12 from rhodecode.lib.vcs.backends.base import BaseChangeset
13 from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, RemovedFileNode
14 from rhodecode.lib.vcs.utils import safe_unicode
15 from rhodecode.lib.vcs.utils import date_fromtimestamp
16 from rhodecode.lib.vcs.utils.lazy import LazyProperty
17
18
19 class GitChangeset(BaseChangeset):
20 """
21 Represents state of the repository at single revision.
22 """
23
24 def __init__(self, repository, revision):
25 self._stat_modes = {}
26 self.repository = repository
27 self.raw_id = revision
28 self.revision = repository.revisions.index(revision)
29
30 self.short_id = self.raw_id[:12]
31 self.id = self.raw_id
32 try:
33 commit = self.repository._repo.get_object(self.raw_id)
34 except KeyError:
35 raise RepositoryError("Cannot get object with id %s" % self.raw_id)
36 self._commit = commit
37 self._tree_id = commit.tree
38
39 try:
40 self.message = safe_unicode(commit.message[:-1])
41 # Always strip last eol
42 except UnicodeDecodeError:
43 self.message = commit.message[:-1].decode(commit.encoding
44 or 'utf-8')
45 #self.branch = None
46 self.tags = []
47 #tree = self.repository.get_object(self._tree_id)
48 self.nodes = {}
49 self._paths = {}
50
51 @LazyProperty
52 def author(self):
53 return safe_unicode(self._commit.committer)
54
55 @LazyProperty
56 def date(self):
57 return date_fromtimestamp(self._commit.commit_time,
58 self._commit.commit_timezone)
59
60 @LazyProperty
61 def status(self):
62 """
63 Returns modified, added, removed, deleted files for current changeset
64 """
65 return self.changed, self.added, self.removed
66
67 @LazyProperty
68 def branch(self):
69 # TODO: Cache as we walk (id <-> branch name mapping)
70 refs = self.repository._repo.get_refs()
71 heads = [(key[len('refs/heads/'):], val) for key, val in refs.items()
72 if key.startswith('refs/heads/')]
73
74 for name, id in heads:
75 walker = self.repository._repo.object_store.get_graph_walker([id])
76 while True:
77 id = walker.next()
78 if not id:
79 break
80 if id == self.id:
81 return safe_unicode(name)
82 raise ChangesetError("This should not happen... Have you manually "
83 "change id of the changeset?")
84
85 def _fix_path(self, path):
86 """
87 Paths are stored without trailing slash so we need to get rid off it if
88 needed.
89 """
90 if path.endswith('/'):
91 path = path.rstrip('/')
92 return path
93
94 def _get_id_for_path(self, path):
95 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
96 if not path in self._paths:
97 path = path.strip('/')
98 # set root tree
99 tree = self.repository._repo[self._commit.tree]
100 if path == '':
101 self._paths[''] = tree.id
102 return tree.id
103 splitted = path.split('/')
104 dirs, name = splitted[:-1], splitted[-1]
105 curdir = ''
106 for dir in dirs:
107 if curdir:
108 curdir = '/'.join((curdir, dir))
109 else:
110 curdir = dir
111 #if curdir in self._paths:
112 ## This path have been already traversed
113 ## Update tree and continue
114 #tree = self.repository._repo[self._paths[curdir]]
115 #continue
116 dir_id = None
117 for item, stat, id in tree.iteritems():
118 if curdir:
119 item_path = '/'.join((curdir, item))
120 else:
121 item_path = item
122 self._paths[item_path] = id
123 self._stat_modes[item_path] = stat
124 if dir == item:
125 dir_id = id
126 if dir_id:
127 # Update tree
128 tree = self.repository._repo[dir_id]
129 if not isinstance(tree, objects.Tree):
130 raise ChangesetError('%s is not a directory' % curdir)
131 else:
132 raise ChangesetError('%s have not been found' % curdir)
133 for item, stat, id in tree.iteritems():
134 if curdir:
135 name = '/'.join((curdir, item))
136 else:
137 name = item
138 self._paths[name] = id
139 self._stat_modes[name] = stat
140 if not path in self._paths:
141 raise NodeDoesNotExistError("There is no file nor directory "
142 "at the given path %r at revision %r"
143 % (path, self.short_id))
144 return self._paths[path]
145
146 def _get_kind(self, path):
147 id = self._get_id_for_path(path)
148 obj = self.repository._repo[id]
149 if isinstance(obj, objects.Blob):
150 return NodeKind.FILE
151 elif isinstance(obj, objects.Tree):
152 return NodeKind.DIR
153
154 def _get_file_nodes(self):
155 return chain(*(t[2] for t in self.walk()))
156
157 @LazyProperty
158 def parents(self):
159 """
160 Returns list of parents changesets.
161 """
162 return [self.repository.get_changeset(parent)
163 for parent in self._commit.parents]
164
165 def next(self, branch=None):
166
167 if branch and self.branch != branch:
168 raise VCSError('Branch option used on changeset not belonging '
169 'to that branch')
170
171 def _next(changeset, branch):
172 try:
173 next_ = changeset.revision + 1
174 next_rev = changeset.repository.revisions[next_]
175 except IndexError:
176 raise ChangesetDoesNotExistError
177 cs = changeset.repository.get_changeset(next_rev)
178
179 if branch and branch != cs.branch:
180 return _next(cs, branch)
181
182 return cs
183
184 return _next(self, branch)
185
186 def prev(self, branch=None):
187 if branch and self.branch != branch:
188 raise VCSError('Branch option used on changeset not belonging '
189 'to that branch')
190
191 def _prev(changeset, branch):
192 try:
193 prev_ = changeset.revision - 1
194 if prev_ < 0:
195 raise IndexError
196 prev_rev = changeset.repository.revisions[prev_]
197 except IndexError:
198 raise ChangesetDoesNotExistError
199
200 cs = changeset.repository.get_changeset(prev_rev)
201
202 if branch and branch != cs.branch:
203 return _prev(cs, branch)
204
205 return cs
206
207 return _prev(self, branch)
208
209 def get_file_mode(self, path):
210 """
211 Returns stat mode of the file at the given ``path``.
212 """
213 # ensure path is traversed
214 self._get_id_for_path(path)
215 return self._stat_modes[path]
216
217 def get_file_content(self, path):
218 """
219 Returns content of the file at given ``path``.
220 """
221 id = self._get_id_for_path(path)
222 blob = self.repository._repo[id]
223 return blob.as_pretty_string()
224
225 def get_file_size(self, path):
226 """
227 Returns size of the file at given ``path``.
228 """
229 id = self._get_id_for_path(path)
230 blob = self.repository._repo[id]
231 return blob.raw_length()
232
233 def get_file_changeset(self, path):
234 """
235 Returns last commit of the file at the given ``path``.
236 """
237 node = self.get_node(path)
238 return node.history[0]
239
240 def get_file_history(self, path):
241 """
242 Returns history of file as reversed list of ``Changeset`` objects for
243 which file at given ``path`` has been modified.
244
245 TODO: This function now uses os underlying 'git' and 'grep' commands
246 which is generally not good. Should be replaced with algorithm
247 iterating commits.
248 """
249 cmd = 'log --name-status -p %s -- "%s" | grep "^commit"' \
250 % (self.id, path)
251 so, se = self.repository.run_git_command(cmd)
252 ids = re.findall(r'\w{40}', so)
253 return [self.repository.get_changeset(id) for id in ids]
254
255 def get_file_annotate(self, path):
256 """
257 Returns a list of three element tuples with lineno,changeset and line
258
259 TODO: This function now uses os underlying 'git' command which is
260 generally not good. Should be replaced with algorithm iterating
261 commits.
262 """
263 cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
264 # -l ==> outputs long shas (and we need all 40 characters)
265 # --root ==> doesn't put '^' character for bounderies
266 # -r sha ==> blames for the given revision
267 so, se = self.repository.run_git_command(cmd)
268 annotate = []
269 for i, blame_line in enumerate(so.split('\n')[:-1]):
270 ln_no = i + 1
271 id, line = re.split(r' \(.+?\) ', blame_line, 1)
272 annotate.append((ln_no, self.repository.get_changeset(id), line))
273 return annotate
274
275 def fill_archive(self, stream=None, kind='tgz', prefix=None,
276 subrepos=False):
277 """
278 Fills up given stream.
279
280 :param stream: file like object.
281 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
282 Default: ``tgz``.
283 :param prefix: name of root directory in archive.
284 Default is repository name and changeset's raw_id joined with dash
285 (``repo-tip.<KIND>``).
286 :param subrepos: include subrepos in this archive.
287
288 :raise ImproperArchiveTypeError: If given kind is wrong.
289 :raise VcsError: If given stream is None
290
291 """
292 allowed_kinds = settings.ARCHIVE_SPECS.keys()
293 if kind not in allowed_kinds:
294 raise ImproperArchiveTypeError('Archive kind not supported use one'
295 'of %s', allowed_kinds)
296
297 if prefix is None:
298 prefix = '%s-%s' % (self.repository.name, self.short_id)
299 elif prefix.startswith('/'):
300 raise VCSError("Prefix cannot start with leading slash")
301 elif prefix.strip() == '':
302 raise VCSError("Prefix cannot be empty")
303
304 if kind == 'zip':
305 frmt = 'zip'
306 else:
307 frmt = 'tar'
308 cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
309 self.raw_id)
310 if kind == 'tgz':
311 cmd += ' | gzip -9'
312 elif kind == 'tbz2':
313 cmd += ' | bzip2 -9'
314
315 if stream is None:
316 raise VCSError('You need to pass in a valid stream for filling'
317 ' with archival data')
318 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
319 cwd=self.repository.path)
320
321 buffer_size = 1024 * 8
322 chunk = popen.stdout.read(buffer_size)
323 while chunk:
324 stream.write(chunk)
325 chunk = popen.stdout.read(buffer_size)
326 # Make sure all descriptors would be read
327 popen.communicate()
328
329 def get_nodes(self, path):
330 if self._get_kind(path) != NodeKind.DIR:
331 raise ChangesetError("Directory does not exist for revision %r at "
332 " %r" % (self.revision, path))
333 path = self._fix_path(path)
334 id = self._get_id_for_path(path)
335 tree = self.repository._repo[id]
336 dirnodes = []
337 filenodes = []
338 for name, stat, id in tree.iteritems():
339 obj = self.repository._repo.get_object(id)
340 if path != '':
341 obj_path = '/'.join((path, name))
342 else:
343 obj_path = name
344 if obj_path not in self._stat_modes:
345 self._stat_modes[obj_path] = stat
346 if isinstance(obj, objects.Tree):
347 dirnodes.append(DirNode(obj_path, changeset=self))
348 elif isinstance(obj, objects.Blob):
349 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
350 else:
351 raise ChangesetError("Requested object should be Tree "
352 "or Blob, is %r" % type(obj))
353 nodes = dirnodes + filenodes
354 for node in nodes:
355 if not node.path in self.nodes:
356 self.nodes[node.path] = node
357 nodes.sort()
358 return nodes
359
360 def get_node(self, path):
361 if isinstance(path, unicode):
362 path = path.encode('utf-8')
363 path = self._fix_path(path)
364 if not path in self.nodes:
365 try:
366 id = self._get_id_for_path(path)
367 except ChangesetError:
368 raise NodeDoesNotExistError("Cannot find one of parents' "
369 "directories for a given path: %s" % path)
370 obj = self.repository._repo.get_object(id)
371 if isinstance(obj, objects.Tree):
372 if path == '':
373 node = RootNode(changeset=self)
374 else:
375 node = DirNode(path, changeset=self)
376 node._tree = obj
377 elif isinstance(obj, objects.Blob):
378 node = FileNode(path, changeset=self)
379 node._blob = obj
380 else:
381 raise NodeDoesNotExistError("There is no file nor directory "
382 "at the given path %r at revision %r"
383 % (path, self.short_id))
384 # cache node
385 self.nodes[path] = node
386 return self.nodes[path]
387
388 @LazyProperty
389 def affected_files(self):
390 """
391 Get's a fast accessible file changes for given changeset
392 """
393
394 return self.added + self.changed
395
396 @LazyProperty
397 def _diff_name_status(self):
398 output = []
399 for parent in self.parents:
400 cmd = 'diff --name-status %s %s' % (parent.raw_id, self.raw_id)
401 so, se = self.repository.run_git_command(cmd)
402 output.append(so.strip())
403 return '\n'.join(output)
404
405 def _get_paths_for_status(self, status):
406 """
407 Returns sorted list of paths for given ``status``.
408
409 :param status: one of: *added*, *modified* or *deleted*
410 """
411 paths = set()
412 char = status[0].upper()
413 for line in self._diff_name_status.splitlines():
414 if not line:
415 continue
416 if line.startswith(char):
417 splitted = line.split(char,1)
418 if not len(splitted) == 2:
419 raise VCSError("Couldn't parse diff result:\n%s\n\n and "
420 "particularly that line: %s" % (self._diff_name_status,
421 line))
422 paths.add(splitted[1].strip())
423 return sorted(paths)
424
425 @LazyProperty
426 def added(self):
427 """
428 Returns list of added ``FileNode`` objects.
429 """
430 if not self.parents:
431 return list(self._get_file_nodes())
432 return [self.get_node(path) for path in self._get_paths_for_status('added')]
433
434 @LazyProperty
435 def changed(self):
436 """
437 Returns list of modified ``FileNode`` objects.
438 """
439 if not self.parents:
440 return []
441 return [self.get_node(path) for path in self._get_paths_for_status('modified')]
442
443 @LazyProperty
444 def removed(self):
445 """
446 Returns list of removed ``FileNode`` objects.
447 """
448 if not self.parents:
449 return []
450 return [RemovedFileNode(path) for path in self._get_paths_for_status('deleted')]
@@ -0,0 +1,347 b''
1 # config.py - Reading and writing Git config files
2 # Copyright (C) 2011 Jelmer Vernooij <jelmer@samba.org>
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # of the License or (at your option) a later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # MA 02110-1301, USA.
18
19 """Reading and writing Git configuration files.
20
21 TODO:
22 * preserve formatting when updating configuration files
23 * treat subsection names as case-insensitive for [branch.foo] style
24 subsections
25 """
26
27 # Taken from dulwich not yet released 0.8.3 version (until it is actually
28 # released)
29
30 import errno
31 import os
32 import re
33
34 from dulwich.file import GitFile
35
36
37 class Config(object):
38 """A Git configuration."""
39
40 def get(self, section, name):
41 """Retrieve the contents of a configuration setting.
42
43 :param section: Tuple with section name and optional subsection namee
44 :param subsection: Subsection name
45 :return: Contents of the setting
46 :raise KeyError: if the value is not set
47 """
48 raise NotImplementedError(self.get)
49
50 def get_boolean(self, section, name, default=None):
51 """Retrieve a configuration setting as boolean.
52
53 :param section: Tuple with section name and optional subsection namee
54 :param name: Name of the setting, including section and possible
55 subsection.
56 :return: Contents of the setting
57 :raise KeyError: if the value is not set
58 """
59 try:
60 value = self.get(section, name)
61 except KeyError:
62 return default
63 if value.lower() == "true":
64 return True
65 elif value.lower() == "false":
66 return False
67 raise ValueError("not a valid boolean string: %r" % value)
68
69 def set(self, section, name, value):
70 """Set a configuration value.
71
72 :param name: Name of the configuration value, including section
73 and optional subsection
74 :param: Value of the setting
75 """
76 raise NotImplementedError(self.set)
77
78
79 class ConfigDict(Config):
80 """Git configuration stored in a dictionary."""
81
82 def __init__(self, values=None):
83 """Create a new ConfigDict."""
84 if values is None:
85 values = {}
86 self._values = values
87
88 def __repr__(self):
89 return "%s(%r)" % (self.__class__.__name__, self._values)
90
91 def __eq__(self, other):
92 return (
93 isinstance(other, self.__class__) and
94 other._values == self._values)
95
96 @classmethod
97 def _parse_setting(cls, name):
98 parts = name.split(".")
99 if len(parts) == 3:
100 return (parts[0], parts[1], parts[2])
101 else:
102 return (parts[0], None, parts[1])
103
104 def get(self, section, name):
105 if isinstance(section, basestring):
106 section = (section, )
107 if len(section) > 1:
108 try:
109 return self._values[section][name]
110 except KeyError:
111 pass
112 return self._values[(section[0],)][name]
113
114 def set(self, section, name, value):
115 if isinstance(section, basestring):
116 section = (section, )
117 self._values.setdefault(section, {})[name] = value
118
119
120 def _format_string(value):
121 if (value.startswith(" ") or
122 value.startswith("\t") or
123 value.endswith(" ") or
124 value.endswith("\t")):
125 return '"%s"' % _escape_value(value)
126 return _escape_value(value)
127
128
129 def _parse_string(value):
130 value = value.strip()
131 ret = []
132 block = []
133 in_quotes = False
134 for c in value:
135 if c == "\"":
136 in_quotes = (not in_quotes)
137 ret.append(_unescape_value("".join(block)))
138 block = []
139 elif c in ("#", ";") and not in_quotes:
140 # the rest of the line is a comment
141 break
142 else:
143 block.append(c)
144
145 if in_quotes:
146 raise ValueError("value starts with quote but lacks end quote")
147
148 ret.append(_unescape_value("".join(block)).rstrip())
149
150 return "".join(ret)
151
152
153 def _unescape_value(value):
154 """Unescape a value."""
155 def unescape(c):
156 return {
157 "\\\\": "\\",
158 "\\\"": "\"",
159 "\\n": "\n",
160 "\\t": "\t",
161 "\\b": "\b",
162 }[c.group(0)]
163 return re.sub(r"(\\.)", unescape, value)
164
165
166 def _escape_value(value):
167 """Escape a value."""
168 return value.replace("\\", "\\\\").replace("\n", "\\n")\
169 .replace("\t", "\\t").replace("\"", "\\\"")
170
171
172 def _check_variable_name(name):
173 for c in name:
174 if not c.isalnum() and c != '-':
175 return False
176 return True
177
178
179 def _check_section_name(name):
180 for c in name:
181 if not c.isalnum() and c not in ('-', '.'):
182 return False
183 return True
184
185
186 def _strip_comments(line):
187 line = line.split("#")[0]
188 line = line.split(";")[0]
189 return line
190
191
192 class ConfigFile(ConfigDict):
193 """A Git configuration file, like .git/config or ~/.gitconfig.
194 """
195
196 @classmethod
197 def from_file(cls, f):
198 """Read configuration from a file-like object."""
199 ret = cls()
200 section = None
201 setting = None
202 for lineno, line in enumerate(f.readlines()):
203 line = line.lstrip()
204 if setting is None:
205 if _strip_comments(line).strip() == "":
206 continue
207 if line[0] == "[":
208 line = _strip_comments(line).rstrip()
209 if line[-1] != "]":
210 raise ValueError("expected trailing ]")
211 key = line.strip()
212 pts = key[1:-1].split(" ", 1)
213 pts[0] = pts[0].lower()
214 if len(pts) == 2:
215 if pts[1][0] != "\"" or pts[1][-1] != "\"":
216 raise ValueError(
217 "Invalid subsection " + pts[1])
218 else:
219 pts[1] = pts[1][1:-1]
220 if not _check_section_name(pts[0]):
221 raise ValueError("invalid section name %s" %
222 pts[0])
223 section = (pts[0], pts[1])
224 else:
225 if not _check_section_name(pts[0]):
226 raise ValueError("invalid section name %s" %
227 pts[0])
228 pts = pts[0].split(".", 1)
229 if len(pts) == 2:
230 section = (pts[0], pts[1])
231 else:
232 section = (pts[0], )
233 ret._values[section] = {}
234 else:
235 if section is None:
236 raise ValueError("setting %r without section" % line)
237 try:
238 setting, value = line.split("=", 1)
239 except ValueError:
240 setting = line
241 value = "true"
242 setting = setting.strip().lower()
243 if not _check_variable_name(setting):
244 raise ValueError("invalid variable name %s" % setting)
245 if value.endswith("\\\n"):
246 value = value[:-2]
247 continuation = True
248 else:
249 continuation = False
250 value = _parse_string(value)
251 ret._values[section][setting] = value
252 if not continuation:
253 setting = None
254 else: # continuation line
255 if line.endswith("\\\n"):
256 line = line[:-2]
257 continuation = True
258 else:
259 continuation = False
260 value = _parse_string(line)
261 ret._values[section][setting] += value
262 if not continuation:
263 setting = None
264 return ret
265
266 @classmethod
267 def from_path(cls, path):
268 """Read configuration from a file on disk."""
269 f = GitFile(path, 'rb')
270 try:
271 ret = cls.from_file(f)
272 ret.path = path
273 return ret
274 finally:
275 f.close()
276
277 def write_to_path(self, path=None):
278 """Write configuration to a file on disk."""
279 if path is None:
280 path = self.path
281 f = GitFile(path, 'wb')
282 try:
283 self.write_to_file(f)
284 finally:
285 f.close()
286
287 def write_to_file(self, f):
288 """Write configuration to a file-like object."""
289 for section, values in self._values.iteritems():
290 try:
291 section_name, subsection_name = section
292 except ValueError:
293 (section_name, ) = section
294 subsection_name = None
295 if subsection_name is None:
296 f.write("[%s]\n" % section_name)
297 else:
298 f.write("[%s \"%s\"]\n" % (section_name, subsection_name))
299 for key, value in values.iteritems():
300 f.write("%s = %s\n" % (key, _escape_value(value)))
301
302
303 class StackedConfig(Config):
304 """Configuration which reads from multiple config files.."""
305
306 def __init__(self, backends, writable=None):
307 self.backends = backends
308 self.writable = writable
309
310 def __repr__(self):
311 return "<%s for %r>" % (self.__class__.__name__, self.backends)
312
313 @classmethod
314 def default_backends(cls):
315 """Retrieve the default configuration.
316
317 This will look in the repository configuration (if for_path is
318 specified), the users' home directory and the system
319 configuration.
320 """
321 paths = []
322 paths.append(os.path.expanduser("~/.gitconfig"))
323 paths.append("/etc/gitconfig")
324 backends = []
325 for path in paths:
326 try:
327 cf = ConfigFile.from_path(path)
328 except (IOError, OSError), e:
329 if e.errno != errno.ENOENT:
330 raise
331 else:
332 continue
333 backends.append(cf)
334 return backends
335
336 def get(self, section, name):
337 for backend in self.backends:
338 try:
339 return backend.get(section, name)
340 except KeyError:
341 pass
342 raise KeyError(name)
343
344 def set(self, section, name, value):
345 if self.writable is None:
346 raise NotImplementedError(self.set)
347 return self.writable.set(section, name, value)
@@ -0,0 +1,192 b''
1 import time
2 import datetime
3 import posixpath
4 from dulwich import objects
5 from dulwich.repo import Repo
6 from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
7 from rhodecode.lib.vcs.exceptions import RepositoryError
8
9
10 class GitInMemoryChangeset(BaseInMemoryChangeset):
11
12 def commit(self, message, author, parents=None, branch=None, date=None,
13 **kwargs):
14 """
15 Performs in-memory commit (doesn't check workdir in any way) and
16 returns newly created ``Changeset``. Updates repository's
17 ``revisions``.
18
19 :param message: message of the commit
20 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
21 :param parents: single parent or sequence of parents from which commit
22 would be derieved
23 :param date: ``datetime.datetime`` instance. Defaults to
24 ``datetime.datetime.now()``.
25 :param branch: branch name, as string. If none given, default backend's
26 branch would be used.
27
28 :raises ``CommitError``: if any error occurs while committing
29 """
30 self.check_integrity(parents)
31
32 from .repository import GitRepository
33 if branch is None:
34 branch = GitRepository.DEFAULT_BRANCH_NAME
35
36 repo = self.repository._repo
37 object_store = repo.object_store
38
39 ENCODING = "UTF-8"
40 DIRMOD = 040000
41
42 # Create tree and populates it with blobs
43 commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\
44 objects.Tree()
45 for node in self.added + self.changed:
46 # Compute subdirs if needed
47 dirpath, nodename = posixpath.split(node.path)
48 dirnames = dirpath and dirpath.split('/') or []
49 parent = commit_tree
50 ancestors = [('', parent)]
51
52 # Tries to dig for the deepest existing tree
53 while dirnames:
54 curdir = dirnames.pop(0)
55 try:
56 dir_id = parent[curdir][1]
57 except KeyError:
58 # put curdir back into dirnames and stops
59 dirnames.insert(0, curdir)
60 break
61 else:
62 # If found, updates parent
63 parent = self.repository._repo[dir_id]
64 ancestors.append((curdir, parent))
65 # Now parent is deepest exising tree and we need to create subtrees
66 # for dirnames (in reverse order) [this only applies for nodes from added]
67 new_trees = []
68 blob = objects.Blob.from_string(node.content.encode(ENCODING))
69 node_path = node.name.encode(ENCODING)
70 if dirnames:
71 # If there are trees which should be created we need to build
72 # them now (in reverse order)
73 reversed_dirnames = list(reversed(dirnames))
74 curtree = objects.Tree()
75 curtree[node_path] = node.mode, blob.id
76 new_trees.append(curtree)
77 for dirname in reversed_dirnames[:-1]:
78 newtree = objects.Tree()
79 #newtree.add(DIRMOD, dirname, curtree.id)
80 newtree[dirname] = DIRMOD, curtree.id
81 new_trees.append(newtree)
82 curtree = newtree
83 parent[reversed_dirnames[-1]] = DIRMOD, curtree.id
84 else:
85 parent.add(node.mode, node_path, blob.id)
86 new_trees.append(parent)
87 # Update ancestors
88 for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in
89 zip(ancestors, ancestors[1:])]):
90 parent[path] = DIRMOD, tree.id
91 object_store.add_object(tree)
92
93 object_store.add_object(blob)
94 for tree in new_trees:
95 object_store.add_object(tree)
96 for node in self.removed:
97 paths = node.path.split('/')
98 tree = commit_tree
99 trees = [tree]
100 # Traverse deep into the forest...
101 for path in paths:
102 try:
103 obj = self.repository._repo[tree[path][1]]
104 if isinstance(obj, objects.Tree):
105 trees.append(obj)
106 tree = obj
107 except KeyError:
108 break
109 # Cut down the blob and all rotten trees on the way back...
110 for path, tree in reversed(zip(paths, trees)):
111 del tree[path]
112 if tree:
113 # This tree still has elements - don't remove it or any
114 # of it's parents
115 break
116
117 object_store.add_object(commit_tree)
118
119 # Create commit
120 commit = objects.Commit()
121 commit.tree = commit_tree.id
122 commit.parents = [p._commit.id for p in self.parents if p]
123 commit.author = commit.committer = author
124 commit.encoding = ENCODING
125 commit.message = message + ' '
126
127 # Compute date
128 if date is None:
129 date = time.time()
130 elif isinstance(date, datetime.datetime):
131 date = time.mktime(date.timetuple())
132
133 author_time = kwargs.pop('author_time', date)
134 commit.commit_time = int(date)
135 commit.author_time = int(author_time)
136 tz = time.timezone
137 author_tz = kwargs.pop('author_timezone', tz)
138 commit.commit_timezone = tz
139 commit.author_timezone = author_tz
140
141 object_store.add_object(commit)
142
143 ref = 'refs/heads/%s' % branch
144 repo.refs[ref] = commit.id
145 repo.refs.set_symbolic_ref('HEAD', ref)
146
147 # Update vcs repository object & recreate dulwich repo
148 self.repository.revisions.append(commit.id)
149 self.repository._repo = Repo(self.repository.path)
150 tip = self.repository.get_changeset()
151 self.reset()
152 return tip
153
154 def _get_missing_trees(self, path, root_tree):
155 """
156 Creates missing ``Tree`` objects for the given path.
157
158 :param path: path given as a string. It may be a path to a file node
159 (i.e. ``foo/bar/baz.txt``) or directory path - in that case it must
160 end with slash (i.e. ``foo/bar/``).
161 :param root_tree: ``dulwich.objects.Tree`` object from which we start
162 traversing (should be commit's root tree)
163 """
164 dirpath = posixpath.split(path)[0]
165 dirs = dirpath.split('/')
166 if not dirs or dirs == ['']:
167 return []
168
169 def get_tree_for_dir(tree, dirname):
170 for name, mode, id in tree.iteritems():
171 if name == dirname:
172 obj = self.repository._repo[id]
173 if isinstance(obj, objects.Tree):
174 return obj
175 else:
176 raise RepositoryError("Cannot create directory %s "
177 "at tree %s as path is occupied and is not a "
178 "Tree" % (dirname, tree))
179 return None
180
181 trees = []
182 parent = root_tree
183 for dirname in dirs:
184 tree = get_tree_for_dir(parent, dirname)
185 if tree is None:
186 tree = objects.Tree()
187 dirmode = 040000
188 parent.add(dirmode, dirname, tree.id)
189 parent = tree
190 # Always append tree
191 trees.append(tree)
192 return trees
This diff has been collapsed as it changes many lines, (508 lines changed) Show them Hide them
@@ -0,0 +1,508 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs.backends.git
4 ~~~~~~~~~~~~~~~~
5
6 Git backend implementation.
7
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
11
12 import os
13 import re
14 import time
15 import posixpath
16 from dulwich.repo import Repo, NotGitRepository
17 #from dulwich.config import ConfigFile
18 from string import Template
19 from subprocess import Popen, PIPE
20 from rhodecode.lib.vcs.backends.base import BaseRepository
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
30 from rhodecode.lib.vcs.utils.paths import abspath
31 from rhodecode.lib.vcs.utils.paths import get_user_home
32 from .workdir import GitWorkdir
33 from .changeset import GitChangeset
34 from .inmemory import GitInMemoryChangeset
35 from .config import ConfigFile
36
37
38 class GitRepository(BaseRepository):
39 """
40 Git repository backend.
41 """
42 DEFAULT_BRANCH_NAME = 'master'
43 scm = 'git'
44
45 def __init__(self, repo_path, create=False, src_url=None,
46 update_after_clone=False, bare=False):
47
48 self.path = abspath(repo_path)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
50 try:
51 self.head = self._repo.head()
52 except KeyError:
53 self.head = None
54
55 self._config_files = [
56 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
57 'config'),
58 abspath(get_user_home(), '.gitconfig'),
59 ]
60
61 @LazyProperty
62 def revisions(self):
63 """
64 Returns list of revisions' ids, in ascending order. Being lazy
65 attribute allows external tools to inject shas from cache.
66 """
67 return self._get_all_revisions()
68
69 def run_git_command(self, cmd):
70 """
71 Runs given ``cmd`` as git command and returns tuple
72 (returncode, stdout, stderr).
73
74 .. note::
75 This method exists only until log/blame functionality is implemented
76 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
77 os command's output is road to hell...
78
79 :param cmd: git command to be executed
80 """
81 #cmd = '(cd %s && git %s)' % (self.path, cmd)
82 if isinstance(cmd, basestring):
83 cmd = 'git %s' % cmd
84 else:
85 cmd = ['git'] + cmd
86 try:
87 opts = dict(
88 shell=isinstance(cmd, basestring),
89 stdout=PIPE,
90 stderr=PIPE)
91 if os.path.isdir(self.path):
92 opts['cwd'] = self.path
93 p = Popen(cmd, **opts)
94 except OSError, err:
95 raise RepositoryError("Couldn't run git command (%s).\n"
96 "Original error was:%s" % (cmd, err))
97 so, se = p.communicate()
98 if not se.startswith("fatal: bad default revision 'HEAD'") and \
99 p.returncode != 0:
100 raise RepositoryError("Couldn't run git command (%s).\n"
101 "stderr:\n%s" % (cmd, se))
102 return so, se
103
104 def _check_url(self, url):
105 """
106 Functon will check given url and try to verify if it's a valid
107 link. Sometimes it may happened that mercurial will issue basic
108 auth request that can cause whole API to hang when used from python
109 or other external calls.
110
111 On failures it'll raise urllib2.HTTPError
112 """
113
114 #TODO: implement this
115 pass
116
117 def _get_repo(self, create, src_url=None, update_after_clone=False,
118 bare=False):
119 if create and os.path.exists(self.path):
120 raise RepositoryError("Location already exist")
121 if src_url and not create:
122 raise RepositoryError("Create should be set to True if src_url is "
123 "given (clone operation creates repository)")
124 try:
125 if create and src_url:
126 self._check_url(src_url)
127 self.clone(src_url, update_after_clone, bare)
128 return Repo(self.path)
129 elif create:
130 os.mkdir(self.path)
131 if bare:
132 return Repo.init_bare(self.path)
133 else:
134 return Repo.init(self.path)
135 else:
136 return Repo(self.path)
137 except (NotGitRepository, OSError), err:
138 raise RepositoryError(err)
139
140 def _get_all_revisions(self):
141 cmd = 'rev-list --all --date-order'
142 try:
143 so, se = self.run_git_command(cmd)
144 except RepositoryError:
145 # Can be raised for empty repositories
146 return []
147 revisions = so.splitlines()
148 revisions.reverse()
149 return revisions
150
151 def _get_revision(self, revision):
152 """
153 For git backend we always return integer here. This way we ensure
154 that changset's revision attribute would become integer.
155 """
156 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
157 is_bstr = lambda o: isinstance(o, (str, unicode))
158 is_null = lambda o: len(o) == revision.count('0')
159
160 if len(self.revisions) == 0:
161 raise EmptyRepositoryError("There are no changesets yet")
162
163 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
164 revision = self.revisions[-1]
165
166 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
167 or isinstance(revision, int) or is_null(revision)):
168 try:
169 revision = self.revisions[int(revision)]
170 except:
171 raise ChangesetDoesNotExistError("Revision %r does not exist "
172 "for this repository %s" % (revision, self))
173
174 elif is_bstr(revision):
175 if not pattern.match(revision) or revision not in self.revisions:
176 raise ChangesetDoesNotExistError("Revision %r does not exist "
177 "for this repository %s" % (revision, self))
178
179 # Ensure we return full id
180 if not pattern.match(str(revision)):
181 raise ChangesetDoesNotExistError("Given revision %r not recognized"
182 % revision)
183 return revision
184
185 def _get_archives(self, archive_name='tip'):
186
187 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
188 yield {"type": i[0], "extension": i[1], "node": archive_name}
189
190 def _get_url(self, url):
191 """
192 Returns normalized url. If schema is not given, would fall to
193 filesystem (``file:///``) schema.
194 """
195 url = str(url)
196 if url != 'default' and not '://' in url:
197 url = ':///'.join(('file', url))
198 return url
199
200 @LazyProperty
201 def name(self):
202 return os.path.basename(self.path)
203
204 @LazyProperty
205 def last_change(self):
206 """
207 Returns last change made on this repository as datetime object
208 """
209 return date_fromtimestamp(self._get_mtime(), makedate()[1])
210
211 def _get_mtime(self):
212 try:
213 return time.mktime(self.get_changeset().date.timetuple())
214 except RepositoryError:
215 # fallback to filesystem
216 in_path = os.path.join(self.path, '.git', "index")
217 he_path = os.path.join(self.path, '.git', "HEAD")
218 if os.path.exists(in_path):
219 return os.stat(in_path).st_mtime
220 else:
221 return os.stat(he_path).st_mtime
222
223 @LazyProperty
224 def description(self):
225 undefined_description = u'unknown'
226 description_path = os.path.join(self.path, '.git', 'description')
227 if os.path.isfile(description_path):
228 return safe_unicode(open(description_path).read())
229 else:
230 return undefined_description
231
232 @LazyProperty
233 def contact(self):
234 undefined_contact = u'Unknown'
235 return undefined_contact
236
237 @property
238 def branches(self):
239 if not self.revisions:
240 return {}
241 refs = self._repo.refs.as_dict()
242 sortkey = lambda ctx: ctx[0]
243 _branches = [('/'.join(ref.split('/')[2:]), head)
244 for ref, head in refs.items()
245 if ref.startswith('refs/heads/') or
246 ref.startswith('refs/remotes/') and not ref.endswith('/HEAD')]
247 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
248
249 def _get_tags(self):
250 if not self.revisions:
251 return {}
252 sortkey = lambda ctx: ctx[0]
253 _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
254 self._repo.get_refs().items() if ref.startswith('refs/tags/')]
255 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
256
257 @LazyProperty
258 def tags(self):
259 return self._get_tags()
260
261 def tag(self, name, user, revision=None, message=None, date=None,
262 **kwargs):
263 """
264 Creates and returns a tag for the given ``revision``.
265
266 :param name: name for new tag
267 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
268 :param revision: changeset id for which new tag would be created
269 :param message: message of the tag's commit
270 :param date: date of tag's commit
271
272 :raises TagAlreadyExistError: if tag with same name already exists
273 """
274 if name in self.tags:
275 raise TagAlreadyExistError("Tag %s already exists" % name)
276 changeset = self.get_changeset(revision)
277 message = message or "Added tag %s for commit %s" % (name,
278 changeset.raw_id)
279 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
280
281 self.tags = self._get_tags()
282 return changeset
283
284 def remove_tag(self, name, user, message=None, date=None):
285 """
286 Removes tag with the given ``name``.
287
288 :param name: name of the tag to be removed
289 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
290 :param message: message of the tag's removal commit
291 :param date: date of tag's removal commit
292
293 :raises TagDoesNotExistError: if tag with given name does not exists
294 """
295 if name not in self.tags:
296 raise TagDoesNotExistError("Tag %s does not exist" % name)
297 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
298 try:
299 os.remove(tagpath)
300 self.tags = self._get_tags()
301 except OSError, e:
302 raise RepositoryError(e.strerror)
303
304 def get_changeset(self, revision=None):
305 """
306 Returns ``GitChangeset`` object representing commit from git repository
307 at the given revision or head (most recent commit) if None given.
308 """
309 if isinstance(revision, GitChangeset):
310 return revision
311 revision = self._get_revision(revision)
312 changeset = GitChangeset(repository=self, revision=revision)
313 return changeset
314
315 def get_changesets(self, start=None, end=None, start_date=None,
316 end_date=None, branch_name=None, reverse=False):
317 """
318 Returns iterator of ``GitChangeset`` objects from start to end (both
319 are inclusive), in ascending date order (unless ``reverse`` is set).
320
321 :param start: changeset ID, as str; first returned changeset
322 :param end: changeset ID, as str; last returned changeset
323 :param start_date: if specified, changesets with commit date less than
324 ``start_date`` would be filtered out from returned set
325 :param end_date: if specified, changesets with commit date greater than
326 ``end_date`` would be filtered out from returned set
327 :param branch_name: if specified, changesets not reachable from given
328 branch would be filtered out from returned set
329 :param reverse: if ``True``, returned generator would be reversed
330 (meaning that returned changesets would have descending date order)
331
332 :raise BranchDoesNotExistError: If given ``branch_name`` does not
333 exist.
334 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
335 ``end`` could not be found.
336
337 """
338 if branch_name and branch_name not in self.branches:
339 raise BranchDoesNotExistError("Branch '%s' not found" \
340 % branch_name)
341 # %H at format means (full) commit hash, initial hashes are retrieved
342 # in ascending date order
343 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
344 cmd_params = {}
345 if start_date:
346 cmd_template += ' --since "$since"'
347 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
348 if end_date:
349 cmd_template += ' --until "$until"'
350 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
351 if branch_name:
352 cmd_template += ' $branch_name'
353 cmd_params['branch_name'] = branch_name
354 else:
355 cmd_template += ' --all'
356
357 cmd = Template(cmd_template).safe_substitute(**cmd_params)
358 revs = self.run_git_command(cmd)[0].splitlines()
359 start_pos = 0
360 end_pos = len(revs)
361 if start:
362 _start = self._get_revision(start)
363 try:
364 start_pos = revs.index(_start)
365 except ValueError:
366 pass
367
368 if end is not None:
369 _end = self._get_revision(end)
370 try:
371 end_pos = revs.index(_end)
372 except ValueError:
373 pass
374
375 if None not in [start, end] and start_pos > end_pos:
376 raise RepositoryError('start cannot be after end')
377
378 if end_pos is not None:
379 end_pos += 1
380
381 revs = revs[start_pos:end_pos]
382 if reverse:
383 revs = reversed(revs)
384 for rev in revs:
385 yield self.get_changeset(rev)
386
387 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
388 context=3):
389 """
390 Returns (git like) *diff*, as plain text. Shows changes introduced by
391 ``rev2`` since ``rev1``.
392
393 :param rev1: Entry point from which diff is shown. Can be
394 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
395 the changes since empty state of the repository until ``rev2``
396 :param rev2: Until which revision changes should be shown.
397 :param ignore_whitespace: If set to ``True``, would not show whitespace
398 changes. Defaults to ``False``.
399 :param context: How many lines before/after changed lines should be
400 shown. Defaults to ``3``.
401 """
402 flags = ['-U%s' % context]
403 if ignore_whitespace:
404 flags.append('-w')
405
406 if rev1 == self.EMPTY_CHANGESET:
407 rev2 = self.get_changeset(rev2).raw_id
408 cmd = ' '.join(['show'] + flags + [rev2])
409 else:
410 rev1 = self.get_changeset(rev1).raw_id
411 rev2 = self.get_changeset(rev2).raw_id
412 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
413
414 if path:
415 cmd += ' -- "%s"' % path
416 stdout, stderr = self.run_git_command(cmd)
417 # If we used 'show' command, strip first few lines (until actual diff
418 # starts)
419 if rev1 == self.EMPTY_CHANGESET:
420 lines = stdout.splitlines()
421 x = 0
422 for line in lines:
423 if line.startswith('diff'):
424 break
425 x += 1
426 # Append new line just like 'diff' command do
427 stdout = '\n'.join(lines[x:]) + '\n'
428 return stdout
429
430 @LazyProperty
431 def in_memory_changeset(self):
432 """
433 Returns ``GitInMemoryChangeset`` object for this repository.
434 """
435 return GitInMemoryChangeset(self)
436
437 def clone(self, url, update_after_clone=True, bare=False):
438 """
439 Tries to clone changes from external location.
440
441 :param update_after_clone: If set to ``False``, git won't checkout
442 working directory
443 :param bare: If set to ``True``, repository would be cloned into
444 *bare* git repository (no working directory at all).
445 """
446 url = self._get_url(url)
447 cmd = ['clone']
448 if bare:
449 cmd.append('--bare')
450 elif not update_after_clone:
451 cmd.append('--no-checkout')
452 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
453 cmd = ' '.join(cmd)
454 # If error occurs run_git_command raises RepositoryError already
455 self.run_git_command(cmd)
456
457 @LazyProperty
458 def workdir(self):
459 """
460 Returns ``Workdir`` instance for this repository.
461 """
462 return GitWorkdir(self)
463
464 def get_config_value(self, section, name, config_file=None):
465 """
466 Returns configuration value for a given [``section``] and ``name``.
467
468 :param section: Section we want to retrieve value from
469 :param name: Name of configuration we want to retrieve
470 :param config_file: A path to file which should be used to retrieve
471 configuration from (might also be a list of file paths)
472 """
473 if config_file is None:
474 config_file = []
475 elif isinstance(config_file, basestring):
476 config_file = [config_file]
477
478 def gen_configs():
479 for path in config_file + self._config_files:
480 try:
481 yield ConfigFile.from_path(path)
482 except (IOError, OSError, ValueError):
483 continue
484
485 for config in gen_configs():
486 try:
487 return config.get(section, name)
488 except KeyError:
489 continue
490 return None
491
492 def get_user_name(self, config_file=None):
493 """
494 Returns user's name from global configuration file.
495
496 :param config_file: A path to file which should be used to retrieve
497 configuration from (might also be a list of file paths)
498 """
499 return self.get_config_value('user', 'name', config_file)
500
501 def get_user_email(self, config_file=None):
502 """
503 Returns user's email from global configuration file.
504
505 :param config_file: A path to file which should be used to retrieve
506 configuration from (might also be a list of file paths)
507 """
508 return self.get_config_value('user', 'email', config_file)
@@ -0,0 +1,31 b''
1 import re
2 from rhodecode.lib.vcs.backends.base import BaseWorkdir
3 from rhodecode.lib.vcs.exceptions import RepositoryError
4 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
5
6
7 class GitWorkdir(BaseWorkdir):
8
9 def get_branch(self):
10 headpath = self.repository._repo.refs.refpath('HEAD')
11 try:
12 content = open(headpath).read()
13 match = re.match(r'^ref: refs/heads/(?P<branch>.+)\n$', content)
14 if match:
15 return match.groupdict()['branch']
16 else:
17 raise RepositoryError("Couldn't compute workdir's branch")
18 except IOError:
19 # Try naive way...
20 raise RepositoryError("Couldn't compute workdir's branch")
21
22 def get_changeset(self):
23 return self.repository.get_changeset(
24 self.repository._repo.refs.as_dict().get('HEAD'))
25
26 def checkout_branch(self, branch=None):
27 if branch is None:
28 branch = self.repository.DEFAULT_BRANCH_NAME
29 if branch not in self.repository.branches:
30 raise BranchDoesNotExistError
31 self.repository.run_git_command(['checkout', branch])
@@ -0,0 +1,21 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs.backends.hg
4 ~~~~~~~~~~~~~~~~
5
6 Mercurial backend implementation.
7
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
11
12 from .repository import MercurialRepository
13 from .changeset import MercurialChangeset
14 from .inmemory import MercurialInMemoryChangeset
15 from .workdir import MercurialWorkdir
16
17
18 __all__ = [
19 'MercurialRepository', 'MercurialChangeset',
20 'MercurialInMemoryChangeset', 'MercurialWorkdir',
21 ]
@@ -0,0 +1,338 b''
1 import os
2 import posixpath
3
4 from rhodecode.lib.vcs.backends.base import BaseChangeset
5 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, ChangedFileNodesGenerator, \
9 DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode
10
11 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
12 from rhodecode.lib.vcs.utils.lazy import LazyProperty
13 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
14
15 from ...utils.hgcompat import archival, hex
16
17
18 class MercurialChangeset(BaseChangeset):
19 """
20 Represents state of the repository at the single revision.
21 """
22
23 def __init__(self, repository, revision):
24 self.repository = repository
25 self.raw_id = revision
26 self._ctx = repository._repo[revision]
27 self.revision = self._ctx._rev
28 self.nodes = {}
29
30 @LazyProperty
31 def tags(self):
32 return map(safe_unicode, self._ctx.tags())
33
34 @LazyProperty
35 def branch(self):
36 return safe_unicode(self._ctx.branch())
37
38 @LazyProperty
39 def message(self):
40 return safe_unicode(self._ctx.description())
41
42 @LazyProperty
43 def author(self):
44 return safe_unicode(self._ctx.user())
45
46 @LazyProperty
47 def date(self):
48 return date_fromtimestamp(*self._ctx.date())
49
50 @LazyProperty
51 def status(self):
52 """
53 Returns modified, added, removed, deleted files for current changeset
54 """
55 return self.repository._repo.status(self._ctx.p1().node(),
56 self._ctx.node())
57
58 @LazyProperty
59 def _file_paths(self):
60 return list(self._ctx)
61
62 @LazyProperty
63 def _dir_paths(self):
64 p = list(set(get_dirs_for_path(*self._file_paths)))
65 p.insert(0, '')
66 return p
67
68 @LazyProperty
69 def _paths(self):
70 return self._dir_paths + self._file_paths
71
72 @LazyProperty
73 def id(self):
74 if self.last:
75 return u'tip'
76 return self.short_id
77
78 @LazyProperty
79 def short_id(self):
80 return self.raw_id[:12]
81
82 @LazyProperty
83 def parents(self):
84 """
85 Returns list of parents changesets.
86 """
87 return [self.repository.get_changeset(parent.rev())
88 for parent in self._ctx.parents() if parent.rev() >= 0]
89
90 def next(self, branch=None):
91
92 if branch and self.branch != branch:
93 raise VCSError('Branch option used on changeset not belonging '
94 'to that branch')
95
96 def _next(changeset, branch):
97 try:
98 next_ = changeset.revision + 1
99 next_rev = changeset.repository.revisions[next_]
100 except IndexError:
101 raise ChangesetDoesNotExistError
102 cs = changeset.repository.get_changeset(next_rev)
103
104 if branch and branch != cs.branch:
105 return _next(cs, branch)
106
107 return cs
108
109 return _next(self, branch)
110
111 def prev(self, branch=None):
112 if branch and self.branch != branch:
113 raise VCSError('Branch option used on changeset not belonging '
114 'to that branch')
115
116 def _prev(changeset, branch):
117 try:
118 prev_ = changeset.revision - 1
119 if prev_ < 0:
120 raise IndexError
121 prev_rev = changeset.repository.revisions[prev_]
122 except IndexError:
123 raise ChangesetDoesNotExistError
124
125 cs = changeset.repository.get_changeset(prev_rev)
126
127 if branch and branch != cs.branch:
128 return _prev(cs, branch)
129
130 return cs
131
132 return _prev(self, branch)
133
134 def _fix_path(self, path):
135 """
136 Paths are stored without trailing slash so we need to get rid off it if
137 needed. Also mercurial keeps filenodes as str so we need to decode
138 from unicode to str
139 """
140 if path.endswith('/'):
141 path = path.rstrip('/')
142
143 return safe_str(path)
144
145 def _get_kind(self, path):
146 path = self._fix_path(path)
147 if path in self._file_paths:
148 return NodeKind.FILE
149 elif path in self._dir_paths:
150 return NodeKind.DIR
151 else:
152 raise ChangesetError("Node does not exist at the given path %r"
153 % (path))
154
155 def _get_filectx(self, path):
156 path = self._fix_path(path)
157 if self._get_kind(path) != NodeKind.FILE:
158 raise ChangesetError("File does not exist for revision %r at "
159 " %r" % (self.revision, path))
160 return self._ctx.filectx(path)
161
162 def get_file_mode(self, path):
163 """
164 Returns stat mode of the file at the given ``path``.
165 """
166 fctx = self._get_filectx(path)
167 if 'x' in fctx.flags():
168 return 0100755
169 else:
170 return 0100644
171
172 def get_file_content(self, path):
173 """
174 Returns content of the file at given ``path``.
175 """
176 fctx = self._get_filectx(path)
177 return fctx.data()
178
179 def get_file_size(self, path):
180 """
181 Returns size of the file at given ``path``.
182 """
183 fctx = self._get_filectx(path)
184 return fctx.size()
185
186 def get_file_changeset(self, path):
187 """
188 Returns last commit of the file at the given ``path``.
189 """
190 fctx = self._get_filectx(path)
191 changeset = self.repository.get_changeset(fctx.linkrev())
192 return changeset
193
194 def get_file_history(self, path):
195 """
196 Returns history of file as reversed list of ``Changeset`` objects for
197 which file at given ``path`` has been modified.
198 """
199 fctx = self._get_filectx(path)
200 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
201 changesets = [self.repository.get_changeset(hex(node))
202 for node in reversed(nodes)]
203 return changesets
204
205 def get_file_annotate(self, path):
206 """
207 Returns a list of three element tuples with lineno,changeset and line
208 """
209 fctx = self._get_filectx(path)
210 annotate = []
211 for i, annotate_data in enumerate(fctx.annotate()):
212 ln_no = i + 1
213 annotate.append((ln_no, self.repository\
214 .get_changeset(hex(annotate_data[0].node())),
215 annotate_data[1],))
216
217 return annotate
218
219 def fill_archive(self, stream=None, kind='tgz', prefix=None,
220 subrepos=False):
221 """
222 Fills up given stream.
223
224 :param stream: file like object.
225 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
226 Default: ``tgz``.
227 :param prefix: name of root directory in archive.
228 Default is repository name and changeset's raw_id joined with dash
229 (``repo-tip.<KIND>``).
230 :param subrepos: include subrepos in this archive.
231
232 :raise ImproperArchiveTypeError: If given kind is wrong.
233 :raise VcsError: If given stream is None
234 """
235
236 allowed_kinds = settings.ARCHIVE_SPECS.keys()
237 if kind not in allowed_kinds:
238 raise ImproperArchiveTypeError('Archive kind not supported use one'
239 'of %s', allowed_kinds)
240
241 if stream is None:
242 raise VCSError('You need to pass in a valid stream for filling'
243 ' with archival data')
244
245 if prefix is None:
246 prefix = '%s-%s' % (self.repository.name, self.short_id)
247 elif prefix.startswith('/'):
248 raise VCSError("Prefix cannot start with leading slash")
249 elif prefix.strip() == '':
250 raise VCSError("Prefix cannot be empty")
251
252 archival.archive(self.repository._repo, stream, self.raw_id,
253 kind, prefix=prefix, subrepos=subrepos)
254
255 #stream.close()
256
257 if stream.closed and hasattr(stream, 'name'):
258 stream = open(stream.name, 'rb')
259 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
260 stream = open(stream.name, 'rb')
261 else:
262 stream.seek(0)
263
264 def get_nodes(self, path):
265 """
266 Returns combined ``DirNode`` and ``FileNode`` objects list representing
267 state of changeset at the given ``path``. If node at the given ``path``
268 is not instance of ``DirNode``, ChangesetError would be raised.
269 """
270
271 if self._get_kind(path) != NodeKind.DIR:
272 raise ChangesetError("Directory does not exist for revision %r at "
273 " %r" % (self.revision, path))
274 path = self._fix_path(path)
275 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
276 if os.path.dirname(f) == path]
277 dirs = path == '' and '' or [d for d in self._dir_paths
278 if d and posixpath.dirname(d) == path]
279 dirnodes = [DirNode(d, changeset=self) for d in dirs
280 if os.path.dirname(d) == path]
281 nodes = dirnodes + filenodes
282 # cache nodes
283 for node in nodes:
284 self.nodes[node.path] = node
285 nodes.sort()
286 return nodes
287
288 def get_node(self, path):
289 """
290 Returns ``Node`` object from the given ``path``. If there is no node at
291 the given ``path``, ``ChangesetError`` would be raised.
292 """
293
294 path = self._fix_path(path)
295
296 if not path in self.nodes:
297 if path in self._file_paths:
298 node = FileNode(path, changeset=self)
299 elif path in self._dir_paths or path in self._dir_paths:
300 if path == '':
301 node = RootNode(changeset=self)
302 else:
303 node = DirNode(path, changeset=self)
304 else:
305 raise NodeDoesNotExistError("There is no file nor directory "
306 "at the given path: %r at revision %r"
307 % (path, self.short_id))
308 # cache node
309 self.nodes[path] = node
310 return self.nodes[path]
311
312 @LazyProperty
313 def affected_files(self):
314 """
315 Get's a fast accessible file changes for given changeset
316 """
317 return self._ctx.files()
318
319 @property
320 def added(self):
321 """
322 Returns list of added ``FileNode`` objects.
323 """
324 return AddedFileNodesGenerator([n for n in self.status[1]], self)
325
326 @property
327 def changed(self):
328 """
329 Returns list of modified ``FileNode`` objects.
330 """
331 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
332
333 @property
334 def removed(self):
335 """
336 Returns list of removed ``FileNode`` objects.
337 """
338 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
@@ -0,0 +1,110 b''
1 import datetime
2 import errno
3
4 from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
5 from rhodecode.lib.vcs.exceptions import RepositoryError
6
7 from ...utils.hgcompat import memfilectx, memctx, hex
8
9
10 class MercurialInMemoryChangeset(BaseInMemoryChangeset):
11
12 def commit(self, message, author, parents=None, branch=None, date=None,
13 **kwargs):
14 """
15 Performs in-memory commit (doesn't check workdir in any way) and
16 returns newly created ``Changeset``. Updates repository's
17 ``revisions``.
18
19 :param message: message of the commit
20 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
21 :param parents: single parent or sequence of parents from which commit
22 would be derieved
23 :param date: ``datetime.datetime`` instance. Defaults to
24 ``datetime.datetime.now()``.
25 :param branch: branch name, as string. If none given, default backend's
26 branch would be used.
27
28 :raises ``CommitError``: if any error occurs while committing
29 """
30 self.check_integrity(parents)
31
32 from .repository import MercurialRepository
33 if not isinstance(message, str) or not isinstance(author, str):
34 raise RepositoryError('Given message and author needs to be '
35 'an <str> instance')
36
37 if branch is None:
38 branch = MercurialRepository.DEFAULT_BRANCH_NAME
39 kwargs['branch'] = branch
40
41 def filectxfn(_repo, memctx, path):
42 """
43 Marks given path as added/changed/removed in a given _repo. This is
44 for internal mercurial commit function.
45 """
46
47 # check if this path is removed
48 if path in (node.path for node in self.removed):
49 # Raising exception is a way to mark node for removal
50 raise IOError(errno.ENOENT, '%s is deleted' % path)
51
52 # check if this path is added
53 for node in self.added:
54 if node.path == path:
55 return memfilectx(path=node.path,
56 data=(node.content.encode('utf8')
57 if not node.is_binary else node.content),
58 islink=False,
59 isexec=node.is_executable,
60 copied=False)
61
62 # or changed
63 for node in self.changed:
64 if node.path == path:
65 return memfilectx(path=node.path,
66 data=(node.content.encode('utf8')
67 if not node.is_binary else node.content),
68 islink=False,
69 isexec=node.is_executable,
70 copied=False)
71
72 raise RepositoryError("Given path haven't been marked as added,"
73 "changed or removed (%s)" % path)
74
75 parents = [None, None]
76 for i, parent in enumerate(self.parents):
77 if parent is not None:
78 parents[i] = parent._ctx.node()
79
80 if date and isinstance(date, datetime.datetime):
81 date = date.ctime()
82
83 commit_ctx = memctx(repo=self.repository._repo,
84 parents=parents,
85 text='',
86 files=self.get_paths(),
87 filectxfn=filectxfn,
88 user=author,
89 date=date,
90 extra=kwargs)
91
92 # injecting given _repo params
93 commit_ctx._text = message
94 commit_ctx._user = author
95 commit_ctx._date = date
96
97 # TODO: Catch exceptions!
98 n = self.repository._repo.commitctx(commit_ctx)
99 # Returns mercurial node
100 self._commit_ctx = commit_ctx # For reference
101 # Update vcs repository object & recreate mercurial _repo
102 # new_ctx = self.repository._repo[node]
103 # new_tip = self.repository.get_changeset(new_ctx.hex())
104 new_id = hex(n)
105 self.repository.revisions.append(new_id)
106 self._repo = self.repository._get_repo(create=False)
107 self.repository.branches = self.repository._get_branches()
108 tip = self.repository.get_changeset()
109 self.reset()
110 return tip
This diff has been collapsed as it changes many lines, (521 lines changed) Show them Hide them
@@ -0,0 +1,521 b''
1 import os
2 import time
3 import datetime
4 import urllib
5 import urllib2
6
7 from rhodecode.lib.vcs.backends.base import BaseRepository
8 from .workdir import MercurialWorkdir
9 from .changeset import MercurialChangeset
10 from .inmemory import MercurialInMemoryChangeset
11
12 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError, \
13 ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError, \
14 VCSError, TagAlreadyExistError, TagDoesNotExistError
15 from rhodecode.lib.vcs.utils import author_email, author_name, date_fromtimestamp, \
16 makedate, safe_unicode
17 from rhodecode.lib.vcs.utils.lazy import LazyProperty
18 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
19 from rhodecode.lib.vcs.utils.paths import abspath
20
21 from ...utils.hgcompat import ui, nullid, match, patch, diffopts, clone, \
22 get_contact, pull, localrepository, RepoLookupError, Abort, RepoError, hex
23
24
25 class MercurialRepository(BaseRepository):
26 """
27 Mercurial repository backend
28 """
29 DEFAULT_BRANCH_NAME = 'default'
30 scm = 'hg'
31
32 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
33 update_after_clone=False):
34 """
35 Raises RepositoryError if repository could not be find at the given
36 ``repo_path``.
37
38 :param repo_path: local path of the repository
39 :param create=False: if set to True, would try to create repository if
40 it does not exist rather than raising exception
41 :param baseui=None: user data
42 :param src_url=None: would try to clone repository from given location
43 :param update_after_clone=False: sets update of working copy after
44 making a clone
45 """
46
47 if not isinstance(repo_path, str):
48 raise VCSError('Mercurial backend requires repository path to '
49 'be instance of <str> got %s instead' %
50 type(repo_path))
51
52 self.path = abspath(repo_path)
53 self.baseui = baseui or ui.ui()
54 # We've set path and ui, now we can set _repo itself
55 self._repo = self._get_repo(create, src_url, update_after_clone)
56
57 @property
58 def _empty(self):
59 """
60 Checks if repository is empty without any changesets
61 """
62 # TODO: Following raises errors when using InMemoryChangeset...
63 # return len(self._repo.changelog) == 0
64 return len(self.revisions) == 0
65
66 @LazyProperty
67 def revisions(self):
68 """
69 Returns list of revisions' ids, in ascending order. Being lazy
70 attribute allows external tools to inject shas from cache.
71 """
72 return self._get_all_revisions()
73
74 @LazyProperty
75 def name(self):
76 return os.path.basename(self.path)
77
78 @LazyProperty
79 def branches(self):
80 return self._get_branches()
81
82 def _get_branches(self, closed=False):
83 """
84 Get's branches for this repository
85 Returns only not closed branches by default
86
87 :param closed: return also closed branches for mercurial
88 """
89
90 if self._empty:
91 return {}
92
93 def _branchtags(localrepo):
94 """
95 Patched version of mercurial branchtags to not return the closed
96 branches
97
98 :param localrepo: locarepository instance
99 """
100
101 bt = {}
102 bt_closed = {}
103 for bn, heads in localrepo.branchmap().iteritems():
104 tip = heads[-1]
105 if 'close' in localrepo.changelog.read(tip)[5]:
106 bt_closed[bn] = tip
107 else:
108 bt[bn] = tip
109
110 if closed:
111 bt.update(bt_closed)
112 return bt
113
114 sortkey = lambda ctx: ctx[0] # sort by name
115 _branches = [(safe_unicode(n), hex(h),) for n, h in
116 _branchtags(self._repo).items()]
117
118 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
119
120 @LazyProperty
121 def tags(self):
122 """
123 Get's tags for this repository
124 """
125 return self._get_tags()
126
127 def _get_tags(self):
128 if self._empty:
129 return {}
130
131 sortkey = lambda ctx: ctx[0] # sort by name
132 _tags = [(safe_unicode(n), hex(h),) for n, h in
133 self._repo.tags().items()]
134
135 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
136
137 def tag(self, name, user, revision=None, message=None, date=None,
138 **kwargs):
139 """
140 Creates and returns a tag for the given ``revision``.
141
142 :param name: name for new tag
143 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
144 :param revision: changeset id for which new tag would be created
145 :param message: message of the tag's commit
146 :param date: date of tag's commit
147
148 :raises TagAlreadyExistError: if tag with same name already exists
149 """
150 if name in self.tags:
151 raise TagAlreadyExistError("Tag %s already exists" % name)
152 changeset = self.get_changeset(revision)
153 local = kwargs.setdefault('local', False)
154
155 if message is None:
156 message = "Added tag %s for changeset %s" % (name,
157 changeset.short_id)
158
159 if date is None:
160 date = datetime.datetime.now().ctime()
161
162 try:
163 self._repo.tag(name, changeset._ctx.node(), message, local, user,
164 date)
165 except Abort, e:
166 raise RepositoryError(e.message)
167
168 # Reinitialize tags
169 self.tags = self._get_tags()
170 tag_id = self.tags[name]
171
172 return self.get_changeset(revision=tag_id)
173
174 def remove_tag(self, name, user, message=None, date=None):
175 """
176 Removes tag with the given ``name``.
177
178 :param name: name of the tag to be removed
179 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
180 :param message: message of the tag's removal commit
181 :param date: date of tag's removal commit
182
183 :raises TagDoesNotExistError: if tag with given name does not exists
184 """
185 if name not in self.tags:
186 raise TagDoesNotExistError("Tag %s does not exist" % name)
187 if message is None:
188 message = "Removed tag %s" % name
189 if date is None:
190 date = datetime.datetime.now().ctime()
191 local = False
192
193 try:
194 self._repo.tag(name, nullid, message, local, user, date)
195 self.tags = self._get_tags()
196 except Abort, e:
197 raise RepositoryError(e.message)
198
199 @LazyProperty
200 def bookmarks(self):
201 """
202 Get's bookmarks for this repository
203 """
204 return self._get_bookmarks()
205
206 def _get_bookmarks(self):
207 if self._empty:
208 return {}
209
210 sortkey = lambda ctx: ctx[0] # sort by name
211 _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
212 self._repo._bookmarks.items()]
213 return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
214
215 def _get_all_revisions(self):
216
217 return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
218
219 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
220 context=3):
221 """
222 Returns (git like) *diff*, as plain text. Shows changes introduced by
223 ``rev2`` since ``rev1``.
224
225 :param rev1: Entry point from which diff is shown. Can be
226 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
227 the changes since empty state of the repository until ``rev2``
228 :param rev2: Until which revision changes should be shown.
229 :param ignore_whitespace: If set to ``True``, would not show whitespace
230 changes. Defaults to ``False``.
231 :param context: How many lines before/after changed lines should be
232 shown. Defaults to ``3``.
233 """
234 # Check if given revisions are present at repository (may raise
235 # ChangesetDoesNotExistError)
236 if rev1 != self.EMPTY_CHANGESET:
237 self.get_changeset(rev1)
238 self.get_changeset(rev2)
239
240 file_filter = match(self.path, '', [path])
241 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
242 opts=diffopts(git=True,
243 ignorews=ignore_whitespace,
244 context=context)))
245
246 def _check_url(self, url):
247 """
248 Function will check given url and try to verify if it's a valid
249 link. Sometimes it may happened that mercurial will issue basic
250 auth request that can cause whole API to hang when used from python
251 or other external calls.
252
253 On failures it'll raise urllib2.HTTPError, return code 200 if url
254 is valid or True if it's a local path
255 """
256
257 from mercurial.util import url as Url
258
259 # those authnadlers are patched for python 2.6.5 bug an
260 # infinit looping when given invalid resources
261 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
262
263 # check first if it's not an local url
264 if os.path.isdir(url) or url.startswith('file:'):
265 return True
266
267 handlers = []
268 test_uri, authinfo = Url(url).authinfo()
269
270 if authinfo:
271 #create a password manager
272 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
273 passmgr.add_password(*authinfo)
274
275 handlers.extend((httpbasicauthhandler(passmgr),
276 httpdigestauthhandler(passmgr)))
277
278 o = urllib2.build_opener(*handlers)
279 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
280 ('Accept', 'application/mercurial-0.1')]
281
282 q = {"cmd": 'between'}
283 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
284 qs = '?%s' % urllib.urlencode(q)
285 cu = "%s%s" % (test_uri, qs)
286 req = urllib2.Request(cu, None, {})
287
288 try:
289 resp = o.open(req)
290 return resp.code == 200
291 except Exception, e:
292 # means it cannot be cloned
293 raise urllib2.URLError(e)
294
295 def _get_repo(self, create, src_url=None, update_after_clone=False):
296 """
297 Function will check for mercurial repository in given path and return
298 a localrepo object. If there is no repository in that path it will
299 raise an exception unless ``create`` parameter is set to True - in
300 that case repository would be created and returned.
301 If ``src_url`` is given, would try to clone repository from the
302 location at given clone_point. Additionally it'll make update to
303 working copy accordingly to ``update_after_clone`` flag
304 """
305 try:
306 if src_url:
307 url = str(self._get_url(src_url))
308 opts = {}
309 if not update_after_clone:
310 opts.update({'noupdate': True})
311 try:
312 self._check_url(url)
313 clone(self.baseui, url, self.path, **opts)
314 # except urllib2.URLError:
315 # raise Abort("Got HTTP 404 error")
316 except Exception:
317 raise
318 # Don't try to create if we've already cloned repo
319 create = False
320 return localrepository(self.baseui, self.path, create=create)
321 except (Abort, RepoError), err:
322 if create:
323 msg = "Cannot create repository at %s. Original error was %s"\
324 % (self.path, err)
325 else:
326 msg = "Not valid repository at %s. Original error was %s"\
327 % (self.path, err)
328 raise RepositoryError(msg)
329
330 @LazyProperty
331 def in_memory_changeset(self):
332 return MercurialInMemoryChangeset(self)
333
334 @LazyProperty
335 def description(self):
336 undefined_description = u'unknown'
337 return safe_unicode(self._repo.ui.config('web', 'description',
338 undefined_description, untrusted=True))
339
340 @LazyProperty
341 def contact(self):
342 undefined_contact = u'Unknown'
343 return safe_unicode(get_contact(self._repo.ui.config)
344 or undefined_contact)
345
346 @LazyProperty
347 def last_change(self):
348 """
349 Returns last change made on this repository as datetime object
350 """
351 return date_fromtimestamp(self._get_mtime(), makedate()[1])
352
353 def _get_mtime(self):
354 try:
355 return time.mktime(self.get_changeset().date.timetuple())
356 except RepositoryError:
357 #fallback to filesystem
358 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
359 st_path = os.path.join(self.path, '.hg', "store")
360 if os.path.exists(cl_path):
361 return os.stat(cl_path).st_mtime
362 else:
363 return os.stat(st_path).st_mtime
364
365 def _get_hidden(self):
366 return self._repo.ui.configbool("web", "hidden", untrusted=True)
367
368 def _get_revision(self, revision):
369 """
370 Get's an ID revision given as str. This will always return a fill
371 40 char revision number
372
373 :param revision: str or int or None
374 """
375
376 if self._empty:
377 raise EmptyRepositoryError("There are no changesets yet")
378
379 if revision in [-1, 'tip', None]:
380 revision = 'tip'
381
382 try:
383 revision = hex(self._repo.lookup(revision))
384 except (IndexError, ValueError, RepoLookupError, TypeError):
385 raise ChangesetDoesNotExistError("Revision %r does not "
386 "exist for this repository %s" \
387 % (revision, self))
388 return revision
389
390 def _get_archives(self, archive_name='tip'):
391 allowed = self.baseui.configlist("web", "allow_archive",
392 untrusted=True)
393 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
394 if i[0] in allowed or self._repo.ui.configbool("web",
395 "allow" + i[0],
396 untrusted=True):
397 yield {"type": i[0], "extension": i[1], "node": archive_name}
398
399 def _get_url(self, url):
400 """
401 Returns normalized url. If schema is not given, would fall
402 to filesystem
403 (``file:///``) schema.
404 """
405 url = str(url)
406 if url != 'default' and not '://' in url:
407 url = "file:" + urllib.pathname2url(url)
408 return url
409
410 def get_changeset(self, revision=None):
411 """
412 Returns ``MercurialChangeset`` object representing repository's
413 changeset at the given ``revision``.
414 """
415 revision = self._get_revision(revision)
416 changeset = MercurialChangeset(repository=self, revision=revision)
417 return changeset
418
419 def get_changesets(self, start=None, end=None, start_date=None,
420 end_date=None, branch_name=None, reverse=False):
421 """
422 Returns iterator of ``MercurialChangeset`` objects from start to end
423 (both are inclusive)
424
425 :param start: None, str, int or mercurial lookup format
426 :param end: None, str, int or mercurial lookup format
427 :param start_date:
428 :param end_date:
429 :param branch_name:
430 :param reversed: return changesets in reversed order
431 """
432
433 start_raw_id = self._get_revision(start)
434 start_pos = self.revisions.index(start_raw_id) if start else None
435 end_raw_id = self._get_revision(end)
436 end_pos = self.revisions.index(end_raw_id) if end else None
437
438 if None not in [start, end] and start_pos > end_pos:
439 raise RepositoryError("start revision '%s' cannot be "
440 "after end revision '%s'" % (start, end))
441
442 if branch_name and branch_name not in self.branches.keys():
443 raise BranchDoesNotExistError('Such branch %s does not exists for'
444 ' this repository' % branch_name)
445 if end_pos is not None:
446 end_pos += 1
447
448 slice_ = reversed(self.revisions[start_pos:end_pos]) if reverse else \
449 self.revisions[start_pos:end_pos]
450
451 for id_ in slice_:
452 cs = self.get_changeset(id_)
453 if branch_name and cs.branch != branch_name:
454 continue
455 if start_date and cs.date < start_date:
456 continue
457 if end_date and cs.date > end_date:
458 continue
459
460 yield cs
461
462 def pull(self, url):
463 """
464 Tries to pull changes from external location.
465 """
466 url = self._get_url(url)
467 try:
468 pull(self.baseui, self._repo, url)
469 except Abort, err:
470 # Propagate error but with vcs's type
471 raise RepositoryError(str(err))
472
473 @LazyProperty
474 def workdir(self):
475 """
476 Returns ``Workdir`` instance for this repository.
477 """
478 return MercurialWorkdir(self)
479
480 def get_config_value(self, section, name, config_file=None):
481 """
482 Returns configuration value for a given [``section``] and ``name``.
483
484 :param section: Section we want to retrieve value from
485 :param name: Name of configuration we want to retrieve
486 :param config_file: A path to file which should be used to retrieve
487 configuration from (might also be a list of file paths)
488 """
489 if config_file is None:
490 config_file = []
491 elif isinstance(config_file, basestring):
492 config_file = [config_file]
493
494 config = self._repo.ui
495 for path in config_file:
496 config.readconfig(path)
497 return config.config(section, name)
498
499 def get_user_name(self, config_file=None):
500 """
501 Returns user's name from global configuration file.
502
503 :param config_file: A path to file which should be used to retrieve
504 configuration from (might also be a list of file paths)
505 """
506 username = self.get_config_value('ui', 'username')
507 if username:
508 return author_name(username)
509 return None
510
511 def get_user_email(self, config_file=None):
512 """
513 Returns user's email from global configuration file.
514
515 :param config_file: A path to file which should be used to retrieve
516 configuration from (might also be a list of file paths)
517 """
518 username = self.get_config_value('ui', 'username')
519 if username:
520 return author_email(username)
521 return None
@@ -0,0 +1,21 b''
1 from rhodecode.lib.vcs.backends.base import BaseWorkdir
2 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
3
4 from ...utils.hgcompat import hg_merge
5
6
7 class MercurialWorkdir(BaseWorkdir):
8
9 def get_branch(self):
10 return self.repository._repo.dirstate.branch()
11
12 def get_changeset(self):
13 return self.repository.get_changeset()
14
15 def checkout_branch(self, branch=None):
16 if branch is None:
17 branch = self.repository.DEFAULT_BRANCH_NAME
18 if branch not in self.repository.branches:
19 raise BranchDoesNotExistError
20
21 hg_merge.update(self.repository._repo, branch, False, False, None)
1 NO CONTENT: new file 100644
@@ -0,0 +1,33 b''
1 import os
2 import tempfile
3 from rhodecode.lib.vcs.utils.paths import get_user_home
4
5 abspath = lambda * p: os.path.abspath(os.path.join(*p))
6
7 VCSRC_PATH = os.environ.get('VCSRC_PATH')
8
9 if not VCSRC_PATH:
10 HOME_ = get_user_home()
11 if not HOME_:
12 HOME_ = tempfile.gettempdir()
13
14 VCSRC_PATH = VCSRC_PATH or abspath(HOME_, '.vcsrc')
15 if os.path.isdir(VCSRC_PATH):
16 VCSRC_PATH = os.path.join(VCSRC_PATH, '__init__.py')
17
18 BACKENDS = {
19 'hg': 'vcs.backends.hg.MercurialRepository',
20 'git': 'vcs.backends.git.GitRepository',
21 }
22
23 ARCHIVE_SPECS = {
24 'tar': ('application/x-tar', '.tar'),
25 'tbz2': ('application/x-bzip2', '.tar.bz2'),
26 'tgz': ('application/x-gzip', '.tar.gz'),
27 'zip': ('application/zip', '.zip'),
28 }
29
30 BACKENDS = {
31 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
32 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
33 }
@@ -0,0 +1,93 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs.exceptions
4 ~~~~~~~~~~~~~~
5
6 Custom exceptions module
7
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
11
12
13 class VCSError(Exception):
14 pass
15
16
17 class RepositoryError(VCSError):
18 pass
19
20
21 class EmptyRepositoryError(RepositoryError):
22 pass
23
24
25 class TagAlreadyExistError(RepositoryError):
26 pass
27
28
29 class TagDoesNotExistError(RepositoryError):
30 pass
31
32
33 class BranchAlreadyExistError(RepositoryError):
34 pass
35
36
37 class BranchDoesNotExistError(RepositoryError):
38 pass
39
40
41 class ChangesetError(RepositoryError):
42 pass
43
44
45 class ChangesetDoesNotExistError(ChangesetError):
46 pass
47
48
49 class CommitError(RepositoryError):
50 pass
51
52
53 class NothingChangedError(CommitError):
54 pass
55
56
57 class NodeError(VCSError):
58 pass
59
60
61 class RemovedFileNodeError(NodeError):
62 pass
63
64
65 class NodeAlreadyExistsError(CommitError):
66 pass
67
68
69 class NodeAlreadyChangedError(CommitError):
70 pass
71
72
73 class NodeDoesNotExistError(CommitError):
74 pass
75
76
77 class NodeNotChangedError(CommitError):
78 pass
79
80
81 class NodeAlreadyAddedError(CommitError):
82 pass
83
84
85 class NodeAlreadyRemovedError(CommitError):
86 pass
87
88
89 class ImproperArchiveTypeError(VCSError):
90 pass
91
92 class CommandError(VCSError):
93 pass
This diff has been collapsed as it changes many lines, (551 lines changed) Show them Hide them
@@ -0,0 +1,551 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs.nodes
4 ~~~~~~~~~
5
6 Module holding everything related to vcs nodes.
7
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
11 import stat
12 import posixpath
13 import mimetypes
14
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
16 from rhodecode.lib.vcs.utils import safe_unicode
17 from rhodecode.lib.vcs.exceptions import NodeError
18 from rhodecode.lib.vcs.exceptions import RemovedFileNodeError
19
20 from pygments import lexers
21
22
23 class NodeKind:
24 DIR = 1
25 FILE = 2
26
27
28 class NodeState:
29 ADDED = u'added'
30 CHANGED = u'changed'
31 NOT_CHANGED = u'not changed'
32 REMOVED = u'removed'
33
34
35 class NodeGeneratorBase(object):
36 """
37 Base class for removed added and changed filenodes, it's a lazy generator
38 class that will create filenodes only on iteration or call
39
40 The len method doesn't need to create filenodes at all
41 """
42
43 def __init__(self, current_paths, cs):
44 self.cs = cs
45 self.current_paths = current_paths
46
47 def __call__(self):
48 return [n for n in self]
49
50 def __getslice__(self, i, j):
51 for p in self.current_paths[i:j]:
52 yield self.cs.get_node(p)
53
54 def __len__(self):
55 return len(self.current_paths)
56
57 def __iter__(self):
58 for p in self.current_paths:
59 yield self.cs.get_node(p)
60
61
62 class AddedFileNodesGenerator(NodeGeneratorBase):
63 """
64 Class holding Added files for current changeset
65 """
66 pass
67
68
69 class ChangedFileNodesGenerator(NodeGeneratorBase):
70 """
71 Class holding Changed files for current changeset
72 """
73 pass
74
75
76 class RemovedFileNodesGenerator(NodeGeneratorBase):
77 """
78 Class holding removed files for current changeset
79 """
80 def __iter__(self):
81 for p in self.current_paths:
82 yield RemovedFileNode(path=p)
83
84 def __getslice__(self, i, j):
85 for p in self.current_paths[i:j]:
86 yield RemovedFileNode(path=p)
87
88
89 class Node(object):
90 """
91 Simplest class representing file or directory on repository. SCM backends
92 should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
93 directly.
94
95 Node's ``path`` cannot start with slash as we operate on *relative* paths
96 only. Moreover, every single node is identified by the ``path`` attribute,
97 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
98 """
99
100 def __init__(self, path, kind):
101 if path.startswith('/'):
102 raise NodeError("Cannot initialize Node objects with slash at "
103 "the beginning as only relative paths are supported")
104 self.path = path.rstrip('/')
105 if path == '' and kind != NodeKind.DIR:
106 raise NodeError("Only DirNode and its subclasses may be "
107 "initialized with empty path")
108 self.kind = kind
109 #self.dirs, self.files = [], []
110 if self.is_root() and not self.is_dir():
111 raise NodeError("Root node cannot be FILE kind")
112
113 @LazyProperty
114 def parent(self):
115 parent_path = self.get_parent_path()
116 if parent_path:
117 if self.changeset:
118 return self.changeset.get_node(parent_path)
119 return DirNode(parent_path)
120 return None
121
122 @LazyProperty
123 def name(self):
124 """
125 Returns name of the node so if its path
126 then only last part is returned.
127 """
128 return safe_unicode(self.path.rstrip('/').split('/')[-1])
129
130 def _get_kind(self):
131 return self._kind
132
133 def _set_kind(self, kind):
134 if hasattr(self, '_kind'):
135 raise NodeError("Cannot change node's kind")
136 else:
137 self._kind = kind
138 # Post setter check (path's trailing slash)
139 if self.path.endswith('/'):
140 raise NodeError("Node's path cannot end with slash")
141
142 kind = property(_get_kind, _set_kind)
143
144 def __cmp__(self, other):
145 """
146 Comparator using name of the node, needed for quick list sorting.
147 """
148 kind_cmp = cmp(self.kind, other.kind)
149 if kind_cmp:
150 return kind_cmp
151 return cmp(self.name, other.name)
152
153 def __eq__(self, other):
154 for attr in ['name', 'path', 'kind']:
155 if getattr(self, attr) != getattr(other, attr):
156 return False
157 if self.is_file():
158 if self.content != other.content:
159 return False
160 else:
161 # For DirNode's check without entering each dir
162 self_nodes_paths = list(sorted(n.path for n in self.nodes))
163 other_nodes_paths = list(sorted(n.path for n in self.nodes))
164 if self_nodes_paths != other_nodes_paths:
165 return False
166 return True
167
168 def __nq__(self, other):
169 return not self.__eq__(other)
170
171 def __repr__(self):
172 return '<%s %r>' % (self.__class__.__name__, self.path)
173
174 def __str__(self):
175 return self.__repr__()
176
177 def __unicode__(self):
178 return self.name
179
180 def get_parent_path(self):
181 """
182 Returns node's parent path or empty string if node is root.
183 """
184 if self.is_root():
185 return ''
186 return posixpath.dirname(self.path.rstrip('/')) + '/'
187
188 def is_file(self):
189 """
190 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
191 otherwise.
192 """
193 return self.kind == NodeKind.FILE
194
195 def is_dir(self):
196 """
197 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
198 otherwise.
199 """
200 return self.kind == NodeKind.DIR
201
202 def is_root(self):
203 """
204 Returns ``True`` if node is a root node and ``False`` otherwise.
205 """
206 return self.kind == NodeKind.DIR and self.path == ''
207
208 @LazyProperty
209 def added(self):
210 return self.state is NodeState.ADDED
211
212 @LazyProperty
213 def changed(self):
214 return self.state is NodeState.CHANGED
215
216 @LazyProperty
217 def not_changed(self):
218 return self.state is NodeState.NOT_CHANGED
219
220 @LazyProperty
221 def removed(self):
222 return self.state is NodeState.REMOVED
223
224
225 class FileNode(Node):
226 """
227 Class representing file nodes.
228
229 :attribute: path: path to the node, relative to repostiory's root
230 :attribute: content: if given arbitrary sets content of the file
231 :attribute: changeset: if given, first time content is accessed, callback
232 :attribute: mode: octal stat mode for a node. Default is 0100644.
233 """
234
235 def __init__(self, path, content=None, changeset=None, mode=None):
236 """
237 Only one of ``content`` and ``changeset`` may be given. Passing both
238 would raise ``NodeError`` exception.
239
240 :param path: relative path to the node
241 :param content: content may be passed to constructor
242 :param changeset: if given, will use it to lazily fetch content
243 :param mode: octal representation of ST_MODE (i.e. 0100644)
244 """
245
246 if content and changeset:
247 raise NodeError("Cannot use both content and changeset")
248 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
249 self.changeset = changeset
250 self._content = content
251 self._mode = mode or 0100644
252
253 @LazyProperty
254 def mode(self):
255 """
256 Returns lazily mode of the FileNode. If ``changeset`` is not set, would
257 use value given at initialization or 0100644 (default).
258 """
259 if self.changeset:
260 mode = self.changeset.get_file_mode(self.path)
261 else:
262 mode = self._mode
263 return mode
264
265 @property
266 def content(self):
267 """
268 Returns lazily content of the FileNode. If possible, would try to
269 decode content from UTF-8.
270 """
271 if self.changeset:
272 content = self.changeset.get_file_content(self.path)
273 else:
274 content = self._content
275
276 if bool(content and '\0' in content):
277 return content
278 return safe_unicode(content)
279
280 @LazyProperty
281 def size(self):
282 if self.changeset:
283 return self.changeset.get_file_size(self.path)
284 raise NodeError("Cannot retrieve size of the file without related "
285 "changeset attribute")
286
287 @LazyProperty
288 def message(self):
289 if self.changeset:
290 return self.last_changeset.message
291 raise NodeError("Cannot retrieve message of the file without related "
292 "changeset attribute")
293
294 @LazyProperty
295 def last_changeset(self):
296 if self.changeset:
297 return self.changeset.get_file_changeset(self.path)
298 raise NodeError("Cannot retrieve last changeset of the file without "
299 "related changeset attribute")
300
301 def get_mimetype(self):
302 """
303 Mimetype is calculated based on the file's content. If ``_mimetype``
304 attribute is available, it will be returned (backends which store
305 mimetypes or can easily recognize them, should set this private
306 attribute to indicate that type should *NOT* be calculated).
307 """
308 if hasattr(self, '_mimetype'):
309 if (isinstance(self._mimetype,(tuple,list,)) and
310 len(self._mimetype) == 2):
311 return self._mimetype
312 else:
313 raise NodeError('given _mimetype attribute must be an 2 '
314 'element list or tuple')
315
316 mtype,encoding = mimetypes.guess_type(self.name)
317
318 if mtype is None:
319 if self.is_binary:
320 mtype = 'application/octet-stream'
321 encoding = None
322 else:
323 mtype = 'text/plain'
324 encoding = None
325 return mtype,encoding
326
327 @LazyProperty
328 def mimetype(self):
329 """
330 Wrapper around full mimetype info. It returns only type of fetched
331 mimetype without the encoding part. use get_mimetype function to fetch
332 full set of (type,encoding)
333 """
334 return self.get_mimetype()[0]
335
336 @LazyProperty
337 def mimetype_main(self):
338 return self.mimetype.split('/')[0]
339
340 @LazyProperty
341 def lexer(self):
342 """
343 Returns pygment's lexer class. Would try to guess lexer taking file's
344 content, name and mimetype.
345 """
346 try:
347 lexer = lexers.guess_lexer_for_filename(self.name, self.content)
348 except lexers.ClassNotFound:
349 lexer = lexers.TextLexer()
350 # returns first alias
351 return lexer
352
353 @LazyProperty
354 def lexer_alias(self):
355 """
356 Returns first alias of the lexer guessed for this file.
357 """
358 return self.lexer.aliases[0]
359
360 @LazyProperty
361 def history(self):
362 """
363 Returns a list of changeset for this file in which the file was changed
364 """
365 if self.changeset is None:
366 raise NodeError('Unable to get changeset for this FileNode')
367 return self.changeset.get_file_history(self.path)
368
369 @LazyProperty
370 def annotate(self):
371 """
372 Returns a list of three element tuples with lineno,changeset and line
373 """
374 if self.changeset is None:
375 raise NodeError('Unable to get changeset for this FileNode')
376 return self.changeset.get_file_annotate(self.path)
377
378 @LazyProperty
379 def state(self):
380 if not self.changeset:
381 raise NodeError("Cannot check state of the node if it's not "
382 "linked with changeset")
383 elif self.path in (node.path for node in self.changeset.added):
384 return NodeState.ADDED
385 elif self.path in (node.path for node in self.changeset.changed):
386 return NodeState.CHANGED
387 else:
388 return NodeState.NOT_CHANGED
389
390 @property
391 def is_binary(self):
392 """
393 Returns True if file has binary content.
394 """
395 bin = '\0' in self.content
396 return bin
397
398 @LazyProperty
399 def extension(self):
400 """Returns filenode extension"""
401 return self.name.split('.')[-1]
402
403 def is_executable(self):
404 """
405 Returns ``True`` if file has executable flag turned on.
406 """
407 return bool(self.mode & stat.S_IXUSR)
408
409
410 class RemovedFileNode(FileNode):
411 """
412 Dummy FileNode class - trying to access any public attribute except path,
413 name, kind or state (or methods/attributes checking those two) would raise
414 RemovedFileNodeError.
415 """
416 ALLOWED_ATTRIBUTES = ['name', 'path', 'state', 'is_root', 'is_file',
417 'is_dir', 'kind', 'added', 'changed', 'not_changed', 'removed']
418
419 def __init__(self, path):
420 """
421 :param path: relative path to the node
422 """
423 super(RemovedFileNode, self).__init__(path=path)
424
425 def __getattribute__(self, attr):
426 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
427 return super(RemovedFileNode, self).__getattribute__(attr)
428 raise RemovedFileNodeError("Cannot access attribute %s on "
429 "RemovedFileNode" % attr)
430
431 @LazyProperty
432 def state(self):
433 return NodeState.REMOVED
434
435
436 class DirNode(Node):
437 """
438 DirNode stores list of files and directories within this node.
439 Nodes may be used standalone but within repository context they
440 lazily fetch data within same repositorty's changeset.
441 """
442
443 def __init__(self, path, nodes=(), changeset=None):
444 """
445 Only one of ``nodes`` and ``changeset`` may be given. Passing both
446 would raise ``NodeError`` exception.
447
448 :param path: relative path to the node
449 :param nodes: content may be passed to constructor
450 :param changeset: if given, will use it to lazily fetch content
451 :param size: always 0 for ``DirNode``
452 """
453 if nodes and changeset:
454 raise NodeError("Cannot use both nodes and changeset")
455 super(DirNode, self).__init__(path, NodeKind.DIR)
456 self.changeset = changeset
457 self._nodes = nodes
458
459 @LazyProperty
460 def content(self):
461 raise NodeError("%s represents a dir and has no ``content`` attribute"
462 % self)
463
464 @LazyProperty
465 def nodes(self):
466 if self.changeset:
467 nodes = self.changeset.get_nodes(self.path)
468 else:
469 nodes = self._nodes
470 self._nodes_dict = dict((node.path, node) for node in nodes)
471 return sorted(nodes)
472
473 @LazyProperty
474 def files(self):
475 return sorted((node for node in self.nodes if node.is_file()))
476
477 @LazyProperty
478 def dirs(self):
479 return sorted((node for node in self.nodes if node.is_dir()))
480
481 def __iter__(self):
482 for node in self.nodes:
483 yield node
484
485 def get_node(self, path):
486 """
487 Returns node from within this particular ``DirNode``, so it is now
488 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
489 'docs'. In order to access deeper nodes one must fetch nodes between
490 them first - this would work::
491
492 docs = root.get_node('docs')
493 docs.get_node('api').get_node('index.rst')
494
495 :param: path - relative to the current node
496
497 .. note::
498 To access lazily (as in example above) node have to be initialized
499 with related changeset object - without it node is out of
500 context and may know nothing about anything else than nearest
501 (located at same level) nodes.
502 """
503 try:
504 path = path.rstrip('/')
505 if path == '':
506 raise NodeError("Cannot retrieve node without path")
507 self.nodes # access nodes first in order to set _nodes_dict
508 paths = path.split('/')
509 if len(paths) == 1:
510 if not self.is_root():
511 path = '/'.join((self.path, paths[0]))
512 else:
513 path = paths[0]
514 return self._nodes_dict[path]
515 elif len(paths) > 1:
516 if self.changeset is None:
517 raise NodeError("Cannot access deeper "
518 "nodes without changeset")
519 else:
520 path1, path2 = paths[0], '/'.join(paths[1:])
521 return self.get_node(path1).get_node(path2)
522 else:
523 raise KeyError
524 except KeyError:
525 raise NodeError("Node does not exist at %s" % path)
526
527 @LazyProperty
528 def state(self):
529 raise NodeError("Cannot access state of DirNode")
530
531 @LazyProperty
532 def size(self):
533 size = 0
534 for root, dirs, files in self.changeset.walk(self.path):
535 for f in files:
536 size += f.size
537
538 return size
539
540
541 class RootNode(DirNode):
542 """
543 DirNode being the root node of the repository.
544 """
545
546 def __init__(self, nodes=(), changeset=None):
547 super(RootNode, self).__init__(path='', nodes=nodes,
548 changeset=changeset)
549
550 def __repr__(self):
551 return '<%s>' % self.__class__.__name__
@@ -0,0 +1,139 b''
1 """
2 This module provides some useful tools for ``vcs`` like annotate/diff html
3 output. It also includes some internal helpers.
4 """
5 import sys
6 import time
7 import datetime
8
9
10 def makedate():
11 lt = time.localtime()
12 if lt[8] == 1 and time.daylight:
13 tz = time.altzone
14 else:
15 tz = time.timezone
16 return time.mktime(lt), tz
17
18
19 def date_fromtimestamp(unixts, tzoffset=0):
20 """
21 Makes a local datetime object out of unix timestamp
22
23 :param unixts:
24 :param tzoffset:
25 """
26
27 return datetime.datetime.fromtimestamp(float(unixts))
28
29
30 def safe_unicode(str_, from_encoding=None):
31 """
32 safe unicode function. Does few trick to turn str_ into unicode
33
34 In case of UnicodeDecode error we try to return it with encoding detected
35 by chardet library if it fails fallback to unicode with errors replaced
36
37 :param str_: string to decode
38 :rtype: unicode
39 :returns: unicode object
40 """
41 if isinstance(str_, unicode):
42 return str_
43 if not from_encoding:
44 import rhodecode
45 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding', 'utf8')
46 from_encoding = DEFAULT_ENCODING
47 try:
48 return unicode(str_)
49 except UnicodeDecodeError:
50 pass
51
52 try:
53 return unicode(str_, from_encoding)
54 except UnicodeDecodeError:
55 pass
56
57 try:
58 import chardet
59 encoding = chardet.detect(str_)['encoding']
60 if encoding is None:
61 raise Exception()
62 return str_.decode(encoding)
63 except (ImportError, UnicodeDecodeError, Exception):
64 return unicode(str_, from_encoding, 'replace')
65
66
67 def safe_str(unicode_, to_encoding=None):
68 """
69 safe str function. Does few trick to turn unicode_ into string
70
71 In case of UnicodeEncodeError we try to return it with encoding detected
72 by chardet library if it fails fallback to string with errors replaced
73
74 :param unicode_: unicode to encode
75 :rtype: str
76 :returns: str object
77 """
78
79 if isinstance(unicode_, str):
80 return unicode_
81 if not to_encoding:
82 import rhodecode
83 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding', 'utf8')
84 to_encoding = DEFAULT_ENCODING
85 try:
86 return unicode_.encode(to_encoding)
87 except UnicodeEncodeError:
88 pass
89
90 try:
91 import chardet
92 encoding = chardet.detect(unicode_)['encoding']
93 print encoding
94 if encoding is None:
95 raise UnicodeEncodeError()
96
97 return unicode_.encode(encoding)
98 except (ImportError, UnicodeEncodeError):
99 return unicode_.encode(to_encoding, 'replace')
100
101 return safe_str
102
103
104 def author_email(author):
105 """
106 returns email address of given author.
107 If any of <,> sign are found, it fallbacks to regex findall()
108 and returns first found result or empty string
109
110 Regex taken from http://www.regular-expressions.info/email.html
111 """
112 import re
113 r = author.find('>')
114 l = author.find('<')
115
116 if l == -1 or r == -1:
117 # fallback to regex match of email out of a string
118 email_re = re.compile(r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!"""
119 r"""#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z"""
120 r"""0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]"""
121 r"""*[a-z0-9])?""", re.IGNORECASE)
122 m = re.findall(email_re, author)
123 return m[0] if m else ''
124
125 return author[l + 1:r].strip()
126
127
128 def author_name(author):
129 """
130 get name of author, or else username.
131 It'll try to find an email in the author string and just cut it off
132 to get the username
133 """
134
135 if not '@' in author:
136 return author
137 else:
138 return author.replace(author_email(author), '').replace('<', '')\
139 .replace('>', '').strip()
@@ -0,0 +1,177 b''
1 from rhodecode.lib.vcs.exceptions import VCSError
2 from rhodecode.lib.vcs.nodes import FileNode
3 from pygments.formatters import HtmlFormatter
4 from pygments import highlight
5
6 import StringIO
7
8
9 def annotate_highlight(filenode, annotate_from_changeset_func=None,
10 order=None, headers=None, **options):
11 """
12 Returns html portion containing annotated table with 3 columns: line
13 numbers, changeset information and pygmentized line of code.
14
15 :param filenode: FileNode object
16 :param annotate_from_changeset_func: function taking changeset and
17 returning single annotate cell; needs break line at the end
18 :param order: ordered sequence of ``ls`` (line numbers column),
19 ``annotate`` (annotate column), ``code`` (code column); Default is
20 ``['ls', 'annotate', 'code']``
21 :param headers: dictionary with headers (keys are whats in ``order``
22 parameter)
23 """
24 options['linenos'] = True
25 formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
26 headers=headers,
27 annotate_from_changeset_func=annotate_from_changeset_func, **options)
28 lexer = filenode.lexer
29 highlighted = highlight(filenode.content, lexer, formatter)
30 return highlighted
31
32
33 class AnnotateHtmlFormatter(HtmlFormatter):
34
35 def __init__(self, filenode, annotate_from_changeset_func=None,
36 order=None, **options):
37 """
38 If ``annotate_from_changeset_func`` is passed it should be a function
39 which returns string from the given changeset. For example, we may pass
40 following function as ``annotate_from_changeset_func``::
41
42 def changeset_to_anchor(changeset):
43 return '<a href="/changesets/%s/">%s</a>\n' %\
44 (changeset.id, changeset.id)
45
46 :param annotate_from_changeset_func: see above
47 :param order: (default: ``['ls', 'annotate', 'code']``); order of
48 columns;
49 :param options: standard pygment's HtmlFormatter options, there is
50 extra option tough, ``headers``. For instance we can pass::
51
52 formatter = AnnotateHtmlFormatter(filenode, headers={
53 'ls': '#',
54 'annotate': 'Annotate',
55 'code': 'Code',
56 })
57
58 """
59 super(AnnotateHtmlFormatter, self).__init__(**options)
60 self.annotate_from_changeset_func = annotate_from_changeset_func
61 self.order = order or ('ls', 'annotate', 'code')
62 headers = options.pop('headers', None)
63 if headers and not ('ls' in headers and 'annotate' in headers and
64 'code' in headers):
65 raise ValueError("If headers option dict is specified it must "
66 "all 'ls', 'annotate' and 'code' keys")
67 self.headers = headers
68 if isinstance(filenode, FileNode):
69 self.filenode = filenode
70 else:
71 raise VCSError("This formatter expect FileNode parameter, not %r"
72 % type(filenode))
73
74 def annotate_from_changeset(self, changeset):
75 """
76 Returns full html line for single changeset per annotated line.
77 """
78 if self.annotate_from_changeset_func:
79 return self.annotate_from_changeset_func(changeset)
80 else:
81 return ''.join((changeset.id, '\n'))
82
83 def _wrap_tablelinenos(self, inner):
84 dummyoutfile = StringIO.StringIO()
85 lncount = 0
86 for t, line in inner:
87 if t:
88 lncount += 1
89 dummyoutfile.write(line)
90
91 fl = self.linenostart
92 mw = len(str(lncount + fl - 1))
93 sp = self.linenospecial
94 st = self.linenostep
95 la = self.lineanchors
96 aln = self.anchorlinenos
97 if sp:
98 lines = []
99
100 for i in range(fl, fl + lncount):
101 if i % st == 0:
102 if i % sp == 0:
103 if aln:
104 lines.append('<a href="#%s-%d" class="special">'
105 '%*d</a>' %
106 (la, i, mw, i))
107 else:
108 lines.append('<span class="special">'
109 '%*d</span>' % (mw, i))
110 else:
111 if aln:
112 lines.append('<a href="#%s-%d">'
113 '%*d</a>' % (la, i, mw, i))
114 else:
115 lines.append('%*d' % (mw, i))
116 else:
117 lines.append('')
118 ls = '\n'.join(lines)
119 else:
120 lines = []
121 for i in range(fl, fl + lncount):
122 if i % st == 0:
123 if aln:
124 lines.append('<a href="#%s-%d">%*d</a>' \
125 % (la, i, mw, i))
126 else:
127 lines.append('%*d' % (mw, i))
128 else:
129 lines.append('')
130 ls = '\n'.join(lines)
131
132 annotate_changesets = [tup[1] for tup in self.filenode.annotate]
133 # If pygments cropped last lines break we need do that too
134 ln_cs = len(annotate_changesets)
135 ln_ = len(ls.splitlines())
136 if ln_cs > ln_:
137 annotate_changesets = annotate_changesets[:ln_ - ln_cs]
138 annotate = ''.join((self.annotate_from_changeset(changeset)
139 for changeset in annotate_changesets))
140 # in case you wonder about the seemingly redundant <div> here:
141 # since the content in the other cell also is wrapped in a div,
142 # some browsers in some configurations seem to mess up the formatting.
143 '''
144 yield 0, ('<table class="%stable">' % self.cssclass +
145 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
146 ls + '</pre></div></td>' +
147 '<td class="code">')
148 yield 0, dummyoutfile.getvalue()
149 yield 0, '</td></tr></table>'
150
151 '''
152 headers_row = []
153 if self.headers:
154 headers_row = ['<tr class="annotate-header">']
155 for key in self.order:
156 td = ''.join(('<td>', self.headers[key], '</td>'))
157 headers_row.append(td)
158 headers_row.append('</tr>')
159
160 body_row_start = ['<tr>']
161 for key in self.order:
162 if key == 'ls':
163 body_row_start.append(
164 '<td class="linenos"><div class="linenodiv"><pre>' +
165 ls + '</pre></div></td>')
166 elif key == 'annotate':
167 body_row_start.append(
168 '<td class="annotate"><div class="annotatediv"><pre>' +
169 annotate + '</pre></div></td>')
170 elif key == 'code':
171 body_row_start.append('<td class="code">')
172 yield 0, ('<table class="%stable">' % self.cssclass +
173 ''.join(headers_row) +
174 ''.join(body_row_start)
175 )
176 yield 0, dummyoutfile.getvalue()
177 yield 0, '</td></tr></table>'
@@ -0,0 +1,67 b''
1 # -*- coding: utf-8 -*-
2 """
3 vcs.utils.archivers
4 ~~~~~~~~~~~~~~~~~~~
5
6 set of archiver functions for creating archives from repository content
7
8 :created_on: Jan 21, 2011
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
11
12
13 class BaseArchiver(object):
14
15 def __init__(self):
16 self.archive_file = self._get_archive_file()
17
18 def addfile(self):
19 """
20 Adds a file to archive container
21 """
22 pass
23
24 def close(self):
25 """
26 Closes and finalizes operation of archive container object
27 """
28 self.archive_file.close()
29
30 def _get_archive_file(self):
31 """
32 Returns container for specific archive
33 """
34 raise NotImplementedError()
35
36
37 class TarArchiver(BaseArchiver):
38 pass
39
40
41 class Tbz2Archiver(BaseArchiver):
42 pass
43
44
45 class TgzArchiver(BaseArchiver):
46 pass
47
48
49 class ZipArchiver(BaseArchiver):
50 pass
51
52
53 def get_archiver(self, kind):
54 """
55 Returns instance of archiver class specific to given kind
56
57 :param kind: archive kind
58 """
59
60 archivers = {
61 'tar': TarArchiver,
62 'tbz2': Tbz2Archiver,
63 'tgz': TgzArchiver,
64 'zip': ZipArchiver,
65 }
66
67 return archivers[kind]()
@@ -0,0 +1,47 b''
1 from mercurial import ui, config
2
3
4 def make_ui(self, path='hgwebdir.config'):
5 """
6 A funcion that will read python rc files and make an ui from read options
7
8 :param path: path to mercurial config file
9 """
10 #propagated from mercurial documentation
11 sections = [
12 'alias',
13 'auth',
14 'decode/encode',
15 'defaults',
16 'diff',
17 'email',
18 'extensions',
19 'format',
20 'merge-patterns',
21 'merge-tools',
22 'hooks',
23 'http_proxy',
24 'smtp',
25 'patch',
26 'paths',
27 'profiling',
28 'server',
29 'trusted',
30 'ui',
31 'web',
32 ]
33
34 repos = path
35 baseui = ui.ui()
36 cfg = config.config()
37 cfg.read(repos)
38 self.paths = cfg.items('paths')
39 self.base_path = self.paths[0][1].replace('*', '')
40 self.check_repo_dir(self.paths)
41 self.set_statics(cfg)
42
43 for section in sections:
44 for k, v in cfg.items(section):
45 baseui.setconfig(section, k, v)
46
47 return baseui
@@ -0,0 +1,13 b''
1 """
2 Various utilities to work with Python < 2.7.
3
4 Those utilities may be deleted once ``vcs`` stops support for older Python
5 versions.
6 """
7 import sys
8
9
10 if sys.version_info >= (2, 7):
11 unittest = __import__('unittest')
12 else:
13 unittest = __import__('unittest2')
@@ -0,0 +1,460 b''
1 # -*- coding: utf-8 -*-
2 # original copyright: 2007-2008 by Armin Ronacher
3 # licensed under the BSD license.
4
5 import re
6 import difflib
7 import logging
8
9 from difflib import unified_diff
10 from itertools import tee, imap
11
12 from mercurial.match import match
13
14 from rhodecode.lib.vcs.exceptions import VCSError
15 from rhodecode.lib.vcs.nodes import FileNode, NodeError
16
17
18 def get_udiff(filenode_old, filenode_new,show_whitespace=True):
19 """
20 Returns unified diff between given ``filenode_old`` and ``filenode_new``.
21 """
22 try:
23 filenode_old_date = filenode_old.last_changeset.date
24 except NodeError:
25 filenode_old_date = None
26
27 try:
28 filenode_new_date = filenode_new.last_changeset.date
29 except NodeError:
30 filenode_new_date = None
31
32 for filenode in (filenode_old, filenode_new):
33 if not isinstance(filenode, FileNode):
34 raise VCSError("Given object should be FileNode object, not %s"
35 % filenode.__class__)
36
37 if filenode_old_date and filenode_new_date:
38 if not filenode_old_date < filenode_new_date:
39 logging.debug("Generating udiff for filenodes with not increasing "
40 "dates")
41
42 vcs_udiff = unified_diff(filenode_old.content.splitlines(True),
43 filenode_new.content.splitlines(True),
44 filenode_old.name,
45 filenode_new.name,
46 filenode_old_date,
47 filenode_old_date)
48 return vcs_udiff
49
50
51 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True):
52 """
53 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
54
55 :param ignore_whitespace: ignore whitespaces in diff
56 """
57
58 for filenode in (filenode_old, filenode_new):
59 if not isinstance(filenode, FileNode):
60 raise VCSError("Given object should be FileNode object, not %s"
61 % filenode.__class__)
62
63 old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40)
64 new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40)
65
66 repo = filenode_new.changeset.repository
67 vcs_gitdiff = repo._get_diff(old_raw_id, new_raw_id, filenode_new.path,
68 ignore_whitespace)
69
70 return vcs_gitdiff
71
72
73 class DiffProcessor(object):
74 """
75 Give it a unified diff and it returns a list of the files that were
76 mentioned in the diff together with a dict of meta information that
77 can be used to render it in a HTML template.
78 """
79 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
80
81 def __init__(self, diff, differ='diff', format='udiff'):
82 """
83 :param diff: a text in diff format or generator
84 :param format: format of diff passed, `udiff` or `gitdiff`
85 """
86 if isinstance(diff, basestring):
87 diff = [diff]
88
89 self.__udiff = diff
90 self.__format = format
91 self.adds = 0
92 self.removes = 0
93
94 if isinstance(self.__udiff, basestring):
95 self.lines = iter(self.__udiff.splitlines(1))
96
97 elif self.__format == 'gitdiff':
98 udiff_copy = self.copy_iterator()
99 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
100 else:
101 udiff_copy = self.copy_iterator()
102 self.lines = imap(self.escaper, udiff_copy)
103
104 # Select a differ.
105 if differ == 'difflib':
106 self.differ = self._highlight_line_difflib
107 else:
108 self.differ = self._highlight_line_udiff
109
110 def escaper(self, string):
111 return string.replace('<', '&lt;').replace('>', '&gt;')
112
113 def copy_iterator(self):
114 """
115 make a fresh copy of generator, we should not iterate thru
116 an original as it's needed for repeating operations on
117 this instance of DiffProcessor
118 """
119 self.__udiff, iterator_copy = tee(self.__udiff)
120 return iterator_copy
121
122 def _extract_rev(self, line1, line2):
123 """
124 Extract the filename and revision hint from a line.
125 """
126
127 try:
128 if line1.startswith('--- ') and line2.startswith('+++ '):
129 l1 = line1[4:].split(None, 1)
130 old_filename = l1[0].lstrip('a/') if len(l1) >= 1 else None
131 old_rev = l1[1] if len(l1) == 2 else 'old'
132
133 l2 = line2[4:].split(None, 1)
134 new_filename = l2[0].lstrip('b/') if len(l1) >= 1 else None
135 new_rev = l2[1] if len(l2) == 2 else 'new'
136
137 filename = old_filename if (old_filename !=
138 'dev/null') else new_filename
139
140 return filename, new_rev, old_rev
141 except (ValueError, IndexError):
142 pass
143
144 return None, None, None
145
146 def _parse_gitdiff(self, diffiterator):
147 def line_decoder(l):
148 if l.startswith('+') and not l.startswith('+++'):
149 self.adds += 1
150 elif l.startswith('-') and not l.startswith('---'):
151 self.removes += 1
152 return l.decode('utf8', 'replace')
153
154 output = list(diffiterator)
155 size = len(output)
156
157 if size == 2:
158 l = []
159 l.extend([output[0]])
160 l.extend(output[1].splitlines(1))
161 return map(line_decoder, l)
162 elif size == 1:
163 return map(line_decoder, output[0].splitlines(1))
164 elif size == 0:
165 return []
166
167 raise Exception('wrong size of diff %s' % size)
168
169 def _highlight_line_difflib(self, line, next):
170 """
171 Highlight inline changes in both lines.
172 """
173
174 if line['action'] == 'del':
175 old, new = line, next
176 else:
177 old, new = next, line
178
179 oldwords = re.split(r'(\W)', old['line'])
180 newwords = re.split(r'(\W)', new['line'])
181
182 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
183
184 oldfragments, newfragments = [], []
185 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
186 oldfrag = ''.join(oldwords[i1:i2])
187 newfrag = ''.join(newwords[j1:j2])
188 if tag != 'equal':
189 if oldfrag:
190 oldfrag = '<del>%s</del>' % oldfrag
191 if newfrag:
192 newfrag = '<ins>%s</ins>' % newfrag
193 oldfragments.append(oldfrag)
194 newfragments.append(newfrag)
195
196 old['line'] = "".join(oldfragments)
197 new['line'] = "".join(newfragments)
198
199 def _highlight_line_udiff(self, line, next):
200 """
201 Highlight inline changes in both lines.
202 """
203 start = 0
204 limit = min(len(line['line']), len(next['line']))
205 while start < limit and line['line'][start] == next['line'][start]:
206 start += 1
207 end = -1
208 limit -= start
209 while -end <= limit and line['line'][end] == next['line'][end]:
210 end -= 1
211 end += 1
212 if start or end:
213 def do(l):
214 last = end + len(l['line'])
215 if l['action'] == 'add':
216 tag = 'ins'
217 else:
218 tag = 'del'
219 l['line'] = '%s<%s>%s</%s>%s' % (
220 l['line'][:start],
221 tag,
222 l['line'][start:last],
223 tag,
224 l['line'][last:]
225 )
226 do(line)
227 do(next)
228
229 def _parse_udiff(self):
230 """
231 Parse the diff an return data for the template.
232 """
233 lineiter = self.lines
234 files = []
235 try:
236 line = lineiter.next()
237 # skip first context
238 skipfirst = True
239 while 1:
240 # continue until we found the old file
241 if not line.startswith('--- '):
242 line = lineiter.next()
243 continue
244
245 chunks = []
246 filename, old_rev, new_rev = \
247 self._extract_rev(line, lineiter.next())
248 files.append({
249 'filename': filename,
250 'old_revision': old_rev,
251 'new_revision': new_rev,
252 'chunks': chunks
253 })
254
255 line = lineiter.next()
256 while line:
257 match = self._chunk_re.match(line)
258 if not match:
259 break
260
261 lines = []
262 chunks.append(lines)
263
264 old_line, old_end, new_line, new_end = \
265 [int(x or 1) for x in match.groups()[:-1]]
266 old_line -= 1
267 new_line -= 1
268 context = len(match.groups()) == 5
269 old_end += old_line
270 new_end += new_line
271
272 if context:
273 if not skipfirst:
274 lines.append({
275 'old_lineno': '...',
276 'new_lineno': '...',
277 'action': 'context',
278 'line': line,
279 })
280 else:
281 skipfirst = False
282
283 line = lineiter.next()
284 while old_line < old_end or new_line < new_end:
285 if line:
286 command, line = line[0], line[1:]
287 else:
288 command = ' '
289 affects_old = affects_new = False
290
291 # ignore those if we don't expect them
292 if command in '#@':
293 continue
294 elif command == '+':
295 affects_new = True
296 action = 'add'
297 elif command == '-':
298 affects_old = True
299 action = 'del'
300 else:
301 affects_old = affects_new = True
302 action = 'unmod'
303
304 old_line += affects_old
305 new_line += affects_new
306 lines.append({
307 'old_lineno': affects_old and old_line or '',
308 'new_lineno': affects_new and new_line or '',
309 'action': action,
310 'line': line
311 })
312 line = lineiter.next()
313
314 except StopIteration:
315 pass
316
317 # highlight inline changes
318 for file in files:
319 for chunk in chunks:
320 lineiter = iter(chunk)
321 #first = True
322 try:
323 while 1:
324 line = lineiter.next()
325 if line['action'] != 'unmod':
326 nextline = lineiter.next()
327 if nextline['action'] == 'unmod' or \
328 nextline['action'] == line['action']:
329 continue
330 self.differ(line, nextline)
331 except StopIteration:
332 pass
333
334 return files
335
336 def prepare(self):
337 """
338 Prepare the passed udiff for HTML rendering. It'l return a list
339 of dicts
340 """
341 return self._parse_udiff()
342
343 def _safe_id(self, idstring):
344 """Make a string safe for including in an id attribute.
345
346 The HTML spec says that id attributes 'must begin with
347 a letter ([A-Za-z]) and may be followed by any number
348 of letters, digits ([0-9]), hyphens ("-"), underscores
349 ("_"), colons (":"), and periods (".")'. These regexps
350 are slightly over-zealous, in that they remove colons
351 and periods unnecessarily.
352
353 Whitespace is transformed into underscores, and then
354 anything which is not a hyphen or a character that
355 matches \w (alphanumerics and underscore) is removed.
356
357 """
358 # Transform all whitespace to underscore
359 idstring = re.sub(r'\s', "_", '%s' % idstring)
360 # Remove everything that is not a hyphen or a member of \w
361 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
362 return idstring
363
364 def raw_diff(self):
365 """
366 Returns raw string as udiff
367 """
368 udiff_copy = self.copy_iterator()
369 if self.__format == 'gitdiff':
370 udiff_copy = self._parse_gitdiff(udiff_copy)
371 return u''.join(udiff_copy)
372
373 def as_html(self, table_class='code-difftable', line_class='line',
374 new_lineno_class='lineno old', old_lineno_class='lineno new',
375 code_class='code'):
376 """
377 Return udiff as html table with customized css classes
378 """
379 def _link_to_if(condition, label, url):
380 """
381 Generates a link if condition is meet or just the label if not.
382 """
383
384 if condition:
385 return '''<a href="%(url)s">%(label)s</a>''' % {'url': url,
386 'label': label}
387 else:
388 return label
389 diff_lines = self.prepare()
390 _html_empty = True
391 _html = []
392 _html.append('''<table class="%(table_class)s">\n''' \
393 % {'table_class': table_class})
394 for diff in diff_lines:
395 for line in diff['chunks']:
396 _html_empty = False
397 for change in line:
398 _html.append('''<tr class="%(line_class)s %(action)s">\n''' \
399 % {'line_class': line_class,
400 'action': change['action']})
401 anchor_old_id = ''
402 anchor_new_id = ''
403 anchor_old = "%(filename)s_o%(oldline_no)s" % \
404 {'filename': self._safe_id(diff['filename']),
405 'oldline_no': change['old_lineno']}
406 anchor_new = "%(filename)s_n%(oldline_no)s" % \
407 {'filename': self._safe_id(diff['filename']),
408 'oldline_no': change['new_lineno']}
409 cond_old = change['old_lineno'] != '...' and \
410 change['old_lineno']
411 cond_new = change['new_lineno'] != '...' and \
412 change['new_lineno']
413 if cond_old:
414 anchor_old_id = 'id="%s"' % anchor_old
415 if cond_new:
416 anchor_new_id = 'id="%s"' % anchor_new
417 ###########################################################
418 # OLD LINE NUMBER
419 ###########################################################
420 _html.append('''\t<td %(a_id)s class="%(old_lineno_cls)s">''' \
421 % {'a_id': anchor_old_id,
422 'old_lineno_cls': old_lineno_class})
423
424 _html.append('''<pre>%(link)s</pre>''' \
425 % {'link':
426 _link_to_if(cond_old, change['old_lineno'], '#%s' \
427 % anchor_old)})
428 _html.append('''</td>\n''')
429 ###########################################################
430 # NEW LINE NUMBER
431 ###########################################################
432
433 _html.append('''\t<td %(a_id)s class="%(new_lineno_cls)s">''' \
434 % {'a_id': anchor_new_id,
435 'new_lineno_cls': new_lineno_class})
436
437 _html.append('''<pre>%(link)s</pre>''' \
438 % {'link':
439 _link_to_if(cond_new, change['new_lineno'], '#%s' \
440 % anchor_new)})
441 _html.append('''</td>\n''')
442 ###########################################################
443 # CODE
444 ###########################################################
445 _html.append('''\t<td class="%(code_class)s">''' \
446 % {'code_class': code_class})
447 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' \
448 % {'code': change['line']})
449 _html.append('''\t</td>''')
450 _html.append('''\n</tr>\n''')
451 _html.append('''</table>''')
452 if _html_empty:
453 return None
454 return ''.join(_html)
455
456 def stat(self):
457 """
458 Returns tuple of adde,and removed lines for this instance
459 """
460 return self.adds, self.removes
@@ -0,0 +1,13 b''
1 import imp
2
3
4 def create_module(name, path):
5 """
6 Returns module created *on the fly*. Returned module would have name same
7 as given ``name`` and would contain code read from file at the given
8 ``path`` (it may also be a zip or package containing *__main__* module).
9 """
10 module = imp.new_module(name)
11 module.__file__ = path
12 execfile(path, module.__dict__)
13 return module
@@ -0,0 +1,28 b''
1 def filesizeformat(bytes, sep=' '):
2 """
3 Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
4 102 B, 2.3 GB etc).
5
6 Grabbed from Django (http://www.djangoproject.com), slightly modified.
7
8 :param bytes: size in bytes (as integer)
9 :param sep: string separator between number and abbreviation
10 """
11 try:
12 bytes = float(bytes)
13 except (TypeError, ValueError, UnicodeDecodeError):
14 return '0%sB' % sep
15
16 if bytes < 1024:
17 size = bytes
18 template = '%.0f%sB'
19 elif bytes < 1024 * 1024:
20 size = bytes / 1024
21 template = '%.0f%sKB'
22 elif bytes < 1024 * 1024 * 1024:
23 size = bytes / 1024 / 1024
24 template = '%.1f%sMB'
25 else:
26 size = bytes / 1024 / 1024 / 1024
27 template = '%.2f%sGB'
28 return template % (size, sep)
@@ -0,0 +1,252 b''
1 """
2 Utitlites aimed to help achieve mostly basic tasks.
3 """
4 from __future__ import division
5
6 import re
7 import time
8 import datetime
9 import os.path
10 from subprocess import Popen, PIPE
11 from rhodecode.lib.vcs.exceptions import VCSError
12 from rhodecode.lib.vcs.exceptions import RepositoryError
13 from rhodecode.lib.vcs.utils.paths import abspath
14
15 ALIASES = ['hg', 'git']
16
17
18 def get_scm(path, search_recursively=False, explicit_alias=None):
19 """
20 Returns one of alias from ``ALIASES`` (in order of precedence same as
21 shortcuts given in ``ALIASES``) and top working dir path for the given
22 argument. If no scm-specific directory is found or more than one scm is
23 found at that directory, ``VCSError`` is raised.
24
25 :param search_recursively: if set to ``True``, this function would try to
26 move up to parent directory every time no scm is recognized for the
27 currently checked path. Default: ``False``.
28 :param explicit_alias: can be one of available backend aliases, when given
29 it will return given explicit alias in repositories under more than one
30 version control, if explicit_alias is different than found it will raise
31 VCSError
32 """
33 if not os.path.isdir(path):
34 raise VCSError("Given path %s is not a directory" % path)
35
36 def get_scms(path):
37 return [(scm, path) for scm in get_scms_for_path(path)]
38
39 found_scms = get_scms(path)
40 while not found_scms and search_recursively:
41 newpath = abspath(path, '..')
42 if newpath == path:
43 break
44 path = newpath
45 found_scms = get_scms(path)
46
47 if len(found_scms) > 1:
48 for scm in found_scms:
49 if scm[0] == explicit_alias:
50 return scm
51 raise VCSError('More than one [%s] scm found at given path %s'
52 % (','.join((x[0] for x in found_scms)), path))
53
54 if len(found_scms) is 0:
55 raise VCSError('No scm found at given path %s' % path)
56
57 return found_scms[0]
58
59
60 def get_scms_for_path(path):
61 """
62 Returns all scm's found at the given path. If no scm is recognized
63 - empty list is returned.
64
65 :param path: path to directory which should be checked. May be callable.
66
67 :raises VCSError: if given ``path`` is not a directory
68 """
69 from rhodecode.lib.vcs.backends import get_backend
70 if hasattr(path, '__call__'):
71 path = path()
72 if not os.path.isdir(path):
73 raise VCSError("Given path %r is not a directory" % path)
74
75 result = []
76 for key in ALIASES:
77 dirname = os.path.join(path, '.' + key)
78 if os.path.isdir(dirname):
79 result.append(key)
80 continue
81 # We still need to check if it's not bare repository as
82 # bare repos don't have working directories
83 try:
84 get_backend(key)(path)
85 result.append(key)
86 continue
87 except RepositoryError:
88 # Wrong backend
89 pass
90 except VCSError:
91 # No backend at all
92 pass
93 return result
94
95
96 def get_repo_paths(path):
97 """
98 Returns path's subdirectories which seems to be a repository.
99 """
100 repo_paths = []
101 dirnames = (os.path.abspath(dirname) for dirname in os.listdir(path))
102 for dirname in dirnames:
103 try:
104 get_scm(dirname)
105 repo_paths.append(dirname)
106 except VCSError:
107 pass
108 return repo_paths
109
110
111 def run_command(cmd, *args):
112 """
113 Runs command on the system with given ``args``.
114 """
115 command = ' '.join((cmd, args))
116 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
117 stdout, stderr = p.communicate()
118 return p.retcode, stdout, stderr
119
120
121 def get_highlighted_code(name, code, type='terminal'):
122 """
123 If pygments are available on the system
124 then returned output is colored. Otherwise
125 unchanged content is returned.
126 """
127 import logging
128 try:
129 import pygments
130 pygments
131 except ImportError:
132 return code
133 from pygments import highlight
134 from pygments.lexers import guess_lexer_for_filename, ClassNotFound
135 from pygments.formatters import TerminalFormatter
136
137 try:
138 lexer = guess_lexer_for_filename(name, code)
139 formatter = TerminalFormatter()
140 content = highlight(code, lexer, formatter)
141 except ClassNotFound:
142 logging.debug("Couldn't guess Lexer, will not use pygments.")
143 content = code
144 return content
145
146 def parse_changesets(text):
147 """
148 Returns dictionary with *start*, *main* and *end* ids.
149
150 Examples::
151
152 >>> parse_changesets('aaabbb')
153 {'start': None, 'main': 'aaabbb', 'end': None}
154 >>> parse_changesets('aaabbb..cccddd')
155 {'start': 'aaabbb', 'main': None, 'end': 'cccddd'}
156
157 """
158 text = text.strip()
159 CID_RE = r'[a-zA-Z0-9]+'
160 if not '..' in text:
161 m = re.match(r'^(?P<cid>%s)$' % CID_RE, text)
162 if m:
163 return {
164 'start': None,
165 'main': text,
166 'end': None,
167 }
168 else:
169 RE = r'^(?P<start>%s)?\.{2,3}(?P<end>%s)?$' % (CID_RE, CID_RE)
170 m = re.match(RE, text)
171 if m:
172 result = m.groupdict()
173 result['main'] = None
174 return result
175 raise ValueError("IDs not recognized")
176
177 def parse_datetime(text):
178 """
179 Parses given text and returns ``datetime.datetime`` instance or raises
180 ``ValueError``.
181
182 :param text: string of desired date/datetime or something more verbose,
183 like *yesterday*, *2weeks 3days*, etc.
184 """
185
186 text = text.strip().lower()
187
188 INPUT_FORMATS = (
189 '%Y-%m-%d %H:%M:%S',
190 '%Y-%m-%d %H:%M',
191 '%Y-%m-%d',
192 '%m/%d/%Y %H:%M:%S',
193 '%m/%d/%Y %H:%M',
194 '%m/%d/%Y',
195 '%m/%d/%y %H:%M:%S',
196 '%m/%d/%y %H:%M',
197 '%m/%d/%y',
198 )
199 for format in INPUT_FORMATS:
200 try:
201 return datetime.datetime(*time.strptime(text, format)[:6])
202 except ValueError:
203 pass
204
205 # Try descriptive texts
206 if text == 'tomorrow':
207 future = datetime.datetime.now() + datetime.timedelta(days=1)
208 args = future.timetuple()[:3] + (23, 59, 59)
209 return datetime.datetime(*args)
210 elif text == 'today':
211 return datetime.datetime(*datetime.datetime.today().timetuple()[:3])
212 elif text == 'now':
213 return datetime.datetime.now()
214 elif text == 'yesterday':
215 past = datetime.datetime.now() - datetime.timedelta(days=1)
216 return datetime.datetime(*past.timetuple()[:3])
217 else:
218 days = 0
219 matched = re.match(
220 r'^((?P<weeks>\d+) ?w(eeks?)?)? ?((?P<days>\d+) ?d(ays?)?)?$', text)
221 if matched:
222 groupdict = matched.groupdict()
223 if groupdict['days']:
224 days += int(matched.groupdict()['days'])
225 if groupdict['weeks']:
226 days += int(matched.groupdict()['weeks']) * 7
227 past = datetime.datetime.now() - datetime.timedelta(days=days)
228 return datetime.datetime(*past.timetuple()[:3])
229
230 raise ValueError('Wrong date: "%s"' % text)
231
232
233 def get_dict_for_attrs(obj, attrs):
234 """
235 Returns dictionary for each attribute from given ``obj``.
236 """
237 data = {}
238 for attr in attrs:
239 data[attr] = getattr(obj, attr)
240 return data
241
242
243 def get_total_seconds(timedelta):
244 """
245 Backported for Python 2.5.
246
247 See http://docs.python.org/library/datetime.html.
248 """
249 return ((timedelta.microseconds + (
250 timedelta.seconds +
251 timedelta.days * 24 * 60 * 60
252 ) * 10**6) / 10**6)
@@ -0,0 +1,12 b''
1 """Mercurial libs compatibility
2
3 """
4 from mercurial import archival, merge as hg_merge, patch, ui
5 from mercurial.commands import clone, nullid, pull
6 from mercurial.context import memctx, memfilectx
7 from mercurial.error import RepoError, RepoLookupError, Abort
8 from mercurial.hgweb.common import get_contact
9 from mercurial.localrepo import localrepository
10 from mercurial.match import match
11 from mercurial.mdiff import diffopts
12 from mercurial.node import hex
@@ -0,0 +1,27 b''
1 from rhodecode.lib.vcs.exceptions import VCSError
2
3
4 def import_class(class_path):
5 """
6 Returns class from the given path.
7
8 For example, in order to get class located at
9 ``vcs.backends.hg.MercurialRepository``:
10
11 try:
12 hgrepo = import_class('vcs.backends.hg.MercurialRepository')
13 except VCSError:
14 # hadle error
15 """
16 splitted = class_path.split('.')
17 mod_path = '.'.join(splitted[:-1])
18 class_name = splitted[-1]
19 try:
20 class_mod = __import__(mod_path, {}, {}, [class_name])
21 except ImportError, err:
22 msg = "There was problem while trying to import backend class. "\
23 "Original error was:\n%s" % err
24 raise VCSError(msg)
25 cls = getattr(class_mod, class_name)
26
27 return cls
@@ -0,0 +1,27 b''
1 class LazyProperty(object):
2 """
3 Decorator for easier creation of ``property`` from potentially expensive to
4 calculate attribute of the class.
5
6 Usage::
7
8 class Foo(object):
9 @LazyProperty
10 def bar(self):
11 print 'Calculating self._bar'
12 return 42
13
14 Taken from http://blog.pythonisito.com/2008/08/lazy-descriptors.html and
15 used widely.
16 """
17
18 def __init__(self, func):
19 self._func = func
20 self.__name__ = func.__name__
21 self.__doc__ = func.__doc__
22
23 def __get__(self, obj, klass=None):
24 if obj is None:
25 return None
26 result = obj.__dict__[self.__name__] = self._func(obj)
27 return result
@@ -0,0 +1,72 b''
1 import os
2
3
4 class LockFile(object):
5 """Provides methods to obtain, check for, and release a file based lock which
6 should be used to handle concurrent access to the same file.
7
8 As we are a utility class to be derived from, we only use protected methods.
9
10 Locks will automatically be released on destruction"""
11 __slots__ = ("_file_path", "_owns_lock")
12
13 def __init__(self, file_path):
14 self._file_path = file_path
15 self._owns_lock = False
16
17 def __del__(self):
18 self._release_lock()
19
20 def _lock_file_path(self):
21 """:return: Path to lockfile"""
22 return "%s.lock" % (self._file_path)
23
24 def _has_lock(self):
25 """:return: True if we have a lock and if the lockfile still exists
26 :raise AssertionError: if our lock-file does not exist"""
27 if not self._owns_lock:
28 return False
29
30 return True
31
32 def _obtain_lock_or_raise(self):
33 """Create a lock file as flag for other instances, mark our instance as lock-holder
34
35 :raise IOError: if a lock was already present or a lock file could not be written"""
36 if self._has_lock():
37 return
38 lock_file = self._lock_file_path()
39 if os.path.isfile(lock_file):
40 raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" % (self._file_path, lock_file))
41
42 try:
43 fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0)
44 os.close(fd)
45 except OSError,e:
46 raise IOError(str(e))
47
48 self._owns_lock = True
49
50 def _obtain_lock(self):
51 """The default implementation will raise if a lock cannot be obtained.
52 Subclasses may override this method to provide a different implementation"""
53 return self._obtain_lock_or_raise()
54
55 def _release_lock(self):
56 """Release our lock if we have one"""
57 if not self._has_lock():
58 return
59
60 # if someone removed our file beforhand, lets just flag this issue
61 # instead of failing, to make it more usable.
62 lfp = self._lock_file_path()
63 try:
64 # on bloody windows, the file needs write permissions to be removable.
65 # Why ...
66 if os.name == 'nt':
67 os.chmod(lfp, 0777)
68 # END handle win32
69 os.remove(lfp)
70 except OSError:
71 pass
72 self._owns_lock = False
@@ -0,0 +1,102 b''
1 """Ordered dict implementation"""
2 from UserDict import DictMixin
3
4
5 class OrderedDict(dict, DictMixin):
6
7 def __init__(self, *args, **kwds):
8 if len(args) > 1:
9 raise TypeError('expected at most 1 arguments, got %d' % len(args))
10 try:
11 self.__end
12 except AttributeError:
13 self.clear()
14 self.update(*args, **kwds)
15
16 def clear(self):
17 self.__end = end = []
18 end += [None, end, end] # sentinel node for doubly linked list
19 self.__map = {} # key --> [key, prev, next]
20 dict.clear(self)
21
22 def __setitem__(self, key, value):
23 if key not in self:
24 end = self.__end
25 curr = end[1]
26 curr[2] = end[1] = self.__map[key] = [key, curr, end]
27 dict.__setitem__(self, key, value)
28
29 def __delitem__(self, key):
30 dict.__delitem__(self, key)
31 key, prev, next = self.__map.pop(key)
32 prev[2] = next
33 next[1] = prev
34
35 def __iter__(self):
36 end = self.__end
37 curr = end[2]
38 while curr is not end:
39 yield curr[0]
40 curr = curr[2]
41
42 def __reversed__(self):
43 end = self.__end
44 curr = end[1]
45 while curr is not end:
46 yield curr[0]
47 curr = curr[1]
48
49 def popitem(self, last=True):
50 if not self:
51 raise KeyError('dictionary is empty')
52 if last:
53 key = reversed(self).next()
54 else:
55 key = iter(self).next()
56 value = self.pop(key)
57 return key, value
58
59 def __reduce__(self):
60 items = [[k, self[k]] for k in self]
61 tmp = self.__map, self.__end
62 del self.__map, self.__end
63 inst_dict = vars(self).copy()
64 self.__map, self.__end = tmp
65 if inst_dict:
66 return (self.__class__, (items,), inst_dict)
67 return self.__class__, (items,)
68
69 def keys(self):
70 return list(self)
71
72 setdefault = DictMixin.setdefault
73 update = DictMixin.update
74 pop = DictMixin.pop
75 values = DictMixin.values
76 items = DictMixin.items
77 iterkeys = DictMixin.iterkeys
78 itervalues = DictMixin.itervalues
79 iteritems = DictMixin.iteritems
80
81 def __repr__(self):
82 if not self:
83 return '%s()' % (self.__class__.__name__,)
84 return '%s(%r)' % (self.__class__.__name__, self.items())
85
86 def copy(self):
87 return self.__class__(self)
88
89 @classmethod
90 def fromkeys(cls, iterable, value=None):
91 d = cls()
92 for key in iterable:
93 d[key] = value
94 return d
95
96 def __eq__(self, other):
97 if isinstance(other, OrderedDict):
98 return len(self) == len(other) and self.items() == other.items()
99 return dict.__eq__(self, other)
100
101 def __ne__(self, other):
102 return not self == other
@@ -0,0 +1,36 b''
1 import os
2
3 abspath = lambda * p: os.path.abspath(os.path.join(*p))
4
5
6 def get_dirs_for_path(*paths):
7 """
8 Returns list of directories, including intermediate.
9 """
10 for path in paths:
11 head = path
12 while head:
13 head, tail = os.path.split(head)
14 if head:
15 yield head
16 else:
17 # We don't need to yield empty path
18 break
19
20
21 def get_dir_size(path):
22 root_path = path
23 size = 0
24 for path, dirs, files in os.walk(root_path):
25 for f in files:
26 try:
27 size += os.path.getsize(os.path.join(path, f))
28 except OSError:
29 pass
30 return size
31
32 def get_user_home():
33 """
34 Returns home path of the user.
35 """
36 return os.getenv('HOME', os.getenv('USERPROFILE'))
@@ -0,0 +1,419 b''
1 # encoding: UTF-8
2 import sys
3 import datetime
4 from string import Template
5 from rhodecode.lib.vcs.utils.filesize import filesizeformat
6 from rhodecode.lib.vcs.utils.helpers import get_total_seconds
7
8
9 class ProgressBarError(Exception):
10 pass
11
12 class AlreadyFinishedError(ProgressBarError):
13 pass
14
15
16 class ProgressBar(object):
17
18 default_elements = ['percentage', 'bar', 'steps']
19
20 def __init__(self, steps=100, stream=None, elements=None):
21 self.step = 0
22 self.steps = steps
23 self.stream = stream or sys.stderr
24 self.bar_char = '='
25 self.width = 50
26 self.separator = ' | '
27 self.elements = elements or self.default_elements
28 self.started = None
29 self.finished = False
30 self.steps_label = 'Step'
31 self.time_label = 'Time'
32 self.eta_label = 'ETA'
33 self.speed_label = 'Speed'
34 self.transfer_label = 'Transfer'
35
36 def __str__(self):
37 return self.get_line()
38
39 def __iter__(self):
40 start = self.step
41 end = self.steps + 1
42 for x in xrange(start, end):
43 self.render(x)
44 yield x
45
46 def get_separator(self):
47 return self.separator
48
49 def get_bar_char(self):
50 return self.bar_char
51
52 def get_bar(self):
53 char = self.get_bar_char()
54 perc = self.get_percentage()
55 length = int(self.width * perc / 100)
56 bar = char * length
57 bar = bar.ljust(self.width)
58 return bar
59
60 def get_elements(self):
61 return self.elements
62
63 def get_template(self):
64 separator = self.get_separator()
65 elements = self.get_elements()
66 return Template(separator.join((('$%s' % e) for e in elements)))
67
68 def get_total_time(self, current_time=None):
69 if current_time is None:
70 current_time = datetime.datetime.now()
71 if not self.started:
72 return datetime.timedelta()
73 return current_time - self.started
74
75 def get_rendered_total_time(self):
76 delta = self.get_total_time()
77 if not delta:
78 ttime = '-'
79 else:
80 ttime = str(delta)
81 return '%s %s' % (self.time_label, ttime)
82
83 def get_eta(self, current_time=None):
84 if current_time is None:
85 current_time = datetime.datetime.now()
86 if self.step == 0:
87 return datetime.timedelta()
88 total_seconds = get_total_seconds(self.get_total_time())
89 eta_seconds = total_seconds * self.steps / self.step - total_seconds
90 return datetime.timedelta(seconds=int(eta_seconds))
91
92 def get_rendered_eta(self):
93 eta = self.get_eta()
94 if not eta:
95 eta = '--:--:--'
96 else:
97 eta = str(eta).rjust(8)
98 return '%s: %s' % (self.eta_label, eta)
99
100 def get_percentage(self):
101 return float(self.step) / self.steps * 100
102
103 def get_rendered_percentage(self):
104 perc = self.get_percentage()
105 return ('%s%%' % (int(perc))).rjust(5)
106
107 def get_rendered_steps(self):
108 return '%s: %s/%s' % (self.steps_label, self.step, self.steps)
109
110 def get_rendered_speed(self, step=None, total_seconds=None):
111 if step is None:
112 step = self.step
113 if total_seconds is None:
114 total_seconds = get_total_seconds(self.get_total_time())
115 if step <= 0 or total_seconds <= 0:
116 speed = '-'
117 else:
118 speed = filesizeformat(float(step) / total_seconds)
119 return '%s: %s/s' % (self.speed_label, speed)
120
121 def get_rendered_transfer(self, step=None, steps=None):
122 if step is None:
123 step = self.step
124 if steps is None:
125 steps = self.steps
126
127 if steps <= 0:
128 return '%s: -' % self.transfer_label
129 total = filesizeformat(float(steps))
130 if step <= 0:
131 transferred = '-'
132 else:
133 transferred = filesizeformat(float(step))
134 return '%s: %s / %s' % (self.transfer_label, transferred, total)
135
136 def get_context(self):
137 return {
138 'percentage': self.get_rendered_percentage(),
139 'bar': self.get_bar(),
140 'steps': self.get_rendered_steps(),
141 'time': self.get_rendered_total_time(),
142 'eta': self.get_rendered_eta(),
143 'speed': self.get_rendered_speed(),
144 'transfer': self.get_rendered_transfer(),
145 }
146
147 def get_line(self):
148 template = self.get_template()
149 context = self.get_context()
150 return template.safe_substitute(**context)
151
152 def write(self, data):
153 self.stream.write(data)
154
155 def render(self, step):
156 if not self.started:
157 self.started = datetime.datetime.now()
158 if self.finished:
159 raise AlreadyFinishedError
160 self.step = step
161 self.write('\r%s' % self)
162 if step == self.steps:
163 self.finished = True
164 if step == self.steps:
165 self.write('\n')
166
167
168 """
169 termcolors.py
170
171 Grabbed from Django (http://www.djangoproject.com)
172 """
173
174 color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
175 foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
176 background = dict([(color_names[x], '4%s' % x) for x in range(8)])
177
178 RESET = '0'
179 opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
180
181 def colorize(text='', opts=(), **kwargs):
182 """
183 Returns your text, enclosed in ANSI graphics codes.
184
185 Depends on the keyword arguments 'fg' and 'bg', and the contents of
186 the opts tuple/list.
187
188 Returns the RESET code if no parameters are given.
189
190 Valid colors:
191 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
192
193 Valid options:
194 'bold'
195 'underscore'
196 'blink'
197 'reverse'
198 'conceal'
199 'noreset' - string will not be auto-terminated with the RESET code
200
201 Examples:
202 colorize('hello', fg='red', bg='blue', opts=('blink',))
203 colorize()
204 colorize('goodbye', opts=('underscore',))
205 print colorize('first line', fg='red', opts=('noreset',))
206 print 'this should be red too'
207 print colorize('and so should this')
208 print 'this should not be red'
209 """
210 code_list = []
211 if text == '' and len(opts) == 1 and opts[0] == 'reset':
212 return '\x1b[%sm' % RESET
213 for k, v in kwargs.iteritems():
214 if k == 'fg':
215 code_list.append(foreground[v])
216 elif k == 'bg':
217 code_list.append(background[v])
218 for o in opts:
219 if o in opt_dict:
220 code_list.append(opt_dict[o])
221 if 'noreset' not in opts:
222 text = text + '\x1b[%sm' % RESET
223 return ('\x1b[%sm' % ';'.join(code_list)) + text
224
225 def make_style(opts=(), **kwargs):
226 """
227 Returns a function with default parameters for colorize()
228
229 Example:
230 bold_red = make_style(opts=('bold',), fg='red')
231 print bold_red('hello')
232 KEYWORD = make_style(fg='yellow')
233 COMMENT = make_style(fg='blue', opts=('bold',))
234 """
235 return lambda text: colorize(text, opts, **kwargs)
236
237 NOCOLOR_PALETTE = 'nocolor'
238 DARK_PALETTE = 'dark'
239 LIGHT_PALETTE = 'light'
240
241 PALETTES = {
242 NOCOLOR_PALETTE: {
243 'ERROR': {},
244 'NOTICE': {},
245 'SQL_FIELD': {},
246 'SQL_COLTYPE': {},
247 'SQL_KEYWORD': {},
248 'SQL_TABLE': {},
249 'HTTP_INFO': {},
250 'HTTP_SUCCESS': {},
251 'HTTP_REDIRECT': {},
252 'HTTP_NOT_MODIFIED': {},
253 'HTTP_BAD_REQUEST': {},
254 'HTTP_NOT_FOUND': {},
255 'HTTP_SERVER_ERROR': {},
256 },
257 DARK_PALETTE: {
258 'ERROR': { 'fg': 'red', 'opts': ('bold',) },
259 'NOTICE': { 'fg': 'red' },
260 'SQL_FIELD': { 'fg': 'green', 'opts': ('bold',) },
261 'SQL_COLTYPE': { 'fg': 'green' },
262 'SQL_KEYWORD': { 'fg': 'yellow' },
263 'SQL_TABLE': { 'opts': ('bold',) },
264 'HTTP_INFO': { 'opts': ('bold',) },
265 'HTTP_SUCCESS': { },
266 'HTTP_REDIRECT': { 'fg': 'green' },
267 'HTTP_NOT_MODIFIED': { 'fg': 'cyan' },
268 'HTTP_BAD_REQUEST': { 'fg': 'red', 'opts': ('bold',) },
269 'HTTP_NOT_FOUND': { 'fg': 'yellow' },
270 'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
271 },
272 LIGHT_PALETTE: {
273 'ERROR': { 'fg': 'red', 'opts': ('bold',) },
274 'NOTICE': { 'fg': 'red' },
275 'SQL_FIELD': { 'fg': 'green', 'opts': ('bold',) },
276 'SQL_COLTYPE': { 'fg': 'green' },
277 'SQL_KEYWORD': { 'fg': 'blue' },
278 'SQL_TABLE': { 'opts': ('bold',) },
279 'HTTP_INFO': { 'opts': ('bold',) },
280 'HTTP_SUCCESS': { },
281 'HTTP_REDIRECT': { 'fg': 'green', 'opts': ('bold',) },
282 'HTTP_NOT_MODIFIED': { 'fg': 'green' },
283 'HTTP_BAD_REQUEST': { 'fg': 'red', 'opts': ('bold',) },
284 'HTTP_NOT_FOUND': { 'fg': 'red' },
285 'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
286 }
287 }
288 DEFAULT_PALETTE = DARK_PALETTE
289
290 # ---------------------------- #
291 # --- End of termcolors.py --- #
292 # ---------------------------- #
293
294
295 class ColoredProgressBar(ProgressBar):
296
297 BAR_COLORS = (
298 (10, 'red'),
299 (30, 'magenta'),
300 (50, 'yellow'),
301 (99, 'green'),
302 (100, 'blue'),
303 )
304
305 def get_line(self):
306 line = super(ColoredProgressBar, self).get_line()
307 perc = self.get_percentage()
308 if perc > 100:
309 color = 'blue'
310 for max_perc, color in self.BAR_COLORS:
311 if perc <= max_perc:
312 break
313 return colorize(line, fg=color)
314
315
316 class AnimatedProgressBar(ProgressBar):
317
318 def get_bar_char(self):
319 chars = '-/|\\'
320 if self.step >= self.steps:
321 return '='
322 return chars[self.step % len(chars)]
323
324
325 class BarOnlyProgressBar(ProgressBar):
326
327 default_elements = ['bar', 'steps']
328
329 def get_bar(self):
330 bar = super(BarOnlyProgressBar, self).get_bar()
331 perc = self.get_percentage()
332 perc_text = '%s%%' % int(perc)
333 text = (' %s%% ' % (perc_text)).center(self.width, '=')
334 L = text.find(' ')
335 R = text.rfind(' ')
336 bar = ' '.join((bar[:L], perc_text, bar[R:]))
337 return bar
338
339
340 class AnimatedColoredProgressBar(AnimatedProgressBar,
341 ColoredProgressBar):
342 pass
343
344
345 class BarOnlyColoredProgressBar(ColoredProgressBar,
346 BarOnlyProgressBar):
347 pass
348
349
350
351 def main():
352 import time
353
354 print "Standard progress bar..."
355 bar = ProgressBar(30)
356 for x in xrange(1, 31):
357 bar.render(x)
358 time.sleep(0.02)
359 bar.stream.write('\n')
360 print
361
362 print "Empty bar..."
363 bar = ProgressBar(50)
364 bar.render(0)
365 print
366 print
367
368 print "Colored bar..."
369 bar = ColoredProgressBar(20)
370 for x in bar:
371 time.sleep(0.01)
372 print
373
374 print "Animated char bar..."
375 bar = AnimatedProgressBar(20)
376 for x in bar:
377 time.sleep(0.01)
378 print
379
380 print "Animated + colored char bar..."
381 bar = AnimatedColoredProgressBar(20)
382 for x in bar:
383 time.sleep(0.01)
384 print
385
386 print "Bar only ..."
387 bar = BarOnlyProgressBar(20)
388 for x in bar:
389 time.sleep(0.01)
390 print
391
392 print "Colored, longer bar-only, eta, total time ..."
393 bar = BarOnlyColoredProgressBar(40)
394 bar.width = 60
395 bar.elements += ['time', 'eta']
396 for x in bar:
397 time.sleep(0.01)
398 print
399 print
400
401 print "File transfer bar, breaks after 2 seconds ..."
402 total_bytes = 1024 * 1024 * 2
403 bar = ProgressBar(total_bytes)
404 bar.width = 50
405 bar.elements.remove('steps')
406 bar.elements += ['transfer', 'time', 'eta', 'speed']
407 for x in xrange(0, bar.steps, 1024):
408 bar.render(x)
409 time.sleep(0.01)
410 now = datetime.datetime.now()
411 if now - bar.started >= datetime.timedelta(seconds=2):
412 break
413 print
414 print
415
416
417
418 if __name__ == '__main__':
419 main()
@@ -0,0 +1,200 b''
1 """
2 termcolors.py
3
4 Grabbed from Django (http://www.djangoproject.com)
5 """
6
7 color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
8 foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
9 background = dict([(color_names[x], '4%s' % x) for x in range(8)])
10
11 RESET = '0'
12 opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
13
14 def colorize(text='', opts=(), **kwargs):
15 """
16 Returns your text, enclosed in ANSI graphics codes.
17
18 Depends on the keyword arguments 'fg' and 'bg', and the contents of
19 the opts tuple/list.
20
21 Returns the RESET code if no parameters are given.
22
23 Valid colors:
24 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
25
26 Valid options:
27 'bold'
28 'underscore'
29 'blink'
30 'reverse'
31 'conceal'
32 'noreset' - string will not be auto-terminated with the RESET code
33
34 Examples:
35 colorize('hello', fg='red', bg='blue', opts=('blink',))
36 colorize()
37 colorize('goodbye', opts=('underscore',))
38 print colorize('first line', fg='red', opts=('noreset',))
39 print 'this should be red too'
40 print colorize('and so should this')
41 print 'this should not be red'
42 """
43 code_list = []
44 if text == '' and len(opts) == 1 and opts[0] == 'reset':
45 return '\x1b[%sm' % RESET
46 for k, v in kwargs.iteritems():
47 if k == 'fg':
48 code_list.append(foreground[v])
49 elif k == 'bg':
50 code_list.append(background[v])
51 for o in opts:
52 if o in opt_dict:
53 code_list.append(opt_dict[o])
54 if 'noreset' not in opts:
55 text = text + '\x1b[%sm' % RESET
56 return ('\x1b[%sm' % ';'.join(code_list)) + text
57
58 def make_style(opts=(), **kwargs):
59 """
60 Returns a function with default parameters for colorize()
61
62 Example:
63 bold_red = make_style(opts=('bold',), fg='red')
64 print bold_red('hello')
65 KEYWORD = make_style(fg='yellow')
66 COMMENT = make_style(fg='blue', opts=('bold',))
67 """
68 return lambda text: colorize(text, opts, **kwargs)
69
70 NOCOLOR_PALETTE = 'nocolor'
71 DARK_PALETTE = 'dark'
72 LIGHT_PALETTE = 'light'
73
74 PALETTES = {
75 NOCOLOR_PALETTE: {
76 'ERROR': {},
77 'NOTICE': {},
78 'SQL_FIELD': {},
79 'SQL_COLTYPE': {},
80 'SQL_KEYWORD': {},
81 'SQL_TABLE': {},
82 'HTTP_INFO': {},
83 'HTTP_SUCCESS': {},
84 'HTTP_REDIRECT': {},
85 'HTTP_NOT_MODIFIED': {},
86 'HTTP_BAD_REQUEST': {},
87 'HTTP_NOT_FOUND': {},
88 'HTTP_SERVER_ERROR': {},
89 },
90 DARK_PALETTE: {
91 'ERROR': { 'fg': 'red', 'opts': ('bold',) },
92 'NOTICE': { 'fg': 'red' },
93 'SQL_FIELD': { 'fg': 'green', 'opts': ('bold',) },
94 'SQL_COLTYPE': { 'fg': 'green' },
95 'SQL_KEYWORD': { 'fg': 'yellow' },
96 'SQL_TABLE': { 'opts': ('bold',) },
97 'HTTP_INFO': { 'opts': ('bold',) },
98 'HTTP_SUCCESS': { },
99 'HTTP_REDIRECT': { 'fg': 'green' },
100 'HTTP_NOT_MODIFIED': { 'fg': 'cyan' },
101 'HTTP_BAD_REQUEST': { 'fg': 'red', 'opts': ('bold',) },
102 'HTTP_NOT_FOUND': { 'fg': 'yellow' },
103 'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
104 },
105 LIGHT_PALETTE: {
106 'ERROR': { 'fg': 'red', 'opts': ('bold',) },
107 'NOTICE': { 'fg': 'red' },
108 'SQL_FIELD': { 'fg': 'green', 'opts': ('bold',) },
109 'SQL_COLTYPE': { 'fg': 'green' },
110 'SQL_KEYWORD': { 'fg': 'blue' },
111 'SQL_TABLE': { 'opts': ('bold',) },
112 'HTTP_INFO': { 'opts': ('bold',) },
113 'HTTP_SUCCESS': { },
114 'HTTP_REDIRECT': { 'fg': 'green', 'opts': ('bold',) },
115 'HTTP_NOT_MODIFIED': { 'fg': 'green' },
116 'HTTP_BAD_REQUEST': { 'fg': 'red', 'opts': ('bold',) },
117 'HTTP_NOT_FOUND': { 'fg': 'red' },
118 'HTTP_SERVER_ERROR': { 'fg': 'magenta', 'opts': ('bold',) },
119 }
120 }
121 DEFAULT_PALETTE = DARK_PALETTE
122
123 def parse_color_setting(config_string):
124 """Parse a DJANGO_COLORS environment variable to produce the system palette
125
126 The general form of a pallete definition is:
127
128 "palette;role=fg;role=fg/bg;role=fg,option,option;role=fg/bg,option,option"
129
130 where:
131 palette is a named palette; one of 'light', 'dark', or 'nocolor'.
132 role is a named style used by Django
133 fg is a background color.
134 bg is a background color.
135 option is a display options.
136
137 Specifying a named palette is the same as manually specifying the individual
138 definitions for each role. Any individual definitions following the pallete
139 definition will augment the base palette definition.
140
141 Valid roles:
142 'error', 'notice', 'sql_field', 'sql_coltype', 'sql_keyword', 'sql_table',
143 'http_info', 'http_success', 'http_redirect', 'http_bad_request',
144 'http_not_found', 'http_server_error'
145
146 Valid colors:
147 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
148
149 Valid options:
150 'bold', 'underscore', 'blink', 'reverse', 'conceal'
151
152 """
153 if not config_string:
154 return PALETTES[DEFAULT_PALETTE]
155
156 # Split the color configuration into parts
157 parts = config_string.lower().split(';')
158 palette = PALETTES[NOCOLOR_PALETTE].copy()
159 for part in parts:
160 if part in PALETTES:
161 # A default palette has been specified
162 palette.update(PALETTES[part])
163 elif '=' in part:
164 # Process a palette defining string
165 definition = {}
166
167 # Break the definition into the role,
168 # plus the list of specific instructions.
169 # The role must be in upper case
170 role, instructions = part.split('=')
171 role = role.upper()
172
173 styles = instructions.split(',')
174 styles.reverse()
175
176 # The first instruction can contain a slash
177 # to break apart fg/bg.
178 colors = styles.pop().split('/')
179 colors.reverse()
180 fg = colors.pop()
181 if fg in color_names:
182 definition['fg'] = fg
183 if colors and colors[-1] in color_names:
184 definition['bg'] = colors[-1]
185
186 # All remaining instructions are options
187 opts = tuple(s for s in styles if s in opt_dict.keys())
188 if opts:
189 definition['opts'] = opts
190
191 # The nocolor palette has all available roles.
192 # Use that palette as the basis for determining
193 # if the role is valid.
194 if role in PALETTES[NOCOLOR_PALETTE] and definition:
195 palette[role] = definition
196
197 # If there are no colors specified, return the empty palette.
198 if palette == PALETTES[NOCOLOR_PALETTE]:
199 return None
200 return palette
@@ -0,0 +1,143 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
6 comments model for RhodeCode
7
8 :created_on: Nov 11, 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
26 import logging
27 import traceback
28
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
31
32 from rhodecode.lib import extract_mentioned_users
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
36 from rhodecode.model.notification import NotificationModel
37
38 log = logging.getLogger(__name__)
39
40
41 class ChangesetCommentsModel(BaseModel):
42
43 def __get_changeset_comment(self, changeset_comment):
44 return self._get_instance(ChangesetComment, changeset_comment)
45
46 def _extract_mentions(self, s):
47 user_objects = []
48 for username in extract_mentioned_users(s):
49 user_obj = User.get_by_username(username, case_insensitive=True)
50 if user_obj:
51 user_objects.append(user_obj)
52 return user_objects
53
54 def create(self, text, repo_id, user_id, revision, f_path=None,
55 line_no=None):
56 """
57 Creates new comment for changeset
58
59 :param text:
60 :param repo_id:
61 :param user_id:
62 :param revision:
63 :param f_path:
64 :param line_no:
65 """
66 if text:
67 repo = Repository.get(repo_id)
68 cs = repo.scm_instance.get_changeset(revision)
69 desc = cs.message
70 author = cs.author_email
71 comment = ChangesetComment()
72 comment.repo = repo
73 comment.user_id = user_id
74 comment.revision = revision
75 comment.text = text
76 comment.f_path = f_path
77 comment.line_no = line_no
78
79 self.sa.add(comment)
80 self.sa.flush()
81
82 # make notification
83 line = ''
84 if line_no:
85 line = _('on line %s') % line_no
86 subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
87 {'commit_desc': desc, 'line': line},
88 h.url('changeset_home', repo_name=repo.repo_name,
89 revision=revision,
90 anchor='comment-%s' % comment.comment_id,
91 qualified=True,
92 )
93 )
94 body = text
95 recipients = ChangesetComment.get_users(revision=revision)
96 # add changeset author
97 recipients += [User.get_by_email(author)]
98
99 NotificationModel().create(created_by=user_id, subject=subj,
100 body=body, recipients=recipients,
101 type_=Notification.TYPE_CHANGESET_COMMENT)
102
103 mention_recipients = set(self._extract_mentions(body))\
104 .difference(recipients)
105 if mention_recipients:
106 subj = _('[Mention]') + ' ' + subj
107 NotificationModel().create(created_by=user_id, subject=subj,
108 body=body,
109 recipients=mention_recipients,
110 type_=Notification.TYPE_CHANGESET_COMMENT)
111
112 return comment
113
114 def delete(self, comment):
115 """
116 Deletes given comment
117
118 :param comment_id:
119 """
120 comment = self.__get_changeset_comment(comment)
121 self.sa.delete(comment)
122
123 return comment
124
125 def get_comments(self, repo_id, revision):
126 return ChangesetComment.query()\
127 .filter(ChangesetComment.repo_id == repo_id)\
128 .filter(ChangesetComment.revision == revision)\
129 .filter(ChangesetComment.line_no == None)\
130 .filter(ChangesetComment.f_path == None).all()
131
132 def get_inline_comments(self, repo_id, revision):
133 comments = self.sa.query(ChangesetComment)\
134 .filter(ChangesetComment.repo_id == repo_id)\
135 .filter(ChangesetComment.revision == revision)\
136 .filter(ChangesetComment.line_no != None)\
137 .filter(ChangesetComment.f_path != None).all()
138
139 paths = defaultdict(lambda: defaultdict(list))
140
141 for co in comments:
142 paths[co.f_path][co.line_no].append(co)
143 return paths.items()
@@ -0,0 +1,216 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Model for notifications
7
8
9 :created_on: Nov 20, 2011
10 :author: marcink
11 :copyright: (C) 2010-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 os
28 import logging
29 import traceback
30 import datetime
31
32 from pylons.i18n.translation import _
33
34 import rhodecode
35 from rhodecode.lib import helpers as h
36 from rhodecode.model import BaseModel
37 from rhodecode.model.db import Notification, User, UserNotification
38
39 log = logging.getLogger(__name__)
40
41
42 class NotificationModel(BaseModel):
43
44 def __get_user(self, user):
45 return self._get_instance(User, user, callback=User.get_by_username)
46
47 def __get_notification(self, notification):
48 if isinstance(notification, Notification):
49 return notification
50 elif isinstance(notification, int):
51 return Notification.get(notification)
52 else:
53 if notification:
54 raise Exception('notification must be int or Instance'
55 ' of Notification got %s' % type(notification))
56
57 def create(self, created_by, subject, body, recipients=None,
58 type_=Notification.TYPE_MESSAGE, with_email=True,
59 email_kwargs={}):
60 """
61
62 Creates notification of given type
63
64 :param created_by: int, str or User instance. User who created this
65 notification
66 :param subject:
67 :param body:
68 :param recipients: list of int, str or User objects, when None
69 is given send to all admins
70 :param type_: type of notification
71 :param with_email: send email with this notification
72 :param email_kwargs: additional dict to pass as args to email template
73 """
74 from rhodecode.lib.celerylib import tasks, run_task
75
76 if recipients and not getattr(recipients, '__iter__', False):
77 raise Exception('recipients must be a list of iterable')
78
79 created_by_obj = self.__get_user(created_by)
80
81 if recipients:
82 recipients_objs = []
83 for u in recipients:
84 obj = self.__get_user(u)
85 if obj:
86 recipients_objs.append(obj)
87 recipients_objs = set(recipients_objs)
88 else:
89 # empty recipients means to all admins
90 recipients_objs = User.query().filter(User.admin == True).all()
91
92 notif = Notification.create(created_by=created_by_obj, subject=subject,
93 body=body, recipients=recipients_objs,
94 type_=type_)
95
96 if with_email is False:
97 return notif
98
99 # send email with notification
100 for rec in recipients_objs:
101 email_subject = NotificationModel().make_description(notif, False)
102 type_ = type_
103 email_body = body
104 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
105 kwargs.update(email_kwargs)
106 email_body_html = EmailNotificationModel()\
107 .get_email_tmpl(type_, **kwargs)
108 run_task(tasks.send_email, rec.email, email_subject, email_body,
109 email_body_html)
110
111 return notif
112
113 def delete(self, user, notification):
114 # we don't want to remove actual notification just the assignment
115 try:
116 notification = self.__get_notification(notification)
117 user = self.__get_user(user)
118 if notification and user:
119 obj = UserNotification.query()\
120 .filter(UserNotification.user == user)\
121 .filter(UserNotification.notification
122 == notification)\
123 .one()
124 self.sa.delete(obj)
125 return True
126 except Exception:
127 log.error(traceback.format_exc())
128 raise
129
130 def get_for_user(self, user):
131 user = self.__get_user(user)
132 return user.notifications
133
134 def mark_all_read_for_user(self, user):
135 user = self.__get_user(user)
136 UserNotification.query()\
137 .filter(UserNotification.read==False)\
138 .update({'read': True})
139
140 def get_unread_cnt_for_user(self, user):
141 user = self.__get_user(user)
142 return UserNotification.query()\
143 .filter(UserNotification.read == False)\
144 .filter(UserNotification.user == user).count()
145
146 def get_unread_for_user(self, user):
147 user = self.__get_user(user)
148 return [x.notification for x in UserNotification.query()\
149 .filter(UserNotification.read == False)\
150 .filter(UserNotification.user == user).all()]
151
152 def get_user_notification(self, user, notification):
153 user = self.__get_user(user)
154 notification = self.__get_notification(notification)
155
156 return UserNotification.query()\
157 .filter(UserNotification.notification == notification)\
158 .filter(UserNotification.user == user).scalar()
159
160 def make_description(self, notification, show_age=True):
161 """
162 Creates a human readable description based on properties
163 of notification object
164 """
165
166 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
167 notification.TYPE_MESSAGE:_('sent message'),
168 notification.TYPE_MENTION:_('mentioned you'),
169 notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
170
171 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
172
173 tmpl = "%(user)s %(action)s %(when)s"
174 if show_age:
175 when = h.age(notification.created_on)
176 else:
177 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
178 when = DTF(notification.created_on)
179 data = dict(user=notification.created_by_user.username,
180 action=_map[notification.type_],
181 when=when)
182 return tmpl % data
183
184
185 class EmailNotificationModel(BaseModel):
186
187 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
188 TYPE_PASSWORD_RESET = 'passoword_link'
189 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
190 TYPE_DEFAULT = 'default'
191
192 def __init__(self):
193 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
194 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
195
196 self.email_types = {
197 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
198 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
199 self.TYPE_REGISTRATION:'email_templates/registration.html',
200 self.TYPE_DEFAULT:'email_templates/default.html'
201 }
202
203 def get_email_tmpl(self, type_, **kwargs):
204 """
205 return generated template for email based on given type
206
207 :param type_:
208 """
209
210 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
211 email_template = self._tmpl_lookup.get_template(base)
212 # translator inject
213 _kwargs = {'_':_}
214 _kwargs.update(kwargs)
215 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
216 return email_template.render(**_kwargs)
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,78 b''
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
3 ## <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
4
5 <%def name="quick_menu(repo_name)">
6 <ul class="menu_items hidden">
7 <li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li>
8 <li>
9 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
10 <span class="icon">
11 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
12 </span>
13 <span>${_('Summary')}</span>
14 </a>
15 </li>
16 <li>
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 <span class="icon">
19 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
20 </span>
21 <span>${_('Changelog')}</span>
22 </a>
23 </li>
24 <li>
25 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
26 <span class="icon">
27 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
28 </span>
29 <span>${_('Files')}</span>
30 </a>
31 </li>
32 <li>
33 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
34 <span class="icon">
35 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Fork')}" />
36 </span>
37 <span>${_('Fork')}</span>
38 </a>
39 </li>
40 </ul>
41 </%def>
42
43 <%def name="repo_name(name,rtype,private,fork_of)">
44 <div style="white-space: nowrap">
45 ##TYPE OF REPO
46 %if h.is_hg(rtype):
47 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
48 %elif h.is_git(rtype):
49 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
50 %endif
51
52 ##PRIVATE/PUBLIC
53 %if private:
54 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
55 %else:
56 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
57 %endif
58
59 ##NAME
60 ${h.link_to(name,h.url('summary_home',repo_name=name),class_="repo_name")}
61 %if fork_of:
62 <a href="${h.url('summary_home',repo_name=fork_of)}">
63 <img class="icon" alt="${_('fork')}" title="${_('Fork of')} ${fork_of}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
64 %endif
65 </div>
66 </%def>
67
68
69
70 <%def name="revision(name,rev,tip,author,last_msg)">
71 <div>
72 %if rev >= 0:
73 <pre><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></pre>
74 %else:
75 ${_('No changesets yet')}
76 %endif
77 </div>
78 </%def>
@@ -0,0 +1,53 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${_('My Notifications')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 </%def>
7
8 <%def name="breadcrumbs_links()">
9 ${_('My Notifications')}
10 </%def>
11
12 <%def name="page_nav()">
13 ${self.menu('admin')}
14 </%def>
15
16 <%def name="main()">
17 <div class="box">
18 <!-- box / title -->
19 <div class="title">
20 ${self.breadcrumbs()}
21 ##<ul class="links">
22 ## <li>
23 ## <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
24 ## </li>
25 ##</ul>
26 </div>
27 %if c.notifications:
28 <div style="padding:10px 15px;text-align: right">
29 <span id='mark_all_read' class="ui-btn">${_('Mark all read')}</span>
30 </div>
31 %endif
32 <div id='notification_data'>
33 <%include file='notifications_data.html'/>
34 </div>
35 </div>
36 <script type="text/javascript">
37 var url_del = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
38 YUE.on(YUQ('.delete-notification'),'click',function(e){
39 var notification_id = e.currentTarget.id;
40 deleteNotification(url_del,notification_id)
41 })
42 YUE.on('mark_all_read','click',function(e){
43 var url = "${h.url('notifications_mark_all_read')}";
44 ypjax(url,'notification_data',function(){
45 YUD.get('notification_counter').innerHTML=0;
46 YUE.on(YUQ('.delete-notification'),'click',function(e){
47 var notification_id = e.currentTarget.id;
48 deleteNotification(url_del,notification_id)
49 })
50 });
51 })
52 </script>
53 </%def>
@@ -0,0 +1,28 b''
1
2 %if c.notifications:
3 <%
4 unread = lambda n:{False:'unread'}.get(n)
5 %>
6 <div class="table">
7 <div class="notification-list">
8 %for notification in c.notifications:
9 <div id="notification_${notification.notification.notification_id}" class="container ${unread(notification.read)}">
10 <div class="notification-header">
11 <div class="gravatar">
12 <img alt="gravatar" src="${h.gravatar_url(h.email(notification.notification.created_by_user.email),24)}"/>
13 </div>
14 <div class="desc ${unread(notification.read)}">
15 <a href="${url('notification', notification_id=notification.notification.notification_id)}">${notification.notification.description}</a>
16 </div>
17 <div class="delete-notifications">
18 <span id="${notification.notification.notification_id}" class="delete-notification delete_icon action"></span>
19 </div>
20 </div>
21 <div class="notification-subject">${h.literal(notification.notification.subject)}</div>
22 </div>
23 %endfor
24 </div>
25 </div>
26 %else:
27 <div class="table">${_('No notifications here yet')}</div>
28 %endif
@@ -0,0 +1,54 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${_('Show notification')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 </%def>
7
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Notifications'),h.url('notifications'))}
10 &raquo;
11 ${_('Show notification')}
12 </%def>
13
14 <%def name="page_nav()">
15 ${self.menu('admin')}
16 </%def>
17
18 <%def name="main()">
19 <div class="box">
20 <!-- box / title -->
21 <div class="title">
22 ${self.breadcrumbs()}
23 ##<ul class="links">
24 ## <li>
25 ## <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
26 ## </li>
27 ##</ul>
28 </div>
29 <div class="table">
30 <div id="notification_${c.notification.notification_id}">
31 <div class="notification-header">
32 <div class="gravatar">
33 <img alt="gravatar" src="${h.gravatar_url(h.email(c.notification.created_by_user.email),24)}"/>
34 </div>
35 <div class="desc">
36 ${c.notification.description}
37 </div>
38 <div class="delete-notifications">
39 <span id="${c.notification.notification_id}" class="delete-notification delete_icon action"></span>
40 </div>
41 </div>
42 <div>${h.rst_w_mentions(c.notification.body)}</div>
43 </div>
44 </div>
45 </div>
46 <script type="text/javascript">
47 var url = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
48 var main = "${url('notifications')}";
49 YUE.on(YUQ('.delete-notification'),'click',function(e){
50 var notification_id = e.currentTarget.id;
51 deleteNotification(url,notification_id,[function(){window.location=main}])
52 })
53 </script>
54 </%def>
@@ -0,0 +1,270 b''
1 <table id="permissions_manage" class="noborder">
2 <tr>
3 <td>${_('none')}</td>
4 <td>${_('read')}</td>
5 <td>${_('write')}</td>
6 <td>${_('admin')}</td>
7 <td>${_('member')}</td>
8 <td></td>
9 </tr>
10 ## USERS
11 %for r2p in c.repos_group.repo_group_to_perm:
12 <tr id="id${id(r2p.user.username)}">
13 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
14 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
17 <td style="white-space: nowrap;">
18 <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}
19 </td>
20 <td>
21 %if r2p.user.username !='default':
22 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
23 ${_('revoke')}
24 </span>
25 %endif
26 </td>
27 </tr>
28 %endfor
29
30 ## USERS GROUPS
31 %for g2p in c.repos_group.users_group_to_perm:
32 <tr id="id${id(g2p.users_group.users_group_name)}">
33 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
34 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
37 <td style="white-space: nowrap;">
38 <img style="vertical-align:bottom" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
39 </td>
40 <td>
41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
42 ${_('revoke')}
43 </span>
44 </td>
45 </tr>
46 %endfor
47 <tr id="add_perm_input">
48 <td>${h.radio('perm_new_member','group.none')}</td>
49 <td>${h.radio('perm_new_member','group.read')}</td>
50 <td>${h.radio('perm_new_member','group.write')}</td>
51 <td>${h.radio('perm_new_member','group.admin')}</td>
52 <td class='ac'>
53 <div class="perm_ac" id="perm_ac">
54 ${h.text('perm_new_member_name',class_='yui-ac-input')}
55 ${h.hidden('perm_new_member_type')}
56 <div id="perm_container"></div>
57 </div>
58 </td>
59 <td></td>
60 </tr>
61 <tr>
62 <td colspan="6">
63 <span id="add_perm" class="add_icon" style="cursor: pointer;">
64 ${_('Add another member')}
65 </span>
66 </td>
67 </tr>
68 </table>
69 <script type="text/javascript">
70 function ajaxActionUser(user_id, field_id) {
71 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.name)}";
72 var callback = {
73 success: function (o) {
74 var tr = YUD.get(String(field_id));
75 tr.parentNode.removeChild(tr);
76 },
77 failure: function (o) {
78 alert("${_('Failed to remove user')}");
79 },
80 };
81 var postData = '_method=delete&user_id=' + user_id;
82 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
83 };
84
85 function ajaxActionUsersGroup(users_group_id,field_id){
86 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.name)}";
87 var callback = {
88 success:function(o){
89 var tr = YUD.get(String(field_id));
90 tr.parentNode.removeChild(tr);
91 },
92 failure:function(o){
93 alert("${_('Failed to remove users group')}");
94 },
95 };
96 var postData = '_method=delete&users_group_id='+users_group_id;
97 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
98 };
99
100 YUE.onDOMReady(function () {
101 if (!YUD.hasClass('perm_new_member_name', 'error')) {
102 YUD.setStyle('add_perm_input', 'display', 'none');
103 }
104 YAHOO.util.Event.addListener('add_perm', 'click', function () {
105 YUD.setStyle('add_perm_input', 'display', '');
106 YUD.setStyle('add_perm', 'opacity', '0.6');
107 YUD.setStyle('add_perm', 'cursor', 'default');
108 });
109 });
110
111 YAHOO.example.FnMultipleFields = function () {
112 var myUsers = ${c.users_array|n};
113 var myGroups = ${c.users_groups_array|n};
114
115 // Define a custom search function for the DataSource of users
116 var matchUsers = function (sQuery) {
117 // Case insensitive matching
118 var query = sQuery.toLowerCase();
119 var i = 0;
120 var l = myUsers.length;
121 var matches = [];
122
123 // Match against each name of each contact
124 for (; i < l; i++) {
125 contact = myUsers[i];
126 if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
127 matches[matches.length] = contact;
128 }
129 }
130 return matches;
131 };
132
133 // Define a custom search function for the DataSource of usersGroups
134 var matchGroups = function (sQuery) {
135 // Case insensitive matching
136 var query = sQuery.toLowerCase();
137 var i = 0;
138 var l = myGroups.length;
139 var matches = [];
140
141 // Match against each name of each contact
142 for (; i < l; i++) {
143 matched_group = myGroups[i];
144 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
145 matches[matches.length] = matched_group;
146 }
147 }
148 return matches;
149 };
150
151 //match all
152 var matchAll = function (sQuery) {
153 u = matchUsers(sQuery);
154 g = matchGroups(sQuery);
155 return u.concat(g);
156 };
157
158 // DataScheme for members
159 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
160 memberDS.responseSchema = {
161 fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
162 };
163
164 // DataScheme for owner
165 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
166 ownerDS.responseSchema = {
167 fields: ["id", "fname", "lname", "nname"]
168 };
169
170 // Instantiate AutoComplete for perms
171 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
172 membersAC.useShadow = false;
173 membersAC.resultTypeList = false;
174
175 // Instantiate AutoComplete for owner
176 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
177 ownerAC.useShadow = false;
178 ownerAC.resultTypeList = false;
179
180
181 // Helper highlight function for the formatter
182 var highlightMatch = function (full, snippet, matchindex) {
183 return full.substring(0, matchindex) + "<span class='match'>" + full.substr(matchindex, snippet.length) + "</span>" + full.substring(matchindex + snippet.length);
184 };
185
186 // Custom formatter to highlight the matching letters
187 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
188 var query = sQuery.toLowerCase();
189
190 if (oResultData.grname != undefined) {
191 var grname = oResultData.grname;
192 var grmembers = oResultData.grmembers;
193 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
194 var grprefix = "${_('Group')}: ";
195 var grsuffix = " (" + grmembers + " ${_('members')})";
196
197 if (grnameMatchIndex > -1) {
198 return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix;
199 }
200
201 return grprefix + oResultData.grname + grsuffix;
202 } else if (oResultData.fname != undefined) {
203
204 var fname = oResultData.fname,
205 lname = oResultData.lname,
206 nname = oResultData.nname || "",
207 // Guard against null value
208 fnameMatchIndex = fname.toLowerCase().indexOf(query),
209 lnameMatchIndex = lname.toLowerCase().indexOf(query),
210 nnameMatchIndex = nname.toLowerCase().indexOf(query),
211 displayfname, displaylname, displaynname;
212
213 if (fnameMatchIndex > -1) {
214 displayfname = highlightMatch(fname, query, fnameMatchIndex);
215 } else {
216 displayfname = fname;
217 }
218
219 if (lnameMatchIndex > -1) {
220 displaylname = highlightMatch(lname, query, lnameMatchIndex);
221 } else {
222 displaylname = lname;
223 }
224
225 if (nnameMatchIndex > -1) {
226 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
227 } else {
228 displaynname = nname ? "(" + nname + ")" : "";
229 }
230
231 return displayfname + " " + displaylname + " " + displaynname;
232 } else {
233 return '';
234 }
235 };
236 membersAC.formatResult = custom_formatter;
237 ownerAC.formatResult = custom_formatter;
238
239 var myHandler = function (sType, aArgs) {
240
241 var myAC = aArgs[0]; // reference back to the AC instance
242 var elLI = aArgs[1]; // reference to the selected LI element
243 var oData = aArgs[2]; // object literal of selected item's result data
244 //fill the autocomplete with value
245 if (oData.nname != undefined) {
246 //users
247 myAC.getInputEl().value = oData.nname;
248 YUD.get('perm_new_member_type').value = 'user';
249 } else {
250 //groups
251 myAC.getInputEl().value = oData.grname;
252 YUD.get('perm_new_member_type').value = 'users_group';
253 }
254
255 };
256
257 membersAC.itemSelectEvent.subscribe(myHandler);
258 if(ownerAC.itemSelectEvent){
259 ownerAC.itemSelectEvent.subscribe(myHandler);
260 }
261
262 return {
263 memberDS: memberDS,
264 ownerDS: ownerDS,
265 membersAC: membersAC,
266 ownerAC: ownerAC,
267 };
268 }();
269
270 </script>
@@ -0,0 +1,78 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${c.repo_name} ${_('Bookmarks')} - ${c.rhodecode_name}
6 </%def>
7
8
9 <%def name="breadcrumbs_links()">
10 <input class="q_filter_box" id="q_filter_bookmarks" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
11 ${h.link_to(u'Home',h.url('/'))}
12 &raquo;
13 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
14 &raquo;
15 ${_('bookmarks')}
16 </%def>
17
18 <%def name="page_nav()">
19 ${self.menu('bookmarks')}
20 </%def>
21 <%def name="main()">
22 <div class="box">
23 <!-- box / title -->
24 <div class="title">
25 ${self.breadcrumbs()}
26 </div>
27 <!-- end box / title -->
28 <div class="table">
29 <%include file='bookmarks_data.html'/>
30 </div>
31 </div>
32 <script type="text/javascript">
33
34 // main table sorting
35 var myColumnDefs = [
36 {key:"name",label:"${_('Name')}",sortable:true},
37 {key:"date",label:"${_('Date')}",sortable:true,
38 sortOptions: { sortFunction: dateSort }},
39 {key:"author",label:"${_('Author')}",sortable:true},
40 {key:"revision",label:"${_('Revision')}",sortable:true,
41 sortOptions: { sortFunction: revisionSort }},
42 ];
43
44 var myDataSource = new YAHOO.util.DataSource(YUD.get("bookmarks_data"));
45
46 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
47
48 myDataSource.responseSchema = {
49 fields: [
50 {key:"name"},
51 {key:"date"},
52 {key:"author"},
53 {key:"revision"},
54 ]
55 };
56
57 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
58 {
59 sortedBy:{key:"name",dir:"asc"},
60 MSG_SORTASC:"${_('Click to sort ascending')}",
61 MSG_SORTDESC:"${_('Click to sort descending')}",
62 MSG_EMPTY:"${_('No records found.')}",
63 MSG_ERROR:"${_('Data error.')}",
64 MSG_LOADING:"${_('Loading...')}",
65 }
66 );
67 myDataTable.subscribe('postRenderEvent',function(oArgs) {
68 tooltip_activate();
69 var func = function(node){
70 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
71 }
72 q_filter('q_filter_bookmarks',YUQ('div.table tr td .logbooks .bookbook a'),func);
73 });
74
75 </script>
76
77
78 </%def>
@@ -0,0 +1,33 b''
1 %if c.repo_bookmarks:
2 <div id="table_wrap" class="yui-skin-sam">
3 <table id="bookmarks_data">
4 <thead>
5 <tr>
6 <th class="left">${_('Name')}</th>
7 <th class="left">${_('Date')}</th>
8 <th class="left">${_('Author')}</th>
9 <th class="left">${_('Revision')}</th>
10 </tr>
11 </thead>
12 %for cnt,book in enumerate(c.repo_bookmarks.items()):
13 <tr class="parity${cnt%2}">
14 <td>
15 <span class="logbooks">
16 <span class="bookbook">${h.link_to(book[0],
17 h.url('files_home',repo_name=c.repo_name,revision=book[1].raw_id))}</span>
18 </span>
19 </td>
20 <td><span class="tooltip" title="${h.age(book[1].date)}">${book[1].date}</span></td>
21 <td title="${book[1].author}">${h.person(book[1].author)}</td>
22 <td>
23 <div>
24 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=book[1].raw_id)}">r${book[1].revision}:${h.short_id(book[1].raw_id)}</a></pre>
25 </div>
26 </td>
27 </tr>
28 %endfor
29 </table>
30 </div>
31 %else:
32 ${_('There are no bookmarks yet')}
33 %endif
@@ -0,0 +1,105 b''
1 ## -*- coding: utf-8 -*-
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
4 ## ${comment.comment_block(co)}
5 ##
6 <%def name="comment_block(co)">
7 <div class="comment" id="comment-${co.comment_id}">
8 <div class="comment-wrapp">
9 <div class="meta">
10 <span class="user">
11 <img src="${h.gravatar_url(co.author.email, 20)}" />
12 ${co.author.username}
13 </span>
14 <span class="date">
15 ${h.age(co.modified_at)}
16 </span>
17 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
18 <span class="buttons">
19 <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
20 </span>
21 %endif
22 </div>
23 <div class="text">
24 ${h.rst_w_mentions(co.text)|n}
25 </div>
26 </div>
27 </div>
28 </%def>
29
30
31 <%def name="comment_inline_form(changeset)">
32 <div id='comment-inline-form-template' style="display:none">
33 <div class="comment-inline-form">
34 %if c.rhodecode_user.username != 'default':
35 ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
36 <div class="clearfix">
37 <div class="comment-help">${_('Commenting on line')} {1}. ${_('Comments parsed using')}
38 <a href="${h.url('rst_help')}">RST</a> ${_('syntax')} ${_('with')}
39 <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
40 </div>
41 <textarea id="text_{1}" name="text"></textarea>
42 </div>
43 <div class="comment-button">
44 <input type="hidden" name="f_path" value="{0}">
45 <input type="hidden" name="line" value="{1}">
46 ${h.submit('save', _('Comment'), class_='ui-btn')}
47 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
48 </div>
49 ${h.end_form()}
50 %else:
51 ${h.form('')}
52 <div class="clearfix">
53 <div class="comment-help">
54 ${'You need to be logged in to comment.'} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
55 </div>
56 </div>
57 <div class="comment-button">
58 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
59 </div>
60 ${h.end_form()}
61 %endif
62 </div>
63 </div>
64 </%def>
65
66
67 <%def name="comments(changeset)">
68
69 <div class="comments">
70 <div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
71
72 %for path, lines in c.inline_comments:
73 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.FID(changeset.raw_id,path)}">
74 % for line,comments in lines.iteritems():
75 <div class="inline-comment-placeholder-line" line="${line}" target_id="${h.safeid(h.safe_unicode(path))}">
76 %for co in comments:
77 ${comment_block(co)}
78 %endfor
79 </div>
80 %endfor
81 </div>
82 %endfor
83
84 %for co in c.comments:
85 ${comment_block(co)}
86 %endfor
87 %if c.rhodecode_user.username != 'default':
88 <div class="comment-form">
89 ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
90 <strong>${_('Leave a comment')}</strong>
91 <div class="clearfix">
92 <div class="comment-help">
93 ${_('Comments parsed using')} <a href="${h.url('rst_help')}">RST</a> ${_('syntax')}
94 ${_('with')} <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
95 </div>
96 ${h.textarea('text')}
97 </div>
98 <div class="comment-button">
99 ${h.submit('save', _('Comment'), class_='ui-button')}
100 </div>
101 ${h.end_form()}
102 </div>
103 %endif
104 </div>
105 </%def>
@@ -0,0 +1,41 b''
1 ## -*- coding: utf-8 -*-
2 ##usage:
3 ## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
4 ## ${diff_block.diff_block(changes)}
5 ##
6 <%def name="diff_block(changes)">
7
8 %for change,filenode,diff,cs1,cs2,stat in changes:
9 %if change !='removed':
10 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;height:90px;margin-top:-60px"></div>
11 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock margined comm">
12 <div class="code-header">
13 <div class="changeset_header">
14 <div class="changeset_file">
15 ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
16 revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
17 </div>
18 <div class="diff-actions">
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" title="${_('diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
22 ${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}
23 ${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}
24 </div>
25 <span style="float:right;margin-top:-3px">
26 <label>
27 ${_('show inline comments')}
28 ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
29 </label>
30 </span>
31 </div>
32 </div>
33 <div class="code-body">
34 <div class="full_f_path" path="${h.safe_unicode(filenode.path)}"></div>
35 ${diff|n}
36 </div>
37 </div>
38 %endif
39 %endfor
40
41 </%def>
@@ -0,0 +1,6 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
3
4 <h4>${subject}</h4>
5
6 ${body}
@@ -0,0 +1,4 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
3
4 ${body}
@@ -0,0 +1,9 b''
1 ${self.body()}
2
3
4 <div>
5 --
6 <br/>
7 <br/>
8 <b>${_('This is an notification from RhodeCode.')}</b>
9 </div>
@@ -0,0 +1,12 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
3
4 Hello ${user}
5
6 We received a request to create a new password for your account.
7
8 You can generate it by clicking following URL:
9
10 ${reset_url}
11
12 If you didn't request new password please ignore this email.
@@ -0,0 +1,9 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
3
4 A new user have registered in RhodeCode
5
6 ${body}
7
8
9 View this user here: ${registered_user_url}
@@ -0,0 +1,15 b''
1 %if c.file:
2 <h3 class="files_location">
3 ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}
4 </h3>
5 %if c.file.is_dir():
6 <%include file='files_browser.html'/>
7 %else:
8 <%include file='files_source.html'/>
9 %endif
10 %else:
11 <h2>
12 <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
13 ${_('No files at given path')}: "${c.f_path or "/"}"
14 </h2>
15 %endif
@@ -0,0 +1,39 b''
1 ## -*- coding: utf-8 -*-
2 <li>
3 ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
4 <ul>
5 %if c.rhodecode_repo.branches.values():
6 %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
7 <li><div><pre>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</pre></div></li>
8 %endfor
9 %else:
10 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
11 %endif
12 </ul>
13 </li>
14 <li>
15 ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
16 <ul>
17 %if c.rhodecode_repo.tags.values():
18 %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
19 <li><div><pre>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</pre></div></li>
20 %endfor
21 %else:
22 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
23 %endif
24 </ul>
25 </li>
26 %if c.rhodecode_repo.alias == 'hg':
27 <li>
28 ${h.link_to('%s (%s)' % (_('bookmarks'),len(c.rhodecode_repo.bookmarks.values()),),h.url('bookmarks_home',repo_name=c.repo_name),class_='bookmarks childs')}
29 <ul>
30 %if c.rhodecode_repo.bookmarks.values():
31 %for cnt,book in enumerate(c.rhodecode_repo.bookmarks.items()):
32 <li><div><pre>${h.link_to('%s - %s' % (book[0],h.short_id(book[1])),h.url('files_home',repo_name=c.repo_name,revision=book[1]))}</pre></div></li>
33 %endfor
34 %else:
35 <li>${h.link_to(_('There are no bookmarks yet'),'#')}</li>
36 %endif
37 </ul>
38 </li>
39 %endif
@@ -0,0 +1,119 b''
1 from rhodecode.tests import *
2 from rhodecode.model.db import Notification, User, UserNotification
3
4 from rhodecode.model.user import UserModel
5 from rhodecode.model.notification import NotificationModel
6 from rhodecode.model.meta import Session
7
8 class TestNotificationsController(TestController):
9
10
11 def tearDown(self):
12 for n in Notification.query().all():
13 inst = Notification.get(n.notification_id)
14 Session.delete(inst)
15 Session.commit()
16
17 def test_index(self):
18 self.log_user()
19
20 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
21 email='u1@rhodecode.org',
22 name='u1', lastname='u1').user_id
23
24 response = self.app.get(url('notifications'))
25 self.assertTrue('''<div class="table">No notifications here yet</div>'''
26 in response.body)
27
28 cur_user = self._get_logged_user()
29
30 NotificationModel().create(created_by=u1, subject=u'test_notification_1',
31 body=u'notification_1',
32 recipients=[cur_user])
33 Session.commit()
34 response = self.app.get(url('notifications'))
35 self.assertTrue(u'test_notification_1' in response.body)
36
37 # def test_index_as_xml(self):
38 # response = self.app.get(url('formatted_notifications', format='xml'))
39 #
40 # def test_create(self):
41 # response = self.app.post(url('notifications'))
42 #
43 # def test_new(self):
44 # response = self.app.get(url('new_notification'))
45 #
46 # def test_new_as_xml(self):
47 # response = self.app.get(url('formatted_new_notification', format='xml'))
48 #
49 # def test_update(self):
50 # response = self.app.put(url('notification', notification_id=1))
51 #
52 # def test_update_browser_fakeout(self):
53 # response = self.app.post(url('notification', notification_id=1), params=dict(_method='put'))
54
55 def test_delete(self):
56 self.log_user()
57 cur_user = self._get_logged_user()
58
59 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
60 email='u1@rhodecode.org',
61 name='u1', lastname='u1')
62 u2 = UserModel().create_or_update(username='u2', password='qweqwe',
63 email='u2@rhodecode.org',
64 name='u2', lastname='u2')
65
66 # make notifications
67 notification = NotificationModel().create(created_by=cur_user,
68 subject=u'test',
69 body=u'hi there',
70 recipients=[cur_user, u1, u2])
71 Session.commit()
72 u1 = User.get(u1.user_id)
73 u2 = User.get(u2.user_id)
74
75 # check DB
76 get_notif = lambda un:[x.notification for x in un]
77 self.assertEqual(get_notif(cur_user.notifications), [notification])
78 self.assertEqual(get_notif(u1.notifications), [notification])
79 self.assertEqual(get_notif(u2.notifications), [notification])
80 cur_usr_id = cur_user.user_id
81
82
83 response = self.app.delete(url('notification',
84 notification_id=
85 notification.notification_id))
86
87 cur_user = User.get(cur_usr_id)
88 self.assertEqual(cur_user.notifications, [])
89
90
91 # def test_delete_browser_fakeout(self):
92 # response = self.app.post(url('notification', notification_id=1), params=dict(_method='delete'))
93
94 def test_show(self):
95 self.log_user()
96 cur_user = self._get_logged_user()
97 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
98 email='u1@rhodecode.org',
99 name='u1', lastname='u1')
100 u2 = UserModel().create_or_update(username='u2', password='qweqwe',
101 email='u2@rhodecode.org',
102 name='u2', lastname='u2')
103
104 notification = NotificationModel().create(created_by=cur_user,
105 subject=u'test',
106 body=u'hi there',
107 recipients=[cur_user, u1, u2])
108
109 response = self.app.get(url('notification',
110 notification_id=notification.notification_id))
111
112 # def test_show_as_xml(self):
113 # response = self.app.get(url('formatted_notification', notification_id=1, format='xml'))
114 #
115 # def test_edit(self):
116 # response = self.app.get(url('edit_notification', notification_id=1))
117 #
118 # def test_edit_as_xml(self):
119 # response = self.app.get(url('formatted_edit_notification', notification_id=1, format='xml'))
@@ -0,0 +1,139 b''
1 from rhodecode.tests import *
2 from rhodecode.model.db import ChangesetComment, Notification, User, \
3 UserNotification
4
5 class TestChangeSetCommentrController(TestController):
6
7 def setUp(self):
8 for x in ChangesetComment.query().all():
9 self.Session.delete(x)
10 self.Session.commit()
11
12 for x in Notification.query().all():
13 self.Session.delete(x)
14 self.Session.commit()
15
16 def tearDown(self):
17 for x in ChangesetComment.query().all():
18 self.Session.delete(x)
19 self.Session.commit()
20
21 for x in Notification.query().all():
22 self.Session.delete(x)
23 self.Session.commit()
24
25 def test_create(self):
26 self.log_user()
27 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
28 text = u'CommentOnRevision'
29
30 params = {'text':text}
31 response = self.app.post(url(controller='changeset', action='comment',
32 repo_name=HG_REPO, revision=rev),
33 params=params)
34 # Test response...
35 self.assertEqual(response.status, '302 Found')
36 response.follow()
37
38 response = self.app.get(url(controller='changeset', action='index',
39 repo_name=HG_REPO, revision=rev))
40 # test DB
41 self.assertEqual(ChangesetComment.query().count(), 1)
42 self.assertTrue('''<div class="comments-number">%s '''
43 '''comment(s) (0 inline)</div>''' % 1 in response.body)
44
45
46 self.assertEqual(Notification.query().count(), 1)
47 notification = Notification.query().all()[0]
48
49 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
50 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
51 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
52
53 def test_create_inline(self):
54 self.log_user()
55 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
56 text = u'CommentOnRevision'
57 f_path = 'vcs/web/simplevcs/views/repository.py'
58 line = 'n1'
59
60 params = {'text':text, 'f_path':f_path, 'line':line}
61 response = self.app.post(url(controller='changeset', action='comment',
62 repo_name=HG_REPO, revision=rev),
63 params=params)
64 # Test response...
65 self.assertEqual(response.status, '302 Found')
66 response.follow()
67
68 response = self.app.get(url(controller='changeset', action='index',
69 repo_name=HG_REPO, revision=rev))
70 #test DB
71 self.assertEqual(ChangesetComment.query().count(), 1)
72 self.assertTrue('''<div class="comments-number">0 comment(s)'''
73 ''' (%s inline)</div>''' % 1 in response.body)
74 self.assertTrue('''<div class="inline-comment-placeholder-line"'''
75 ''' line="n1" target_id="vcswebsimplevcsviews'''
76 '''repositorypy">''' in response.body)
77
78 self.assertEqual(Notification.query().count(), 1)
79 notification = Notification.query().all()[0]
80
81 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
82 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
83 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
84
85 def test_create_with_mention(self):
86 self.log_user()
87
88 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
89 text = u'@test_regular check CommentOnRevision'
90
91 params = {'text':text}
92 response = self.app.post(url(controller='changeset', action='comment',
93 repo_name=HG_REPO, revision=rev),
94 params=params)
95 # Test response...
96 self.assertEqual(response.status, '302 Found')
97 response.follow()
98
99 response = self.app.get(url(controller='changeset', action='index',
100 repo_name=HG_REPO, revision=rev))
101 # test DB
102 self.assertEqual(ChangesetComment.query().count(), 1)
103 self.assertTrue('''<div class="comments-number">%s '''
104 '''comment(s) (0 inline)</div>''' % 1 in response.body)
105
106
107 self.assertEqual(Notification.query().count(), 2)
108 users = [x.user.username for x in UserNotification.query().all()]
109
110 # test_regular get's notification by @mention
111 self.assertEqual(sorted(users), [u'test_admin', u'test_regular'])
112
113 def test_delete(self):
114 self.log_user()
115 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
116 text = u'CommentOnRevision'
117
118 params = {'text':text}
119 response = self.app.post(url(controller='changeset', action='comment',
120 repo_name=HG_REPO, revision=rev),
121 params=params)
122
123 comments = ChangesetComment.query().all()
124 self.assertEqual(len(comments), 1)
125 comment_id = comments[0].comment_id
126
127
128 self.app.delete(url(controller='changeset',
129 action='delete_comment',
130 repo_name=HG_REPO,
131 comment_id=comment_id))
132
133 comments = ChangesetComment.query().all()
134 self.assertEqual(len(comments), 0)
135
136 response = self.app.get(url(controller='changeset', action='index',
137 repo_name=HG_REPO, revision=rev))
138 self.assertTrue('''<div class="comments-number">0 comment(s)'''
139 ''' (0 inline)</div>''' in response.body)
@@ -1,6 +1,7 b''
1 1 syntax: glob
2 2 *.pyc
3 3 *.swp
4 *.sqlite
4 5 *.egg-info
5 6 *.egg
6 7
@@ -12,6 +13,9 b' syntax: regexp'
12 13 ^\.settings$
13 14 ^\.project$
14 15 ^\.pydevproject$
16 ^\.coverage$
15 17 ^rhodecode\.db$
16 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 13 Ankit Solanki <ankit.solanki@gmail.com>
14 14 Liad Shani <liadff@gmail.com>
15 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 =================================================
2 Welcome to RhodeCode (RhodiumCode) documentation!
3 =================================================
1 =========
2 RhodeCode
3 =========
4 4
5 ``RhodeCode`` is a Pylons framework based Mercurial repository
6 browser/management tool with a built in push/pull server and full text search.
5 About
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 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
9 simple API so it's easy integrable with existing systems.
11 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
12 simple API so it's easy integrable with existing external systems.
10 13
11 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 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 18 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 19 handle multiple different version control systems.
17 20
18 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 38 RhodeCode demo
21 39 --------------
22 40
@@ -45,16 +63,11 b' Sources at github_'
45 63
46 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 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 71 Each request can be logged and authenticated.
59 72 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
60 73 Supports http/https and LDAP
@@ -75,6 +88,9 b' RhodeCode Features'
75 88 - Server side forks. It is possible to fork a project and modify it freely
76 89 without breaking the main repository. You can even write Your own hooks
77 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 94 - Full text search powered by Whoosh on the source files, and file names.
79 95 Build in indexing daemons, with optional incremental index build
80 96 (no external search servers required all in one application)
@@ -88,20 +104,14 b' RhodeCode Features'
88 104 location
89 105 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
90 106
91
92 .. include:: ./docs/screenshots.rst
93
94 107
95 108 Incoming / Plans
96 109 ----------------
97 110
98 111 - Finer granular permissions per branch, repo group or subrepo
99 112 - pull requests and web based merges
100 - notification and message system
113 - per line file history
101 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 115 - Commit based built in wiki system
106 116 - More statistics and graph (global annotation + some more statistics)
107 117 - Other advancements as development continues (or you can of course make
@@ -113,21 +123,35 b' License'
113 123 ``RhodeCode`` is released under the GPLv3 license.
114 124
115 125
116 Mailing group Q&A
117 -----------------
126 Getting help
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)
124 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
138 - Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
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 148 Online documentation
127 149 --------------------
128 150
129 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 155 You may also build the documentation for yourself - go into ``docs/`` and run::
132 156
133 157 make html
@@ -17,6 +17,7 b' pdebug = false'
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 #email_prefix = [RhodeCode]
20 21
21 22 #smtp_server = mail.server.com
22 23 #smtp_username =
@@ -32,7 +33,7 b' pdebug = false'
32 33 threadpool_workers = 5
33 34
34 35 ##max request before thread respawn
35 threadpool_max_requests = 6
36 threadpool_max_requests = 10
36 37
37 38 ##option to use threads of process
38 39 use_threadpool = true
@@ -45,14 +46,52 b' port = 5000'
45 46 use = egg:rhodecode
46 47 full_stack = true
47 48 static_files = true
48 lang=en
49 lang = en
49 50 cache_dir = %(here)s/data
50 51 index_dir = %(here)s/data/index
51 app_instance_uuid = develop
52 app_instance_uuid = rc-develop
52 53 cut_off_limit = 256000
53 54 force_https = false
54 55 commit_parse_limit = 25
55 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 97 ### CELERY CONFIG ####
@@ -91,21 +130,27 b' beaker.cache.regions=super_short_term,sh'
91 130
92 131 beaker.cache.super_short_term.type=memory
93 132 beaker.cache.super_short_term.expire=10
133 beaker.cache.super_short_term.key_length = 256
94 134
95 135 beaker.cache.short_term.type=memory
96 136 beaker.cache.short_term.expire=60
137 beaker.cache.short_term.key_length = 256
97 138
98 139 beaker.cache.long_term.type=memory
99 140 beaker.cache.long_term.expire=36000
141 beaker.cache.long_term.key_length = 256
100 142
101 143 beaker.cache.sql_cache_short.type=memory
102 144 beaker.cache.sql_cache_short.expire=10
145 beaker.cache.sql_cache_short.key_length = 256
103 146
104 147 beaker.cache.sql_cache_med.type=memory
105 148 beaker.cache.sql_cache_med.expire=360
149 beaker.cache.sql_cache_med.key_length = 256
106 150
107 151 beaker.cache.sql_cache_long.type=file
108 152 beaker.cache.sql_cache_long.expire=3600
153 beaker.cache.sql_cache_long.key_length = 256
109 154
110 155 ####################################
111 156 ### BEAKER SESSION ####
@@ -113,12 +158,26 b' beaker.cache.sql_cache_long.expire=3600'
113 158 ## Type of storage used for the session, current types are
114 159 ## dbm, file, memcached, database, and memory.
115 160 ## The storage uses the Container API
116 ##that is also used by the cache system.
117 beaker.session.type = file
161 ## that is also used by the cache system.
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 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 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 182 ##auto save the session to not to use .save()
124 183 beaker.session.auto = False
@@ -126,7 +185,7 b' beaker.session.auto = False'
126 185 ##true exire at browser close
127 186 #beaker.session.cookie_expires = 3600
128 187
129
188
130 189 ################################################################################
131 190 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
132 191 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
@@ -26,16 +26,18 b' API ACCESS'
26 26
27 27 All clients are required to send JSON-RPC spec JSON data::
28 28
29 {
29 {
30 "id:<id>,
30 31 "api_key":"<api_key>",
31 32 "method":"<method_name>",
32 33 "args":{"<arg_key>":"<arg_val>"}
33 34 }
34 35
35 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 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 41 - *api_key* for access and permission validation.
40 42 - *method* is name of method to call
41 43 - *args* is an key:value list of arguments to pass to method
@@ -47,7 +49,8 b' Simply provide'
47 49
48 50 RhodeCode API will return always a JSON-RPC response::
49 51
50 {
52 {
53 "id":<id>,
51 54 "result": "<result>",
52 55 "error": null
53 56 }
@@ -72,21 +75,55 b' INPUT::'
72 75 api_key : "<api_key>"
73 76 method : "pull"
74 77 args : {
75 "repo" : "<repo_name>"
78 "repo_name" : "<reponame>"
76 79 }
77 80
78 81 OUTPUT::
79 82
80 result : "Pulled from <repo_name>"
83 result : "Pulled from <reponame>"
81 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 120 get_users
85 121 ---------
86 122
87 123 Lists all existing users. This command can be executed only using api_key
88 124 belonging to user with admin rights.
89 125
126
90 127 INPUT::
91 128
92 129 api_key : "<api_key>"
@@ -104,18 +141,20 b' OUTPUT::'
104 141 "email" : "<email>",
105 142 "active" : "<bool>",
106 143 "admin" :  "<bool>",
107 "ldap" : "<ldap_dn>"
144 "ldap_dn" : "<ldap_dn>"
108 145 },
109 146 …
110 147 ]
111 148 error: null
112 149
150
113 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 155 be executed only using api_key belonging to user with admin rights.
118 156
157
119 158 INPUT::
120 159
121 160 api_key : "<api_key>"
@@ -123,9 +162,9 b' INPUT::'
123 162 args : {
124 163 "username" : "<username>",
125 164 "password" : "<password>",
126 "firstname" : "<firstname>",
127 "lastname" : "<lastname>",
128 "email" : "<useremail>"
165 "email" : "<useremail>",
166 "firstname" : "<firstname> = None",
167 "lastname" : "<lastname> = None",
129 168 "active" : "<bool> = True",
130 169 "admin" : "<bool> = False",
131 170 "ldap_dn" : "<ldap_dn> = None"
@@ -134,15 +173,88 b' INPUT::'
134 173 OUTPUT::
135 174
136 175 result: {
176 "id" : "<new_user_id>",
137 177 "msg" : "created new user <username>"
138 178 }
139 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 252 get_users_groups
142 253 ----------------
143 254
144 Lists all existing users groups. This command can be executed only using api_key
145 belonging to user with admin rights.
255 Lists all existing users groups. This command can be executed only using
256 api_key belonging to user with admin rights.
257
146 258
147 259 INPUT::
148 260
@@ -154,9 +266,9 b' OUTPUT::'
154 266
155 267 result : [
156 268 {
157 "id" : "<id>",
158 "name" : "<name>",
159 "active": "<bool>",
269 "id" : "<id>",
270 "group_name" : "<groupname>",
271 "active": "<bool>",
160 272 "members" : [
161 273 {
162 274 "id" : "<userid>",
@@ -174,41 +286,6 b' OUTPUT::'
174 286 ]
175 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 290 create_users_group
214 291 ------------------
@@ -216,12 +293,13 b' create_users_group'
216 293 Creates new users group. This command can be executed only using api_key
217 294 belonging to user with admin rights
218 295
296
219 297 INPUT::
220 298
221 299 api_key : "<api_key>"
222 300 method : "create_users_group"
223 301 args: {
224 "name": "<name>",
302 "group_name": "<groupname>",
225 303 "active":"<bool> = True"
226 304 }
227 305
@@ -229,39 +307,120 b' OUTPUT::'
229 307
230 308 result: {
231 309 "id": "<newusersgroupid>",
232 "msg": "created new users group <name>"
310 "msg": "created new users group <groupname>"
233 311 }
234 312 error: null
235 313
314
236 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 320 belonging to user with admin rights
241 321
322
242 323 INPUT::
243 324
244 325 api_key : "<api_key>"
245 326 method : "add_user_users_group"
246 327 args: {
247 328 "group_name" : "<groupname>",
248 "user_name" : "<username>"
329 "username" : "<username>"
249 330 }
250 331
251 332 OUTPUT::
252 333
253 334 result: {
254 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 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 417 get_repos
260 418 ---------
261 419
262 420 Lists all existing repositories. This command can be executed only using api_key
263 421 belonging to user with admin rights
264 422
423
265 424 INPUT::
266 425
267 426 api_key : "<api_key>"
@@ -273,7 +432,7 b' OUTPUT::'
273 432 result: [
274 433 {
275 434 "id" : "<id>",
276 "name" : "<name>"
435 "repo_name" : "<reponame>"
277 436 "type" : "<type>",
278 437 "description" : "<description>"
279 438 },
@@ -281,51 +440,39 b' OUTPUT::'
281 440 ]
282 441 error: null
283 442
284 get_repo
285 --------
443
444 get_repo_nodes
445 --------------
286 446
287 Gets an existing repository. This command can be executed only using api_key
288 belonging to user with admin rights
447 returns a list of nodes and it's children in a flat list for a given path
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 453 INPUT::
291 454
292 455 api_key : "<api_key>"
293 method : "get_repo"
456 method : "get_repo_nodes"
294 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 464 OUTPUT::
299 465
300 result: None if repository not exist
301 {
302 "id" : "<id>",
466 result: [
467 {
303 468 "name" : "<name>"
304 469 "type" : "<type>",
305 "description" : "<description>",
306 "members" : [
307 { "id" : "<userid>",
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 }
470 },
471 …
472 ]
327 473 error: null
328 474
475
329 476 create_repo
330 477 -----------
331 478
@@ -335,58 +482,146 b' If repository name contains "/", all nee'
335 482 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
336 483 and create "baz" repository with "bar" as group.
337 484
485
338 486 INPUT::
339 487
340 488 api_key : "<api_key>"
341 489 method : "create_repo"
342 490 args: {
343 "name" : "<name>",
491 "repo_name" : "<reponame>",
344 492 "owner_name" : "<ownername>",
345 493 "description" : "<description> = ''",
346 494 "repo_type" : "<type> = 'hg'",
347 "private" : "<bool> = False"
495 "private" : "<bool> = False",
496 "clone_uri" : "<clone_uri> = None",
348 497 }
349 498
350 499 OUTPUT::
351 500
352 result: None
501 result: {
502 "id": "<newrepoid>",
503 "msg": "Created new repository <reponame>",
504 }
353 505 error: null
354 506
355 add_user_to_repo
356 ----------------
507
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
359 belonging to user with admin rights.
360 If "perm" is None, user will be removed from the repository.
523 OUTPUT::
524
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 539 INPUT::
363 540
364 541 api_key : "<api_key>"
365 method : "add_user_to_repo"
542 method : "grant_user_permission"
366 543 args: {
367 544 "repo_name" : "<reponame>",
368 "user_name" : "<username>",
369 "perm" : "(None|repository.(read|write|admin))",
545 "username" : "<username>",
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 573 OUTPUT::
373 574
374 result: None
575 result: {
576 "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
577 }
375 578 error: null
376 579
377 add_users_group_to_repo
378 -----------------------
580
581 grant_users_group_permission
582 ----------------------------
379 583
380 Add a users group to a repository. This command can be executed only using
381 api_key belonging to user with admin rights. If "perm" is None, group will
382 be removed from the repository.
584 Grant permission for users group on given repository, or update
585 existing one if found. This command can be executed only using
586 api_key belonging to user with admin rights.
587
383 588
384 589 INPUT::
385 590
386 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 617 args: {
389 618 "repo_name" : "<reponame>",
390 "group_name" : "<groupname>",
391 "perm" : "(None|repository.(read|write|admin))",
392 } No newline at end of file
619 "users_group" : "<usersgroupname>",
620 }
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 6 .. automodule:: rhodecode.model
7 7 :members:
8 8
9 .. automodule:: rhodecode.model.comment
10 :members:
11
12 .. automodule:: rhodecode.model.notification
13 :members:
14
9 15 .. automodule:: rhodecode.model.permission
10 16 :members:
11
17
18 .. automodule:: rhodecode.model.repo_permission
19 :members:
20
12 21 .. automodule:: rhodecode.model.repo
13 22 :members:
14 23
24 .. automodule:: rhodecode.model.repos_group
25 :members:
26
15 27 .. automodule:: rhodecode.model.scm
16 28 :members:
17
29
18 30 .. automodule:: rhodecode.model.user
19 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**)
8 ======================
7 1.3.0 (**2012-02-XX**)
8 ----------------------
9
10 :status: in-progress
11 :branch: beta
9 12
10 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 72 fixes
14 -----
73 +++++
15 74
16 75 - #340 Celery complains about MySQL server gone away, added session cleanup
17 76 for celery tasks
@@ -24,10 +83,10 b' fixes'
24 83 forking on windows impossible
25 84
26 85 1.2.4 (**2012-01-19**)
27 ======================
86 ----------------------
28 87
29 88 news
30 ----
89 ++++
31 90
32 91 - RhodeCode is bundled with mercurial series 2.0.X by default, with
33 92 full support to largefiles extension. Enabled by default in new installations
@@ -35,7 +94,7 b' news'
35 94 - added requires.txt file with requirements
36 95
37 96 fixes
38 -----
97 +++++
39 98
40 99 - fixes db session issues with celery when emailing admins
41 100 - #331 RhodeCode mangles repository names if the a repository group
@@ -52,10 +111,10 b' fixes'
52 111 - #316 fixes issues with web description in hgrc files
53 112
54 113 1.2.3 (**2011-11-02**)
55 ======================
114 ----------------------
56 115
57 116 news
58 ----
117 ++++
59 118
60 119 - added option to manage repos group for non admin users
61 120 - added following API methods for get_users, create_user, get_users_groups,
@@ -67,24 +126,23 b' news'
67 126 administrator users, and global config email.
68 127
69 128 fixes
70 -----
129 +++++
71 130
72 131 - added option for passing auth method for smtp mailer
73 132 - #276 issue with adding a single user with id>10 to usergroups
74 133 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
75 134 - #288 fixes managing of repos in a group for non admin user
76 135
77
78 136 1.2.2 (**2011-10-17**)
79 ======================
137 ----------------------
80 138
81 139 news
82 ----
140 ++++
83 141
84 142 - #226 repo groups are available by path instead of numerical id
85 143
86 144 fixes
87 -----
145 +++++
88 146
89 147 - #259 Groups with the same name but with different parent group
90 148 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
@@ -98,27 +156,25 b' fixes'
98 156 - fixes #248 cannot edit repos inside a group on windows
99 157 - fixes #219 forking problems on windows
100 158
101
102 159 1.2.1 (**2011-10-08**)
103 ======================
160 ----------------------
104 161
105 162 news
106 ----
163 ++++
107 164
108 165
109 166 fixes
110 -----
167 +++++
111 168
112 169 - fixed problems with basic auth and push problems
113 170 - gui fixes
114 171 - fixed logger
115 172
116
117 173 1.2.0 (**2011-10-07**)
118 ======================
174 ----------------------
119 175
120 176 news
121 ----
177 ++++
122 178
123 179 - implemented #47 repository groups
124 180 - implemented #89 Can setup google analytics code from settings menu
@@ -158,7 +214,7 b' news'
158 214 - Implemented advanced hook management
159 215
160 216 fixes
161 -----
217 +++++
162 218
163 219 - fixed file browser bug, when switching into given form revision the url was
164 220 not changing
@@ -185,18 +241,17 b' fixes'
185 241 - fixes #218 os.kill patch for windows was missing sig param
186 242 - improved rendering of dag (they are not trimmed anymore when number of
187 243 heads exceeds 5)
188
189
244
190 245 1.1.8 (**2011-04-12**)
191 ======================
246 ----------------------
192 247
193 248 news
194 ----
249 ++++
195 250
196 251 - improved windows support
197 252
198 253 fixes
199 -----
254 +++++
200 255
201 256 - fixed #140 freeze of python dateutil library, since new version is python2.x
202 257 incompatible
@@ -219,40 +274,40 b' fixes'
219 274
220 275
221 276 1.1.7 (**2011-03-23**)
222 ======================
277 ----------------------
223 278
224 279 news
225 ----
280 ++++
226 281
227 282 fixes
228 -----
283 +++++
229 284
230 285 - fixed (again) #136 installation support for FreeBSD
231 286
232 287
233 288 1.1.6 (**2011-03-21**)
234 ======================
289 ----------------------
235 290
236 291 news
237 ----
292 ++++
238 293
239 294 fixes
240 -----
295 +++++
241 296
242 297 - fixed #136 installation support for FreeBSD
243 298 - RhodeCode will check for python version during installation
244 299
245 300 1.1.5 (**2011-03-17**)
246 ======================
301 ----------------------
247 302
248 303 news
249 ----
304 ++++
250 305
251 306 - basic windows support, by exchanging pybcrypt into sha256 for windows only
252 307 highly inspired by idea of mantis406
253 308
254 309 fixes
255 -----
310 +++++
256 311
257 312 - fixed sorting by author in main page
258 313 - fixed crashes with diffs on binary files
@@ -264,13 +319,13 b' fixes'
264 319 - cleaned out docs, big thanks to Jason Harris
265 320
266 321 1.1.4 (**2011-02-19**)
267 ======================
322 ----------------------
268 323
269 324 news
270 ----
325 ++++
271 326
272 327 fixes
273 -----
328 +++++
274 329
275 330 - fixed formencode import problem on settings page, that caused server crash
276 331 when that page was accessed as first after server start
@@ -278,17 +333,17 b' fixes'
278 333 - fixed option to access repository just by entering http://server/<repo_name>
279 334
280 335 1.1.3 (**2011-02-16**)
281 ======================
336 ----------------------
282 337
283 338 news
284 ----
339 ++++
285 340
286 341 - implemented #102 allowing the '.' character in username
287 342 - added option to access repository just by entering http://server/<repo_name>
288 343 - celery task ignores result for better performance
289 344
290 345 fixes
291 -----
346 +++++
292 347
293 348 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
294 349 apollo13 and Johan Walles
@@ -304,31 +359,31 b' fixes'
304 359 - fixed static files paths links to use of url() method
305 360
306 361 1.1.2 (**2011-01-12**)
307 ======================
362 ----------------------
308 363
309 364 news
310 ----
365 ++++
311 366
312 367
313 368 fixes
314 -----
369 +++++
315 370
316 371 - fixes #98 protection against float division of percentage stats
317 372 - fixed graph bug
318 373 - forced webhelpers version since it was making troubles during installation
319 374
320 375 1.1.1 (**2011-01-06**)
321 ======================
376 ----------------------
322 377
323 378 news
324 ----
379 ++++
325 380
326 381 - added force https option into ini files for easier https usage (no need to
327 382 set server headers with this options)
328 383 - small css updates
329 384
330 385 fixes
331 -----
386 +++++
332 387
333 388 - fixed #96 redirect loop on files view on repositories without changesets
334 389 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
@@ -337,16 +392,16 b' fixes'
337 392 - fixed #92 whoosh indexer is more error proof
338 393
339 394 1.1.0 (**2010-12-18**)
340 ======================
395 ----------------------
341 396
342 397 news
343 ----
398 ++++
344 399
345 400 - rewrite of internals for vcs >=0.1.10
346 401 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
347 402 with older clients
348 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 405 cache that's invalidated when needed.
351 406 - performance upgrades on repositories with large amount of commits (20K+)
352 407 - main page quick filter for filtering repositories
@@ -365,7 +420,7 b' news'
365 420 - other than sqlite database backends can be used
366 421
367 422 fixes
368 -----
423 +++++
369 424
370 425 - fixes #61 forked repo was showing only after cache expired
371 426 - fixes #76 no confirmation on user deletes
@@ -385,16 +440,16 b' fixes'
385 440
386 441
387 442 1.0.2 (**2010-11-12**)
388 ======================
443 ----------------------
389 444
390 445 news
391 ----
446 ++++
392 447
393 448 - tested under python2.7
394 449 - bumped sqlalchemy and celery versions
395 450
396 451 fixes
397 -----
452 +++++
398 453
399 454 - fixed #59 missing graph.js
400 455 - fixed repo_size crash when repository had broken symlinks
@@ -402,15 +457,15 b' fixes'
402 457
403 458
404 459 1.0.1 (**2010-11-10**)
405 ======================
460 ----------------------
406 461
407 462 news
408 ----
463 ++++
409 464
410 465 - small css updated
411 466
412 467 fixes
413 -----
468 +++++
414 469
415 470 - fixed #53 python2.5 incompatible enumerate calls
416 471 - fixed #52 disable mercurial extension for web
@@ -418,7 +473,7 b' fixes'
418 473
419 474
420 475 1.0.0 (**2010-11-02**)
421 ======================
476 ----------------------
422 477
423 478 - security bugfix simplehg wasn't checking for permissions on commands
424 479 other than pull or push.
@@ -428,7 +483,7 b' 1.0.0 (**2010-11-02**)'
428 483 - permissions cached queries
429 484
430 485 1.0.0rc4 (**2010-10-12**)
431 ==========================
486 --------------------------
432 487
433 488 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
434 489 - removed cache_manager settings from sqlalchemy meta
@@ -438,12 +493,12 b' 1.0.0rc4 (**2010-10-12**)'
438 493
439 494
440 495 1.0.0rc3 (**2010-10-11**)
441 =========================
496 -------------------------
442 497
443 498 - fixed i18n during installation.
444 499
445 500 1.0.0rc2 (**2010-10-11**)
446 =========================
501 -------------------------
447 502
448 503 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
449 504 occure. After vcs is fixed it'll be put back again.
@@ -2,8 +2,8 b''
2 2
3 3 .. include:: ./../README.rst
4 4
5 Documentation
6 -------------
5 Users Guide
6 -----------
7 7
8 8 **Installation:**
9 9
@@ -20,10 +20,9 b' Documentation'
20 20 :maxdepth: 1
21 21
22 22 usage/general
23 usage/enable_git
23 usage/git_support
24 24 usage/statistics
25 25 usage/backup
26 usage/api_key_access
27 26
28 27 **Develop**
29 28
@@ -36,9 +35,10 b' Documentation'
36 35 **API**
37 36
38 37 .. toctree::
39 :maxdepth: 2
38 :maxdepth: 1
40 39
41 api/index
40 api/api
41 api/models
42 42
43 43
44 44 Other topics
@@ -346,6 +346,106 b' All other LDAP settings will likely be s'
346 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 450 Hook management
351 451 ---------------
@@ -361,6 +461,17 b' To add another custom hook simply fill i'
361 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 475 Setting Up Celery
365 476 -----------------
366 477
@@ -397,27 +508,36 b' Nginx virtual host example'
397 508
398 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 518 server {
401 519 listen 80;
402 520 server_name hg.myserver.com;
403 521 access_log /var/log/nginx/rhodecode.access.log;
404 522 error_log /var/log/nginx/rhodecode.error.log;
523
405 524 location / {
406 root /var/www/rhodecode/rhodecode/public/;
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;
525 try_files $uri @rhode;
413 526 }
527
528 location @rhode {
529 proxy_pass http://rc;
530 include /etc/nginx/proxy.conf;
531 }
532
414 533 }
415 534
416 535 Here's the proxy.conf. It's tuned so it will not timeout on long
417 536 pushes or large pushes::
418
537
419 538 proxy_redirect off;
420 539 proxy_set_header Host $host;
540 proxy_set_header X-Url-Scheme $scheme;
421 541 proxy_set_header X-Host $http_host;
422 542 proxy_set_header X-Real-IP $remote_addr;
423 543 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -1,12 +1,12 b''
1 1 {% extends "basic/layout.html" %}
2 2
3 3 {% block sidebarlogo %}
4 <h3>Support my development effort.</h3>
4 <h3>Support RhodeCode development.</h3>
5 5 <div style="text-align:center">
6 6 <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
7 7 <input type="hidden" name="cmd" value="_s-xclick">
8 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 10 border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
11 11 <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
12 12 </form>
@@ -36,6 +36,31 b' Compare view is also available from the '
36 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 65 Mailing
41 66 -------
@@ -1,13 +1,41 b''
1 .. _enable_git:
1 .. _git_support:
2 2
3 Enabling GIT support (beta)
4 ===========================
3 GIT support
4 ===========
5 5
6 6
7 Git support in RhodeCode 1.1 was disabled due to current instability issues.
8 However,if you would like to test git support please feel free to re-enable it.
9 To re-enable GIT support just uncomment the git line in the
10 file **rhodecode/__init__.py**
7 Git support in RhodeCode 1.3 was enabled by default.
8 Although There are some limitations on git usage.
9
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 40 .. code-block:: python
13 41
@@ -16,9 +44,5 b' file **rhodecode/__init__.py**'
16 44 #'git': 'Git repository',
17 45 }
18 46
19 .. note::
20 Please note that the git support provided by RhodeCode is not yet fully
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
47 .. _waitress: http://pypi.python.org/pypi/waitress
48 .. _gunicorn: http://pypi.python.org/pypi/gunicorn No newline at end of file
1 NO CONTENT: modified file chmod 100755 => 100644
@@ -17,6 +17,7 b' pdebug = false'
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 #email_prefix = [RhodeCode]
20 21
21 22 #smtp_server = mail.server.com
22 23 #smtp_username =
@@ -45,14 +46,52 b' port = 8001'
45 46 use = egg:rhodecode
46 47 full_stack = true
47 48 static_files = true
48 lang=en
49 lang = en
49 50 cache_dir = %(here)s/data
50 51 index_dir = %(here)s/data/index
51 app_instance_uuid = prod1234
52 app_instance_uuid = rc-production
52 53 cut_off_limit = 256000
53 force_https = false
54 force_https = false
54 55 commit_parse_limit = 50
55 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 97 ### CELERY CONFIG ####
@@ -91,21 +130,27 b' beaker.cache.regions=super_short_term,sh'
91 130
92 131 beaker.cache.super_short_term.type=memory
93 132 beaker.cache.super_short_term.expire=10
133 beaker.cache.super_short_term.key_length = 256
94 134
95 135 beaker.cache.short_term.type=memory
96 136 beaker.cache.short_term.expire=60
137 beaker.cache.short_term.key_length = 256
97 138
98 139 beaker.cache.long_term.type=memory
99 140 beaker.cache.long_term.expire=36000
141 beaker.cache.long_term.key_length = 256
100 142
101 143 beaker.cache.sql_cache_short.type=memory
102 144 beaker.cache.sql_cache_short.expire=10
145 beaker.cache.sql_cache_short.key_length = 256
103 146
104 147 beaker.cache.sql_cache_med.type=memory
105 148 beaker.cache.sql_cache_med.expire=360
149 beaker.cache.sql_cache_med.key_length = 256
106 150
107 151 beaker.cache.sql_cache_long.type=file
108 152 beaker.cache.sql_cache_long.expire=3600
153 beaker.cache.sql_cache_long.key_length = 256
109 154
110 155 ####################################
111 156 ### BEAKER SESSION ####
@@ -113,12 +158,27 b' beaker.cache.sql_cache_long.expire=3600'
113 158 ## Type of storage used for the session, current types are
114 159 ## dbm, file, memcached, database, and memory.
115 160 ## The storage uses the Container API
116 ##that is also used by the cache system.
117 beaker.session.type = file
161 ## that is also used by the cache system.
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 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 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 183 ##auto save the session to not to use .save()
124 184 beaker.session.auto = False
@@ -126,7 +186,7 b' beaker.session.auto = False'
126 186 ##true exire at browser close
127 187 #beaker.session.cookie_expires = 3600
128 188
129
189
130 190 ################################################################################
131 191 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
132 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 292 [formatter_color_formatter_sql]
233 293 class=rhodecode.lib.colored_formatter.ColorFormatterSql
234 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 1 Pylons==1.0.0
2 Beaker==1.5.4
2 Beaker==1.6.2
3 3 WebHelpers>=1.2
4 4 formencode==1.2.4
5 5 SQLAlchemy==0.7.4
6 6 Mako==0.5.0
7 7 pygments>=1.4
8 whoosh<1.8
8 whoosh>=2.3.0,<2.4
9 9 celery>=2.2.5,<2.3
10 10 babel
11 11 python-dateutil>=1.5.0,<2.0.0
12 12 dulwich>=0.8.0,<0.9.0
13 vcs==0.2.2
14 13 webob==1.0.8
14 markdown==2.1.1
15 docutils==0.8.1
15 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 26 import sys
27 27 import platform
28 28
29 VERSION = (1, 2, 5)
29 VERSION = (1, 3, 0)
30 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 32 __platform__ = platform.system()
33 33 __license__ = 'GPLv3'
34 34 __py_version__ = sys.version_info
@@ -38,19 +38,20 b" PLATFORM_OTHERS = ('Linux', 'Darwin', 'F"
38 38
39 39 requirements = [
40 40 "Pylons==1.0.0",
41 "Beaker==1.5.4",
41 "Beaker==1.6.2",
42 42 "WebHelpers>=1.2",
43 43 "formencode==1.2.4",
44 44 "SQLAlchemy==0.7.4",
45 45 "Mako==0.5.0",
46 46 "pygments>=1.4",
47 "whoosh<1.8",
47 "whoosh>=2.3.0,<2.4",
48 48 "celery>=2.2.5,<2.3",
49 49 "babel",
50 50 "python-dateutil>=1.5.0,<2.0.0",
51 51 "dulwich>=0.8.0,<0.9.0",
52 "vcs==0.2.2",
53 "webob==1.0.8"
52 "webob==1.0.8",
53 "markdown==2.1.1",
54 "docutils==0.8.1",
54 55 ]
55 56
56 57 if __py_version__ < (2, 6):
@@ -58,15 +59,15 b' if __py_version__ < (2, 6):'
58 59 requirements.append("pysqlite")
59 60
60 61 if __platform__ in PLATFORM_WIN:
61 requirements.append("mercurial==2.0.1")
62 requirements.append("mercurial>=2.1,<2.2")
62 63 else:
63 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 68 try:
68 69 from rhodecode.lib import get_current_revision
69 _rev = get_current_revision(quiet=True)
70 _rev = get_current_revision()
70 71 except ImportError:
71 72 # this is needed when doing some setup.py operations
72 73 _rev = False
@@ -82,5 +83,10 b' def get_version():'
82 83
83 84 BACKENDS = {
84 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 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 #email_prefix = [RhodeCode]
20 21
21 22 #smtp_server = mail.server.com
22 23 #smtp_username =
@@ -45,14 +46,52 b' port = 5000'
45 46 use = egg:rhodecode
46 47 full_stack = true
47 48 static_files = true
48 lang=en
49 lang = en
49 50 cache_dir = %(here)s/data
50 51 index_dir = %(here)s/data/index
51 52 app_instance_uuid = ${app_instance_uuid}
52 53 cut_off_limit = 256000
53 force_https = false
54 force_https = false
54 55 commit_parse_limit = 50
55 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 97 ### CELERY CONFIG ####
@@ -91,21 +130,27 b' beaker.cache.regions=super_short_term,sh'
91 130
92 131 beaker.cache.super_short_term.type=memory
93 132 beaker.cache.super_short_term.expire=10
133 beaker.cache.super_short_term.key_length = 256
94 134
95 135 beaker.cache.short_term.type=memory
96 136 beaker.cache.short_term.expire=60
137 beaker.cache.short_term.key_length = 256
97 138
98 139 beaker.cache.long_term.type=memory
99 140 beaker.cache.long_term.expire=36000
141 beaker.cache.long_term.key_length = 256
100 142
101 143 beaker.cache.sql_cache_short.type=memory
102 144 beaker.cache.sql_cache_short.expire=10
145 beaker.cache.sql_cache_short.key_length = 256
103 146
104 147 beaker.cache.sql_cache_med.type=memory
105 148 beaker.cache.sql_cache_med.expire=360
149 beaker.cache.sql_cache_med.key_length = 256
106 150
107 151 beaker.cache.sql_cache_long.type=file
108 152 beaker.cache.sql_cache_long.expire=3600
153 beaker.cache.sql_cache_long.key_length = 256
109 154
110 155 ####################################
111 156 ### BEAKER SESSION ####
@@ -113,12 +158,27 b' beaker.cache.sql_cache_long.expire=3600'
113 158 ## Type of storage used for the session, current types are
114 159 ## dbm, file, memcached, database, and memory.
115 160 ## The storage uses the Container API
116 ##that is also used by the cache system.
117 beaker.session.type = file
161 ## that is also used by the cache system.
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 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 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 183 ##auto save the session to not to use .save()
124 184 beaker.session.auto = False
@@ -126,7 +186,7 b' beaker.session.auto = False'
126 186 ##true exire at browser close
127 187 #beaker.session.cookie_expires = 3600
128 188
129
189
130 190 ################################################################################
131 191 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
132 192 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
@@ -154,6 +214,7 b' sqlalchemy.db1.url = sqlite:///%(here)s/'
154 214 # MySQL
155 215 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
156 216
217 # see sqlalchemy docs for others
157 218
158 219 sqlalchemy.db1.echo = false
159 220 sqlalchemy.db1.pool_recycle = 3600
@@ -217,13 +278,13 b' propagate = 0'
217 278 class = StreamHandler
218 279 args = (sys.stderr,)
219 280 level = INFO
220 formatter = color_formatter
281 formatter = generic
221 282
222 283 [handler_console_sql]
223 284 class = StreamHandler
224 285 args = (sys.stderr,)
225 286 level = WARN
226 formatter = color_formatter_sql
287 formatter = generic
227 288
228 289 ################
229 290 ## FORMATTERS ##
@@ -241,4 +302,4 b' datefmt = %Y-%m-%d %H:%M:%S'
241 302 [formatter_color_formatter_sql]
242 303 class=rhodecode.lib.colored_formatter.ColorFormatterSql
243 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 7 from pylons.configuration import PylonsConfig
8 8 from pylons.error import handle_mako_error
9 9
10 import rhodecode
10 11 import rhodecode.lib.app_globals as app_globals
11 12 import rhodecode.lib.helpers
12 13
13 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 17 from rhodecode.lib import engine_from_config
16 from rhodecode.lib.timerproxy import TimerProxy
17 18 from rhodecode.lib.auth import set_available_permissions
18 19 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
19 20 from rhodecode.model import init_model
@@ -38,10 +39,13 b' def load_environment(global_conf, app_co'
38 39 # Initialize config with the basic options
39 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 45 config['routes.map'] = make_map(config)
42 46 config['pylons.app_globals'] = app_globals.Globals(config)
43 47 config['pylons.h'] = rhodecode.lib.helpers
44
48 rhodecode.CONFIG = config
45 49 # Setup cache object as early as possible
46 50 import pylons
47 51 pylons.cache._push_object(config['pylons.app_globals'].cache)
@@ -54,7 +58,7 b' def load_environment(global_conf, app_co'
54 58 input_encoding='utf-8', default_filters=['escape'],
55 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 62 config['pylons.strict_tmpl_context'] = True
59 63 test = os.path.split(config['__file__'])[-1] == 'test.ini'
60 64 if test:
@@ -63,7 +67,7 b' def load_environment(global_conf, app_co'
63 67 create_test_env(TESTS_TMP_PATH, config)
64 68 create_test_index(TESTS_TMP_PATH, config, True)
65 69
66 #MULTIPLE DB configs
70 # MULTIPLE DB configs
67 71 # Setup the SQLAlchemy database engine
68 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 81 # CONFIGURATION OPTIONS HERE (note: all config options will override
78 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 87 return config
@@ -51,15 +51,16 b' def make_app(global_conf, full_stack=Tru'
51 51 from rhodecode.lib.profiler import ProfilingMiddleware
52 52 app = ProfilingMiddleware(app)
53 53
54 # we want our low level middleware to get to the request ASAP. We don't
55 # need any pylons stack middleware in them
56 app = SimpleHg(app, config)
57 app = SimpleGit(app, config)
54 if asbool(full_stack):
58 55
59 if asbool(full_stack):
60 56 # Handle Python exceptions
61 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 64 # Display error documents for 401, 403, 404 status codes (and
64 65 # 500 when debug is disabled)
65 66 if asbool(config['debug']):
@@ -8,7 +8,6 b' refer to the routes manual at http://rou'
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11
12 11 # prefix for non repository related links needs to be prefixed with `/`
13 12 ADMIN_PREFIX = '/_admin'
14 13
@@ -26,18 +25,27 b' def make_map(config):'
26 25 def check_repo(environ, match_dict):
27 26 """
28 27 check for valid repository for proper 404 handling
29
28
30 29 :param environ:
31 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 43 return is_valid_repo(repo_name, config['base_path'])
36 44
37 45 def check_group(environ, match_dict):
38 46 """
39 47 check for valid repositories group for proper 404 handling
40
48
41 49 :param environ:
42 50 :param match_dict:
43 51 """
@@ -45,7 +53,6 b' def make_map(config):'
45 53
46 54 return is_valid_repos_group(repos_group_name, config['base_path'])
47 55
48
49 56 def check_int(environ, match_dict):
50 57 return match_dict.get('id').isdigit()
51 58
@@ -62,9 +69,14 b' def make_map(config):'
62 69 rmap.connect('home', '/', controller='home', action='index')
63 70 rmap.connect('repo_switcher', '/repos', controller='home',
64 71 action='repo_switcher')
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
73 controller='home', action='branch_tag_switcher')
65 74 rmap.connect('bugtracker',
66 75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
67 76 _static=True)
77 rmap.connect('rst_help',
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 _static=True)
68 80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
69 81
70 82 #ADMIN REPOSITORY REST ROUTES
@@ -101,8 +113,9 b' def make_map(config):'
101 113 function=check_repo))
102 114 #ajax delete repo perm user
103 115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
104 action="delete_perm_user", conditions=dict(method=["DELETE"],
105 function=check_repo))
116 action="delete_perm_user",
117 conditions=dict(method=["DELETE"], function=check_repo))
118
106 119 #ajax delete repo perm users_group
107 120 m.connect('delete_repo_users_group',
108 121 "/repos_delete_users_group/{repo_name:.*}",
@@ -111,18 +124,20 b' def make_map(config):'
111 124
112 125 #settings actions
113 126 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
114 action="repo_stats", conditions=dict(method=["DELETE"],
115 function=check_repo))
127 action="repo_stats", conditions=dict(method=["DELETE"],
128 function=check_repo))
116 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 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 135 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
124 action="repo_pull", conditions=dict(method=["PUT"],
125 function=check_repo))
136 action="repo_pull", conditions=dict(method=["PUT"],
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 142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
128 143 controller='admin/repos_groups') as m:
@@ -155,6 +170,17 b' def make_map(config):'
155 170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
156 171 action="show", conditions=dict(method=["GET"],
157 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 185 #ADMIN USER REST ROUTES
160 186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -267,6 +293,34 b' def make_map(config):'
267 293 m.connect("admin_settings_create_repository", "/create_repository",
268 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 325 #ADMIN MAIN PAGES
272 326 with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -276,13 +330,12 b' def make_map(config):'
276 330 action='add_repo')
277 331
278 332 #==========================================================================
279 # API V1
333 # API V2
280 334 #==========================================================================
281 335 with rmap.submapper(path_prefix=ADMIN_PREFIX,
282 336 controller='api/api') as m:
283 337 m.connect('api', '/api')
284 338
285
286 339 #USER JOURNAL
287 340 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
288 341
@@ -344,6 +397,16 b' def make_map(config):'
344 397 controller='changeset', revision='tip',
345 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 410 rmap.connect('raw_changeset_home',
348 411 '/{repo_name:.*}/raw-changeset/{revision}',
349 412 controller='changeset', action='raw_changeset',
@@ -361,6 +424,9 b' def make_map(config):'
361 424 rmap.connect('tags_home', '/{repo_name:.*}/tags',
362 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 430 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
365 431 controller='changelog', conditions=dict(function=check_repo))
366 432
@@ -423,19 +489,19 b' def make_map(config):'
423 489 conditions=dict(function=check_repo))
424 490
425 491 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
426 controller='settings', action='fork_create',
492 controller='forks', action='fork_create',
427 493 conditions=dict(function=check_repo, method=["POST"]))
428 494
429 495 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
430 controller='settings', action='fork',
496 controller='forks', action='fork',
431 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 503 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
434 504 controller='followers', action='followers',
435 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 507 return rmap
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 7, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Nov 26, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
40 40 from rhodecode.lib.exceptions import LdapImportError
41 41 from rhodecode.model.forms import LdapSettingsForm
42 from rhodecode.model.db import RhodeCodeSettings
42 from rhodecode.model.db import RhodeCodeSetting
43 43
44 44 log = logging.getLogger(__name__)
45 45
@@ -83,7 +83,7 b' class LdapSettingsController(BaseControl'
83 83 super(LdapSettingsController, self).__before__()
84 84
85 85 def index(self):
86 defaults = RhodeCodeSettings.get_ldap_settings()
86 defaults = RhodeCodeSetting.get_ldap_settings()
87 87 c.search_scope_cur = defaults.get('ldap_search_scope')
88 88 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
89 89 c.tls_kind_cur = defaults.get('ldap_tls_kind')
@@ -107,7 +107,7 b' class LdapSettingsController(BaseControl'
107 107
108 108 for k, v in form_result.items():
109 109 if k.startswith('ldap_'):
110 setting = RhodeCodeSettings.get_by_name(k)
110 setting = RhodeCodeSetting.get_by_name(k)
111 111 setting.app_settings_value = v
112 112 self.sa.add(setting)
113 113
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 27, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 38 from rhodecode.model.forms import DefaultPermissionsForm
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.db import User
41 from rhodecode.model.meta import Session
41 42
42 43 log = logging.getLogger(__name__)
43 44
@@ -101,6 +102,7 b' class PermissionsController(BaseControll'
101 102 form_result = _form.to_python(dict(request.POST))
102 103 form_result.update({'perm_user_name': id})
103 104 permission_model.update(form_result)
105 Session.commit()
104 106 h.flash(_('Default permissions updated successfully'),
105 107 category='success')
106 108
@@ -3,11 +3,11 b''
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 Admin controller for RhodeCode
6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -29,9 +29,10 b' import formencode'
29 29 from formencode import htmlfill
30 30
31 31 from paste.httpexceptions import HTTPInternalServerError
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 36
36 37 from rhodecode.lib import helpers as h
37 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
@@ -39,11 +40,11 b' from rhodecode.lib.auth import LoginRequ'
39 40 from rhodecode.lib.base import BaseController, render
40 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 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 45 from rhodecode.model.forms import RepoForm
44 46 from rhodecode.model.scm import ScmModel
45 47 from rhodecode.model.repo import RepoModel
46 from sqlalchemy.exc import IntegrityError
47 48
48 49 log = logging.getLogger(__name__)
49 50
@@ -63,9 +64,9 b' class ReposController(BaseController):'
63 64 super(ReposController, self).__before__()
64 65
65 66 def __load_defaults(self):
66 c.repo_groups = Group.groups_choices()
67 c.repo_groups = RepoGroup.groups_choices()
67 68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
68
69
69 70 repo_model = RepoModel()
70 71 c.users_array = repo_model.get_users_js()
71 72 c.users_groups_array = repo_model.get_users_groups_js()
@@ -96,12 +97,13 b' class ReposController(BaseController):'
96 97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97 98
98 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 102 else:
101 103 last_rev = 0
102 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 108 if last_rev == 0 or c.repo_last_rev == 0:
107 109 c.stats_percentage = 0
@@ -110,6 +112,10 b' class ReposController(BaseController):'
110 112 c.repo_last_rev) * 100)
111 113
112 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 119 return defaults
114 120
115 121 @HasPermissionAllDecorator('hg.admin')
@@ -127,13 +133,13 b' class ReposController(BaseController):'
127 133 """
128 134 POST /repos: Create a new item"""
129 135 # url('repos')
130 repo_model = RepoModel()
136
131 137 self.__load_defaults()
132 138 form_result = {}
133 139 try:
134 140 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
135 141 .to_python(dict(request.POST))
136 repo_model.create(form_result, self.rhodecode_user)
142 RepoModel().create(form_result, self.rhodecode_user)
137 143 if form_result['clone_uri']:
138 144 h.flash(_('created repository %s from %s') \
139 145 % (form_result['repo_name'], form_result['clone_uri']),
@@ -143,13 +149,13 b' class ReposController(BaseController):'
143 149 category='success')
144 150
145 151 if request.POST.get('user_created'):
146 #created by regular non admin user
152 # created by regular non admin user
147 153 action_logger(self.rhodecode_user, 'user_created_repo',
148 154 form_result['repo_name_full'], '', self.sa)
149 155 else:
150 156 action_logger(self.rhodecode_user, 'admin_created_repo',
151 157 form_result['repo_name_full'], '', self.sa)
152
158 Session.commit()
153 159 except formencode.Invalid, errors:
154 160
155 161 c.new_repo = errors.value['repo_name']
@@ -207,7 +213,7 b' class ReposController(BaseController):'
207 213 changed_name = repo.repo_name
208 214 action_logger(self.rhodecode_user, 'admin_updated_repo',
209 215 changed_name, '', self.sa)
210
216 Session.commit()
211 217 except formencode.Invalid, errors:
212 218 defaults = self.__load_data(repo_name)
213 219 defaults.update(errors.value)
@@ -251,9 +257,9 b' class ReposController(BaseController):'
251 257 repo_model.delete(repo)
252 258 invalidate_cache('get_repo_cached_%s' % repo_name)
253 259 h.flash(_('deleted repository %s') % repo_name, category='success')
254
260 Session.commit()
255 261 except IntegrityError, e:
256 if e.message.find('repositories_fork_id_fkey'):
262 if e.message.find('repositories_fork_id_fkey') != -1:
257 263 log.error(traceback.format_exc())
258 264 h.flash(_('Cannot delete %s it still contains attached '
259 265 'forks') % repo_name,
@@ -271,8 +277,7 b' class ReposController(BaseController):'
271 277
272 278 return redirect(url('repos'))
273 279
274
275 @HasRepoPermissionAllDecorator('repository.admin')
280 @HasRepoPermissionAllDecorator('repository.admin')
276 281 def delete_perm_user(self, repo_name):
277 282 """
278 283 DELETE an existing repository permission user
@@ -281,9 +286,11 b' class ReposController(BaseController):'
281 286 """
282 287
283 288 try:
284 repo_model = RepoModel()
285 repo_model.delete_perm_user(request.POST, repo_name)
286 except Exception, e:
289 RepoModel().revoke_user_permission(repo=repo_name,
290 user=request.POST['user_id'])
291 Session.commit()
292 except Exception:
293 log.error(traceback.format_exc())
287 294 h.flash(_('An error occurred during deletion of repository user'),
288 295 category='error')
289 296 raise HTTPInternalServerError()
@@ -295,10 +302,14 b' class ReposController(BaseController):'
295 302
296 303 :param repo_name:
297 304 """
305
298 306 try:
299 repo_model = RepoModel()
300 repo_model.delete_perm_users_group(request.POST, repo_name)
301 except Exception, e:
307 RepoModel().revoke_users_group_permission(
308 repo=repo_name, group_name=request.POST['users_group_id']
309 )
310 Session.commit()
311 except Exception:
312 log.error(traceback.format_exc())
302 313 h.flash(_('An error occurred during deletion of repository'
303 314 ' users groups'),
304 315 category='error')
@@ -313,8 +324,8 b' class ReposController(BaseController):'
313 324 """
314 325
315 326 try:
316 repo_model = RepoModel()
317 repo_model.delete_stats(repo_name)
327 RepoModel().delete_stats(repo_name)
328 Session.commit()
318 329 except Exception, e:
319 330 h.flash(_('An error occurred during deletion of repository stats'),
320 331 category='error')
@@ -330,6 +341,7 b' class ReposController(BaseController):'
330 341
331 342 try:
332 343 ScmModel().mark_for_invalidation(repo_name)
344 Session.commit()
333 345 except Exception, e:
334 346 h.flash(_('An error occurred during cache invalidation'),
335 347 category='error')
@@ -353,6 +365,7 b' class ReposController(BaseController):'
353 365 self.scm_model.toggle_following_repo(repo_id, user_id)
354 366 h.flash(_('Updated repository visibility in public journal'),
355 367 category='success')
368 Session.commit()
356 369 except:
357 370 h.flash(_('An error occurred during setting this'
358 371 ' repository in public journal'),
@@ -380,6 +393,28 b' class ReposController(BaseController):'
380 393 return redirect(url('edit_repo', repo_name=repo_name))
381 394
382 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 418 def show(self, repo_name, format='html'):
384 419 """GET /repos/repo_name: Show a specific item"""
385 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 26 import logging
2 27 import traceback
3 28 import formencode
4 29
5 30 from formencode import htmlfill
6 from operator import itemgetter
7 31
8 from pylons import request, response, session, tmpl_context as c, url
9 from pylons.controllers.util import abort, redirect
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
10 34 from pylons.i18n.translation import _
11 35
12 36 from sqlalchemy.exc import IntegrityError
13 37
14 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 41 from rhodecode.lib.base import BaseController, render
17 from rhodecode.model.db import Group
42 from rhodecode.model.db import RepoGroup
18 43 from rhodecode.model.repos_group import ReposGroupModel
19 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 49 log = logging.getLogger(__name__)
22 50
@@ -32,9 +60,13 b' class ReposGroupsController(BaseControll'
32 60 super(ReposGroupsController, self).__before__()
33 61
34 62 def __load_defaults(self):
35 c.repo_groups = Group.groups_choices()
63 c.repo_groups = RepoGroup.groups_choices()
36 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 70 def __load_data(self, group_id):
39 71 """
40 72 Load defaults settings for edit, and update
@@ -43,21 +75,30 b' class ReposGroupsController(BaseControll'
43 75 """
44 76 self.__load_defaults()
45 77
46 repo_group = Group.get(group_id)
78 repo_group = RepoGroup.get(group_id)
47 79
48 80 data = repo_group.get_dict()
49 81
50 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 94 return data
53 95
54 96 @HasPermissionAnyDecorator('hg.admin')
55 97 def index(self, format='html'):
56 98 """GET /repos_groups: All items in the collection"""
57 99 # url('repos_groups')
58
59 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
60 c.groups = sorted(Group.query().all(), key=sk)
100 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
101 c.groups = sorted(RepoGroup.query().all(), key=sk)
61 102 return render('admin/repos_groups/repos_groups_show.html')
62 103
63 104 @HasPermissionAnyDecorator('hg.admin')
@@ -65,12 +106,16 b' class ReposGroupsController(BaseControll'
65 106 """POST /repos_groups: Create a new item"""
66 107 # url('repos_groups')
67 108 self.__load_defaults()
68 repos_group_model = ReposGroupModel()
69 repos_group_form = ReposGroupForm(available_groups=
109 repos_group_form = ReposGroupForm(available_groups =
70 110 c.repo_groups_choices)()
71 111 try:
72 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 119 h.flash(_('created repos group %s') \
75 120 % form_result['group_name'], category='success')
76 121 #TODO: in futureaction_logger(, '', '', '', self.sa)
@@ -89,7 +134,6 b' class ReposGroupsController(BaseControll'
89 134
90 135 return redirect(url('repos_groups'))
91 136
92
93 137 @HasPermissionAnyDecorator('hg.admin')
94 138 def new(self, format='html'):
95 139 """GET /repos_groups/new: Form to create a new item"""
@@ -108,16 +152,17 b' class ReposGroupsController(BaseControll'
108 152 # url('repos_group', id=ID)
109 153
110 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()
114 repos_group_form = ReposGroupForm(edit=True,
115 old_data=c.repos_group.get_dict(),
116 available_groups=
117 c.repo_groups_choices)()
157 repos_group_form = ReposGroupForm(
158 edit=True,
159 old_data=c.repos_group.get_dict(),
160 available_groups=c.repo_groups_choices
161 )()
118 162 try:
119 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 166 h.flash(_('updated repos group %s') \
122 167 % form_result['group_name'], category='success')
123 168 #TODO: in futureaction_logger(, '', '', '', self.sa)
@@ -136,7 +181,6 b' class ReposGroupsController(BaseControll'
136 181
137 182 return redirect(url('repos_groups'))
138 183
139
140 184 @HasPermissionAnyDecorator('hg.admin')
141 185 def delete(self, id):
142 186 """DELETE /repos_groups/id: Delete an existing item"""
@@ -147,8 +191,7 b' class ReposGroupsController(BaseControll'
147 191 # method='delete')
148 192 # url('repos_group', id=ID)
149 193
150 repos_group_model = ReposGroupModel()
151 gr = Group.get(id)
194 gr = RepoGroup.get(id)
152 195 repos = gr.repositories.all()
153 196 if repos:
154 197 h.flash(_('This group contains %s repositores and cannot be '
@@ -157,11 +200,12 b' class ReposGroupsController(BaseControll'
157 200 return redirect(url('repos_groups'))
158 201
159 202 try:
160 repos_group_model.delete(id)
203 ReposGroupModel().delete(id)
204 Session.commit()
161 205 h.flash(_('removed repos group %s' % gr.group_name), category='success')
162 206 #TODO: in future action_logger(, '', '', '', self.sa)
163 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 209 log.error(traceback.format_exc())
166 210 h.flash(_('Cannot delete this group it still contains '
167 211 'subgroups'),
@@ -178,15 +222,57 b' class ReposGroupsController(BaseControll'
178 222
179 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 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 267 return self.show(id_)
184 268
269 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
270 'group.admin')
185 271 def show(self, id, format='html'):
186 272 """GET /repos_groups/id: Show a specific item"""
187 273 # url('repos_group', id=ID)
188 274
189 c.group = Group.get(id)
275 c.group = RepoGroup.get(id)
190 276
191 277 if c.group:
192 278 c.group_repos = c.group.repositories.all()
@@ -201,8 +287,8 b' class ReposGroupsController(BaseControll'
201 287
202 288 c.repo_cnt = 0
203 289
204 c.groups = self.sa.query(Group).order_by(Group.group_name)\
205 .filter(Group.group_parent_id == id).all()
290 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
291 .filter(RepoGroup.group_parent_id == id).all()
206 292
207 293 return render('admin/repos_groups/repos_groups.html')
208 294
@@ -213,11 +299,11 b' class ReposGroupsController(BaseControll'
213 299
214 300 id_ = int(id)
215 301
216 c.repos_group = Group.get(id_)
302 c.repos_group = RepoGroup.get(id_)
217 303 defaults = self.__load_data(id_)
218 304
219 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 308 return htmlfill.render(
223 309 render('admin/repos_groups/repos_groups_edit.html'),
@@ -225,5 +311,3 b' class ReposGroupsController(BaseControll'
225 311 encoding="UTF-8",
226 312 force_defaults=False
227 313 )
228
229
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Jul 14, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 40 from rhodecode.lib.celerylib import tasks, run_task
41 41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 42 set_rhodecode_config, repo_name_slug
43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
44 RhodeCodeSettings
43 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
44 RhodeCodeSetting
45 45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
46 46 ApplicationUiSettingsForm
47 47 from rhodecode.model.scm import ScmModel
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import User
50 from rhodecode.model.notification import EmailNotificationModel
51 from rhodecode.model.meta import Session
50 52
51 53 log = logging.getLogger(__name__)
52 54
@@ -69,7 +71,7 b' class SettingsController(BaseController)'
69 71 """GET /admin/settings: All items in the collection"""
70 72 # url('admin_settings')
71 73
72 defaults = RhodeCodeSettings.get_app_settings()
74 defaults = RhodeCodeSetting.get_app_settings()
73 75 defaults.update(self.get_hg_ui_settings())
74 76 return htmlfill.render(
75 77 render('admin/settings/settings.html'),
@@ -99,7 +101,7 b' class SettingsController(BaseController)'
99 101 # url('admin_setting', setting_id=ID)
100 102 if setting_id == 'mapping':
101 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 105 initial = ScmModel().repo_scan()
104 106 log.debug('invalidating all repositories')
105 107 for repo_name in initial.keys():
@@ -124,15 +126,15 b' class SettingsController(BaseController)'
124 126 form_result = application_form.to_python(dict(request.POST))
125 127
126 128 try:
127 hgsettings1 = RhodeCodeSettings.get_by_name('title')
129 hgsettings1 = RhodeCodeSetting.get_by_name('title')
128 130 hgsettings1.app_settings_value = \
129 131 form_result['rhodecode_title']
130 132
131 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
133 hgsettings2 = RhodeCodeSetting.get_by_name('realm')
132 134 hgsettings2.app_settings_value = \
133 135 form_result['rhodecode_realm']
134 136
135 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
137 hgsettings3 = RhodeCodeSetting.get_by_name('ga_code')
136 138 hgsettings3.app_settings_value = \
137 139 form_result['rhodecode_ga_code']
138 140
@@ -226,12 +228,11 b' class SettingsController(BaseController)'
226 228 prefix_error=False,
227 229 encoding="UTF-8")
228 230
229
230 231 if setting_id == 'hooks':
231 232 ui_key = request.POST.get('new_hook_ui_key')
232 233 ui_value = request.POST.get('new_hook_ui_value')
233 234 try:
234
235
235 236 if ui_value and ui_key:
236 237 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
237 238 h.flash(_('Added new hook'),
@@ -240,13 +241,14 b' class SettingsController(BaseController)'
240 241 # check for edits
241 242 update = False
242 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 246 RhodeCodeUi.create_or_update_hook(k, v)
245 247 update = True
246 248
247 249 if update:
248 250 h.flash(_('Updated hooks'), category='success')
249
251 Session.commit()
250 252 except:
251 253 log.error(traceback.format_exc())
252 254 h.flash(_('error occurred during hook creation'),
@@ -254,6 +256,21 b' class SettingsController(BaseController)'
254 256
255 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 274 return redirect(url('admin_settings'))
258 275
259 276 @HasPermissionAllDecorator('hg.admin')
@@ -268,8 +285,8 b' class SettingsController(BaseController)'
268 285 if setting_id == 'hooks':
269 286 hook_id = request.POST.get('hook_id')
270 287 RhodeCodeUi.delete(hook_id)
271
272
288
289
273 290 @HasPermissionAllDecorator('hg.admin')
274 291 def show(self, setting_id, format='html'):
275 292 """
@@ -339,7 +356,7 b' class SettingsController(BaseController)'
339 356 user_model.update_my_account(uid, form_result)
340 357 h.flash(_('Your account was updated successfully'),
341 358 category='success')
342
359 Session.commit()
343 360 except formencode.Invalid, errors:
344 361 c.user = User.get(self.rhodecode_user.user_id)
345 362 all_repos = self.sa.query(Repository)\
@@ -366,7 +383,7 b' class SettingsController(BaseController)'
366 383 def create_repository(self):
367 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 387 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
371 388
372 389 new_repo = request.GET.get('repo', '')
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 4, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -29,7 +29,7 b' import formencode'
29 29
30 30 from formencode import htmlfill
31 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 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib.exceptions import DefaultUserException, \
@@ -38,9 +38,10 b' from rhodecode.lib import helpers as h'
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
39 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 42 from rhodecode.model.forms import UserForm
43 43 from rhodecode.model.user import UserModel
44 from rhodecode.model.meta import Session
44 45
45 46 log = logging.getLogger(__name__)
46 47
@@ -71,12 +72,13 b' class UsersController(BaseController):'
71 72 # url('users')
72 73
73 74 user_model = UserModel()
74 login_form = UserForm()()
75 user_form = UserForm()()
75 76 try:
76 form_result = login_form.to_python(dict(request.POST))
77 form_result = user_form.to_python(dict(request.POST))
77 78 user_model.create(form_result)
78 79 h.flash(_('created user %s') % form_result['username'],
79 80 category='success')
81 Session.commit()
80 82 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
81 83 except formencode.Invalid, errors:
82 84 return htmlfill.render(
@@ -114,11 +116,11 b' class UsersController(BaseController):'
114 116 form_result = _form.to_python(dict(request.POST))
115 117 user_model.update(id, form_result)
116 118 h.flash(_('User updated successfully'), category='success')
117
119 Session.commit()
118 120 except formencode.Invalid, errors:
119 121 e = errors.error_dict or {}
120 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 124 return htmlfill.render(
123 125 render('admin/users/user_edit.html'),
124 126 defaults=errors.value,
@@ -144,6 +146,7 b' class UsersController(BaseController):'
144 146 try:
145 147 user_model.delete(id)
146 148 h.flash(_('successfully deleted user'), category='success')
149 Session.commit()
147 150 except (UserOwnsReposException, DefaultUserException), e:
148 151 h.flash(str(e), category='warning')
149 152 except Exception:
@@ -158,20 +161,19 b' class UsersController(BaseController):'
158 161 def edit(self, id, format='html'):
159 162 """GET /users/id/edit: Form to edit an existing item"""
160 163 # url('edit_user', id=ID)
161 user_model = UserModel()
162 c.user = user_model.get(id)
164 c.user = User.get(id)
163 165 if not c.user:
164 166 return redirect(url('users'))
165 167 if c.user.username == 'default':
166 168 h.flash(_("You can't edit this user"), category='warning')
167 169 return redirect(url('users'))
168 170 c.user.permissions = {}
169 c.granted_permissions = user_model.fill_perms(c.user)\
171 c.granted_permissions = UserModel().fill_perms(c.user)\
170 172 .permissions['global']
171 173
172 174 defaults = c.user.get_dict()
173 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 178 return htmlfill.render(
177 179 render('admin/users/user_edit.html'),
@@ -185,23 +187,24 b' class UsersController(BaseController):'
185 187 # url('user_perm', id=ID, method='put')
186 188
187 189 grant_perm = request.POST.get('create_repo_perm', False)
190 user_model = UserModel()
188 191
189 192 if grant_perm:
190 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 196 perm = Permission.get_by_key('hg.create.repository')
194 UserToPerm.grant_perm(id, perm)
197 user_model.grant_perm(id, perm)
195 198 h.flash(_("Granted 'repository create' permission to user"),
196 199 category='success')
197
200 Session.commit()
198 201 else:
199 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 205 perm = Permission.get_by_key('hg.create.none')
203 UserToPerm.grant_perm(id, perm)
206 user_model.grant_perm(id, perm)
204 207 h.flash(_("Revoked 'repository create' permission to user"),
205 208 category='success')
206
209 Session.commit()
207 210 return redirect(url('edit_user', id=id))
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Jan 25, 2011
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 33 from pylons.i18n.translation import _
34 34
35 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 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 38 from rhodecode.lib.base import BaseController, render
39 39
40 from rhodecode.model.users_group import UsersGroupModel
41
40 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 46 log = logging.getLogger(__name__)
44 47
@@ -70,10 +73,12 b' class UsersGroupsController(BaseControll'
70 73 users_group_form = UsersGroupForm()()
71 74 try:
72 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 78 h.flash(_('created users group %s') \
75 79 % form_result['users_group_name'], category='success')
76 80 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
81 Session.commit()
77 82 except formencode.Invalid, errors:
78 83 return htmlfill.render(
79 84 render('admin/users_groups/users_group_add.html'),
@@ -103,29 +108,33 b' class UsersGroupsController(BaseControll'
103 108 # url('users_group', id=ID)
104 109
105 110 c.users_group = UsersGroup.get(id)
106 c.group_members = [(x.user_id, x.user.username) for x in
107 c.users_group.members]
111 c.group_members_obj = [x.user for x in c.users_group.members]
112 c.group_members = [(x.user_id, x.username) for x in
113 c.group_members_obj]
108 114
109 115 c.available_members = [(x.user_id, x.username) for x in
110 116 self.sa.query(User).all()]
117
118 available_members = [safe_unicode(x[0]) for x in c.available_members]
119
111 120 users_group_form = UsersGroupForm(edit=True,
112 121 old_data=c.users_group.get_dict(),
113 available_members=[str(x[0]) for x
114 in c.available_members])()
122 available_members=available_members)()
115 123
116 124 try:
117 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 127 h.flash(_('updated users group %s') \
120 128 % form_result['users_group_name'],
121 129 category='success')
122 130 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
131 Session.commit()
123 132 except formencode.Invalid, errors:
124 133 e = errors.error_dict or {}
125 134
126 135 perm = Permission.get_by_key('hg.create.repository')
127 136 e.update({'create_repo_perm':
128 UsersGroupToPerm.has_perm(id, perm)})
137 UsersGroupModel().has_perm(id, perm)})
129 138
130 139 return htmlfill.render(
131 140 render('admin/users_groups/users_group_edit.html'),
@@ -150,8 +159,9 b' class UsersGroupsController(BaseControll'
150 159 # url('users_group', id=ID)
151 160
152 161 try:
153 UsersGroup.delete(id)
162 UsersGroupModel().delete(id)
154 163 h.flash(_('successfully deleted users group'), category='success')
164 Session.commit()
155 165 except UsersGroupsAssignedException, e:
156 166 h.flash(e, category='error')
157 167 except Exception:
@@ -172,14 +182,15 b' class UsersGroupsController(BaseControll'
172 182 return redirect(url('users_groups'))
173 183
174 184 c.users_group.permissions = {}
175 c.group_members = [(x.user_id, x.user.username) for x in
176 c.users_group.members]
185 c.group_members_obj = [x.user for x in c.users_group.members]
186 c.group_members = [(x.user_id, x.username) for x in
187 c.group_members_obj]
177 188 c.available_members = [(x.user_id, x.username) for x in
178 189 self.sa.query(User).all()]
179 190 defaults = c.users_group.get_dict()
180 191 perm = Permission.get_by_key('hg.create.repository')
181 192 defaults.update({'create_repo_perm':
182 UsersGroupToPerm.has_perm(id, perm)})
193 UsersGroupModel().has_perm(c.users_group, perm)})
183 194 return htmlfill.render(
184 195 render('admin/users_groups/users_group_edit.html'),
185 196 defaults=defaults,
@@ -195,20 +206,21 b' class UsersGroupsController(BaseControll'
195 206
196 207 if grant_perm:
197 208 perm = Permission.get_by_key('hg.create.none')
198 UsersGroupToPerm.revoke_perm(id, perm)
209 UsersGroupModel().revoke_perm(id, perm)
199 210
200 211 perm = Permission.get_by_key('hg.create.repository')
201 UsersGroupToPerm.grant_perm(id, perm)
212 UsersGroupModel().grant_perm(id, perm)
202 213 h.flash(_("Granted 'repository create' permission to user"),
203 214 category='success')
204 215
216 Session.commit()
205 217 else:
206 218 perm = Permission.get_by_key('hg.create.repository')
207 UsersGroupToPerm.revoke_perm(id, perm)
219 UsersGroupModel().revoke_perm(id, perm)
208 220
209 221 perm = Permission.get_by_key('hg.create.none')
210 UsersGroupToPerm.grant_perm(id, perm)
222 UsersGroupModel().grant_perm(id, perm)
211 223 h.flash(_("Revoked 'repository create' permission to user"),
212 224 category='success')
213
225 Session.commit()
214 226 return redirect(url('edit_users_group', id=id))
@@ -7,19 +7,19 b''
7 7
8 8 :created_on: Aug 20, 2011
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 #
22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
@@ -62,7 +62,7 b' def jsonrpc_error(message, code=None):'
62 62 Generate a Response object with a JSON-RPC error body
63 63 """
64 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 66 status=code,
67 67 content_type='application/json')
68 68 return resp
@@ -100,7 +100,7 b' class JSONRPCController(WSGIController):'
100 100 else:
101 101 length = environ['CONTENT_LENGTH'] or 0
102 102 length = int(environ['CONTENT_LENGTH'])
103 log.debug('Content-Length: %s', length)
103 log.debug('Content-Length: %s' % length)
104 104
105 105 if length == 0:
106 106 log.debug("Content-Length is 0")
@@ -118,11 +118,13 b' class JSONRPCController(WSGIController):'
118 118 # check AUTH based on API KEY
119 119 try:
120 120 self._req_api_key = json_body['api_key']
121 self._req_id = json_body['id']
121 122 self._req_method = json_body['method']
122 123 self._request_params = json_body['args']
123 log.debug('method: %s, params: %s',
124 self._req_method,
125 self._request_params)
124 log.debug(
125 'method: %s, params: %s' % (self._req_method,
126 self._request_params)
127 )
126 128 except KeyError, e:
127 129 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
128 130
@@ -225,21 +227,26 b' class JSONRPCController(WSGIController):'
225 227 if self._error is not None:
226 228 raw_response = None
227 229
228 response = dict(result=raw_response,
230 response = dict(id=self._req_id, result=raw_response,
229 231 error=self._error)
230 232
231 233 try:
232 234 return json.dumps(response)
233 235 except TypeError, e:
234 log.debug('Error encoding response: %s', e)
235 return json.dumps(dict(result=None,
236 error="Error encoding response"))
236 log.debug('Error encoding response: %s' % e)
237 return json.dumps(
238 dict(
239 self._req_id,
240 result=None,
241 error="Error encoding response"
242 )
243 )
237 244
238 245 def _find_method(self):
239 246 """
240 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 250 if self._req_method.startswith('_'):
244 251 raise AttributeError("Method not allowed")
245 252
@@ -253,4 +260,3 b' class JSONRPCController(WSGIController):'
253 260 return func
254 261 else:
255 262 raise AttributeError("No such method: %s" % self._req_method)
256
@@ -30,17 +30,15 b' import logging'
30 30
31 31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator
33 HasPermissionAnyDecorator, PasswordGenerator
34
35 from rhodecode.model.meta import Session
34 36 from rhodecode.model.scm import ScmModel
35
36 from rhodecode.model.db import User, UsersGroup, Group, Repository
37 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
37 38 from rhodecode.model.repo import RepoModel
38 39 from rhodecode.model.user import UserModel
39 from rhodecode.model.repo_permission import RepositoryPermissionModel
40 40 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model import users_group
42 41 from rhodecode.model.repos_group import ReposGroupModel
43 from sqlalchemy.orm.exc import NoResultFound
44 42
45 43
46 44 log = logging.getLogger(__name__)
@@ -63,26 +61,26 b' class ApiController(JSONRPCController):'
63 61 """
64 62
65 63 @HasPermissionAllDecorator('hg.admin')
66 def pull(self, apiuser, repo):
64 def pull(self, apiuser, repo_name):
67 65 """
68 66 Dispatch pull action on given repo
69 67
70 68
71 69 :param user:
72 :param repo:
70 :param repo_name:
73 71 """
74 72
75 if Repository.is_valid(repo) is False:
76 raise JSONRPCError('Unknown repo "%s"' % repo)
73 if Repository.is_valid(repo_name) is False:
74 raise JSONRPCError('Unknown repo "%s"' % repo_name)
77 75
78 76 try:
79 ScmModel().pull_changes(repo, self.rhodecode_user.username)
80 return 'Pulled from %s' % repo
77 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
78 return 'Pulled from %s' % repo_name
81 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 82 @HasPermissionAllDecorator('hg.admin')
85 def get_user(self, apiuser, username):
83 def get_user(self, apiuser, userid):
86 84 """"
87 85 Get a user by username
88 86
@@ -90,9 +88,9 b' class ApiController(JSONRPCController):'
90 88 :param username:
91 89 """
92 90
93 user = User.get_by_username(username)
94 if not user:
95 return None
91 user = UserModel().get_user(userid)
92 if user is None:
93 return user
96 94
97 95 return dict(
98 96 id=user.user_id,
@@ -102,7 +100,7 b' class ApiController(JSONRPCController):'
102 100 email=user.email,
103 101 active=user.active,
104 102 admin=user.admin,
105 ldap=user.ldap_dn
103 ldap_dn=user.ldap_dn
106 104 )
107 105
108 106 @HasPermissionAllDecorator('hg.admin')
@@ -124,47 +122,85 b' class ApiController(JSONRPCController):'
124 122 email=user.email,
125 123 active=user.active,
126 124 admin=user.admin,
127 ldap=user.ldap_dn
125 ldap_dn=user.ldap_dn
128 126 )
129 127 )
130 128 return result
131 129
132 130 @HasPermissionAllDecorator('hg.admin')
133 def create_user(self, apiuser, username, password, firstname,
134 lastname, email, active=True, admin=False, ldap_dn=None):
131 def create_user(self, apiuser, username, email, password, firstname=None,
132 lastname=None, active=True, admin=False, ldap_dn=None):
135 133 """
136 134 Create new user
137 135
138 136 :param apiuser:
139 137 :param username:
140 138 :param password:
139 :param email:
141 140 :param name:
142 141 :param lastname:
143 :param email:
144 142 :param active:
145 143 :param admin:
146 144 :param ldap_dn:
147 145 """
148
149 146 if User.get_by_username(username):
150 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 156 try:
153 form_data = dict(username=username,
154 password=password,
155 active=active,
156 admin=admin,
157 name=firstname,
158 lastname=lastname,
159 email=email,
160 ldap_dn=ldap_dn)
161 UserModel().create_ldap(username, password, ldap_dn, form_data)
162 return dict(msg='created new user %s' % username)
157 usr = UserModel().create_or_update(
158 username, password, email, firstname,
159 lastname, active, admin, ldap_dn
160 )
161 Session.commit()
162 return dict(
163 id=usr.user_id,
164 msg='created new user %s' % username
165 )
163 166 except Exception:
164 167 log.error(traceback.format_exc())
165 168 raise JSONRPCError('failed to create user %s' % username)
166 169
167 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 204 def get_users_group(self, apiuser, group_name):
169 205 """"
170 206 Get users group by name
@@ -190,7 +226,7 b' class ApiController(JSONRPCController):'
190 226 ldap=user.ldap_dn))
191 227
192 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 230 active=users_group.users_group_active,
195 231 members=members)
196 232
@@ -217,41 +253,40 b' class ApiController(JSONRPCController):'
217 253 ldap=user.ldap_dn))
218 254
219 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 257 active=users_group.users_group_active,
222 258 members=members))
223 259 return result
224 260
225 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 264 Creates an new usergroup
229 265
230 :param name:
266 :param group_name:
231 267 :param active:
232 268 """
233 269
234 if self.get_users_group(apiuser, name):
235 raise JSONRPCError("users group %s already exist" % name)
270 if self.get_users_group(apiuser, group_name):
271 raise JSONRPCError("users group %s already exist" % group_name)
236 272
237 273 try:
238 form_data = dict(users_group_name=name,
239 users_group_active=active)
240 ug = UsersGroup.create(form_data)
274 ug = UsersGroupModel().create(name=group_name, active=active)
275 Session.commit()
241 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 278 except Exception:
244 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 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 285 Add a user to a group
251 286
252 :param apiuser
253 :param group_name
254 :param user_name
287 :param apiuser:
288 :param group_name:
289 :param username:
255 290 """
256 291
257 292 try:
@@ -259,32 +294,65 b' class ApiController(JSONRPCController):'
259 294 if not users_group:
260 295 raise JSONRPCError('unknown users group %s' % group_name)
261 296
262 try:
263 user = User.get_by_username(user_name)
264 except NoResultFound:
265 raise JSONRPCError('unknown user %s' % user_name)
297 user = User.get_by_username(username)
298 if user is None:
299 raise JSONRPCError('unknown user %s' % username)
266 300
267 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,
270 msg='created new users group member')
307 return dict(
308 id=ugm.users_group_member_id if ugm != True else None,
309 success=success,
310 msg=msg
311 )
271 312 except Exception:
272 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 344 @HasPermissionAnyDecorator('hg.admin')
276 def get_repo(self, apiuser, name):
345 def get_repo(self, apiuser, repoid):
277 346 """"
278 347 Get repository by name
279 348
280 :param apiuser
281 :param repo_name
349 :param apiuser:
350 :param repo_name:
282 351 """
283 352
284 try:
285 repo = Repository.get_by_repo_name(name)
286 except NoResultFound:
287 return None
353 repo = RepoModel().get_repo(repoid)
354 if repo is None:
355 raise JSONRPCError('unknown repository %s' % repo)
288 356
289 357 members = []
290 358 for user in repo.repo_to_perm:
@@ -319,7 +387,7 b' class ApiController(JSONRPCController):'
319 387
320 388 return dict(
321 389 id=repo.repo_id,
322 name=repo.repo_name,
390 repo_name=repo.repo_name,
323 391 type=repo.repo_type,
324 392 description=repo.description,
325 393 members=members
@@ -330,7 +398,7 b' class ApiController(JSONRPCController):'
330 398 """"
331 399 Get all repositories
332 400
333 :param apiuser
401 :param apiuser:
334 402 """
335 403
336 404 result = []
@@ -338,85 +406,255 b' class ApiController(JSONRPCController):'
338 406 result.append(
339 407 dict(
340 408 id=repository.repo_id,
341 name=repository.repo_name,
409 repo_name=repository.repo_name,
342 410 type=repository.repo_type,
343 411 description=repository.description
344 412 )
345 413 )
346 414 return result
347 415
348 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
349 def create_repo(self, apiuser, name, owner_name, description='',
350 repo_type='hg', private=False):
416 @HasPermissionAnyDecorator('hg.admin')
417 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
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
355 :param name
356 :param description
357 :param type
358 :param private
359 :param owner_name
424 :param apiuser:
425 :param repo_name: name of repository
426 :param revision: revision for which listing should be done
427 :param root_path: path from which start displaying
428 :param ret_type: return type 'all|files|dirs' nodes
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 459 try:
363 try:
364 owner = User.get_by_username(owner_name)
365 except NoResultFound:
366 raise JSONRPCError('unknown user %s' % owner)
460 owner = User.get_by_username(owner_name)
461 if owner is None:
462 raise JSONRPCError('unknown user %s' % owner_name)
367 463
368 if self.get_repo(apiuser, name):
369 raise JSONRPCError("repo %s already exist" % name)
464 if Repository.get_by_repo_name(repo_name):
465 raise JSONRPCError("repo %s already exist" % repo_name)
370 466
371 groups = name.split('/')
467 groups = repo_name.split('/')
372 468 real_name = groups[-1]
373 469 groups = groups[:-1]
374 470 parent_id = None
375 471 for g in groups:
376 group = Group.get_by_group_name(g)
472 group = RepoGroup.get_by_group_name(g)
377 473 if not group:
378 group = ReposGroupModel().create(dict(group_name=g,
379 group_description='',
380 group_parent_id=parent_id))
474 group = ReposGroupModel().create(g, '', parent_id)
381 475 parent_id = group.group_id
382 476
383 RepoModel().create(dict(repo_name=real_name,
384 repo_name_full=name,
385 description=description,
386 private=private,
387 repo_type=repo_type,
388 repo_group=parent_id,
389 clone_uri=None), owner)
477 repo = RepoModel().create(
478 dict(
479 repo_name=real_name,
480 repo_name_full=repo_name,
481 description=description,
482 private=private,
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 515 except Exception:
391 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 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
400 :param repo_name
401 :param user_name
402 :param perm
541 Session.commit()
542 return dict(
543 msg='Granted perm: %s for user: %s in repo: %s' % (
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 564 try:
406 try:
407 repo = Repository.get_by_repo_name(repo_name)
408 except NoResultFound:
565 repo = Repository.get_by_repo_name(repo_name)
566 if repo is None:
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 603 raise JSONRPCError('unknown repository %s' % repo)
410 604
411 try:
412 user = User.get_by_username(user_name)
413 except NoResultFound:
414 raise JSONRPCError('unknown user %s' % user)
605 user_group = UsersGroup.get_by_group_name(group_name)
606 if user_group is None:
607 raise JSONRPCError('unknown users group %s' % user_group)
415 608
416 RepositoryPermissionModel()\
417 .update_or_delete_user_permission(repo, user, perm)
609 RepoModel().grant_users_group_permission(repo=repo_name,
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 619 except Exception:
419 620 log.error(traceback.format_exc())
420 raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
421 % dict(user=user_name, repo=repo_name))
621 raise JSONRPCError(
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 8 :created_on: Apr 21, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -46,33 +46,30 b' class BranchesController(BaseRepoControl'
46 46 def index(self):
47 47
48 48 def _branchtags(localrepo):
49
50 bt = {}
51 49 bt_closed = {}
52
53 50 for bn, heads in localrepo.branchmap().iteritems():
54 51 tip = heads[-1]
55 if 'close' not in localrepo.changelog.read(tip)[5]:
56 bt[bn] = tip
57 else:
52 if 'close' in localrepo.changelog.read(tip)[5]:
58 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)
63 cs_g = c.rhodecode_repo.get_changeset
64 _branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
65 bt.items()]
58 c.repo_closed_branches = {}
59 if c.rhodecode_db_repo.repo_type == 'hg':
60 bt_closed = _branchtags(c.rhodecode_repo._repo)
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
68 bt_closed.items()]
64 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
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 70 c.repo_branches = OrderedDict(sorted(_branches,
71 71 key=lambda ctx: ctx[0],
72 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 75 return render('branches/branches.html')
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 21, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -24,15 +24,22 b''
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 import traceback
27 28
28 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 35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 36 from rhodecode.lib.base import BaseRepoController, render
33 37 from rhodecode.lib.helpers import RepoPage
34 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 43 log = logging.getLogger(__name__)
37 44
38 45
@@ -62,12 +69,32 b' class ChangelogController(BaseRepoContro'
62 69
63 70 p = int(request.params.get('page', 1))
64 71 branch_name = request.params.get('branch', None)
65 c.total_cs = len(c.rhodecode_repo)
66 c.pagination = RepoPage(c.rhodecode_repo, page=p,
67 item_count=c.total_cs, items_per_page=c.size,
68 branch_name=branch_name)
72 try:
73 if branch_name:
74 collection = [z for z in
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 99 return render('changelog/changelog.html')
73 100
@@ -76,7 +103,7 b' class ChangelogController(BaseRepoContro'
76 103 c.cs = c.rhodecode_repo.get_changeset(cs)
77 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 108 Generates a DAG graph for mercurial
82 109
@@ -84,29 +111,20 b' class ChangelogController(BaseRepoContro'
84 111 :param size: number of commits to show
85 112 :param p: page number
86 113 """
87 if not repo.revisions:
114 if not collection:
88 115 c.jsdata = json.dumps([])
89 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 118 data = []
100 rev_end += 1
119 revs = [x.revision for x in collection]
101 120
102 121 if repo.alias == 'git':
103 for _ in xrange(rev_start, rev_end):
122 for _ in revs:
104 123 vtx = [0, 1]
105 124 edges = [[0, 0, 1]]
106 125 data.append(['', vtx, edges])
107 126
108 127 elif repo.alias == 'hg':
109 revs = list(reversed(xrange(rev_start, rev_end)))
110 128 c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
111 129 for (id, type, ctx, vtx, edges) in c.dag:
112 130 if type != graphmod.CHANGESET:
@@ -8,7 +8,7 b''
8 8
9 9 :created_on: Apr 25, 2010
10 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 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
@@ -25,25 +25,129 b''
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
28 30
29 31 from pylons import tmpl_context as c, url, request, response
30 32 from pylons.i18n.translation import _
31 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 40 import rhodecode.lib.helpers as h
34 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 42 from rhodecode.lib.base import BaseRepoController, render
36 43 from rhodecode.lib.utils import EmptyChangeset
37 44 from rhodecode.lib.compat import OrderedDict
38
39 from vcs.exceptions import RepositoryError, ChangesetError, \
40 ChangesetDoesNotExistError
41 from vcs.nodes import FileNode
42 from vcs.utils import diffs as differ
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.meta import Session
49 from rhodecode.lib.diffs import wrapped_diff
43 50
44 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 151 class ChangesetController(BaseRepoController):
48 152
49 153 @LoginRequired()
@@ -55,20 +159,16 b' class ChangesetController(BaseRepoContro'
55 159
56 160 def index(self, revision):
57 161
58 def wrap_to_table(str):
59
60 return '''<table class="code-difftable">
61 <tr class="line">
62 <td class="lineno new"></td>
63 <td class="code"><pre>%s</pre></td>
64 </tr>
65 </table>''' % str
162 c.anchor_url = anchor_url
163 c.ignorews_url = _ignorews_url
164 c.context_url = _context_url
66 165
67 166 #get ranges of revisions if preset
68 167 rev_range = revision.split('...')[:2]
69
168 enable_comments = True
70 169 try:
71 170 if len(rev_range) == 2:
171 enable_comments = False
72 172 rev_start = rev_range[0]
73 173 rev_end = rev_range[1]
74 174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
@@ -77,6 +177,8 b' class ChangesetController(BaseRepoContro'
77 177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
78 178
79 179 c.cs_ranges = list(rev_ranges)
180 if not c.cs_ranges:
181 raise RepositoryError('Changeset range returned empty result')
80 182
81 183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
82 184 log.error(traceback.format_exc())
@@ -84,14 +186,25 b' class ChangesetController(BaseRepoContro'
84 186 return redirect(url('home'))
85 187
86 188 c.changes = OrderedDict()
87 c.sum_added = 0
88 c.sum_removed = 0
89 c.lines_added = 0
90 c.lines_deleted = 0
189
190 c.lines_added = 0 # count of lines added
191 c.lines_deleted = 0 # count of lines removes
192
193 cumulative_diff = 0
91 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 199 # Iterate over ranges (default changeset view is always one changeset)
94 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 208 c.changes[changeset.raw_id] = []
96 209 try:
97 210 changeset_parent = changeset.parents[0]
@@ -102,32 +215,19 b' class ChangesetController(BaseRepoContro'
102 215 # ADDED FILES
103 216 #==================================================================
104 217 for node in changeset.added:
105
106 filenode_old = FileNode(node.path, '', EmptyChangeset())
107 if filenode_old.is_binary or node.is_binary:
108 diff = wrap_to_table(_('binary file'))
109 st = (0, 0)
110 else:
111 # in this case node.size is good parameter since those are
112 # added nodes and their size defines how many changes were
113 # made
114 c.sum_added += node.size
115 if c.sum_added < self.cut_off_limit:
116 f_gitdiff = differ.get_gitdiff(filenode_old, node)
117 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
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
218 fid = h.FID(revision, node.path)
219 line_context_lcl = get_line_ctx(fid, request.GET)
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
221 lim = self.cut_off_limit
222 if cumulative_diff > self.cut_off_limit:
223 lim = -1
224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
225 filenode_new=node,
226 cut_off_limit=lim,
227 ignore_whitespace=ign_whitespace_lcl,
228 line_context=line_context_lcl,
229 enable_comments=enable_comments)
230 cumulative_diff += size
131 231 c.lines_added += st[0]
132 232 c.lines_deleted += st[1]
133 233 c.changes[changeset.raw_id].append(('added', node, diff,
@@ -136,55 +236,42 b' class ChangesetController(BaseRepoContro'
136 236 #==================================================================
137 237 # CHANGED FILES
138 238 #==================================================================
139 if not c.cut_off:
140 for node in changeset.changed:
141 try:
142 filenode_old = changeset_parent.get_node(node.path)
143 except ChangesetError:
144 log.warning('Unable to fetch parent node for diff')
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:
239 for node in changeset.changed:
240 try:
241 filenode_old = changeset_parent.get_node(node.path)
242 except ChangesetError:
243 log.warning('Unable to fetch parent node for diff')
244 filenode_old = FileNode(node.path, '', EmptyChangeset())
152 245
153 if c.sum_removed < self.cut_off_limit:
154 f_gitdiff = differ.get_gitdiff(filenode_old, node)
155 d = differ.DiffProcessor(f_gitdiff,
156 format='gitdiff')
157 st = d.stat()
158 if (st[0] + st[1]) * 256 > self.cut_off_limit:
159 diff = wrap_to_table(_('Diff is to big '
160 'and was cut off, see '
161 'raw diff instead'))
162 else:
163 diff = d.as_html()
164
165 if diff:
166 c.sum_removed += len(diff)
167 else:
168 diff = wrap_to_table(_('Changeset is to big and '
169 'was cut off, see raw '
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))
246 fid = h.FID(revision, node.path)
247 line_context_lcl = get_line_ctx(fid, request.GET)
248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
249 lim = self.cut_off_limit
250 if cumulative_diff > self.cut_off_limit:
251 lim = -1
252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
253 filenode_new=node,
254 cut_off_limit=lim,
255 ignore_whitespace=ign_whitespace_lcl,
256 line_context=line_context_lcl,
257 enable_comments=enable_comments)
258 cumulative_diff += size
259 c.lines_added += st[0]
260 c.lines_deleted += st[1]
261 c.changes[changeset.raw_id].append(('changed', node, diff,
262 cs1, cs2, st))
180 263
181 264 #==================================================================
182 265 # REMOVED FILES
183 266 #==================================================================
184 if not c.cut_off:
185 for node in changeset.removed:
186 c.changes[changeset.raw_id].append(('removed', node, None,
187 None, None, (0, 0)))
267 for node in changeset.removed:
268 c.changes[changeset.raw_id].append(('removed', node, None,
269 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 276 if len(c.cs_ranges) == 1:
190 277 c.changeset = c.cs_ranges[0]
@@ -197,6 +284,8 b' class ChangesetController(BaseRepoContro'
197 284 def raw_changeset(self, revision):
198 285
199 286 method = request.GET.get('diff', 'show')
287 ignore_whitespace = request.GET.get('ignorews') == '1'
288 line_context = request.GET.get('context', 3)
200 289 try:
201 290 c.scm_type = c.rhodecode_repo.alias
202 291 c.changeset = c.rhodecode_repo.get_changeset(revision)
@@ -215,8 +304,10 b' class ChangesetController(BaseRepoContro'
215 304 if filenode_old.is_binary or node.is_binary:
216 305 diff = _('binary file') + '\n'
217 306 else:
218 f_gitdiff = differ.get_gitdiff(filenode_old, node)
219 diff = differ.DiffProcessor(f_gitdiff,
307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
308 ignore_whitespace=ignore_whitespace,
309 context=line_context)
310 diff = diffs.DiffProcessor(f_gitdiff,
220 311 format='gitdiff').raw_diff()
221 312
222 313 cs1 = None
@@ -228,8 +319,10 b' class ChangesetController(BaseRepoContro'
228 319 if filenode_old.is_binary or node.is_binary:
229 320 diff = _('binary file')
230 321 else:
231 f_gitdiff = differ.get_gitdiff(filenode_old, node)
232 diff = differ.DiffProcessor(f_gitdiff,
322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
323 ignore_whitespace=ignore_whitespace,
324 context=line_context)
325 diff = diffs.DiffProcessor(f_gitdiff,
233 326 format='gitdiff').raw_diff()
234 327
235 328 cs1 = filenode_old.last_changeset.raw_id
@@ -250,3 +343,25 b' class ChangesetController(BaseRepoContro'
250 343 c.diffs += x[2]
251 344
252 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 8 :created_on: Dec 8, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -54,7 +54,7 b' class ErrorController(BaseController):'
54 54 resp = request.environ.get('pylons.original_response')
55 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 59 e = request.environ
60 60 c.serv_p = r'%(protocol)s://%(host)s/' \
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 23, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -51,6 +51,11 b' class FeedController(BaseRepoController)'
51 51 self.ttl = "5"
52 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 59 def __changes(self, cs):
55 60 changes = []
56 61
@@ -72,18 +77,21 b' class FeedController(BaseRepoController)'
72 77
73 78 def atom(self, repo_name):
74 79 """Produce an atom-1.0 feed via feedgenerator module"""
75 feed = Atom1Feed(title=self.title % repo_name,
76 link=url('summary_home', repo_name=repo_name,
77 qualified=True),
78 description=self.description % repo_name,
79 language=self.language,
80 ttl=self.ttl)
81 desc_msg = []
80 feed = Atom1Feed(
81 title=self.title % repo_name,
82 link=url('summary_home', repo_name=repo_name,
83 qualified=True),
84 description=self.description % repo_name,
85 language=self.language,
86 ttl=self.ttl
87 )
88
82 89 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
90 desc_msg = []
83 91 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
84 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 95 link=url('changeset_home', repo_name=repo_name,
88 96 revision=cs.raw_id, qualified=True),
89 97 author_name=cs.author,
@@ -94,18 +102,21 b' class FeedController(BaseRepoController)'
94 102
95 103 def rss(self, repo_name):
96 104 """Produce an rss2 feed via feedgenerator module"""
97 feed = Rss201rev2Feed(title=self.title % repo_name,
98 link=url('summary_home', repo_name=repo_name,
99 qualified=True),
100 description=self.description % repo_name,
101 language=self.language,
102 ttl=self.ttl)
103 desc_msg = []
105 feed = Rss201rev2Feed(
106 title=self.title % repo_name,
107 link=url('summary_home', repo_name=repo_name,
108 qualified=True),
109 description=self.description % repo_name,
110 language=self.language,
111 ttl=self.ttl
112 )
113
104 114 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
115 desc_msg = []
105 116 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
106 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 120 link=url('changeset_home', repo_name=repo_name,
110 121 revision=cs.raw_id, qualified=True),
111 122 author_name=cs.author,
@@ -27,25 +27,29 b' import os'
27 27 import logging
28 28 import traceback
29 29
30 from os.path import join as jn
31
32 from pylons import request, response, session, tmpl_context as c, url
30 from pylons import request, response, tmpl_context as c, url
33 31 from pylons.i18n.translation import _
34 32 from pylons.controllers.util import redirect
35 33 from pylons.decorators import jsonify
36 34
37 from vcs.conf import settings
38 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
39 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
40 from vcs.nodes import FileNode, NodeKind
41 from vcs.utils import diffs as differ
35 from rhodecode.lib.vcs.conf import settings
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
38 NodeAlreadyExistsError
39 from rhodecode.lib.vcs.nodes import FileNode
42 40
41 from rhodecode.lib.compat import OrderedDict
43 42 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
44 43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 44 from rhodecode.lib.base import BaseRepoController, render
46 45 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib import diffs
47 47 import rhodecode.lib.helpers as h
48 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 54 log = logging.getLogger(__name__)
51 55
@@ -104,26 +108,6 b' class FilesController(BaseRepoController'
104 108
105 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 111 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
128 112 'repository.admin')
129 113 def index(self, repo_name, revision, f_path):
@@ -162,9 +146,9 b' class FilesController(BaseRepoController'
162 146
163 147 # files or dirs
164 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 152 c.file_history = self._get_node_history(c.changeset, f_path)
169 153 else:
170 154 c.file_history = []
@@ -405,13 +389,19 b' class FilesController(BaseRepoController'
405 389 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
406 390 'repository.admin')
407 391 def diff(self, repo_name, f_path):
408 diff1 = request.GET.get('diff1')
409 diff2 = request.GET.get('diff2')
392 ignore_whitespace = request.GET.get('ignorews') == '1'
393 line_context = request.GET.get('context', 3)
394 diff1 = request.GET.get('diff1', '')
395 diff2 = request.GET.get('diff2', '')
410 396 c.action = request.GET.get('diff')
411 397 c.no_changes = diff1 == diff2
412 398 c.f_path = f_path
413 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 405 try:
416 406 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
417 407 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
@@ -427,12 +417,14 b' class FilesController(BaseRepoController'
427 417 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
428 418 node2 = FileNode('.', '', changeset=c.changeset_2)
429 419 except RepositoryError:
430 return redirect(url('files_home',
431 repo_name=c.repo_name, f_path=f_path))
420 return redirect(url('files_home', repo_name=c.repo_name,
421 f_path=f_path))
432 422
433 423 if c.action == 'download':
434 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
435 format='gitdiff')
424 _diff = diffs.get_gitdiff(node1, node2,
425 ignore_whitespace=ignore_whitespace,
426 context=line_context)
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
436 428
437 429 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
438 430 response.content_type = 'text/plain'
@@ -441,39 +433,28 b' class FilesController(BaseRepoController'
441 433 return diff.raw_diff()
442 434
443 435 elif c.action == 'raw':
444 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
445 format='gitdiff')
436 _diff = diffs.get_gitdiff(node1, node2,
437 ignore_whitespace=ignore_whitespace,
438 context=line_context)
439 diff = diffs.DiffProcessor(_diff, format='gitdiff')
446 440 response.content_type = 'text/plain'
447 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 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
463 if node1.is_binary or node2.is_binary:
464 c.cur_diff = _('Binary file')
465 elif node1.size > self.cut_off_limit or \
466 node2.size > self.cut_off_limit:
467 c.cur_diff = ''
468 c.big_diff = True
448 lim = request.GET.get('fulldiff') or self.cut_off_limit
449 _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
450 filenode_new=node2,
451 cut_off_limit=lim,
452 ignore_whitespace=ign_whitespace_lcl,
453 line_context=line_context_lcl,
454 enable_comments=False)
469 455
470 else:
471 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
472 format='gitdiff')
473 c.cur_diff = diff.as_html()
456 c.changes = [('', node2, diff, cs1, cs2, st,)]
474 457
475 if not c.cur_diff and not c.big_diff:
476 c.no_changes = True
477 458 return render('files/file_diff.html')
478 459
479 460 def _get_node_history(self, cs, f_path):
@@ -485,18 +466,16 b' class FilesController(BaseRepoController'
485 466 tags_group = ([], _("Tags"))
486 467
487 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 470 changesets_group[0].append((chs.raw_id, n_desc,))
490 471
491 472 hist_l.append(changesets_group)
492 473
493 474 for name, chs in c.rhodecode_repo.branches.items():
494 #chs = chs.split(':')[-1]
495 475 branches_group[0].append((chs, name),)
496 476 hist_l.append(branches_group)
497 477
498 478 for name, chs in c.rhodecode_repo.tags.items():
499 #chs = chs.split(':')[-1]
500 479 tags_group[0].append((chs, name),)
501 480 hist_l.append(tags_group)
502 481
@@ -508,6 +487,6 b' class FilesController(BaseRepoController'
508 487 def nodelist(self, repo_name, revision, f_path):
509 488 if request.environ.get('HTTP_X_PARTIAL_XHR'):
510 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 492 return _d + _f
513
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 23, 2011
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 23, 2011
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -23,13 +23,23 b''
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import 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 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 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 44 log = logging.getLogger(__name__)
35 45
@@ -37,11 +47,59 b' log = logging.getLogger(__name__)'
37 47 class ForksController(BaseRepoController):
38 48
39 49 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
42 50 def __before__(self):
43 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 103 def forks(self, repo_name):
46 104 p = int(request.params.get('page', 1))
47 105 repo_id = c.rhodecode_db_repo.repo_id
@@ -54,3 +112,63 b' class ForksController(BaseRepoController'
54 112 return c.forks_data
55 113
56 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 8 :created_on: Feb 18, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -24,14 +24,13 b''
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 from operator import itemgetter
28 27
29 28 from pylons import tmpl_context as c, request
30 29 from paste.httpexceptions import HTTPBadRequest
31 30
32 31 from rhodecode.lib.auth import LoginRequired
33 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 35 log = logging.getLogger(__name__)
37 36
@@ -43,10 +42,8 b' class HomeController(BaseController):'
43 42 super(HomeController, self).__before__()
44 43
45 44 def index(self):
46
47 45 c.repos_list = self.scm_model.get_repos()
48
49 c.groups = Group.query().filter(Group.group_parent_id == None).all()
46 c.groups = self.scm_model.get_repos_groups()
50 47
51 48 return render('/index.html')
52 49
@@ -58,3 +55,11 b' class HomeController(BaseController):'
58 55 return render('/repo_switcher_list.html')
59 56 else:
60 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 8 :created_on: Nov 21, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -23,21 +23,24 b''
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 from itertools import groupby
26 27
27 28 from sqlalchemy import or_
28 from sqlalchemy.orm import joinedload, make_transient
29 from sqlalchemy.orm import joinedload
29 30 from webhelpers.paginate import Page
30 from itertools import groupby
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 32
32 33 from paste.httpexceptions import HTTPBadRequest
33 34 from pylons import request, tmpl_context as c, response, url
34 35 from pylons.i18n.translation import _
35 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
36 36
37 37 import rhodecode.lib.helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 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 45 log = logging.getLogger(__name__)
43 46
@@ -58,6 +61,13 b' class JournalController(BaseController):'
58 61 # Return a rendered template
59 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 71 c.following = self.sa.query(UserFollowing)\
62 72 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
63 73 .options(joinedload(UserFollowing.follows_repository))\
@@ -124,6 +134,7 b' class JournalController(BaseController):'
124 134 try:
125 135 self.scm_model.toggle_following_user(user_id,
126 136 self.rhodecode_user.user_id)
137 Session.commit()
127 138 return 'ok'
128 139 except:
129 140 raise HTTPBadRequest()
@@ -133,11 +144,12 b' class JournalController(BaseController):'
133 144 try:
134 145 self.scm_model.toggle_following_repo(repo_id,
135 146 self.rhodecode_user.user_id)
147 Session.commit()
136 148 return 'ok'
137 149 except:
138 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 153 raise HTTPBadRequest()
142 154
143 155 @LoginRequired()
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 22, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 38 from rhodecode.model.db import User
39 39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 40 from rhodecode.model.user import UserModel
41 from rhodecode.model.meta import Session
41 42
42 43
43 44 log = logging.getLogger(__name__)
@@ -49,7 +50,7 b' class LoginController(BaseController):'
49 50 super(LoginController, self).__before__()
50 51
51 52 def index(self):
52 #redirect if already logged in
53 # redirect if already logged in
53 54 c.came_from = request.GET.get('came_from', None)
54 55
55 56 if self.rhodecode_user.is_authenticated \
@@ -58,21 +59,28 b' class LoginController(BaseController):'
58 59 return redirect(url('home'))
59 60
60 61 if request.POST:
61 #import Login Form validator class
62 # import Login Form validator class
62 63 login_form = LoginForm()
63 64 try:
64 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 67 username = c.form_result['username']
67 68 user = User.get_by_username(username, case_insensitive=True)
68 69 auth_user = AuthUser(user.user_id)
69 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 78 session.save()
72 79
73 log.info('user %s is now authenticated and stored in session',
74 username)
80 log.info('user %s is now authenticated and stored in '
81 'session, session attrs %s' % (username, cs))
75 82 user.update_lastlogin()
83 Session.commit()
76 84
77 85 if c.came_from:
78 86 return redirect(c.came_from)
@@ -92,7 +100,6 b' class LoginController(BaseController):'
92 100 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
93 101 'hg.register.manual_activate')
94 102 def register(self):
95 user_model = UserModel()
96 103 c.auto_active = False
97 104 for perm in User.get_by_username('default').user_perms:
98 105 if perm.permission.permission_name == 'hg.register.auto_activate':
@@ -105,9 +112,10 b' class LoginController(BaseController):'
105 112 try:
106 113 form_result = register_form.to_python(dict(request.POST))
107 114 form_result['active'] = c.auto_active
108 user_model.create_registration(form_result)
115 UserModel().create_registration(form_result)
109 116 h.flash(_('You have successfully registered into rhodecode'),
110 117 category='success')
118 Session.commit()
111 119 return redirect(url('login_home'))
112 120
113 121 except formencode.Invalid, errors:
@@ -121,13 +129,11 b' class LoginController(BaseController):'
121 129 return render('/register.html')
122 130
123 131 def password_reset(self):
124 user_model = UserModel()
125 132 if request.POST:
126
127 133 password_reset_form = PasswordResetForm()()
128 134 try:
129 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 137 h.flash(_('Your password reset link was sent'),
132 138 category='success')
133 139 return redirect(url('login_home'))
@@ -143,13 +149,11 b' class LoginController(BaseController):'
143 149 return render('/password_reset.html')
144 150
145 151 def password_reset_confirmation(self):
146
147 152 if request.GET and request.GET.get('key'):
148 153 try:
149 user_model = UserModel()
150 154 user = User.get_by_api_key(request.GET.get('key'))
151 155 data = dict(email=user.email)
152 user_model.reset_password(data)
156 UserModel().reset_password(data)
153 157 h.flash(_('Your password reset was successful, '
154 158 'new password has been sent to your email'),
155 159 category='success')
@@ -160,7 +164,6 b' class LoginController(BaseController):'
160 164 return redirect(url('login_home'))
161 165
162 166 def logout(self):
163 del session['rhodecode_user']
164 session.save()
165 log.info('Logging out and setting user as Empty')
167 session.delete()
168 log.info('Logging out and deleting session for user')
166 169 redirect(url('home'))
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Aug 7, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -26,7 +26,7 b' import logging'
26 26 import traceback
27 27
28 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 31 from rhodecode.lib.auth import LoginRequired
32 32 from rhodecode.lib.base import BaseController, render
@@ -76,7 +76,7 b' class SearchController(BaseController):'
76 76 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
77 77 try:
78 78 query = qp.parse(unicode(cur_query))
79
79 # extract words for highlight
80 80 if isinstance(query, Phrase):
81 81 highlight_items.update(query.words)
82 82 elif isinstance(query, Prefix):
@@ -92,18 +92,22 b' class SearchController(BaseController):'
92 92 log.debug(highlight_items)
93 93 results = searcher.search(query)
94 94 res_ln = len(results)
95 c.runtime = '%s results (%.3f seconds)' \
96 % (res_ln, results.runtime)
95 c.runtime = '%s results (%.3f seconds)' % (
96 res_ln, results.runtime
97 )
97 98
98 99 def url_generator(**kw):
99 100 return update_params("?q=%s&type=%s" \
100 101 % (c.cur_query, c.cur_search), **kw)
101 102
102 103 c.formated_results = Page(
103 ResultWrapper(search_type, searcher, matcher,
104 highlight_items),
105 page=p, item_count=res_ln,
106 items_per_page=10, url=url_generator)
104 ResultWrapper(search_type, searcher, matcher,
105 highlight_items),
106 page=p,
107 item_count=res_ln,
108 items_per_page=10,
109 url=url_generator
110 )
107 111
108 112 except QueryParserError:
109 113 c.runtime = _('Invalid search query. Try quoting it.')
@@ -117,5 +121,6 b' class SearchController(BaseController):'
117 121 log.error(traceback.format_exc())
118 122 c.runtime = _('An error occurred during this search operation')
119 123
124
120 125 # Return a rendered template
121 126 return render('/search/search.html')
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Jun 30, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 36 import rhodecode.lib.helpers as h
37 37
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
39 HasRepoPermissionAnyDecorator, NotAnonymous
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
40 39 from rhodecode.lib.base import BaseRepoController, render
41 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 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 47 log = logging.getLogger(__name__)
48 48
@@ -52,15 +52,15 b' class SettingsController(BaseRepoControl'
52 52 @LoginRequired()
53 53 def __before__(self):
54 54 super(SettingsController, self).__before__()
55
55
56 56 def __load_defaults(self):
57 c.repo_groups = Group.groups_choices()
57 c.repo_groups = RepoGroup.groups_choices()
58 58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59
59
60 60 repo_model = RepoModel()
61 61 c.users_array = repo_model.get_users_js()
62 62 c.users_groups_array = repo_model.get_users_groups_js()
63
63
64 64 @HasRepoPermissionAllDecorator('repository.admin')
65 65 def index(self, repo_name):
66 66 repo_model = RepoModel()
@@ -89,15 +89,15 b' class SettingsController(BaseRepoControl'
89 89 def update(self, repo_name):
90 90 repo_model = RepoModel()
91 91 changed_name = repo_name
92
92
93 93 self.__load_defaults()
94
94
95 95 _form = RepoSettingsForm(edit=True,
96 96 old_data={'repo_name': repo_name},
97 97 repo_groups=c.repo_groups_choices)()
98 98 try:
99 99 form_result = _form.to_python(dict(request.POST))
100
100
101 101 repo_model.update(repo_name, form_result)
102 102 invalidate_cache('get_repo_cached_%s' % repo_name)
103 103 h.flash(_('Repository %s updated successfully' % repo_name),
@@ -105,6 +105,7 b' class SettingsController(BaseRepoControl'
105 105 changed_name = form_result['repo_name_full']
106 106 action_logger(self.rhodecode_user, 'user_updated_repo',
107 107 changed_name, '', self.sa)
108 Session.commit()
108 109 except formencode.Invalid, errors:
109 110 c.repo_info = repo_model.get_by_repo_name(repo_name)
110 111 c.users_array = repo_model.get_users_js()
@@ -148,61 +149,10 b' class SettingsController(BaseRepoControl'
148 149 repo_model.delete(repo)
149 150 invalidate_cache('get_repo_cached_%s' % repo_name)
150 151 h.flash(_('deleted repository %s') % repo_name, category='success')
152 Session.commit()
151 153 except Exception:
152 154 log.error(traceback.format_exc())
153 155 h.flash(_('An error occurred during deletion of %s') % repo_name,
154 156 category='error')
155 157
156 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 8 :created_on: Apr 18, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 31 from rhodecode.lib.base import BaseRepoController, render
32 32 from rhodecode.lib.helpers import RepoPage
33 from pylons.controllers.util import redirect
33 34
34 35 log = logging.getLogger(__name__)
35 36
@@ -50,8 +51,11 b' class ShortlogController(BaseRepoControl'
50 51 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
51 52
52 53 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
53 items_per_page=size,
54 url=url_generator)
54 items_per_page=size, url=url_generator)
55
56 if not c.repo_changesets:
57 return redirect(url('summary_home', repo_name=repo_name))
58
55 59 c.shortlog_data = render('shortlog/shortlog_data.html')
56 60 if request.environ.get('HTTP_X_PARTIAL_XHR'):
57 61 return c.shortlog_data
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 18, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -23,23 +23,28 b''
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 import traceback
26 27 import calendar
27 28 import logging
28 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 38 from pylons.i18n.translation import _
35 39
36 from rhodecode.model.db import Statistics, Repository
37 from rhodecode.model.repo import RepoModel
40 from beaker.cache import cache_region, region_invalidate
38 41
42 from rhodecode.model.db import Statistics, CacheInvalidation
43 from rhodecode.lib import ALL_READMES, ALL_EXTS
39 44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 45 from rhodecode.lib.base import BaseRepoController, render
41 46 from rhodecode.lib.utils import EmptyChangeset
42
47 from rhodecode.lib.markup_renderer import MarkupRenderer
43 48 from rhodecode.lib.celerylib import run_task
44 49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
45 50 LANGUAGES_EXTENSIONS_MAP
@@ -48,6 +53,10 b' from rhodecode.lib.compat import json, O'
48 53
49 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 61 class SummaryController(BaseRepoController):
53 62
@@ -58,10 +67,7 b' class SummaryController(BaseRepoControll'
58 67 super(SummaryController, self).__before__()
59 68
60 69 def index(self, repo_name):
61
62 e = request.environ
63 70 c.dbrepo = dbrepo = c.rhodecode_db_repo
64
65 71 c.following = self.scm_model.is_following_repo(repo_name,
66 72 self.rhodecode_user.user_id)
67 73
@@ -72,26 +78,34 b' class SummaryController(BaseRepoControll'
72 78 items_per_page=10, url=url_generator)
73 79
74 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 82 username = ''
77 83 password = ''
78 84 else:
79 85 username = str(self.rhodecode_user.username)
80 86 password = '@'
81 87
82 if e.get('wsgi.url_scheme') == 'https':
83 split_s = 'https://'
84 else:
85 split_s = 'http://'
88 parsed_url = urlparse(url.current(qualified=True))
89
90 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
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)\
88 .split(split_s)[-1]]
89 uri = u'%(proto)s%(user)s%(pass)s%(rest)s' \
90 % {'user': username,
91 'pass': password,
92 'proto': qualified_uri[0],
93 'rest': qualified_uri[1]}
95 uri_dict = {
96 'user': username,
97 'pass': password,
98 'scheme': parsed_url.scheme,
99 'netloc': parsed_url.netloc,
100 'path': parsed_url.path
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 107 c.clone_repo_url = uri
108 c.clone_repo_url_id = uri_id
95 109 c.repo_tags = OrderedDict()
96 110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
97 111 try:
@@ -161,8 +175,44 b' class SummaryController(BaseRepoControll'
161 175 if c.enable_downloads:
162 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 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 216 def _get_download_links(self, repo):
167 217
168 218 download_l = []
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 21, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -24,6 +24,9 b''
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 import re
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29
27 30
28 31 def __get_lem():
29 32 from pygments import lexers
@@ -66,6 +69,34 b" ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}"
66 69
67 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 101 def str2bool(_str):
71 102 """
@@ -107,7 +138,6 b' def convert_line_endings(line, mode):'
107 138 line = replace(line, '\r\n', '\r')
108 139 line = replace(line, '\n', '\r')
109 140 elif mode == 2:
110 import re
111 141 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
112 142 return line
113 143
@@ -151,7 +181,7 b' def generate_api_key(username, salt=None'
151 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 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 195 if isinstance(str_, unicode):
166 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 203 try:
169 204 return unicode(str_)
170 205 except UnicodeDecodeError:
@@ -184,10 +219,11 b" def safe_unicode(str_, from_encoding='ut"
184 219 except (ImportError, UnicodeDecodeError, Exception):
185 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 225 safe str function. Does few trick to turn unicode_ into string
190
226
191 227 In case of UnicodeEncodeError we try to return it with encoding detected
192 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 235 if not isinstance(unicode_, basestring):
200 236 return str(unicode_)
201 237
238 if not isinstance(unicode_, basestring):
239 return str(unicode_)
240
202 241 if isinstance(unicode_, str):
203 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 249 try:
206 250 return unicode_.encode(to_encoding)
207 251 except UnicodeEncodeError:
@@ -221,11 +265,10 b" def safe_str(unicode_, to_encoding='utf8"
221 265 return safe_str
222 266
223 267
224
225 268 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
226 269 """
227 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 272 applies to sqlalchemy versions < 0.7.0
230 273
231 274 """
@@ -284,7 +327,7 b' def engine_from_config(configuration, pr'
284 327 def age(curdate):
285 328 """
286 329 turns a datetime into an age string.
287
330
288 331 :param curdate: datetime object
289 332 :rtype: unicode
290 333 :returns: unicode words describing age
@@ -293,7 +336,7 b' def age(curdate):'
293 336 from datetime import datetime
294 337 from webhelpers.date import time_ago_in_words
295 338
296 _ = lambda s:s
339 _ = lambda s: s
297 340
298 341 if not curdate:
299 342 return ''
@@ -310,7 +353,8 b' def age(curdate):'
310 353 pos = 1
311 354 for scale in agescales:
312 355 if scale[1] <= age_seconds:
313 if pos == 6:pos = 5
356 if pos == 6:
357 pos = 5
314 358 return '%s %s' % (time_ago_in_words(curdate,
315 359 agescales[pos][0]), _('ago'))
316 360 pos += 1
@@ -321,10 +365,10 b' def age(curdate):'
321 365 def uri_filter(uri):
322 366 """
323 367 Removes user:password from given url string
324
368
325 369 :param uri:
326 370 :rtype: unicode
327 :returns: filtered list of strings
371 :returns: filtered list of strings
328 372 """
329 373 if not uri:
330 374 return ''
@@ -353,7 +397,7 b' def uri_filter(uri):'
353 397 def credentials_filter(uri):
354 398 """
355 399 Returns a url with removed credentials
356
400
357 401 :param uri:
358 402 """
359 403
@@ -364,16 +408,17 b' def credentials_filter(uri):'
364 408
365 409 return ''.join(uri)
366 410
411
367 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 415 repo it returns a Dummy one instead
371
416
372 417 :param repo:
373 418 :param rev:
374 419 """
375 from vcs.backends.base import BaseRepository
376 from vcs.exceptions import RepositoryError
420 from rhodecode.lib.vcs.backends.base import BaseRepository
421 from rhodecode.lib.vcs.exceptions import RepositoryError
377 422 if not isinstance(repo, BaseRepository):
378 423 raise Exception('You must pass an Repository '
379 424 'object as first argument got %s', type(repo))
@@ -390,13 +435,13 b' def get_current_revision(quiet=False):'
390 435 """
391 436 Returns tuple of (number, id) from repository containing this package
392 437 or None if repository could not be found.
393
438
394 439 :param quiet: prints error for fetching revision if True
395 440 """
396 441
397 442 try:
398 from vcs import get_repo
399 from vcs.utils.helpers import get_scm
443 from rhodecode.lib.vcs import get_repo
444 from rhodecode.lib.vcs.utils.helpers import get_scm
400 445 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
401 446 scm = get_scm(repopath)[0]
402 447 repo = get_repo(path=repopath, alias=scm)
@@ -408,3 +453,15 b' def get_current_revision(quiet=False):'
408 453 "was: %s" % err)
409 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 6 authentication and permission libraries
7 7
8 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 11 :license: GPLv3, see COPYING for more details.
11 12 """
12 13 # This program is free software: you can redistribute it and/or modify
@@ -30,11 +31,12 b' import hashlib'
30 31 from tempfile import _RandomNameSequence
31 32 from decorator import decorator
32 33
33 from pylons import config, session, url, request
34 from pylons import config, url, request
34 35 from pylons.controllers.util import abort, redirect
35 36 from pylons.i18n.translation import _
36 37
37 38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 from rhodecode.model.meta import Session
38 40
39 41 if __platform__ in PLATFORM_WIN:
40 42 from hashlib import sha256
@@ -43,20 +45,22 b' if __platform__ in PLATFORM_OTHERS:'
43 45
44 46 from rhodecode.lib import str2bool, safe_unicode
45 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 49 from rhodecode.lib.auth_ldap import AuthLdap
48 50
49 51 from rhodecode.model import meta
50 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 55 log = logging.getLogger(__name__)
54 56
55 57
56 58 class PasswordGenerator(object):
57 """This is a simple class for generating password from
58 different sets of characters
59 usage:
59 """
60 This is a simple class for generating password from different sets of
61 characters
62 usage::
63
60 64 passwd_gen = PasswordGenerator()
61 65 #print 8-letter password containing only big and small letters
62 66 of alphabet
@@ -128,15 +132,24 b' def check_password(password, hashed):'
128 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 143 if salt is None:
133 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 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 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 159 def authenticate(username, password):
147 """Authentication function used for access control,
160 """
161 Authentication function used for access control,
148 162 firstly checks for db authentication then if ldap is enabled for ldap
149 163 authentication, also creates ldap user if not in database
150 164
@@ -159,16 +173,16 b' def authenticate(username, password):'
159 173 if user is not None and not user.ldap_dn:
160 174 if user.active:
161 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 177 username)
164 178 return True
165 179
166 180 elif user.username == username and check_password(password,
167 181 user.password):
168 log.info('user %s authenticated correctly', username)
182 log.info('user %s authenticated correctly' % username)
169 183 return True
170 184 else:
171 log.warning('user %s is disabled', username)
185 log.warning('user %s tried auth but is disabled' % username)
172 186
173 187 else:
174 188 log.debug('Regular authentication failed')
@@ -178,7 +192,7 b' def authenticate(username, password):'
178 192 log.debug('this user already exists as non ldap')
179 193 return False
180 194
181 ldap_settings = RhodeCodeSettings.get_ldap_settings()
195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
182 196 #======================================================================
183 197 # FALLBACK TO LDAP AUTH IF ENABLE
184 198 #======================================================================
@@ -202,7 +216,7 b' def authenticate(username, password):'
202 216 aldap = AuthLdap(**kwargs)
203 217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
204 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 221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
208 222 .get(k), [''])[0]
@@ -222,6 +236,7 b' def authenticate(username, password):'
222 236 user_attrs):
223 237 log.info('created new ldap user %s' % username)
224 238
239 Session.commit()
225 240 return True
226 241 except (LdapUsernameError, LdapPasswordError,):
227 242 pass
@@ -231,6 +246,64 b' def authenticate(username, password):'
231 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 307 class AuthUser(object):
235 308 """
236 309 A simple object that handles all attributes of user in RhodeCode
@@ -241,12 +314,12 b' class AuthUser(object):'
241 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 319 self.user_id = user_id
247 320 self.api_key = None
321 self.username = username
248 322
249 self.username = 'None'
250 323 self.name = ''
251 324 self.lastname = ''
252 325 self.email = ''
@@ -255,51 +328,85 b' class AuthUser(object):'
255 328 self.permissions = {}
256 329 self._api_key = api_key
257 330 self.propagate_data()
331 self._instance = None
258 332
259 333 def propagate_data(self):
260 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 339 if self._api_key and self._api_key != self.anonymous_user.api_key:
263 #try go get user by api key
264 log.debug('Auth User lookup by API KEY %s', self._api_key)
265 user_model.fill_data(self, api_key=self._api_key)
266 else:
267 log.debug('Auth User lookup by USER ID %s', self.user_id)
268 if self.user_id is not None \
269 and self.user_id != self.anonymous_user.user_id:
270 user_model.fill_data(self, user_id=self.user_id)
340 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)
342 # lookup by userid
343 elif (self.user_id is not None and
344 self.user_id != self.anonymous_user.user_id):
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
346 is_user_loaded = 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 365 else:
272 if self.anonymous_user.active is True:
273 user_model.fill_data(self,
274 user_id=self.anonymous_user.user_id)
275 #then we set this user is logged in
276 self.is_authenticated = True
277 else:
278 self.is_authenticated = False
366 self.user_id = None
367 self.username = None
368 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 374 user_model.fill_perms(self)
282 375
283 376 @property
284 377 def is_admin(self):
285 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 380 def __repr__(self):
292 381 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
293 382 self.is_authenticated)
294 383
295 384 def set_authenticated(self, authenticated=True):
296
297 385 if self.user_id != self.anonymous_user.user_id:
298 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 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 410 permission given in db. We don't want to check each time from db for new
304 411 permissions since adding a new permission also requires application restart
305 412 ie. to decorate new views with the newly created permission
@@ -309,9 +416,9 b' def set_available_permissions(config):'
309 416 """
310 417 log.info('getting information about all available permissions')
311 418 try:
312 sa = meta.Session()
419 sa = meta.Session
313 420 all_perms = sa.query(Permission).all()
314 except:
421 except Exception:
315 422 pass
316 423 finally:
317 424 meta.Session.remove()
@@ -343,26 +450,31 b' class LoginRequired(object):'
343 450
344 451 api_access_ok = False
345 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 454 if user.api_key == request.GET.get('api_key'):
348 455 api_access_ok = True
349 456 else:
350 457 log.debug("API KEY token not valid")
351
352 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
458 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
459 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
353 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 464 return func(*fargs, **fkwargs)
356 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 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 472 return redirect(url('login_home', came_from=p))
362 473
363 474
364 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 478 redirect to login page"""
367 479
368 480 def __call__(self, func):
@@ -372,7 +484,7 b' class NotAnonymous(object):'
372 484 cls = fargs[0]
373 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 489 anonymous = self.user.username == 'default'
378 490
@@ -411,13 +523,11 b' class PermsDecorator(object):'
411 523 self.user)
412 524
413 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 527 return func(*fargs, **fkwargs)
416 528
417 529 else:
418 log.warning('Permission denied for %s %s', cls, self.user)
419
420
530 log.debug('Permission denied for %s %s' % (cls, self.user))
421 531 anonymous = self.user.username == 'default'
422 532
423 533 if anonymous:
@@ -430,7 +540,7 b' class PermsDecorator(object):'
430 540 return redirect(url('login_home', came_from=p))
431 541
432 542 else:
433 #redirect with forbidden ret code
543 # redirect with forbidden ret code
434 544 return abort(403)
435 545
436 546 def check_permissions(self):
@@ -439,7 +549,8 b' class PermsDecorator(object):'
439 549
440 550
441 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 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 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 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 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 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 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 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 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 646 # CHECK FUNCTIONS
498 647 #==============================================================================
@@ -511,7 +660,7 b' class PermsFunction(object):'
511 660 self.repo_name = None
512 661
513 662 def __call__(self, check_Location=''):
514 user = session.get('rhodecode_user', False)
663 user = request.user
515 664 if not user:
516 665 return False
517 666 self.user_perms = user.permissions
@@ -525,7 +674,7 b' class PermsFunction(object):'
525 674 return True
526 675
527 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 678 check_Location or 'unspecified location')
530 679 return False
531 680
@@ -559,8 +708,9 b' class HasRepoPermissionAll(PermsFunction'
559 708 self.repo_name = get_repo_slug(request)
560 709
561 710 try:
562 self.user_perms = set([self.user_perms['reposit'
563 'ories'][self.repo_name]])
711 self.user_perms = set(
712 [self.user_perms['repositories'][self.repo_name]]
713 )
564 714 except KeyError:
565 715 return False
566 716 self.granted_for = self.repo_name
@@ -580,8 +730,9 b' class HasRepoPermissionAny(PermsFunction'
580 730 self.repo_name = get_repo_slug(request)
581 731
582 732 try:
583 self.user_perms = set([self.user_perms['reposi'
584 'tories'][self.repo_name]])
733 self.user_perms = set(
734 [self.user_perms['repositories'][self.repo_name]]
735 )
585 736 except KeyError:
586 737 return False
587 738 self.granted_for = self.repo_name
@@ -590,6 +741,42 b' class HasRepoPermissionAny(PermsFunction'
590 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 781 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
595 782 #==============================================================================
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Created on Nov 17, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -43,8 +43,7 b' class AuthLdap(object):'
43 43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 search_scope='SUBTREE',
47 attr_login='uid'):
46 search_scope='SUBTREE', attr_login='uid'):
48 47 self.ldap_version = ldap_version
49 48 ldap_server_type = 'ldap'
50 49
@@ -53,14 +52,14 b' class AuthLdap(object):'
53 52 if self.TLS_KIND == 'LDAPS':
54 53 port = port or 689
55 54 ldap_server_type = ldap_server_type + 's'
56
55
57 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 58 OPT_X_TLS_DEMAND)
60 59 self.LDAP_SERVER_ADDRESS = server
61 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 63 self.LDAP_BIND_DN = bind_dn
65 64 self.LDAP_BIND_PASS = bind_pass
66 65
@@ -74,7 +73,8 b' class AuthLdap(object):'
74 73 self.attr_login = attr_login
75 74
76 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 79 Raises AuthenticationError if the credentials are rejected, or
80 80 EnvironmentError if the LDAP server can't be reached.
@@ -87,11 +87,15 b' class AuthLdap(object):'
87 87
88 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 94 if "," in username:
91 95 raise LdapUsernameError("invalid character in username: ,")
92 96 try:
93 if hasattr(ldap,'OPT_X_TLS_CACERTDIR'):
94 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
97 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
98 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
95 99 '/etc/openldap/cacerts')
96 100 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
97 101 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
@@ -112,12 +116,12 b' class AuthLdap(object):'
112 116 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
113 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 120 username)
117 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
118 filt, self.LDAP_SERVER)
121 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
122 filter_, self.LDAP_SERVER)
119 123 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
120 filt)
124 filter_)
121 125
122 126 if not lobjects:
123 127 raise ldap.NO_SUCH_OBJECT()
@@ -127,24 +131,28 b' class AuthLdap(object):'
127 131 continue
128 132
129 133 try:
134 log.debug('Trying simple bind with %s' % dn)
130 135 server.simple_bind_s(dn, password)
131 136 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
132 137 '(objectClass=*)')[0][1]
133 138 break
134 139
135 except ldap.INVALID_CREDENTIALS, e:
136 log.debug("LDAP rejected password for user '%s' (%s): %s",
137 uid, username, dn)
140 except ldap.INVALID_CREDENTIALS:
141 log.debug(
142 "LDAP rejected password for user '%s' (%s): %s" % (
143 uid, username, dn
144 )
145 )
138 146
139 147 else:
140 148 log.debug("No matching LDAP objects for authentication "
141 149 "of '%s' (%s)", uid, username)
142 150 raise LdapPasswordError()
143 151
144 except ldap.NO_SUCH_OBJECT, e:
145 log.debug("LDAP says no such user '%s' (%s)", uid, username)
152 except ldap.NO_SUCH_OBJECT:
153 log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
146 154 raise LdapUsernameError()
147 except ldap.SERVER_DOWN, e:
155 except ldap.SERVER_DOWN:
148 156 raise LdapConnectionError("LDAP can't access "
149 157 "authentication server")
150 158
@@ -3,11 +3,12 b''
3 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 7 repositories and send it to backup server using RSA key via ssh.
8 8
9 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 12 :license: GPLv3, see COPYING for more details.
12 13 """
13 14 # This program is free software: you can redistribute it and/or modify
@@ -3,35 +3,125 b''
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 import time
7 import traceback
8
9 from paste.auth.basic import AuthBasicAuthenticator
6 10
7 11 from pylons import config, tmpl_context as c, request, session, url
8 12 from pylons.controllers import WSGIController
9 13 from pylons.controllers.util import redirect
10 14 from pylons.templating import render_mako as render
11 15
12 from rhodecode import __version__
13 from rhodecode.lib import str2bool
14 from rhodecode.lib.auth import AuthUser
15 from rhodecode.lib.utils import get_repo_slug
16 from rhodecode import __version__, BACKENDS
17
18 from rhodecode.lib import str2bool, safe_unicode
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 22 from rhodecode.model import meta
23
24 from rhodecode.model.db import Repository
25 from rhodecode.model.notification import NotificationModel
17 26 from rhodecode.model.scm import ScmModel
18 from rhodecode import BACKENDS
19 from rhodecode.model.db import Repository
20 27
21 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 110 class BaseController(WSGIController):
24 111
25 112 def __before__(self):
26 113 c.rhodecode_version = __version__
114 c.rhodecode_instanceid = config.get('instance_id')
27 115 c.rhodecode_name = config.get('rhodecode_title')
28 116 c.use_gravatar = str2bool(config.get('use_gravatar'))
29 117 c.ga_code = config.get('rhodecode_ga_code')
30 118 c.repo_name = get_repo_slug(request)
31 119 c.backends = BACKENDS.keys()
120 c.unread_notifications = NotificationModel()\
121 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
32 122 self.cut_off_limit = int(config.get('cut_off_limit'))
33 123
34 self.sa = meta.Session()
124 self.sa = meta.Session
35 125 self.scm_model = ScmModel(self.sa)
36 126
37 127 def __call__(self, environ, start_response):
@@ -39,18 +129,30 b' class BaseController(WSGIController):'
39 129 # WSGIController.__call__ dispatches to the Controller method
40 130 # the request is routed to. This routing information is
41 131 # available in environ['pylons.routes_dict']
132 start = time.time()
42 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 135 api_key = request.GET.get('api_key')
45 user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
46 self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
47 self.rhodecode_user.set_authenticated(
48 getattr(session.get('rhodecode_user'),
49 'is_authenticated', False))
50 session['rhodecode_user'] = self.rhodecode_user
51 session.save()
136 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
137 user_id = cookie_store.get('user_id', None)
138 username = get_container_username(environ, config)
139
140 auth_user = AuthUser(user_id, api_key, username)
141 request.user = auth_user
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 151 return WSGIController.__call__(self, environ, start_response)
53 152 finally:
153 log.info('Request to %s time: %.3fs' % (
154 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
155 )
54 156 meta.Session.remove()
55 157
56 158
@@ -80,4 +182,3 b' class BaseRepoController(BaseController)'
80 182
81 183 c.repository_followers = self.scm_model.get_followers(c.repo_name)
82 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 138 if cache_key is None:
139 139 # cache key - the value arguments from this query's parameters.
140 args = _params_from_query(query)
141 cache_key = " ".join([str(x) for x in args])
140 args = [str(x) for x in _params_from_query(query)]
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 148 # get cache
144 149 #cache = query.cache_manager.get_cache_region(namespace, region)
@@ -275,15 +280,20 b' def _params_from_query(query):'
275 280 """
276 281 v = []
277 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
281 # to late-evaluate params after autoflush is called.
282 # convert to a scalar value.
283 if callable(value):
284 value = value()
284 if bind.key in query._params:
285 value = query._params[bind.key]
286 elif bind.callable:
287 # lazyloader may dig a callable in here, intended
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 294 v.append(value)
287 295 if query._criterion is not None:
288 296 visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
297 for f in query._from_obj:
298 visitors.traverse(f, {}, {'bindparam':visit_bindparam})
289 299 return v
@@ -34,8 +34,8 b' from pylons import config'
34 34 from hashlib import md5
35 35 from decorator import decorator
36 36
37 from vcs.utils.lazy import LazyProperty
38
37 from rhodecode.lib.vcs.utils.lazy import LazyProperty
38 from rhodecode import CELERY_ON
39 39 from rhodecode.lib import str2bool, safe_str
40 40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
41 41 from rhodecode.model import init_model
@@ -48,11 +48,6 b' from celery.messaging import establish_c'
48 48
49 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 52 class ResultWrapper(object):
58 53 def __init__(self, task):
@@ -67,7 +62,7 b' def run_task(task, *args, **kwargs):'
67 62 if CELERY_ON:
68 63 try:
69 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 66 return t
72 67
73 68 except socket.error, e:
@@ -80,7 +75,7 b' def run_task(task, *args, **kwargs):'
80 75 except Exception, e:
81 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 79 return ResultWrapper(task(*args, **kwargs))
85 80
86 81
@@ -100,7 +95,7 b' def locked_task(func):'
100 95 lockkey = __get_lockkey(func, *fargs, **fkwargs)
101 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 99 try:
105 100 l = DaemonLock(file_=jn(lockkey_path, lockkey))
106 101 ret = func(*fargs, **fkwargs)
@@ -28,7 +28,7 b' from celery.decorators import task'
28 28 import os
29 29 import traceback
30 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 33 from time import mktime
34 34 from operator import itemgetter
@@ -37,69 +37,76 b' from string import lower'
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 from rhodecode.lib.vcs import get_backend
41
42 from rhodecode import CELERY_ON
40 43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 __get_lockkey, LockHeld, DaemonLock, get_session, dbsession
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
43 46 from rhodecode.lib.helpers import person
44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 from rhodecode.lib.utils import add_cache
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 from rhodecode.lib.utils import add_cache, action_logger
46 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 54 add_cache(config)
54 55
55 56 __all__ = ['whoosh_index', 'get_commits_stats',
56 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():
62 sa = get_session()
63 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
64 return q.ui_value
60 def get_logger(cls):
61 if CELERY_ON:
62 try:
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 72 @task(ignore_result=True)
68 73 @locked_task
69 74 @dbsession
70 75 def whoosh_index(repo_location, full_index):
71 #log = whoosh_index.get_logger()
72 76 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
77 log = whoosh_index.get_logger(whoosh_index)
78 DBS = get_session()
79
73 80 index_location = config['index_dir']
74 81 WhooshIndexingDaemon(index_location=index_location,
75 repo_location=repo_location, sa=get_session())\
82 repo_location=repo_location, sa=DBS)\
76 83 .run(full_index=full_index)
77 84
78 85
79 86 @task(ignore_result=True)
80 87 @dbsession
81 88 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
82 try:
83 log = get_commits_stats.get_logger()
84 except:
85 log = logging.getLogger(__name__)
86
89 log = get_logger(get_commits_stats)
90 DBS = get_session()
87 91 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
88 92 ts_max_y)
89 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 97 try:
93 sa = get_session()
94 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 101 akc = lambda k: person(k).replace('"', "")
98 102
99 103 co_day_auth_aggr = {}
100 104 commits_by_day_aggregate = {}
101 repos_path = get_repos_path()
102 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
105 repo = Repository.get_by_repo_name(repo_name)
106 if repo is None:
107 return True
108
109 repo = repo.scm_instance
103 110 repo_size = repo.count()
104 111 # return if repo have no revisions
105 112 if repo_size < 1:
@@ -112,9 +119,9 b' def get_commits_stats(repo_name, ts_min_'
112 119 last_cs = None
113 120 timegetter = itemgetter('time')
114 121
115 dbrepo = sa.query(Repository)\
122 dbrepo = DBS.query(Repository)\
116 123 .filter(Repository.repo_name == repo_name).scalar()
117 cur_stats = sa.query(Statistics)\
124 cur_stats = DBS.query(Statistics)\
118 125 .filter(Statistics.repository == dbrepo).scalar()
119 126
120 127 if cur_stats is not None:
@@ -132,7 +139,7 b' def get_commits_stats(repo_name, ts_min_'
132 139 cur_stats.commit_activity_combined))
133 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 143 lmktime = mktime
137 144
138 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 214 stats.commit_activity = json.dumps(co_day_auth_aggr)
208 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 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 221 if last_rev == 0 or leftovers < parse_limit:
215 222 log.debug('getting code trending stats')
@@ -218,18 +225,18 b' def get_commits_stats(repo_name, ts_min_'
218 225 try:
219 226 stats.repository = dbrepo
220 227 stats.stat_on_revision = last_cs.revision if last_cs else 0
221 sa.add(stats)
222 sa.commit()
228 DBS.add(stats)
229 DBS.commit()
223 230 except:
224 231 log.error(traceback.format_exc())
225 sa.rollback()
232 DBS.rollback()
226 233 lock.release()
227 234 return False
228 235
229 # final release
236 #final release
230 237 lock.release()
231 238
232 # execute another task if celery is enabled
239 #execute another task if celery is enabled
233 240 if len(repo.revisions) > 1 and CELERY_ON:
234 241 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
235 242 return True
@@ -240,38 +247,28 b' def get_commits_stats(repo_name, ts_min_'
240 247 @task(ignore_result=True)
241 248 @dbsession
242 249 def send_password_link(user_email):
243 try:
244 log = reset_user_password.get_logger()
245 except:
246 log = logging.getLogger(__name__)
250 from rhodecode.model.notification import EmailNotificationModel
247 251
248 from rhodecode.lib import auth
252 log = get_logger(send_password_link)
253 DBS = get_session()
249 254
250 255 try:
251 sa = get_session()
252 user = sa.query(User).filter(User.email == user_email).scalar()
253
256 user = User.get_by_email(user_email)
254 257 if user:
258 log.debug('password reset user found %s' % user)
255 259 link = url('reset_password_confirmation', key=user.api_key,
256 260 qualified=True)
257 tmpl = """
258 Hello %s
259
260 We received a request to create a new password for your account.
261
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 """
261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
262 body = EmailNotificationModel().get_email_tmpl(reg_type,
263 **{'user':user.short_contact,
264 'reset_url':link})
265 log.debug('sending email')
268 266 run_task(send_email, user_email,
269 "RhodeCode password reset link",
270 tmpl % (user.short_contact, link))
271 log.info('send new password mail to %s', user_email)
272
267 _("password reset link"), body)
268 log.info('send new password mail to %s' % user_email)
269 else:
270 log.debug("password reset email %s not found" % user_email)
273 271 except:
274 log.error('Failed to update user password')
275 272 log.error(traceback.format_exc())
276 273 return False
277 274
@@ -280,36 +277,32 b" If you didn't request new password pleas"
280 277 @task(ignore_result=True)
281 278 @dbsession
282 279 def reset_user_password(user_email):
283 try:
284 log = reset_user_password.get_logger()
285 except:
286 log = logging.getLogger(__name__)
280 from rhodecode.lib import auth
287 281
288 from rhodecode.lib import auth
282 log = get_logger(reset_user_password)
283 DBS = get_session()
289 284
290 285 try:
291 286 try:
292 sa = get_session()
293 user = sa.query(User).filter(User.email == user_email).scalar()
287 user = User.get_by_email(user_email)
294 288 new_passwd = auth.PasswordGenerator().gen_password(8,
295 289 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
296 290 if user:
297 291 user.password = auth.get_crypt_password(new_passwd)
298 292 user.api_key = auth.generate_api_key(user.username)
299 sa.add(user)
300 sa.commit()
301 log.info('change password for %s', user_email)
293 DBS.add(user)
294 DBS.commit()
295 log.info('change password for %s' % user_email)
302 296 if new_passwd is None:
303 297 raise Exception('unable to generate new password')
304
305 298 except:
306 299 log.error(traceback.format_exc())
307 sa.rollback()
300 DBS.rollback()
308 301
309 302 run_task(send_email, user_email,
310 "Your new RhodeCode password",
303 'Your new password',
311 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 307 except:
315 308 log.error('Failed to update user password')
@@ -320,7 +313,7 b' def reset_user_password(user_email):'
320 313
321 314 @task(ignore_result=True)
322 315 @dbsession
323 def send_email(recipients, subject, body):
316 def send_email(recipients, subject, body, html_body=''):
324 317 """
325 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 321 address from field 'email_to' is used instead
329 322 :param subject: subject of the mail
330 323 :param body: body of the mail
324 :param html_body: html version of body
331 325 """
332 try:
333 log = send_email.get_logger()
334 except:
335 log = logging.getLogger(__name__)
336
337 sa = get_session()
326 log = get_logger(send_email)
327 DBS = get_session()
328
338 329 email_config = config
339
330 subject = "%s %s" % (email_config.get('email_prefix'), subject)
340 331 if not recipients:
341 332 # if recipients are not defined we send to email_config + all admins
342 admins = [
343 u.email for u in sa.query(User).filter(User.admin==True).all()
344 ]
333 admins = [u.email for u in User.query()
334 .filter(User.admin == True).all()]
345 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 338 user = email_config.get('smtp_username')
349 339 passwd = email_config.get('smtp_password')
350 340 mail_server = email_config.get('smtp_server')
@@ -355,9 +345,9 b' def send_email(recipients, subject, body'
355 345 smtp_auth = email_config.get('smtp_auth')
356 346
357 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 349 mail_port, ssl, tls, debug=debug)
360 m.send(recipients, subject, body)
350 m.send(recipients, subject, body, html_body)
361 351 except:
362 352 log.error('Mail sending failed')
363 353 log.error(traceback.format_exc())
@@ -368,29 +358,45 b' def send_email(recipients, subject, body'
368 358 @task(ignore_result=True)
369 359 @dbsession
370 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 367 from rhodecode.model.repo import RepoModel
372 368
373 try:
374 log = create_repo_fork.get_logger()
375 except:
376 log = logging.getLogger(__name__)
369 log = get_logger(create_repo_fork)
370 DBS = get_session()
371
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())
379 repo_model.create(form_data, cur_user, just_db=True, fork=True)
380 repo_name = form_data['repo_name']
381 repos_path = get_repos_path()
382 repo_path = os.path.join(repos_path, repo_name)
383 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
384 alias = form_data['repo_type']
383 log.info('creating fork of %s as %s', source_repo_path,
384 destination_fork_path)
385 backend = get_backend(alias)
386 backend(safe_str(destination_fork_path), create=True,
387 src_url=safe_str(source_repo_path),
388 update_after_clone=update_after_clone)
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)
387 backend = get_backend(alias)
388 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
389
392 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
393 fork_name, '', DBS)
394 # finally commit at latest possible stage
395 DBS.commit()
390 396
391 397 def __get_codes_stats(repo_name):
392 repos_path = get_repos_path()
393 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
398 repo = Repository.get_by_repo_name(repo_name).scm_instance
399
394 400 tip = repo.get_changeset()
395 401 code_stats = {}
396 402
@@ -1,7 +1,10 b''
1 import rhodecode
1 2 from rhodecode.lib.utils import BasePasterCommand, Command
2 3 from celery.app import app_or_default
3 4 from celery.bin import camqadm, celerybeat, celeryd, celeryev
4 5
6 from rhodecode.lib import str2bool
7
5 8 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
6 9 'CAMQPAdminCommand', 'CeleryEventCommand']
7 10
@@ -26,6 +29,16 b' class CeleryCommand(BasePasterCommand):'
26 29 self.parser.add_option(x)
27 30
28 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 42 cmd = self.celery_command(app_or_default())
30 43 return cmd.run(**vars(self.options))
31 44
@@ -4,11 +4,11 b''
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Python backward compatibility functions and common libs
7
8
7
8
9 9 :created_on: Oct 7, 2011
10 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 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
@@ -87,10 +87,11 b' class _Nil(object):'
87 87
88 88 _nil = _Nil()
89 89
90
90 91 class _odict(object):
91 92 """Ordered dict data structure, with O(1) complexity for dict operations
92 93 that modify one element.
93
94
94 95 Overwriting values doesn't change their original sequential order.
95 96 """
96 97
@@ -146,7 +147,7 b' class _odict(object):'
146 147 dict_impl = self._dict_impl()
147 148 try:
148 149 dict_impl.__getitem__(self, key)[1] = val
149 except KeyError, e:
150 except KeyError:
150 151 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
151 152 dict_impl.__setitem__(self, key, new)
152 153 if dict_impl.__getattribute__(self, 'lt') == _nil:
@@ -158,7 +159,7 b' class _odict(object):'
158 159
159 160 def __delitem__(self, key):
160 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 163 if pred == _nil:
163 164 dict_impl.__setattr__(self, 'lh', succ)
164 165 else:
@@ -351,6 +352,7 b' class _odict(object):'
351 352 dict_impl.__getattribute__(self, 'lt'),
352 353 dict_impl.__repr__(self))
353 354
355
354 356 class OrderedDict(_odict, dict):
355 357
356 358 def _dict_impl(self):
@@ -8,7 +8,7 b''
8 8
9 9 :created_on: Apr 10, 2010
10 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 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 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 33 from rhodecode import __dbversion__
34 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 37 from rhodecode.lib.utils import ask_ok
38 38 from rhodecode.model import init_model
39 39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 RhodeCodeSettings, UserToPerm, DbMigrateVersion
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
41 UserRepoGroupToPerm
41 42
42 43 from sqlalchemy.engine import create_engine
44 from rhodecode.model.repos_group import ReposGroupModel
43 45
44 46 log = logging.getLogger(__name__)
45 47
@@ -57,10 +59,11 b' class DbManage(object):'
57 59 def init_db(self):
58 60 engine = create_engine(self.dburi, echo=self.log_sql)
59 61 init_model(engine)
60 self.sa = meta.Session()
62 self.sa = meta.Session
61 63
62 64 def create_tables(self, override=False):
63 """Create a auth database
65 """
66 Create a auth database
64 67 """
65 68
66 69 log.info("Any existing database is going to be destroyed")
@@ -75,23 +78,19 b' class DbManage(object):'
75 78
76 79 checkfirst = not override
77 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 83 def set_db_version(self):
81 try:
82 ver = DbMigrateVersion()
83 ver.version = __dbversion__
84 ver.repository_id = 'rhodecode_db_migrations'
85 ver.repository_path = 'versions'
86 self.sa.add(ver)
87 self.sa.commit()
88 except:
89 self.sa.rollback()
90 raise
91 log.info('db version set to: %s', __dbversion__)
84 ver = DbMigrateVersion()
85 ver.version = __dbversion__
86 ver.repository_id = 'rhodecode_db_migrations'
87 ver.repository_path = 'versions'
88 self.sa.add(ver)
89 log.info('db version set to: %s' % __dbversion__)
92 90
93 91 def upgrade(self):
94 """Upgrades given database schema to given revision following
92 """
93 Upgrades given database schema to given revision following
95 94 all needed steps, to perform the upgrade
96 95
97 96 """
@@ -146,7 +145,7 b' class DbManage(object):'
146 145 self.klass = klass
147 146
148 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 149 print ('attempting to do database upgrade to version %s' \
151 150 % __dbversion__)
152 151 api.upgrade(db_uri, repository_path, __dbversion__)
@@ -170,16 +169,26 b' class DbManage(object):'
170 169 self.klass.fix_settings()
171 170 print ('Adding ldap defaults')
172 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 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 184 for step in upgrade_steps:
178 185 print ('performing upgrade step %s' % step)
179 186 getattr(UpgradeSteps(self), 'step_%s' % step)()
187 self.sa.commit()
180 188
181 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 194 paths = self.sa.query(RhodeCodeUi)\
@@ -196,7 +205,8 b' class DbManage(object):'
196 205 raise
197 206
198 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 210 used mostly for anonymous access
201 211 """
202 212 def_user = self.sa.query(User)\
@@ -215,10 +225,11 b' class DbManage(object):'
215 225 raise
216 226
217 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 234 try:
224 235 self.sa.add(hgsettings3)
@@ -258,18 +269,27 b' class DbManage(object):'
258 269 self.create_user(username, password, email, True)
259 270 else:
260 271 log.info('creating admin and regular test users')
261 self.create_user('test_admin', 'test12',
262 'test_admin@mail.com', True)
263 self.create_user('test_regular', 'test12',
264 'test_regular@mail.com', False)
265 self.create_user('test_regular2', 'test12',
266 'test_regular2@mail.com', False)
272 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
273 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
274 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
275 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
276 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
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 287 def create_ui_settings(self):
269 """Creates ui settings, fills out hooks
288 """
289 Creates ui settings, fills out hooks
270 290 and disables dotencode
291 """
271 292
272 """
273 293 #HOOKS
274 294 hooks1_key = RhodeCodeUi.HOOK_UPDATE
275 295 hooks1_ = self.sa.query(RhodeCodeUi)\
@@ -300,7 +320,7 b' class DbManage(object):'
300 320 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
301 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 324 dotencode_disable = RhodeCodeUi()
305 325 dotencode_disable.ui_section = 'format'
306 326 dotencode_disable.ui_key = 'dotencode'
@@ -312,39 +332,43 b' class DbManage(object):'
312 332 largefiles.ui_key = 'largefiles'
313 333 largefiles.ui_value = ''
314 334
315 try:
316 self.sa.add(hooks1)
317 self.sa.add(hooks2)
318 self.sa.add(hooks3)
319 self.sa.add(hooks4)
320 self.sa.add(dotencode_disable)
321 self.sa.add(largefiles)
322 self.sa.commit()
323 except:
324 self.sa.rollback()
325 raise
335 self.sa.add(hooks1)
336 self.sa.add(hooks2)
337 self.sa.add(hooks3)
338 self.sa.add(hooks4)
339 self.sa.add(largefiles)
326 340
327 def create_ldap_options(self,skip_existing=False):
341 def create_ldap_options(self, skip_existing=False):
328 342 """Creates ldap settings"""
329 343
330 try:
331 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
332 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
333 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
334 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
335 ('ldap_filter', ''), ('ldap_search_scope', ''),
336 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
337 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
344 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
345 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
346 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
347 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
348 ('ldap_filter', ''), ('ldap_search_scope', ''),
349 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
350 ('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:
340 log.debug('Skipping option %s' % k)
341 continue
342 setting = RhodeCodeSettings(k, v)
343 self.sa.add(setting)
344 self.sa.commit()
345 except:
346 self.sa.rollback()
347 raise
358 def fixup_groups(self):
359 def_usr = User.get_by_username('default')
360 for g in RepoGroup.query().all():
361 g.group_name = g.get_new_name(g.name)
362 self.sa.add(g)
363 # get default perm
364 default = UserRepoGroupToPerm.query()\
365 .filter(UserRepoGroupToPerm.group == g)\
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 373 def config_prompt(self, test_repo_path='', retries=3):
350 374 if retries == 3:
@@ -359,16 +383,15 b' class DbManage(object):'
359 383 path = test_repo_path
360 384 path_ok = True
361 385
362 #check proper dir
386 # check proper dir
363 387 if not os.path.isdir(path):
364 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 392 if not os.access(path, os.W_OK) and path_ok:
369 393 path_ok = False
370 log.error('No write permission to given path %s', path)
371
394 log.error('No write permission to given path %s' % path)
372 395
373 396 if retries == 0:
374 397 sys.exit('max retries reached')
@@ -408,85 +431,68 b' class DbManage(object):'
408 431 paths.ui_key = '/'
409 432 paths.ui_value = path
410 433
411 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
412 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
413 hgsettings3 = RhodeCodeSettings('ga_code', '')
434 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
435 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
436 hgsettings3 = RhodeCodeSetting('ga_code', '')
414 437
415 try:
416 self.sa.add(web1)
417 self.sa.add(web2)
418 self.sa.add(web3)
419 self.sa.add(web4)
420 self.sa.add(paths)
421 self.sa.add(hgsettings1)
422 self.sa.add(hgsettings2)
423 self.sa.add(hgsettings3)
424
425 self.sa.commit()
426 except:
427 self.sa.rollback()
428 raise
438 self.sa.add(web1)
439 self.sa.add(web2)
440 self.sa.add(web3)
441 self.sa.add(web4)
442 self.sa.add(paths)
443 self.sa.add(hgsettings1)
444 self.sa.add(hgsettings2)
445 self.sa.add(hgsettings3)
429 446
430 447 self.create_ldap_options()
431 448
432 449 log.info('created ui config')
433 450
434 451 def create_user(self, username, password, email='', admin=False):
435 log.info('creating administrator user %s', username)
436
437 form_data = dict(username=username,
438 password=password,
439 active=True,
440 admin=admin,
441 name='RhodeCode',
442 lastname='Admin',
443 email=email)
444 User.create(form_data)
445
452 log.info('creating user %s' % username)
453 UserModel().create_or_update(username, password, email,
454 name='RhodeCode', lastname='Admin',
455 active=True, admin=admin)
446 456
447 457 def create_default_user(self):
448 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 465 def create_permissions(self):
461 #module.(access|create|change|delete)_[name]
462 #module.(read|write|owner)
463 perms = [('repository.none', 'Repository no access'),
464 ('repository.read', 'Repository read access'),
465 ('repository.write', 'Repository write access'),
466 ('repository.admin', 'Repository admin access'),
467 ('hg.admin', 'Hg Administrator'),
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'),
466 # module.(access|create|change|delete)_[name]
467 # module.(none|read|write|admin)
468 perms = [
469 ('repository.none', 'Repository no access'),
470 ('repository.read', 'Repository read access'),
471 ('repository.write', 'Repository write access'),
472 ('repository.admin', 'Repository admin access'),
474 473
475 ('hg.register.auto_activate', 'Register new user with '
476 'RhodeCode without auto '
477 'activation'),
478 ]
474 ('group.none', 'Repositories Group no access'),
475 ('group.read', 'Repositories Group read access'),
476 ('group.write', 'Repositories Group write access'),
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 490 for p in perms:
481 new_perm = Permission()
482 new_perm.permission_name = p[0]
483 new_perm.permission_longname = p[1]
484 try:
491 if not Permission.get_by_key(p[0]):
492 new_perm = Permission()
493 new_perm.permission_name = p[0]
494 new_perm.permission_longname = p[1]
485 495 self.sa.add(new_perm)
486 self.sa.commit()
487 except:
488 self.sa.rollback()
489 raise
490 496
491 497 def populate_default_permissions(self):
492 498 log.info('creating default user permissions')
@@ -512,11 +518,6 b' class DbManage(object):'
512 518 .filter(Permission.permission_name == 'repository.read')\
513 519 .scalar()
514 520
515 try:
516 self.sa.add(reg_perm)
517 self.sa.add(create_repo_perm)
518 self.sa.add(default_repo_perm)
519 self.sa.commit()
520 except:
521 self.sa.rollback()
522 raise
521 self.sa.add(reg_perm)
522 self.sa.add(create_repo_perm)
523 self.sa.add(default_repo_perm)
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Dec 11, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -8,4 +8,4 b''
8 8 from rhodecode.lib.dbmigrate.migrate.versioning import *
9 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 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 16 for x in _sa_version.split("."))
17 17 SQLA_06 = _sa_version >= (0, 6)
18 18 SQLA_07 = _sa_version >= (0, 7)
@@ -17,23 +17,19 b' from sqlalchemy.schema import (ForeignKe'
17 17 Index)
18 18
19 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:
23 from sqlalchemy.sql.compiler import SchemaGenerator, SchemaDropper
24 else:
25 from sqlalchemy.schema import AddConstraint, DropConstraint
26 from sqlalchemy.sql.compiler import DDLCompiler
27 SchemaGenerator = SchemaDropper = DDLCompiler
22 from sqlalchemy.schema import AddConstraint, DropConstraint
23 from sqlalchemy.sql.compiler import DDLCompiler
24 SchemaGenerator = SchemaDropper = DDLCompiler
28 25
29 26
30 27 class AlterTableVisitor(SchemaVisitor):
31 28 """Common operations for ``ALTER TABLE`` statements."""
32 29
33 if SQLA_06:
34 # engine.Compiler looks for .statement
35 # when it spawns off a new compiler
36 statement = ClauseElement()
30 # engine.Compiler looks for .statement
31 # when it spawns off a new compiler
32 statement = ClauseElement()
37 33
38 34 def append(self, s):
39 35 """Append content to the SchemaIterator's query buffer."""
@@ -123,9 +119,8 b' class ANSIColumnGenerator(AlterTableVisi'
123 119 name=column.primary_key_name)
124 120 cons.create()
125 121
126 if SQLA_06:
127 def add_foreignkey(self, fk):
128 self.connection.execute(AddConstraint(fk))
122 def add_foreignkey(self, fk):
123 self.connection.execute(AddConstraint(fk))
129 124
130 125 class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
131 126 """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
@@ -232,10 +227,7 b' class ANSISchemaChanger(AlterTableVisito'
232 227
233 228 def _visit_column_type(self, table, column, delta):
234 229 type_ = delta['type']
235 if SQLA_06:
236 type_text = str(type_.compile(dialect=self.dialect))
237 else:
238 type_text = type_.dialect_impl(self.dialect).get_col_spec()
230 type_text = str(type_.compile(dialect=self.dialect))
239 231 self.append("TYPE %s" % type_text)
240 232
241 233 def _visit_column_name(self, table, column, delta):
@@ -279,75 +271,17 b' class ANSIConstraintCommon(AlterTableVis'
279 271 def visit_migrate_unique_constraint(self, *p, **k):
280 272 self._visit_constraint(*p, **k)
281 273
282 if SQLA_06:
283 class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
284 def _visit_constraint(self, constraint):
285 constraint.name = self.get_constraint_name(constraint)
286 self.append(self.process(AddConstraint(constraint)))
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 """
274 class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
275 def _visit_constraint(self, constraint):
276 constraint.name = self.get_constraint_name(constraint)
277 self.append(self.process(AddConstraint(constraint)))
278 self.execute()
303 279
304 if isinstance(cons, PrimaryKeyConstraint):
305 if cons.name is not None:
306 self.append("CONSTRAINT %s " % self.preparer.format_constraint(cons))
307 self.append("PRIMARY KEY ")
308 self.append("(%s)" % ', '.join(self.preparer.quote(c.name, c.quote)
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")
280 class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
281 def _visit_constraint(self, constraint):
282 constraint.name = self.get_constraint_name(constraint)
283 self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
284 self.execute()
351 285
352 286
353 287 class ANSIDialect(DefaultDialect):
@@ -4,7 +4,7 b''
4 4 from sqlalchemy import schema
5 5
6 6 from rhodecode.lib.dbmigrate.migrate.exceptions import *
7 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06
7
8 8
9 9 class ConstraintChangeset(object):
10 10 """Base class for Constraint classes."""
@@ -85,7 +85,6 b' class PrimaryKeyConstraint(ConstraintCha'
85 85 if table is not None:
86 86 self._set_parent(table)
87 87
88
89 88 def autoname(self):
90 89 """Mimic the database's automatic constraint names"""
91 90 return "%s_pkey" % self.table.name
@@ -111,8 +110,9 b' class ForeignKeyConstraint(ConstraintCha'
111 110 table = kwargs.pop('table', table)
112 111 refcolnames, reftable = self._normalize_columns(refcolumns,
113 112 table_name=True)
114 super(ForeignKeyConstraint, self).__init__(colnames, refcolnames, *args,
115 **kwargs)
113 super(ForeignKeyConstraint, self).__init__(
114 colnames, refcolnames, *args,**kwargs
115 )
116 116 if table is not None:
117 117 self._set_parent(table)
118 118
@@ -165,8 +165,6 b' class CheckConstraint(ConstraintChangese'
165 165 table = kwargs.pop('table', table)
166 166 schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
167 167 if table is not None:
168 if not SQLA_06:
169 self.table = table
170 168 self._set_parent(table)
171 169 self.colnames = colnames
172 170
@@ -199,4 +197,4 b' class UniqueConstraint(ConstraintChanges'
199 197
200 198 def autoname(self):
201 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 4 from sqlalchemy.databases import firebird as sa_base
5 5 from sqlalchemy.schema import PrimaryKeyConstraint
6 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:
11 FBSchemaGenerator = sa_base.FBDDLCompiler
12 else:
13 FBSchemaGenerator = sa_base.FBSchemaGenerator
10 FBSchemaGenerator = sa_base.FBDDLCompiler
14 11
15 12 class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator):
16 13 """Firebird column generator implementation."""
@@ -41,10 +38,7 b' class FBColumnDropper(ansisql.ANSIColumn'
41 38 # is deleted!
42 39 continue
43 40
44 if SQLA_06:
45 should_drop = column.name in cons.columns
46 else:
47 should_drop = cons.contains_column(column) and cons.name
41 should_drop = column.name in cons.columns
48 42 if should_drop:
49 43 self.start_alter_table(column)
50 44 self.append("DROP CONSTRAINT ")
@@ -6,13 +6,10 b' from sqlalchemy.databases import mysql a'
6 6 from sqlalchemy import types as sqltypes
7 7
8 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:
13 MySQLSchemaGenerator = sa_base.MySQLSchemaGenerator
14 else:
15 MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
12 MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
16 13
17 14 class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator):
18 15 pass
@@ -53,37 +50,11 b' class MySQLSchemaChanger(MySQLSchemaGene'
53 50 class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator):
54 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:
63 class MySQLConstraintDropper(ansisql.ANSIConstraintDropper):
64
65 def visit_migrate_primary_key_constraint(self, constraint):
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()
54 class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper):
55 def visit_migrate_check_constraint(self, *p, **k):
56 raise exceptions.NotSupportedError("MySQL does not support CHECK"
57 " constraints, use triggers instead.")
87 58
88 59
89 60 class MySQLDialect(ansisql.ANSIDialect):
@@ -3,14 +3,11 b''
3 3
4 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 postgres as sa_base
10 PGSchemaGenerator = sa_base.PGSchemaGenerator
11 else:
12 from sqlalchemy.databases import postgresql as sa_base
13 PGSchemaGenerator = sa_base.PGDDLCompiler
9 from sqlalchemy.databases import postgresql as sa_base
10 PGSchemaGenerator = sa_base.PGDDLCompiler
14 11
15 12
16 13 class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator):
@@ -11,11 +11,8 b' from sqlalchemy.databases import sqlite '
11 11 from rhodecode.lib.dbmigrate.migrate import exceptions
12 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 17 class SQLiteCommon(object):
21 18
@@ -39,7 +36,7 b' class SQLiteHelper(SQLiteCommon):'
39 36
40 37 insertion_string = self._modify_table(table, column, delta)
41 38
42 table.create()
39 table.create(bind=self.connection)
43 40 self.append(insertion_string % {'table_name': table_name})
44 41 self.execute()
45 42 self.append('DROP TABLE migration_tmp')
@@ -349,10 +349,7 b' class ColumnDelta(DictMixin, sqlalchemy.'
349 349 def process_column(self, column):
350 350 """Processes default values for column"""
351 351 # XXX: this is a snippet from SA processing of positional parameters
352 if not SQLA_06 and column.args:
353 toinit = list(column.args)
354 else:
355 toinit = list()
352 toinit = list()
356 353
357 354 if column.server_default is not None:
358 355 if isinstance(column.server_default, sqlalchemy.FetchedValue):
@@ -368,9 +365,6 b' class ColumnDelta(DictMixin, sqlalchemy.'
368 365 if toinit:
369 366 column._init_items(*toinit)
370 367
371 if not SQLA_06:
372 column.args = []
373
374 368 def _get_table(self):
375 369 return getattr(self, '_table', None)
376 370
@@ -469,14 +463,18 b' class ChangesetTable(object):'
469 463 self._set_parent(self.metadata)
470 464
471 465 def _meta_key(self):
466 """Get the meta key for this table."""
472 467 return sqlalchemy.schema._get_table_key(self.name, self.schema)
473 468
474 469 def deregister(self):
475 470 """Remove this table from its metadata"""
476 key = self._meta_key()
477 meta = self.metadata
478 if key in meta.tables:
479 del meta.tables[key]
471 if SQLA_07:
472 self.metadata._remove_table(self.name, self.schema)
473 else:
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 480 class ChangesetColumn(object):
@@ -83,6 +83,5 b' class NotSupportedError(Error):'
83 83 class InvalidConstraintError(Error):
84 84 """Invalid constraint error"""
85 85
86
87 86 class MigrateDeprecationWarning(DeprecationWarning):
88 87 """Warning for deprecated features in Migrate"""
@@ -119,7 +119,7 b' def script_sql(database, description, re'
119 119
120 120 For instance, manage.py script_sql postgresql description creates:
121 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 124 repo = Repository(repository)
125 125 repo.create_script_sql(database, description, **opts)
@@ -212,14 +212,15 b' def test(url, repository, **opts):'
212 212 """
213 213 engine = opts.pop('engine')
214 214 repos = Repository(repository)
215 script = repos.version(None).script()
216 215
217 216 # Upgrade
218 217 log.info("Upgrading...")
218 script = repos.version(None).script(engine.name, 'upgrade')
219 219 script.run(engine, 1)
220 220 log.info("done")
221 221
222 222 log.info("Downgrading...")
223 script = repos.version(None).script(engine.name, 'downgrade')
223 224 script.run(engine, -1)
224 225 log.info("done")
225 226 log.info("Success")
@@ -282,4 +282,3 b' class ModelGenerator(object):'
282 282 except:
283 283 trans.rollback()
284 284 raise
285
@@ -115,7 +115,7 b' class Repository(pathed.Pathed):'
115 115 options.setdefault('version_table', 'migrate_version')
116 116 options.setdefault('repository_id', name)
117 117 options.setdefault('required_dbs', [])
118 options.setdefault('use_timestamp_numbering', '0')
118 options.setdefault('use_timestamp_numbering', False)
119 119
120 120 tmpl = open(os.path.join(tmpl_dir, cls._config)).read()
121 121 ret = TempitaTemplate(tmpl).substitute(options)
@@ -153,7 +153,7 b' class Repository(pathed.Pathed):'
153 153
154 154 def create_script(self, description, **k):
155 155 """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`"""
156
156
157 157 k['use_timestamp_numbering'] = self.use_timestamp_numbering
158 158 self.versions.create_new_python_version(description, **k)
159 159
@@ -180,9 +180,9 b' class Repository(pathed.Pathed):'
180 180 @property
181 181 def use_timestamp_numbering(self):
182 182 """Returns use_timestamp_numbering specified in config"""
183 ts_numbering = self.config.get('db_settings', 'use_timestamp_numbering', raw=True)
184
185 return ts_numbering
183 if self.config.has_option('db_settings', 'use_timestamp_numbering'):
184 return self.config.getboolean('db_settings', 'use_timestamp_numbering')
185 return False
186 186
187 187 def version(self, *p, **k):
188 188 """API to :attr:`migrate.versioning.version.Collection.version`"""
@@ -17,8 +17,16 b' def getDiffOfModelAgainstDatabase(metada'
17 17 :return: object which will evaluate to :keyword:`True` if there \
18 18 are differences else :keyword:`False`.
19 19 """
20 return SchemaDiff(metadata,
21 sqlalchemy.MetaData(engine, reflect=True),
20 db_metadata = 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 30 labelA='model',
23 31 labelB='database',
24 32 excludeTables=excludeTables)
@@ -31,7 +39,7 b' def getDiffOfModelAgainstModel(metadataA'
31 39 :return: object which will evaluate to :keyword:`True` if there \
32 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 45 class ColDiff(object):
@@ -7,4 +7,6 b" del _vars['__template_name__']"
7 7 _vars.pop('repository_name', None)
8 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 27 # migrate supports passing url as an existing Engine instance (since 0.6.0)
28 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 158 kw['engine'] = engine
159 159 return f(*a, **kw)
160 160 finally:
161 if isinstance(engine, Engine):
161 if isinstance(engine, Engine) and engine is not url:
162 162 log.debug('Disposing SQLAlchemy engine %s', engine)
163 163 engine.dispose()
164 164
@@ -60,7 +60,7 b' class Collection(pathed.Pathed):'
60 60 and store them in self.versions
61 61 """
62 62 super(Collection, self).__init__(path)
63
63
64 64 # Create temporary list of files, allowing skipped version numbers.
65 65 files = os.listdir(path)
66 66 if '1' in files:
@@ -90,9 +90,7 b' class Collection(pathed.Pathed):'
90 90 return max([VerNum(0)] + self.versions.keys())
91 91
92 92 def _next_ver_num(self, use_timestamp_numbering):
93 print use_timestamp_numbering
94 93 if use_timestamp_numbering == True:
95 print "Creating new timestamp version!"
96 94 return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
97 95 else:
98 96 return self.latest + 1
@@ -113,7 +111,7 b' class Collection(pathed.Pathed):'
113 111
114 112 script.PythonScript.create(filepath, **k)
115 113 self.versions[ver] = Version(ver, self.path, [filename])
116
114
117 115 def create_new_sql_version(self, database, description, **k):
118 116 """Create SQL files for new version"""
119 117 ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
@@ -133,7 +131,7 b' class Collection(pathed.Pathed):'
133 131 filepath = self._version_path(filename)
134 132 script.SqlScript.create(filepath, **k)
135 133 self.versions[ver].add_script(filepath)
136
134
137 135 def version(self, vernum=None):
138 136 """Returns latest Version if vernum is not given.
139 137 Otherwise, returns wanted version"""
@@ -152,7 +150,7 b' class Collection(pathed.Pathed):'
152 150
153 151 class Version(object):
154 152 """A single version in a collection
155 :param vernum: Version Number
153 :param vernum: Version Number
156 154 :param path: Path to script files
157 155 :param filelist: List of scripts
158 156 :type vernum: int, VerNum
@@ -169,7 +167,7 b' class Version(object):'
169 167
170 168 for script in filelist:
171 169 self.add_script(os.path.join(path, script))
172
170
173 171 def script(self, database=None, operation=None):
174 172 """Returns SQL or Python Script"""
175 173 for db in (database, 'default'):
@@ -198,7 +196,7 b' class Version(object):'
198 196 def _add_script_sql(self, path):
199 197 basename = os.path.basename(path)
200 198 match = self.SQL_FILENAME.match(basename)
201
199
202 200 if match:
203 201 basename = basename.replace('.sql', '')
204 202 parts = basename.split('_')
@@ -14,7 +14,7 b' from rhodecode.lib.dbmigrate.migrate imp'
14 14
15 15 log = logging.getLogger(__name__)
16 16
17 class RhodeCodeSettings(Base):
17 class RhodeCodeSetting(Base):
18 18 __tablename__ = 'rhodecode_settings'
19 19 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
20 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 74 self.last_login = datetime.datetime.now()
75 75 session.add(self)
76 76 session.commit()
77 log.debug('updated user %s lastlogin', self.username)
77 log.debug('updated user %s lastlogin' % self.username)
78 78 except (DatabaseError,):
79 79 session.rollback()
80 80
@@ -107,7 +107,7 b' class Repository(Base):'
107 107
108 108 user = relation('User')
109 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 111 stats = relation('Statistics', cascade='all', uselist=False)
112 112
113 113 repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
@@ -126,7 +126,7 b' class Permission(Base):'
126 126 def __repr__(self):
127 127 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
128 128
129 class RepoToPerm(Base):
129 class UserRepoToPerm(Base):
130 130 __tablename__ = 'repo_to_perm'
131 131 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
132 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 13 log = logging.getLogger(__name__)
14 14
15
15 16 def upgrade(migrate_engine):
16 17 """ Upgrade operations go here.
17 18 Don't create your own engine; bind migrate_engine to your metadata
@@ -44,8 +45,6 b' def upgrade(migrate_engine):'
44 45 nullable=True, unique=None, default=None)
45 46 revision.create(tbl)
46 47
47
48
49 48 #==========================================================================
50 49 # Upgrade of `repositories` table
51 50 #==========================================================================
@@ -69,47 +68,18 b' def upgrade(migrate_engine):'
69 68 #==========================================================================
70 69 # Add table `user_followings`
71 70 #==========================================================================
72 class UserFollowing(Base, BaseModel):
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
71 from rhodecode.lib.dbmigrate.schema.db_1_1_0 import UserFollowing
88 72 UserFollowing().__table__.create()
89 73
90 74 #==========================================================================
91 75 # Add table `cache_invalidation`
92 76 #==========================================================================
93 class CacheInvalidation(Base, BaseModel):
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)
77 from rhodecode.lib.dbmigrate.schema.db_1_1_0 import CacheInvalidation
109 78 CacheInvalidation().__table__.create()
110 79
111 80 return
112 81
82
113 83 def downgrade(migrate_engine):
114 84 meta = MetaData()
115 85 meta.bind = migrate_engine
@@ -13,6 +13,7 b' from rhodecode.model.meta import Base'
13 13
14 14 log = logging.getLogger(__name__)
15 15
16
16 17 def upgrade(migrate_engine):
17 18 """ Upgrade operations go here.
18 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 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 26 Group().__table__.create()
26 27
27 28 #==========================================================================
28 29 # Add table `group_to_perm`
29 30 #==========================================================================
30 from rhodecode.model.db import GroupToPerm
31 GroupToPerm().__table__.create()
31 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm
32 UserRepoGroupToPerm().__table__.create()
32 33
33 34 #==========================================================================
34 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 38 UsersGroup().__table__.create()
38 39
39 40 #==========================================================================
40 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 44 UsersGroupMember().__table__.create()
44 45
45 46 #==========================================================================
46 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 50 UsersGroupRepoToPerm().__table__.create()
50 51
51 52 #==========================================================================
52 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 56 UsersGroupToPerm().__table__.create()
56 57
57 58 #==========================================================================
58 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 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 65 ldap_dn.create(User().__table__)
65 66
66 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 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 80 #ADD clone_uri column#
80 81
@@ -83,7 +84,7 b' def upgrade(migrate_engine):'
83 84 nullable=True, unique=False, default=None)
84 85
85 86 clone_uri.create(Repository().__table__)
86
87
87 88 #ADD downloads column#
88 89 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
89 90 enable_downloads.create(Repository().__table__)
@@ -104,10 +105,10 b' def upgrade(migrate_engine):'
104 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 nullable=True, unique=None,
110 follows_from = Column('follows_from', DateTime(timezone=False),
111 nullable=True, unique=None,
111 112 default=datetime.datetime.now)
112 113 follows_from.create(UserFollowing().__table__)
113 114
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Dec 11, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -6,7 +6,8 b''
6 6 Set of custom exceptions used in RhodeCode
7 7
8 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 11 :license: GPLv3, see COPYING for more details.
11 12 """
12 13 # This program is free software: you can redistribute it and/or modify
@@ -46,5 +47,6 b' class DefaultUserException(Exception):'
46 47 class UserOwnsReposException(Exception):
47 48 pass
48 49
50
49 51 class UsersGroupsAssignedException(Exception):
50 52 pass
@@ -8,22 +8,24 b' import hashlib'
8 8 import StringIO
9 9 import urllib
10 10 import math
11 import logging
11 12
12 13 from datetime import datetime
13 from pygments.formatters import HtmlFormatter
14 from pygments.formatters.html import HtmlFormatter
14 15 from pygments import highlight as code_highlight
15 16 from pylons import url, request, config
16 17 from pylons.i18n.translation import _, ungettext
18 from hashlib import md5
17 19
18 20 from webhelpers.html import literal, HTML, escape
19 21 from webhelpers.html.tools import *
20 22 from webhelpers.html.builder import make_tag
21 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, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
24 password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
26 mail_to, strip_links, strip_tags, tag_re
24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
27 29 from webhelpers.number import format_byte_size, format_bit_size
28 30 from webhelpers.pylonslib import Flash as _Flash
29 31 from webhelpers.pylonslib.secure_form import secure_form
@@ -33,11 +35,15 b' from webhelpers.text import chop_at, col'
33 35 from webhelpers.date import time_ago_in_words
34 36 from webhelpers.paginate import Page
35 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 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 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 55 return HTML.input(**attrs)
50 56
51 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 73 def get_token():
@@ -104,10 +123,14 b' class _FilesBreadCrumbs(object):'
104 123 paths_l = paths.split('/')
105 124 for cnt, p in enumerate(paths_l):
106 125 if p != '':
107 url_l.append(link_to(p, url('files_home',
108 repo_name=repo_name,
109 revision=rev,
110 f_path='/'.join(paths_l[:cnt + 1]))))
126 url_l.append(link_to(p,
127 url('files_home',
128 repo_name=repo_name,
129 revision=rev,
130 f_path='/'.join(paths_l[:cnt + 1])
131 )
132 )
133 )
111 134
112 135 return literal('/'.join(url_l))
113 136
@@ -198,13 +221,16 b' def pygmentize(filenode, **kwargs):'
198 221 return literal(code_highlight(filenode.content,
199 222 filenode.lexer, CodeHtmlFormatter(**kwargs)))
200 223
224
201 225 def pygmentize_annotation(repo_name, filenode, **kwargs):
202 """pygmentize function for annotation
226 """
227 pygmentize function for annotation
203 228
204 229 :param filenode:
205 230 """
206 231
207 232 color_dict = {}
233
208 234 def gen_color(n=10000):
209 235 """generator for getting n of evenly distributed colors using
210 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 241 def hsv_to_rgb(h, s, v):
216 if s == 0.0: return v, v, v
217 i = int(h * 6.0) # XXX assume int() truncates!
242 if s == 0.0:
243 return v, v, v
244 i = int(h * 6.0) # XXX assume int() truncates!
218 245 f = (h * 6.0) - i
219 246 p = v * (1.0 - s)
220 247 q = v * (1.0 - s * f)
221 248 t = v * (1.0 - s * (1.0 - f))
222 249 i = i % 6
223 if i == 0: return v, t, p
224 if i == 1: return q, v, p
225 if i == 2: return p, v, t
226 if i == 3: return p, q, v
227 if i == 4: return t, p, v
228 if i == 5: return v, p, q
250 if i == 0:
251 return v, t, p
252 if i == 1:
253 return q, v, p
254 if i == 2:
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 263 golden_ratio = 0.618033988749895
231 264 h = 0.22717784590367374
@@ -235,12 +268,12 b' def pygmentize_annotation(repo_name, fil'
235 268 h %= 1
236 269 HSV_tuple = [h, 0.95, 0.95]
237 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 273 cgenerator = gen_color()
241 274
242 275 def get_color_string(cs):
243 if color_dict.has_key(cs):
276 if cs in color_dict:
244 277 col = color_dict[cs]
245 278 else:
246 279 col = color_dict[cs] = cgenerator.next()
@@ -275,6 +308,7 b' def pygmentize_annotation(repo_name, fil'
275 308
276 309 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
277 310
311
278 312 def is_following_repo(repo_name, user_id):
279 313 from rhodecode.model.scm import ScmModel
280 314 return ScmModel().is_following_repo(repo_name, user_id)
@@ -284,17 +318,75 b' flash = _Flash()'
284 318 #==============================================================================
285 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 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 326 capitalize = lambda x: x.capitalize()
292 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 328 short_id = lambda x: x[:12]
296 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 390 def bool2icon(value):
299 391 """Returns True/False values represented as small html image of true/false
300 392 icons
@@ -314,7 +406,8 b' def bool2icon(value):'
314 406
315 407
316 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 411 fancy names with icons and links
319 412
320 413 :param user_log: user log instance
@@ -330,52 +423,84 b' def action_parser(user_log, feed=False):'
330 423 action, action_params = x
331 424
332 425 def get_cs_links():
333 revs_limit = 3 #display this amount always
334 revs_top_limit = 50 #show upto this amount of changesets hidden
335 revs = action_params.split(',')
426 revs_limit = 3 # display this amount always
427 revs_top_limit = 50 # show upto this amount of changesets hidden
428 revs_ids = action_params.split(',')
429 deleted = user_log.repository is None
430 if deleted:
431 return ','.join(revs_ids)
432
336 433 repo_name = user_log.repository.repo_name
337 434
338 from rhodecode.model.scm import ScmModel
339 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 450 cs_links = []
343 cs_links.append(" " + ', '.join ([link_to(rev,
344 url('changeset_home',
345 repo_name=repo_name,
346 revision=rev), title=tooltip(message(rev)),
347 class_='tooltip') for rev in revs[:revs_limit] ]))
451 cs_links.append(" " + ', '.join(
452 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
453 )
454 )
348 455
349 compare_view = (' <div class="compare_view tooltip" title="%s">'
350 '<a href="%s">%s</a> '
351 '</div>' % (_('Show all combined changesets %s->%s' \
352 % (revs[0], revs[-1])),
353 url('changeset_home', repo_name=repo_name,
354 revision='%s...%s' % (revs[0], revs[-1])
355 ),
356 _('compare view'))
357 )
456 compare_view = (
457 ' <div class="compare_view tooltip" title="%s">'
458 '<a href="%s">%s</a> </div>' % (
459 _('Show all combined changesets %s->%s') % (
460 revs_ids[0], revs_ids[-1]
461 ),
462 url('changeset_home', repo_name=repo_name,
463 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
464 ),
465 _('compare view')
466 )
467 )
358 468
359 if len(revs) > revs_limit:
360 uniq_id = revs[0]
361 html_tmpl = ('<span> %s '
362 '<a class="show_more" id="_%s" href="#more">%s</a> '
363 '%s</span>')
469 # if we have exactly one more than normally displayed
470 # just display it, takes less space than displaying
471 # "and 1 more revisions"
472 if len(revs_ids) == revs_limit + 1:
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 483 if not feed:
365 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
366 % (len(revs) - revs_limit),
367 _('revisions')))
484 cs_links.append(html_tmpl % (
485 _('and'),
486 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
487 _('revisions')
488 )
489 )
368 490
369 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 493 else:
372 494 html_tmpl = '<span id="%s"> %s </span>'
373 495
374 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
375 url('changeset_home',
376 repo_name=repo_name, revision=rev),
377 title=message(rev), class_='tooltip')
378 for rev in revs[revs_limit:revs_top_limit]])))
496 morelinks = ', '.join(
497 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
498 )
499
500 if len(revs_ids) > revs_top_limit:
501 morelinks += ', ...'
502
503 cs_links.append(html_tmpl % (uniq_id, morelinks))
379 504 if len(revs) > 1:
380 505 cs_links.append(compare_view)
381 506 return ''.join(cs_links)
@@ -385,36 +510,39 b' def action_parser(user_log, feed=False):'
385 510 return _('fork name ') + str(link_to(action_params, url('summary_home',
386 511 repo_name=repo_name,)))
387 512
388 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
389 'user_created_repo':(_('[created] repository'), None),
390 'user_forked_repo':(_('[forked] repository'), get_fork_name),
391 'user_updated_repo':(_('[updated] repository'), None),
392 'admin_deleted_repo':(_('[delete] repository'), None),
393 'admin_created_repo':(_('[created] repository'), None),
394 'admin_forked_repo':(_('[forked] repository'), None),
395 'admin_updated_repo':(_('[updated] repository'), None),
396 'push':(_('[pushed] into'), get_cs_links),
397 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
398 'push_remote':(_('[pulled from remote] into'), get_cs_links),
399 'pull':(_('[pulled] from'), None),
400 'started_following_repo':(_('[started following] repository'), None),
401 'stopped_following_repo':(_('[stopped following] repository'), None),
513 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
514 'user_created_repo': (_('[created] repository'), None),
515 'user_created_fork': (_('[created] repository as fork'), None),
516 'user_forked_repo': (_('[forked] repository'), get_fork_name),
517 'user_updated_repo': (_('[updated] repository'), None),
518 'admin_deleted_repo': (_('[delete] repository'), None),
519 'admin_created_repo': (_('[created] repository'), None),
520 'admin_forked_repo': (_('[forked] repository'), None),
521 'admin_updated_repo': (_('[updated] repository'), None),
522 'push': (_('[pushed] into'), get_cs_links),
523 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
524 'push_remote': (_('[pulled from remote] into'), get_cs_links),
525 'pull': (_('[pulled] from'), None),
526 'started_following_repo': (_('[started following] repository'), None),
527 'stopped_following_repo': (_('[stopped following] repository'), None),
402 528 }
403 529
404 530 action_str = action_map.get(action, action)
405 531 if feed:
406 532 action = action_str[0].replace('[', '').replace(']', '')
407 533 else:
408 action = action_str[0].replace('[', '<span class="journal_highlight">')\
409 .replace(']', '</span>')
534 action = action_str[0]\
535 .replace('[', '<span class="journal_highlight">')\
536 .replace(']', '</span>')
410 537
411 action_params_func = lambda :""
538 action_params_func = lambda: ""
412 539
413 540 if callable(action_str[1]):
414 541 action_params_func = action_str[1]
415 542
416 543 return [literal(action), action_params_func]
417 544
545
418 546 def action_parser_icon(user_log):
419 547 action = user_log.action
420 548 action_params = None
@@ -426,6 +554,7 b' def action_parser_icon(user_log):'
426 554 tmpl = """<img src="%s%s" alt="%s"/>"""
427 555 map = {'user_deleted_repo':'database_delete.png',
428 556 'user_created_repo':'database_add.png',
557 'user_created_fork':'arrow_divide.png',
429 558 'user_forked_repo':'arrow_divide.png',
430 559 'user_updated_repo':'database_edit.png',
431 560 'admin_deleted_repo':'database_delete.png',
@@ -449,6 +578,7 b' def action_parser_icon(user_log):'
449 578 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
450 579 HasRepoPermissionAny, HasRepoPermissionAll
451 580
581
452 582 #==============================================================================
453 583 # GRAVATAR URL
454 584 #==============================================================================
@@ -456,7 +586,8 b' HasRepoPermissionAny, HasRepoPermissionA'
456 586 def gravatar_url(email_address, size=30):
457 587 if (not str2bool(config['app_conf'].get('use_gravatar')) or
458 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 592 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
462 593 default = 'identicon'
@@ -469,7 +600,7 b' def gravatar_url(email_address, size=30)'
469 600 email_address = safe_str(email_address)
470 601 # construct the url
471 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 605 return gravatar_url
475 606
@@ -480,7 +611,7 b' def gravatar_url(email_address, size=30)'
480 611 class RepoPage(Page):
481 612
482 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 616 """Create a "RepoPage" instance. special pager for paging
486 617 repository
@@ -498,7 +629,7 b' class RepoPage(Page):'
498 629 # The self.page is the number of the current page.
499 630 # The first page has the number 1!
500 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 633 except (ValueError, TypeError):
503 634 self.page = 1
504 635
@@ -518,7 +649,8 b' class RepoPage(Page):'
518 649 self.items_per_page))
519 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 654 if self.page > self.last_page:
523 655 self.page = self.last_page
524 656 elif self.page < self.first_page:
@@ -531,11 +663,7 b' class RepoPage(Page):'
531 663 self.last_item = ((self.item_count - 1) - items_per_page *
532 664 (self.page - 1))
533 665
534 iterator = self.collection.get_changesets(start=self.first_item,
535 end=self.last_item,
536 reverse=True,
537 branch_name=branch_name)
538 self.items = list(iterator)
666 self.items = list(self.collection[self.first_item:self.last_item + 1])
539 667
540 668 # Links to previous and next page
541 669 if self.page > self.first_page:
@@ -560,14 +688,14 b' class RepoPage(Page):'
560 688 self.items = []
561 689
562 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 694 def changed_tooltip(nodes):
567 695 """
568 696 Generates a html string for changed nodes in changeset page.
569 697 It limits the output to 30 entries
570
698
571 699 :param nodes: LazyNodesGenerator
572 700 """
573 701 if nodes:
@@ -581,15 +709,14 b' def changed_tooltip(nodes):'
581 709 return ': ' + _('No Files')
582 710
583 711
584
585 712 def repo_link(groups_and_repos):
586 713 """
587 714 Makes a breadcrumbs link to repo within a group
588 715 joins &raquo; on each group to create a fancy link
589
716
590 717 ex::
591 718 group >> subgroup >> repo
592
719
593 720 :param groups_and_repos:
594 721 """
595 722 groups, repo_name = groups_and_repos
@@ -603,11 +730,12 b' def repo_link(groups_and_repos):'
603 730 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 731 " &raquo; " + repo_name)
605 732
733
606 734 def fancy_file_stats(stats):
607 735 """
608 736 Displays a fancy two colored bar for number of added/deleted
609 737 lines of code on file
610
738
611 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 758 a_v = a if a > 0 else ''
631 759 d_v = d if d > 0 else ''
632 760
633
634 761 def cgen(l_type):
635 mapping = {'tr':'top-right-rounded-corner',
636 'tl':'top-left-rounded-corner',
637 'br':'bottom-right-rounded-corner',
638 'bl':'bottom-left-rounded-corner'}
639 map_getter = lambda x:mapping[x]
762 mapping = {'tr': 'top-right-rounded-corner',
763 'tl': 'top-left-rounded-corner',
764 'br': 'bottom-right-rounded-corner',
765 'bl': 'bottom-left-rounded-corner'}
766 map_getter = lambda x: mapping[x]
640 767
641 768 if l_type == 'a' and d_v:
642 769 #case when added and deleted are present
@@ -651,22 +778,137 b' def fancy_file_stats(stats):'
651 778 if l_type == 'd' and not a_v:
652 779 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
653 780
654
655
656 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
657 a_p, a_v)
658 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
659 d_p, d_v)
781 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
782 cgen('a'), a_p, a_v
783 )
784 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
785 cgen('d'), d_p, d_v
786 )
660 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 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 796 def url_func(match_obj):
669 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 8 :created_on: Aug 6, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 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 39 :param ui:
39 40 :param repo:
40 41 :param hooktype:
41 42 """
42 43
43 if hooktype != 'changegroup':
44 return False
45 44 size_hg, size_root = 0, 0
46 45 for path, dirs, files in os.walk(repo.root):
47 46 if path.find('.hg') != -1:
@@ -60,12 +59,20 b' def repo_size(ui, repo, hooktype=None, *'
60 59 size_hg_f = h.format_byte_size(size_hg)
61 60 size_root_f = h.format_byte_size(size_root)
62 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' \
64 % (size_hg_f, size_root_f, size_total_f))
62
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 73 def log_pull_action(ui, repo, **kwargs):
68 """Logs user last pull action
74 """
75 Logs user last pull action
69 76
70 77 :param ui:
71 78 :param repo:
@@ -76,13 +83,15 b' def log_pull_action(ui, repo, **kwargs):'
76 83 repository = extra_params['repository']
77 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 89 return 0
82 90
83 91
84 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 96 :param ui:
88 97 :param repo:
@@ -110,6 +119,37 b' def log_push_action(ui, repo, **kwargs):'
110 119
111 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 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 8 :created_on: Aug 17, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -37,38 +37,39 b' from whoosh.analysis import RegexTokeniz'
37 37 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
38 38 from whoosh.index import create_in, open_dir
39 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 42 from webhelpers.html.builder import escape
43 43 from sqlalchemy import engine_from_config
44 from vcs.utils.lazy import LazyProperty
45 44
46 45 from rhodecode.model import init_model
47 46 from rhodecode.model.scm import ScmModel
48 47 from rhodecode.model.repo import RepoModel
49 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 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 53 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
55 54
56 #CUSTOM ANALYZER wordsplit + lowercase filter
55 # CUSTOM ANALYZER wordsplit + lowercase filter
57 56 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
58 57
59 58
60 59 #INDEX SCHEMA DEFINITION
61 SCHEMA = Schema(owner=TEXT(),
62 repository=TEXT(stored=True),
63 path=TEXT(stored=True),
64 content=FieldType(format=Characters(ANALYZER),
65 scorable=True, stored=True),
66 modtime=STORED(), extension=TEXT(stored=True))
67
60 SCHEMA = Schema(
61 owner=TEXT(),
62 repository=TEXT(stored=True),
63 path=TEXT(stored=True),
64 content=FieldType(format=Characters(), analyzer=ANALYZER,
65 scorable=True, stored=True),
66 modtime=STORED(),
67 extension=TEXT(stored=True)
68 )
68 69
69 70 IDX_NAME = 'HG_INDEX'
70 71 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
71 FRAGMENTER = SimpleFragmenter(200)
72 FRAGMENTER = ContextFragmenter(200)
72 73
73 74
74 75 class MakeIndex(BasePasterCommand):
@@ -129,13 +130,14 b' class MakeIndex(BasePasterCommand):'
129 130 " destroy old and build from scratch",
130 131 default=False)
131 132
133
132 134 class ResultWrapper(object):
133 135 def __init__(self, search_type, searcher, matcher, highlight_items):
134 136 self.search_type = search_type
135 137 self.searcher = searcher
136 138 self.matcher = matcher
137 139 self.highlight_items = highlight_items
138 self.fragment_size = 200 / 2
140 self.fragment_size = 200
139 141
140 142 @LazyProperty
141 143 def doc_ids(self):
@@ -171,11 +173,10 b' class ResultWrapper(object):'
171 173 """
172 174 i, j = key.start, key.stop
173 175
174 slice = []
176 slices = []
175 177 for docid in self.doc_ids[i:j]:
176 slice.append(self.get_full_content(docid))
177 return slice
178
178 slices.append(self.get_full_content(docid))
179 return slices
179 180
180 181 def get_full_content(self, docid):
181 182 res = self.searcher.stored_fields(docid[0])
@@ -183,9 +184,9 b' class ResultWrapper(object):'
183 184 + len(res['repository']):].lstrip('/')
184 185
185 186 content_short = self.get_short_content(res, docid[1])
186 res.update({'content_short':content_short,
187 'content_short_hl':self.highlight(content_short),
188 'f_path':f_path})
187 res.update({'content_short': content_short,
188 'content_short_hl': self.highlight(content_short),
189 'f_path': f_path})
189 190
190 191 return res
191 192
@@ -198,7 +199,7 b' class ResultWrapper(object):'
198 199 Smart function that implements chunking the content
199 200 but not overlap chunks so it doesn't highlight the same
200 201 close occurrences twice.
201
202
202 203 :param matcher:
203 204 :param size:
204 205 """
@@ -217,10 +218,12 b' class ResultWrapper(object):'
217 218 def highlight(self, content, top=5):
218 219 if self.search_type != 'content':
219 220 return ''
220 hl = highlight(escape(content),
221 self.highlight_items,
222 analyzer=ANALYZER,
223 fragmenter=FRAGMENTER,
224 formatter=FORMATTER,
225 top=top)
221 hl = highlight(
222 text=escape(content),
223 terms=self.highlight_items,
224 analyzer=ANALYZER,
225 fragmenter=FRAGMENTER,
226 formatter=FORMATTER,
227 top=top
228 )
226 229 return hl
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Jan 26, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 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 43 from rhodecode.lib import safe_unicode
44 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 49 from whoosh.index import create_in, open_dir
49 50
50 51
51
52 52 log = logging.getLogger('whooshIndexer')
53 53 # create logger
54 54 log.setLevel(logging.DEBUG)
@@ -67,12 +67,13 b' ch.setFormatter(formatter)'
67 67 # add ch to logger
68 68 log.addHandler(ch)
69 69
70
70 71 class WhooshIndexingDaemon(object):
71 72 """
72 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 77 repo_location=None, sa=None, repo_list=None):
77 78 self.indexname = indexname
78 79
@@ -94,7 +95,6 b' class WhooshIndexingDaemon(object):'
94 95
95 96 self.repo_paths = filtered_repo_paths
96 97
97
98 98 self.initial = False
99 99 if not os.path.isdir(self.index_location):
100 100 os.makedirs(self.index_location)
@@ -154,7 +154,6 b' class WhooshIndexingDaemon(object):'
154 154 modtime=self.get_node_mtime(node),
155 155 extension=node.extension)
156 156
157
158 157 def build_index(self):
159 158 if os.path.exists(self.index_location):
160 159 log.debug('removing previous index')
@@ -176,7 +175,6 b' class WhooshIndexingDaemon(object):'
176 175 writer.commit(merge=True)
177 176 log.debug('>>> FINISHED BUILDING INDEX <<<')
178 177
179
180 178 def update_index(self):
181 179 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
182 180
@@ -198,7 +196,7 b' class WhooshIndexingDaemon(object):'
198 196
199 197 try:
200 198 node = self.get_node(repo, indexed_path)
201 except ChangesetError:
199 except (ChangesetError, NodeDoesNotExistError):
202 200 # This file was deleted since it was indexed
203 201 log.debug('removing from index %s' % indexed_path)
204 202 writer.delete_by_term('path', indexed_path)
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: May 23, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -8,7 +8,7 b''
8 8
9 9 :created_on: Apr 28, 2010
10 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 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
@@ -27,7 +27,6 b''
27 27 import os
28 28 import logging
29 29 import traceback
30 import time
31 30
32 31 from dulwich import server as dulserver
33 32
@@ -67,13 +66,12 b' dulserver.DEFAULT_HANDLERS = {'
67 66 from dulwich.repo import Repo
68 67 from dulwich.web import HTTPGitApplication
69 68
70 from paste.auth.basic import AuthBasicAuthenticator
71 69 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
72 70
73 71 from rhodecode.lib import safe_str
74 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
75 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
76 from rhodecode.model import meta
72 from rhodecode.lib.base import BaseVCSController
73 from rhodecode.lib.auth import get_container_username
74 from rhodecode.lib.utils import is_valid_repo
77 75 from rhodecode.model.db import User
78 76
79 77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
@@ -93,24 +91,7 b' def is_git(environ):'
93 91 return False
94 92
95 93
96 class SimpleGit(object):
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()
94 class SimpleGit(BaseVCSController):
114 95
115 96 def _handle_request(self, environ, start_response):
116 97 if not is_git(environ):
@@ -143,9 +124,8 b' class SimpleGit(object):'
143 124 if action in ['pull', 'push']:
144 125 anonymous_user = self.__get_user('default')
145 126 username = anonymous_user.username
146 anonymous_perm = self.__check_permission(action,
147 anonymous_user,
148 repo_name)
127 anonymous_perm = self._check_permission(action, anonymous_user,
128 repo_name)
149 129
150 130 if anonymous_perm is not True or anonymous_user.active is False:
151 131 if anonymous_perm is not True:
@@ -159,27 +139,29 b' class SimpleGit(object):'
159 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 147 self.authenticate.realm = \
164 148 safe_str(self.config['rhodecode_realm'])
165 149 result = self.authenticate(environ)
166 150 if isinstance(result, str):
167 151 AUTH_TYPE.update(environ, 'basic')
168 152 REMOTE_USER.update(environ, result)
153 username = result
169 154 else:
170 155 return result.wsgi_application(environ, start_response)
171 156
172 157 #==============================================================
173 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
174 # BASIC AUTH
158 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
175 159 #==============================================================
176
177 160 if action in ['pull', 'push']:
178 username = REMOTE_USER(environ)
179 161 try:
180 162 user = self.__get_user(username)
181 if user is None:
182 return HTTPForbidden()(environ, start_response)
163 if user is None or not user.active:
164 return HTTPForbidden()(environ, start_response)
183 165 username = user.username
184 166 except:
185 167 log.error(traceback.format_exc())
@@ -187,16 +169,11 b' class SimpleGit(object):'
187 169 start_response)
188 170
189 171 #check permissions for this repository
190 perm = self.__check_permission(action, user,
172 perm = self._check_permission(action, user,
191 173 repo_name)
192 174 if perm is not True:
193 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 178 # GIT REQUEST HANDLING
202 179 #===================================================================
@@ -211,8 +188,8 b' class SimpleGit(object):'
211 188 try:
212 189 #invalidate cache on push
213 190 if action == 'push':
214 self.__invalidate_cache(repo_name)
215
191 self._invalidate_cache(repo_name)
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
216 193 app = self.__make_app(repo_name, repo_path)
217 194 return app(environ, start_response)
218 195 except Exception:
@@ -222,7 +199,7 b' class SimpleGit(object):'
222 199 def __make_app(self, repo_name, repo_path):
223 200 """
224 201 Make an wsgi application using dulserver
225
202
226 203 :param repo_name: name of the repository
227 204 :param repo_path: full path to the repository
228 205 """
@@ -233,31 +210,6 b' class SimpleGit(object):'
233 210
234 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 213 def __get_repository(self, environ):
262 214 """
263 215 Get's repository name out of PATH_INFO header
@@ -265,6 +217,7 b' class SimpleGit(object):'
265 217 :param environ: environ where PATH_INFO is stored
266 218 """
267 219 try:
220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
268 221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
269 222 if repo_name.endswith('/'):
270 223 repo_name = repo_name.rstrip('/')
@@ -293,10 +246,3 b' class SimpleGit(object):'
293 246 service_cmd if service_cmd else 'other')
294 247 else:
295 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 9 :created_on: Apr 28, 2010
10 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 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
@@ -27,19 +27,16 b''
27 27 import os
28 28 import logging
29 29 import traceback
30 import time
31 30
32 31 from mercurial.error import RepoError
33 32 from mercurial.hgweb import hgweb_mod
34 33
35 from paste.auth.basic import AuthBasicAuthenticator
36 34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
37 35
38 36 from rhodecode.lib import safe_str
39 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
40 from rhodecode.lib.utils import make_ui, invalidate_cache, \
41 is_valid_repo, ui_sections
42 from rhodecode.model import meta
37 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
43 40 from rhodecode.model.db import User
44 41
45 42 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
@@ -57,25 +54,7 b' def is_mercurial(environ):'
57 54 return False
58 55
59 56
60 class SimpleHg(object):
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()
57 class SimpleHg(BaseVCSController):
79 58
80 59 def _handle_request(self, environ, start_response):
81 60 if not is_mercurial(environ):
@@ -101,7 +80,6 b' class SimpleHg(object):'
101 80 # GET ACTION PULL or PUSH
102 81 #======================================================================
103 82 action = self.__get_action(environ)
104
105 83 #======================================================================
106 84 # CHECK ANONYMOUS PERMISSION
107 85 #======================================================================
@@ -109,9 +87,8 b' class SimpleHg(object):'
109 87 anonymous_user = self.__get_user('default')
110 88
111 89 username = anonymous_user.username
112 anonymous_perm = self.__check_permission(action,
113 anonymous_user,
114 repo_name)
90 anonymous_perm = self._check_permission(action, anonymous_user,
91 repo_name)
115 92
116 93 if anonymous_perm is not True or anonymous_user.active is False:
117 94 if anonymous_perm is not True:
@@ -125,26 +102,28 b' class SimpleHg(object):'
125 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 110 self.authenticate.realm = \
130 111 safe_str(self.config['rhodecode_realm'])
131 112 result = self.authenticate(environ)
132 113 if isinstance(result, str):
133 114 AUTH_TYPE.update(environ, 'basic')
134 115 REMOTE_USER.update(environ, result)
116 username = result
135 117 else:
136 118 return result.wsgi_application(environ, start_response)
137 119
138 120 #==============================================================
139 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
140 # BASIC AUTH
121 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
141 122 #==============================================================
142
143 123 if action in ['pull', 'push']:
144 username = REMOTE_USER(environ)
145 124 try:
146 125 user = self.__get_user(username)
147 if user is None:
126 if user is None or not user.active:
148 127 return HTTPForbidden()(environ, start_response)
149 128 username = user.username
150 129 except:
@@ -153,7 +132,7 b' class SimpleHg(object):'
153 132 start_response)
154 133
155 134 #check permissions for this repository
156 perm = self.__check_permission(action, user,
135 perm = self._check_permission(action, user,
157 136 repo_name)
158 137 if perm is not True:
159 138 return HTTPForbidden()(environ, start_response)
@@ -173,16 +152,15 b' class SimpleHg(object):'
173 152 baseui = make_ui('db')
174 153 self.__inject_extras(repo_path, baseui, extras)
175 154
176
177 155 # quick check if that dir exists...
178 156 if is_valid_repo(repo_name, self.basepath) is False:
179 157 return HTTPNotFound()(environ, start_response)
180 158
181 159 try:
182 #invalidate cache on push
160 # invalidate cache on push
183 161 if action == 'push':
184 self.__invalidate_cache(repo_name)
185
162 self._invalidate_cache(repo_name)
163 log.info('%s action on HG repo "%s"' % (action, repo_name))
186 164 app = self.__make_app(repo_path, baseui, extras)
187 165 return app(environ, start_response)
188 166 except RepoError, e:
@@ -199,32 +177,6 b' class SimpleHg(object):'
199 177 """
200 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 180 def __get_repository(self, environ):
229 181 """
230 182 Get's repository name out of PATH_INFO header
@@ -232,6 +184,7 b' class SimpleHg(object):'
232 184 :param environ: environ where PATH_INFO is stored
233 185 """
234 186 try:
187 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
235 188 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
236 189 if repo_name.endswith('/'):
237 190 repo_name = repo_name.rstrip('/')
@@ -265,18 +218,12 b' class SimpleHg(object):'
265 218 else:
266 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 221 def __inject_extras(self, repo_path, baseui, extras={}):
275 222 """
276 223 Injects some extra params into baseui instance
277
224
278 225 also overwrites global settings with those takes from local hgrc file
279
226
280 227 :param baseui: baseui instance
281 228 :param extras: dict with extra params to put into baseui
282 229 """
@@ -298,4 +245,3 b' class SimpleHg(object):'
298 245 for section in ui_sections:
299 246 for k, v in repoui.configitems(section):
300 247 baseui.setconfig(section, k, v)
301
@@ -1,12 +1,12 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 rhodecode.lib.smtp_mailer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
3 rhodecode.lib.rcmail.smtp_mailer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Simple smtp mailer used in RhodeCode
7 7
8 8 :created_on: Sep 13, 2010
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 10 :license: GPLv3, see COPYING for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
@@ -24,16 +24,8 b''
24 24
25 25 import logging
26 26 import smtplib
27 import mimetypes
28 27 from socket import sslerror
29
30 from email.mime.multipart import MIMEMultipart
31 from email.mime.image import MIMEImage
32 from email.mime.audio import MIMEAudio
33 from email.mime.base import MIMEBase
34 from email.mime.text import MIMEText
35 from email.utils import formatdate
36 from email import encoders
28 from rhodecode.lib.rcmail.message import Message
37 29
38 30
39 31 class SmtpMailer(object):
@@ -62,10 +54,15 b' class SmtpMailer(object):'
62 54 self.debug = debug
63 55 self.auth = smtp_auth
64 56
65 def send(self, recipients=[], subject='', body='', attachment_files=None):
57 def send(self, recipients=[], subject='', body='', html='',
58 attachment_files=None):
66 59
67 60 if isinstance(recipients, basestring):
68 61 recipients = [recipients]
62 msg = Message(subject, recipients, body, html, self.mail_from,
63 recipients_separator=", ")
64 raw_msg = msg.to_message()
65
69 66 if self.ssl:
70 67 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
71 68 else:
@@ -87,26 +84,7 b' class SmtpMailer(object):'
87 84 if self.user and self.passwd:
88 85 smtp_serv.login(self.user, self.passwd)
89 86
90 date_ = formatdate(localtime=True)
91 msg = MIMEMultipart()
92 msg.set_type('multipart/alternative')
93 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
94
95 text_msg = MIMEText(body)
96 text_msg.set_type('text/plain')
97 text_msg.set_param('charset', 'UTF-8')
98
99 msg['From'] = self.mail_from
100 msg['To'] = ','.join(recipients)
101 msg['Date'] = date_
102 msg['Subject'] = subject
103
104 msg.attach(text_msg)
105
106 if attachment_files:
107 self.__atach_files(msg, attachment_files)
108
109 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
87 smtp_serv.sendmail(msg.sender, msg.send_to, raw_msg.as_string())
110 88 logging.info('MAIL SEND TO: %s' % recipients)
111 89
112 90 try:
@@ -114,52 +92,3 b' class SmtpMailer(object):'
114 92 except sslerror:
115 93 # sslerror is raised in tls connections on closing sometimes
116 94 pass
117
118 def __atach_files(self, msg, attachment_files):
119 if isinstance(attachment_files, dict):
120 for f_name, msg_file in attachment_files.items():
121 ctype, encoding = mimetypes.guess_type(f_name)
122 logging.info("guessing file %s type based on %s", ctype,
123 f_name)
124 if ctype is None or encoding is not None:
125 # No guess could be made, or the file is encoded
126 # (compressed), so use a generic bag-of-bits type.
127 ctype = 'application/octet-stream'
128 maintype, subtype = ctype.split('/', 1)
129 if maintype == 'text':
130 # Note: we should handle calculating the charset
131 file_part = MIMEText(self.get_content(msg_file),
132 _subtype=subtype)
133 elif maintype == 'image':
134 file_part = MIMEImage(self.get_content(msg_file),
135 _subtype=subtype)
136 elif maintype == 'audio':
137 file_part = MIMEAudio(self.get_content(msg_file),
138 _subtype=subtype)
139 else:
140 file_part = MIMEBase(maintype, subtype)
141 file_part.set_payload(self.get_content(msg_file))
142 # Encode the payload using Base64
143 encoders.encode_base64(msg)
144 # Set the filename parameter
145 file_part.add_header('Content-Disposition', 'attachment',
146 filename=f_name)
147 file_part.add_header('Content-Type', ctype, name=f_name)
148 msg.attach(file_part)
149 else:
150 raise Exception('Attachment files should be'
151 'a dict in format {"filename":"filepath"}')
152
153 def get_content(self, msg_file):
154 """Get content based on type, if content is a string do open first
155 else just read because it's a probably open file object
156
157 :param msg_file:
158 """
159 if isinstance(msg_file, str):
160 return open(msg_file, "rb").read()
161 else:
162 # just for safe seek to 0
163 msg_file.seek(0)
164 return msg_file.read()
165
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 18, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -29,6 +29,9 b' import datetime'
29 29 import traceback
30 30 import paste
31 31 import beaker
32 import tarfile
33 import shutil
34 from os.path import abspath
32 35 from os.path import dirname as dn, join as jn
33 36
34 37 from paste.script.command import Command, BadCommand
@@ -37,25 +40,27 b' from mercurial import ui, config'
37 40
38 41 from webhelpers.text import collapse, remove_formatting, strip_tags
39 42
40 from vcs import get_backend
41 from vcs.backends.base import BaseChangeset
42 from vcs.utils.lazy import LazyProperty
43 from vcs.utils.helpers import get_scm
44 from vcs.exceptions import VCSError
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.exceptions import VCSError
48
49 from rhodecode.lib.caching_query import FromCache
45 50
46 51 from rhodecode.model import meta
47 from rhodecode.model.caching_query import FromCache
48 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
49 RhodeCodeSettings
50 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 from rhodecode.model.meta import Session
55 from rhodecode.model.repos_group import ReposGroupModel
51 56
52 57 log = logging.getLogger(__name__)
53 58
54 59
55 def recursive_replace(str, replace=' '):
60 def recursive_replace(str_, replace=' '):
56 61 """Recursive replace of given sign to just one instance
57 62
58 :param str: given string
63 :param str_: given string
59 64 :param replace: char to find and replace multiple instances
60 65
61 66 Examples::
@@ -63,11 +68,11 b" def recursive_replace(str, replace=' '):"
63 68 'Mighty-Mighty-Bo-sstones'
64 69 """
65 70
66 if str.find(replace * 2) == -1:
67 return str
71 if str_.find(replace * 2) == -1:
72 return str_
68 73 else:
69 str = str.replace(replace * 2, replace)
70 return recursive_replace(str, replace)
74 str_ = str_.replace(replace * 2, replace)
75 return recursive_replace(str_, replace)
71 76
72 77
73 78 def repo_name_slug(value):
@@ -90,7 +95,11 b' def get_repo_slug(request):'
90 95 return request.environ['pylons.routes_dict'].get('repo_name')
91 96
92 97
93 def action_logger(user, action, repo, ipaddr='', sa=None):
98 def get_repos_group_slug(request):
99 return request.environ['pylons.routes_dict'].get('group_name')
100
101
102 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
94 103 """
95 104 Action logger for various actions made by users
96 105
@@ -106,7 +115,7 b' def action_logger(user, action, repo, ip'
106 115 """
107 116
108 117 if not sa:
109 sa = meta.Session()
118 sa = meta.Session
110 119
111 120 try:
112 121 if hasattr(user, 'user_id'):
@@ -116,13 +125,12 b' def action_logger(user, action, repo, ip'
116 125 else:
117 126 raise Exception('You have to provide user object or username')
118 127
119 rm = RepoModel()
120 128 if hasattr(repo, 'repo_id'):
121 repo_obj = rm.get(repo.repo_id, cache=False)
129 repo_obj = Repository.get(repo.repo_id)
122 130 repo_name = repo_obj.repo_name
123 131 elif isinstance(repo, basestring):
124 132 repo_name = repo.lstrip('/')
125 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
133 repo_obj = Repository.get_by_repo_name(repo_name)
126 134 else:
127 135 raise Exception('You have to provide repository to action logger')
128 136
@@ -136,26 +144,25 b' def action_logger(user, action, repo, ip'
136 144 user_log.action_date = datetime.datetime.now()
137 145 user_log.user_ip = ipaddr
138 146 sa.add(user_log)
139 sa.commit()
140 147
141 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
148 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
149 if commit:
150 sa.commit()
142 151 except:
143 152 log.error(traceback.format_exc())
144 sa.rollback()
153 raise
145 154
146 155
147 156 def get_repos(path, recursive=False):
148 157 """
149 158 Scans given path for repos and return (name,(type,path)) tuple
150 159
151 :param path: path to scann for repositories
160 :param path: path to scan for repositories
152 161 :param recursive: recursive search and return names with subdirs in front
153 162 """
154 from vcs.utils.helpers import get_scm
155 from vcs.exceptions import VCSError
156 163
157 164 # remove ending slash for better results
158 path = path.rstrip('/')
165 path = path.rstrip(os.sep)
159 166
160 167 def _get_repos(p):
161 168 if not os.access(p, os.W_OK):
@@ -195,10 +202,11 b' def is_valid_repo(repo_name, base_path):'
195 202 except VCSError:
196 203 return False
197 204
205
198 206 def is_valid_repos_group(repos_group_name, base_path):
199 207 """
200 208 Returns True if given path is a repos group False otherwise
201
209
202 210 :param repo_name:
203 211 :param base_path:
204 212 """
@@ -214,6 +222,7 b' def is_valid_repos_group(repos_group_nam'
214 222
215 223 return False
216 224
225
217 226 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
218 227 while True:
219 228 ok = raw_input(prompt)
@@ -250,28 +259,28 b" def make_ui(read_from='file', path=None,"
250 259
251 260 baseui = ui.ui()
252 261
253 #clean the baseui object
262 # clean the baseui object
254 263 baseui._ocfg = config.config()
255 264 baseui._ucfg = config.config()
256 265 baseui._tcfg = config.config()
257 266
258 267 if read_from == 'file':
259 268 if not os.path.isfile(path):
260 log.warning('Unable to read config file %s' % path)
269 log.debug('hgrc file is not present at %s skipping...' % path)
261 270 return False
262 log.debug('reading hgrc from %s', path)
271 log.debug('reading hgrc from %s' % path)
263 272 cfg = config.config()
264 273 cfg.read(path)
265 274 for section in ui_sections:
266 275 for k, v in cfg.items(section):
267 log.debug('settings ui from file[%s]%s:%s', section, k, v)
276 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
268 277 baseui.setconfig(section, k, v)
269 278
270 279 elif read_from == 'db':
271 sa = meta.Session()
280 sa = meta.Session
272 281 ret = sa.query(RhodeCodeUi)\
273 .options(FromCache("sql_cache_short",
274 "get_hg_ui_settings")).all()
282 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
283 .all()
275 284
276 285 hg_ui = ret
277 286 for ui_ in hg_ui:
@@ -285,18 +294,20 b" def make_ui(read_from='file', path=None,"
285 294
286 295
287 296 def set_rhodecode_config(config):
288 """Updates pylons config with new settings from database
297 """
298 Updates pylons config with new settings from database
289 299
290 300 :param config:
291 301 """
292 hgsettings = RhodeCodeSettings.get_app_settings()
302 hgsettings = RhodeCodeSetting.get_app_settings()
293 303
294 304 for k, v in hgsettings.items():
295 305 config[k] = v
296 306
297 307
298 308 def invalidate_cache(cache_key, *args):
299 """Puts cache invalidation task into db for
309 """
310 Puts cache invalidation task into db for
300 311 further global cache invalidation
301 312 """
302 313
@@ -313,7 +324,8 b' class EmptyChangeset(BaseChangeset):'
313 324 an EmptyChangeset
314 325 """
315 326
316 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
327 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
328 alias=None):
317 329 self._empty_cs = cs
318 330 self.revision = -1
319 331 self.message = ''
@@ -325,7 +337,8 b' class EmptyChangeset(BaseChangeset):'
325 337
326 338 @LazyProperty
327 339 def raw_id(self):
328 """Returns raw string identifying this changeset, useful for web
340 """
341 Returns raw string identifying this changeset, useful for web
329 342 representation.
330 343 """
331 344
@@ -350,66 +363,74 b' class EmptyChangeset(BaseChangeset):'
350 363
351 364
352 365 def map_groups(groups):
353 """Checks for groups existence, and creates groups structures.
366 """
367 Checks for groups existence, and creates groups structures.
354 368 It returns last group in structure
355 369
356 370 :param groups: list of groups structure
357 371 """
358 sa = meta.Session()
372 sa = meta.Session
359 373
360 374 parent = None
361 375 group = None
362 376
363 377 # last element is repo in nested groups structure
364 378 groups = groups[:-1]
365
379 rgm = ReposGroupModel(sa)
366 380 for lvl, group_name in enumerate(groups):
367 381 group_name = '/'.join(groups[:lvl] + [group_name])
368 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
382 group = RepoGroup.get_by_group_name(group_name)
383 desc = '%s group' % group_name
384
385 # # WTF that doesn't work !?
386 # if group is None:
387 # group = rgm.create(group_name, desc, parent, just_db=True)
388 # sa.commit()
369 389
370 390 if group is None:
371 group = Group(group_name, parent)
391 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
392 group = RepoGroup(group_name, parent)
393 group.group_description = desc
372 394 sa.add(group)
395 rgm._create_default_perms(group)
373 396 sa.commit()
374 397 parent = group
375 398 return group
376 399
377 400
378 401 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
379 """maps all repos given in initial_repo_list, non existing repositories
402 """
403 maps all repos given in initial_repo_list, non existing repositories
380 404 are created, if remove_obsolete is True it also check for db entries
381 405 that are not in initial_repo_list and removes them.
382 406
383 407 :param initial_repo_list: list of repositories found by scanning methods
384 408 :param remove_obsolete: check for obsolete entries in database
385 409 """
386
387 sa = meta.Session()
410 from rhodecode.model.repo import RepoModel
411 sa = meta.Session
388 412 rm = RepoModel()
389 413 user = sa.query(User).filter(User.admin == True).first()
414 if user is None:
415 raise Exception('Missing administrative account !')
390 416 added = []
391 # fixup groups paths to new format on the fly
392 # TODO: remove this in future
393 for g in Group.query().all():
394 g.group_name = g.get_new_name(g.name)
395 sa.add(g)
417
396 418 for name, repo in initial_repo_list.items():
397 419 group = map_groups(name.split(Repository.url_sep()))
398 420 if not rm.get_by_repo_name(name, cache=False):
399 log.info('repository %s not found creating default', name)
421 log.info('repository %s not found creating default' % name)
400 422 added.append(name)
401 423 form_data = {
402 'repo_name': name,
403 'repo_name_full': name,
404 'repo_type': repo.alias,
405 'description': repo.description \
406 if repo.description != 'unknown' else \
407 '%s repository' % name,
408 'private': False,
409 'group_id': getattr(group, 'group_id', None)
410 }
424 'repo_name': name,
425 'repo_name_full': name,
426 'repo_type': repo.alias,
427 'description': repo.description \
428 if repo.description != 'unknown' else '%s repository' % name,
429 'private': False,
430 'group_id': getattr(group, 'group_id', None)
431 }
411 432 rm.create(form_data, user, just_db=True)
412
433 sa.commit()
413 434 removed = []
414 435 if remove_obsolete:
415 436 #remove from database those repositories that are not in the filesystem
@@ -421,7 +442,8 b' def repo2db_mapper(initial_repo_list, re'
421 442
422 443 return added, removed
423 444
424 #set cache regions for beaker so celery can utilise it
445
446 # set cache regions for beaker so celery can utilise it
425 447 def add_cache(settings):
426 448 cache_settings = {'regions': None}
427 449 for key in settings.keys():
@@ -455,7 +477,7 b' def add_cache(settings):'
455 477 def create_test_index(repo_location, config, full_index):
456 478 """
457 479 Makes default test index
458
480
459 481 :param config: test config
460 482 :param full_index:
461 483 """
@@ -480,19 +502,16 b' def create_test_index(repo_location, con'
480 502
481 503
482 504 def create_test_env(repos_test_path, config):
483 """Makes a fresh database and
505 """
506 Makes a fresh database and
484 507 install test repository into tmp dir
485 508 """
486 509 from rhodecode.lib.db_manage import DbManage
487 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
488 HG_FORK, GIT_FORK, TESTS_TMP_PATH
489 import tarfile
490 import shutil
491 from os.path import abspath
510 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
492 511
493 512 # PART ONE create db
494 513 dbconf = config['sqlalchemy.db1.url']
495 log.debug('making test db %s', dbconf)
514 log.debug('making test db %s' % dbconf)
496 515
497 516 # create test dir if it doesn't exist
498 517 if not os.path.isdir(repos_test_path):
@@ -507,7 +526,7 b' def create_test_env(repos_test_path, con'
507 526 dbmanage.admin_prompt()
508 527 dbmanage.create_permissions()
509 528 dbmanage.populate_default_permissions()
510
529 Session.commit()
511 530 # PART TWO make test repo
512 531 log.debug('making test vcs repositories')
513 532
@@ -595,4 +614,3 b' class BasePasterCommand(Command):'
595 614 path_to_ini_file = os.path.realpath(conf)
596 615 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
597 616 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
598
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Nov 25, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12
13 13
@@ -56,12 +56,13 b' def init_model(engine):'
56 56
57 57 :param engine: engine to bind to
58 58 """
59 log.info("initializing db for %s", engine)
59 log.info("initializing db for %s" % engine)
60 60 meta.Base.metadata.bind = engine
61 61
62 62
63 63 class BaseModel(object):
64 """Base Model for all RhodeCode models, it adds sql alchemy session
64 """
65 Base Model for all RhodeCode models, it adds sql alchemy session
65 66 into instance of model
66 67
67 68 :param sa: If passed it reuses this session instead of creating a new one
@@ -72,3 +73,26 b' class BaseModel(object):'
72 73 self.sa = sa
73 74 else:
74 75 self.sa = meta.Session
76
77 def _get_instance(self, cls, instance, callback=None):
78 """
79 Get's instance of given cls using some simple lookup mechanism.
80
81 :param cls: class to fetch
82 :param instance: int or Instance
83 :param callback: callback to call if all lookups failed
84 """
85
86 if isinstance(instance, cls):
87 return instance
88 elif isinstance(instance, int) or str(instance).isdigit():
89 return cls.get(instance)
90 else:
91 if instance:
92 if callback is None:
93 raise Exception(
94 'given object must be int or Instance of %s got %s, '
95 'no callback provided' % (cls, type(instance))
96 )
97 else:
98 return callback(instance)
This diff has been collapsed as it changes many lines, (707 lines changed) Show them Hide them
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 08, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -27,25 +27,23 b' import os'
27 27 import logging
28 28 import datetime
29 29 import traceback
30 from datetime import date
30 from collections import defaultdict
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 from vcs import get_backend
38 from vcs.utils.helpers import get_scm
39 from vcs.exceptions import VCSError
40 from vcs.utils.lazy import LazyProperty
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 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
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
45 43 from rhodecode.lib.compat import json
44 from rhodecode.lib.caching_query import FromCache
46 45
47 46 from rhodecode.model.meta import Base, Session
48 from rhodecode.model.caching_query import FromCache
49 47
50 48
51 49 log = logging.getLogger(__name__)
@@ -87,8 +85,8 b' class ModelSerializer(json.JSONEncoder):'
87 85
88 86
89 87 class BaseModel(object):
90 """Base Model for all classess
91
88 """
89 Base Model for all classess
92 90 """
93 91
94 92 @classmethod
@@ -97,7 +95,8 b' class BaseModel(object):'
97 95 return class_mapper(cls).c.keys()
98 96
99 97 def get_dict(self):
100 """return dict with keys and values corresponding
98 """
99 return dict with keys and values corresponding
101 100 to this model data """
102 101
103 102 d = {}
@@ -142,12 +141,14 b' class BaseModel(object):'
142 141 def delete(cls, id_):
143 142 obj = cls.query().get(id_)
144 143 Session.delete(obj)
145 Session.commit()
146 144
147 145
148 class RhodeCodeSettings(Base, BaseModel):
146 class RhodeCodeSetting(Base, BaseModel):
149 147 __tablename__ = 'rhodecode_settings'
150 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
148 __table_args__ = (
149 UniqueConstraint('app_settings_name'),
150 {'extend_existing': True}
151 )
151 152 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
152 153 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
153 154 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -156,7 +157,6 b' class RhodeCodeSettings(Base, BaseModel)'
156 157 self.app_settings_name = k
157 158 self.app_settings_value = v
158 159
159
160 160 @validates('_app_settings_value')
161 161 def validate_settings_value(self, key, val):
162 162 assert type(val) == unicode
@@ -165,7 +165,7 b' class RhodeCodeSettings(Base, BaseModel)'
165 165 @hybrid_property
166 166 def app_settings_value(self):
167 167 v = self._app_settings_value
168 if v == 'ldap_active':
168 if self.app_settings_name == 'ldap_active':
169 169 v = str2bool(v)
170 170 return v
171 171
@@ -179,9 +179,10 b' class RhodeCodeSettings(Base, BaseModel)'
179 179 self._app_settings_value = safe_unicode(val)
180 180
181 181 def __repr__(self):
182 return "<%s('%s:%s')>" % (self.__class__.__name__,
183 self.app_settings_name, self.app_settings_value)
184
182 return "<%s('%s:%s')>" % (
183 self.__class__.__name__,
184 self.app_settings_name, self.app_settings_value
185 )
185 186
186 187 @classmethod
187 188 def get_by_name(cls, ldap_key):
@@ -218,7 +219,10 b' class RhodeCodeSettings(Base, BaseModel)'
218 219
219 220 class RhodeCodeUi(Base, BaseModel):
220 221 __tablename__ = 'rhodecode_ui'
221 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
222 __table_args__ = (
223 UniqueConstraint('ui_key'),
224 {'extend_existing': True}
225 )
222 226
223 227 HOOK_UPDATE = 'changegroup.update'
224 228 HOOK_REPO_SIZE = 'changegroup.repo_size'
@@ -231,12 +235,10 b' class RhodeCodeUi(Base, BaseModel):'
231 235 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 236 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
233 237
234
235 238 @classmethod
236 239 def get_by_key(cls, key):
237 240 return cls.query().filter(cls.ui_key == key)
238 241
239
240 242 @classmethod
241 243 def get_builtin_hooks(cls):
242 244 q = cls.query()
@@ -263,12 +265,14 b' class RhodeCodeUi(Base, BaseModel):'
263 265 new_ui.ui_value = val
264 266
265 267 Session.add(new_ui)
266 Session.commit()
267 268
268 269
269 270 class User(Base, BaseModel):
270 271 __tablename__ = 'users'
271 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
272 __table_args__ = (
273 UniqueConstraint('username'), UniqueConstraint('email'),
274 {'extend_existing': True}
275 )
272 276 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
273 277 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 278 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -286,10 +290,12 b' class User(Base, BaseModel):'
286 290
287 291 repositories = relationship('Repository')
288 292 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
289 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
293 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
290 294
291 295 group_member = relationship('UsersGroupMember', cascade='all')
292 296
297 notifications = relationship('UserNotification',)
298
293 299 @hybrid_property
294 300 def email(self):
295 301 return self._email
@@ -303,6 +309,11 b' class User(Base, BaseModel):'
303 309 return '%s %s' % (self.name, self.lastname)
304 310
305 311 @property
312 def full_name_or_username(self):
313 return ('%s %s' % (self.name, self.lastname)
314 if (self.name and self.lastname) else self.username)
315
316 @property
306 317 def full_contact(self):
307 318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
308 319
@@ -315,60 +326,64 b' class User(Base, BaseModel):'
315 326 return self.admin
316 327
317 328 def __repr__(self):
318 try:
319 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
320 self.user_id, self.username)
321 except:
322 return self.__class__.__name__
329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
330 self.user_id, self.username)
323 331
324 def __json__(self):
325 return {'email': self.email}
332 @classmethod
333 def get_by_username(cls, username, case_insensitive=False, cache=False):
334 if case_insensitive:
335 q = cls.query().filter(cls.username.ilike(username))
336 else:
337 q = cls.query().filter(cls.username == username)
338
339 if cache:
340 q = q.options(FromCache("sql_cache_short",
341 "get_user_%s" % username))
342 return q.scalar()
326 343
327 344 @classmethod
328 def get_by_username(cls, username, case_insensitive=False):
329 if case_insensitive:
330 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
331 else:
332 return Session.query(cls).filter(cls.username == username).scalar()
345 def get_by_api_key(cls, api_key, cache=False):
346 q = cls.query().filter(cls.api_key == api_key)
347
348 if cache:
349 q = q.options(FromCache("sql_cache_short",
350 "get_api_key_%s" % api_key))
351 return q.scalar()
333 352
334 353 @classmethod
335 def get_by_api_key(cls, api_key):
336 return cls.query().filter(cls.api_key == api_key).one()
354 def get_by_email(cls, email, case_insensitive=False, cache=False):
355 if case_insensitive:
356 q = cls.query().filter(cls.email.ilike(email))
357 else:
358 q = cls.query().filter(cls.email == email)
359
360 if cache:
361 q = q.options(FromCache("sql_cache_short",
362 "get_api_key_%s" % email))
363 return q.scalar()
337 364
338 365 def update_lastlogin(self):
339 366 """Update user lastlogin"""
340
341 367 self.last_login = datetime.datetime.now()
342 368 Session.add(self)
343 Session.commit()
344 log.debug('updated user %s lastlogin', self.username)
345
346 @classmethod
347 def create(cls, form_data):
348 from rhodecode.lib.auth import get_crypt_password
369 log.debug('updated user %s lastlogin' % self.username)
349 370
350 try:
351 new_user = cls()
352 for k, v in form_data.items():
353 if k == 'password':
354 v = get_crypt_password(v)
355 setattr(new_user, k, v)
371 def __json__(self):
372 return dict(
373 email=self.email,
374 full_name=self.full_name,
375 full_name_or_username=self.full_name_or_username,
376 short_contact=self.short_contact,
377 full_contact=self.full_contact
378 )
356 379
357 new_user.api_key = generate_api_key(form_data['username'])
358 Session.add(new_user)
359 Session.commit()
360 return new_user
361 except:
362 log.error(traceback.format_exc())
363 Session.rollback()
364 raise
365 380
366 381 class UserLog(Base, BaseModel):
367 382 __tablename__ = 'user_logs'
368 __table_args__ = {'extend_existing':True}
383 __table_args__ = {'extend_existing': True}
369 384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
370 385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
371 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
386 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
372 387 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
373 388 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
374 389 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -376,15 +391,15 b' class UserLog(Base, BaseModel):'
376 391
377 392 @property
378 393 def action_as_day(self):
379 return date(*self.action_date.timetuple()[:3])
394 return datetime.date(*self.action_date.timetuple()[:3])
380 395
381 396 user = relationship('User')
382 repository = relationship('Repository')
397 repository = relationship('Repository',cascade='')
383 398
384 399
385 400 class UsersGroup(Base, BaseModel):
386 401 __tablename__ = 'users_groups'
387 __table_args__ = {'extend_existing':True}
402 __table_args__ = {'extend_existing': True}
388 403
389 404 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
390 405 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
@@ -396,18 +411,16 b' class UsersGroup(Base, BaseModel):'
396 411 return '<userGroup(%s)>' % (self.users_group_name)
397 412
398 413 @classmethod
399 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
414 def get_by_group_name(cls, group_name, cache=False,
415 case_insensitive=False):
400 416 if case_insensitive:
401 gr = cls.query()\
402 .filter(cls.users_group_name.ilike(group_name))
417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
403 418 else:
404 gr = cls.query()\
405 .filter(cls.users_group_name == group_name)
419 q = cls.query().filter(cls.users_group_name == group_name)
406 420 if cache:
407 gr = gr.options(FromCache("sql_cache_short",
408 "get_user_%s" % group_name))
409 return gr.scalar()
410
421 q = q.options(FromCache("sql_cache_short",
422 "get_user_%s" % group_name))
423 return q.scalar()
411 424
412 425 @classmethod
413 426 def get(cls, users_group_id, cache=False):
@@ -417,71 +430,10 b' class UsersGroup(Base, BaseModel):'
417 430 "get_users_group_%s" % users_group_id))
418 431 return users_group.get(users_group_id)
419 432
420 @classmethod
421 def create(cls, form_data):
422 try:
423 new_users_group = cls()
424 for k, v in form_data.items():
425 setattr(new_users_group, k, v)
426
427 Session.add(new_users_group)
428 Session.commit()
429 return new_users_group
430 except:
431 log.error(traceback.format_exc())
432 Session.rollback()
433 raise
434
435 @classmethod
436 def update(cls, users_group_id, form_data):
437
438 try:
439 users_group = cls.get(users_group_id, cache=False)
440
441 for k, v in form_data.items():
442 if k == 'users_group_members':
443 users_group.members = []
444 Session.flush()
445 members_list = []
446 if v:
447 v = [v] if isinstance(v, basestring) else v
448 for u_id in set(v):
449 member = UsersGroupMember(users_group_id, u_id)
450 members_list.append(member)
451 setattr(users_group, 'members', members_list)
452 setattr(users_group, k, v)
453
454 Session.add(users_group)
455 Session.commit()
456 except:
457 log.error(traceback.format_exc())
458 Session.rollback()
459 raise
460
461 @classmethod
462 def delete(cls, users_group_id):
463 try:
464
465 # check if this group is not assigned to repo
466 assigned_groups = UsersGroupRepoToPerm.query()\
467 .filter(UsersGroupRepoToPerm.users_group_id ==
468 users_group_id).all()
469
470 if assigned_groups:
471 raise UsersGroupsAssignedException('Group assigned to %s' %
472 assigned_groups)
473
474 users_group = cls.get(users_group_id, cache=False)
475 Session.delete(users_group)
476 Session.commit()
477 except:
478 log.error(traceback.format_exc())
479 Session.rollback()
480 raise
481 433
482 434 class UsersGroupMember(Base, BaseModel):
483 435 __tablename__ = 'users_groups_members'
484 __table_args__ = {'extend_existing':True}
436 __table_args__ = {'extend_existing': True}
485 437
486 438 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
487 439 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
@@ -494,18 +446,13 b' class UsersGroupMember(Base, BaseModel):'
494 446 self.users_group_id = gr_id
495 447 self.user_id = u_id
496 448
497 @staticmethod
498 def add_user_to_group(group, user):
499 ugm = UsersGroupMember()
500 ugm.users_group = group
501 ugm.user = user
502 Session.add(ugm)
503 Session.commit()
504 return ugm
505 449
506 450 class Repository(Base, BaseModel):
507 451 __tablename__ = 'repositories'
508 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
452 __table_args__ = (
453 UniqueConstraint('repo_name'),
454 {'extend_existing': True},
455 )
509 456
510 457 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
511 458 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
@@ -521,17 +468,16 b' class Repository(Base, BaseModel):'
521 468 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
522 469 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
523 470
524
525 471 user = relationship('User')
526 472 fork = relationship('Repository', remote_side=repo_id)
527 group = relationship('Group')
528 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
473 group = relationship('RepoGroup')
474 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
529 475 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
530 476 stats = relationship('Statistics', cascade='all', uselist=False)
531 477
532 478 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
533 479
534 logs = relationship('UserLog', cascade='all')
480 logs = relationship('UserLog')
535 481
536 482 def __repr__(self):
537 483 return "<%s('%s:%s')>" % (self.__class__.__name__,
@@ -547,7 +493,7 b' class Repository(Base, BaseModel):'
547 493 q = q.options(joinedload(Repository.fork))\
548 494 .options(joinedload(Repository.user))\
549 495 .options(joinedload(Repository.group))
550 return q.one()
496 return q.scalar()
551 497
552 498 @classmethod
553 499 def get_repo_forks(cls, repo_id):
@@ -560,9 +506,9 b' class Repository(Base, BaseModel):'
560 506
561 507 :param cls:
562 508 """
563 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
564 cls.url_sep())
565 q.options(FromCache("sql_cache_short", "repository_repo_path"))
509 q = Session.query(RhodeCodeUi)\
510 .filter(RhodeCodeUi.ui_key == cls.url_sep())
511 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
566 512 return q.one().ui_value
567 513
568 514 @property
@@ -598,7 +544,7 b' class Repository(Base, BaseModel):'
598 544 """
599 545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
600 546 Repository.url_sep())
601 q.options(FromCache("sql_cache_short", "repository_repo_path"))
547 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
602 548 return q.one().ui_value
603 549
604 550 @property
@@ -633,7 +579,6 b' class Repository(Base, BaseModel):'
633 579 baseui._ucfg = config.config()
634 580 baseui._tcfg = config.config()
635 581
636
637 582 ret = RhodeCodeUi.query()\
638 583 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
639 584
@@ -651,14 +596,13 b' class Repository(Base, BaseModel):'
651 596 """
652 597 returns True if given repo name is a valid filesystem repository
653 598
654 @param cls:
655 @param repo_name:
599 :param cls:
600 :param repo_name:
656 601 """
657 602 from rhodecode.lib.utils import is_valid_repo
658 603
659 604 return is_valid_repo(repo_name, cls.base_path())
660 605
661
662 606 #==========================================================================
663 607 # SCM PROPERTIES
664 608 #==========================================================================
@@ -678,35 +622,34 b' class Repository(Base, BaseModel):'
678 622 def last_change(self):
679 623 return self.scm_instance.last_change
680 624
625 def comments(self, revisions=None):
626 """
627 Returns comments for this repository grouped by revisions
628
629 :param revisions: filter query by revisions only
630 """
631 cmts = ChangesetComment.query()\
632 .filter(ChangesetComment.repo == self)
633 if revisions:
634 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
635 grouped = defaultdict(list)
636 for cmt in cmts.all():
637 grouped[cmt.revision].append(cmt)
638 return grouped
639
681 640 #==========================================================================
682 641 # SCM CACHE INSTANCE
683 642 #==========================================================================
684 643
685 644 @property
686 645 def invalidate(self):
687 """
688 Returns Invalidation object if this repo should be invalidated
689 None otherwise. `cache_active = False` means that this cache
690 state is not valid and needs to be invalidated
691 """
692 return CacheInvalidation.query()\
693 .filter(CacheInvalidation.cache_key == self.repo_name)\
694 .filter(CacheInvalidation.cache_active == False)\
695 .scalar()
646 return CacheInvalidation.invalidate(self.repo_name)
696 647
697 648 def set_invalidate(self):
698 649 """
699 650 set a cache for invalidation for this instance
700 651 """
701 inv = CacheInvalidation.query()\
702 .filter(CacheInvalidation.cache_key == self.repo_name)\
703 .scalar()
704
705 if inv is None:
706 inv = CacheInvalidation(self.repo_name)
707 inv.cache_active = True
708 Session.add(inv)
709 Session.commit()
652 CacheInvalidation.set_invalidate(self.repo_name)
710 653
711 654 @LazyProperty
712 655 def scm_instance(self):
@@ -717,28 +660,20 b' class Repository(Base, BaseModel):'
717 660 @cache_region('long_term')
718 661 def _c(repo_name):
719 662 return self.__get_instance()
720
721 # TODO: remove this trick when beaker 1.6 is released
722 # and have fixed this issue with not supporting unicode keys
723 rn = safe_str(self.repo_name)
724
663 rn = self.repo_name
664 log.debug('Getting cached instance of repo')
725 665 inv = self.invalidate
726 666 if inv is not None:
727 667 region_invalidate(_c, None, rn)
728 668 # update our cache
729 inv.cache_active = True
730 Session.add(inv)
731 Session.commit()
732
669 CacheInvalidation.set_valid(inv.cache_key)
733 670 return _c(rn)
734 671
735 672 def __get_instance(self):
736
737 673 repo_full_path = self.repo_full_path
738
739 674 try:
740 675 alias = get_scm(repo_full_path)[0]
741 log.debug('Creating instance of %s repository', alias)
676 log.debug('Creating instance of %s repository' % alias)
742 677 backend = get_backend(alias)
743 678 except VCSError:
744 679 log.error(traceback.format_exc())
@@ -751,7 +686,7 b' class Repository(Base, BaseModel):'
751 686
752 687 repo = backend(safe_str(repo_full_path), create=False,
753 688 baseui=self._ui)
754 #skip hidden web repository
689 # skip hidden web repository
755 690 if repo._get_hidden():
756 691 return
757 692 else:
@@ -760,19 +695,24 b' class Repository(Base, BaseModel):'
760 695 return repo
761 696
762 697
763 class Group(Base, BaseModel):
698 class RepoGroup(Base, BaseModel):
764 699 __tablename__ = 'groups'
765 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
766 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
767 __mapper_args__ = {'order_by':'group_name'}
700 __table_args__ = (
701 UniqueConstraint('group_name', 'group_parent_id'),
702 CheckConstraint('group_id != group_parent_id'),
703 {'extend_existing': True},
704 )
705 __mapper_args__ = {'order_by': 'group_name'}
768 706
769 707 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
770 708 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
771 709 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
772 710 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
773 711
774 parent_group = relationship('Group', remote_side=group_id)
712 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
713 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
775 714
715 parent_group = relationship('RepoGroup', remote_side=group_id)
776 716
777 717 def __init__(self, group_name='', parent_group=None):
778 718 self.group_name = group_name
@@ -838,11 +778,11 b' class Group(Base, BaseModel):'
838 778
839 779 @property
840 780 def children(self):
841 return Group.query().filter(Group.parent_group == self)
781 return RepoGroup.query().filter(RepoGroup.parent_group == self)
842 782
843 783 @property
844 784 def name(self):
845 return self.group_name.split(Group.url_sep())[-1]
785 return self.group_name.split(RepoGroup.url_sep())[-1]
846 786
847 787 @property
848 788 def full_path(self):
@@ -850,7 +790,7 b' class Group(Base, BaseModel):'
850 790
851 791 @property
852 792 def full_path_splitted(self):
853 return self.group_name.split(Group.url_sep())
793 return self.group_name.split(RepoGroup.url_sep())
854 794
855 795 @property
856 796 def repositories(self):
@@ -869,93 +809,100 b' class Group(Base, BaseModel):'
869 809
870 810 return cnt + children_count(self)
871 811
872
873 812 def get_new_name(self, group_name):
874 813 """
875 814 returns new full group name based on parent and new name
876 815
877 816 :param group_name:
878 817 """
879 path_prefix = (self.parent_group.full_path_splitted if
818 path_prefix = (self.parent_group.full_path_splitted if
880 819 self.parent_group else [])
881 return Group.url_sep().join(path_prefix + [group_name])
820 return RepoGroup.url_sep().join(path_prefix + [group_name])
882 821
883 822
884 823 class Permission(Base, BaseModel):
885 824 __tablename__ = 'permissions'
886 __table_args__ = {'extend_existing':True}
825 __table_args__ = {'extend_existing': True}
887 826 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
888 827 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
889 828 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
890 829
891 830 def __repr__(self):
892 return "<%s('%s:%s')>" % (self.__class__.__name__,
893 self.permission_id, self.permission_name)
831 return "<%s('%s:%s')>" % (
832 self.__class__.__name__, self.permission_id, self.permission_name
833 )
894 834
895 835 @classmethod
896 836 def get_by_key(cls, key):
897 837 return cls.query().filter(cls.permission_name == key).scalar()
898 838
899 class RepoToPerm(Base, BaseModel):
839 @classmethod
840 def get_default_perms(cls, default_user_id):
841 q = Session.query(UserRepoToPerm, Repository, cls)\
842 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
843 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
844 .filter(UserRepoToPerm.user_id == default_user_id)
845
846 return q.all()
847
848 @classmethod
849 def get_default_group_perms(cls, default_user_id):
850 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
851 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
852 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
853 .filter(UserRepoGroupToPerm.user_id == default_user_id)
854
855 return q.all()
856
857
858 class UserRepoToPerm(Base, BaseModel):
900 859 __tablename__ = 'repo_to_perm'
901 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
860 __table_args__ = (
861 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
862 {'extend_existing': True}
863 )
902 864 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
903 865 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
904 866 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
905 867 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
906 868
907 869 user = relationship('User')
870 repository = relationship('Repository')
908 871 permission = relationship('Permission')
909 repository = relationship('Repository')
872
873 @classmethod
874 def create(cls, user, repository, permission):
875 n = cls()
876 n.user = user
877 n.repository = repository
878 n.permission = permission
879 Session.add(n)
880 return n
881
882 def __repr__(self):
883 return '<user:%s => %s >' % (self.user, self.repository)
884
910 885
911 886 class UserToPerm(Base, BaseModel):
912 887 __tablename__ = 'user_to_perm'
913 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
888 __table_args__ = (
889 UniqueConstraint('user_id', 'permission_id'),
890 {'extend_existing': True}
891 )
914 892 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
915 893 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
916 894 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
917 895
918 896 user = relationship('User')
919 permission = relationship('Permission')
920
921 @classmethod
922 def has_perm(cls, user_id, perm):
923 if not isinstance(perm, Permission):
924 raise Exception('perm needs to be an instance of Permission class')
925
926 return cls.query().filter(cls.user_id == user_id)\
927 .filter(cls.permission == perm).scalar() is not None
928
929 @classmethod
930 def grant_perm(cls, user_id, perm):
931 if not isinstance(perm, Permission):
932 raise Exception('perm needs to be an instance of Permission class')
897 permission = relationship('Permission', lazy='joined')
933 898
934 new = cls()
935 new.user_id = user_id
936 new.permission = perm
937 try:
938 Session.add(new)
939 Session.commit()
940 except:
941 Session.rollback()
942
943
944 @classmethod
945 def revoke_perm(cls, user_id, perm):
946 if not isinstance(perm, Permission):
947 raise Exception('perm needs to be an instance of Permission class')
948
949 try:
950 cls.query().filter(cls.user_id == user_id)\
951 .filter(cls.permission == perm).delete()
952 Session.commit()
953 except:
954 Session.rollback()
955 899
956 900 class UsersGroupRepoToPerm(Base, BaseModel):
957 901 __tablename__ = 'users_group_repo_to_perm'
958 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
902 __table_args__ = (
903 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
904 {'extend_existing': True}
905 )
959 906 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
960 907 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
961 908 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
@@ -965,11 +912,25 b' class UsersGroupRepoToPerm(Base, BaseMod'
965 912 permission = relationship('Permission')
966 913 repository = relationship('Repository')
967 914
915 @classmethod
916 def create(cls, users_group, repository, permission):
917 n = cls()
918 n.users_group = users_group
919 n.repository = repository
920 n.permission = permission
921 Session.add(n)
922 return n
923
968 924 def __repr__(self):
969 925 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
970 926
927
971 928 class UsersGroupToPerm(Base, BaseModel):
972 929 __tablename__ = 'users_group_to_perm'
930 __table_args__ = (
931 UniqueConstraint('users_group_id', 'permission_id',),
932 {'extend_existing': True}
933 )
973 934 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
974 935 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
975 936 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
@@ -978,60 +939,43 b' class UsersGroupToPerm(Base, BaseModel):'
978 939 permission = relationship('Permission')
979 940
980 941
981 @classmethod
982 def has_perm(cls, users_group_id, perm):
983 if not isinstance(perm, Permission):
984 raise Exception('perm needs to be an instance of Permission class')
985
986 return cls.query().filter(cls.users_group_id ==
987 users_group_id)\
988 .filter(cls.permission == perm)\
989 .scalar() is not None
990
991 @classmethod
992 def grant_perm(cls, users_group_id, perm):
993 if not isinstance(perm, Permission):
994 raise Exception('perm needs to be an instance of Permission class')
995
996 new = cls()
997 new.users_group_id = users_group_id
998 new.permission = perm
999 try:
1000 Session.add(new)
1001 Session.commit()
1002 except:
1003 Session.rollback()
1004
1005
1006 @classmethod
1007 def revoke_perm(cls, users_group_id, perm):
1008 if not isinstance(perm, Permission):
1009 raise Exception('perm needs to be an instance of Permission class')
1010
1011 try:
1012 cls.query().filter(cls.users_group_id == users_group_id)\
1013 .filter(cls.permission == perm).delete()
1014 Session.commit()
1015 except:
1016 Session.rollback()
1017
1018
1019 class GroupToPerm(Base, BaseModel):
1020 __tablename__ = 'group_to_perm'
1021 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
942 class UserRepoGroupToPerm(Base, BaseModel):
943 __tablename__ = 'user_repo_group_to_perm'
944 __table_args__ = (
945 UniqueConstraint('user_id', 'group_id', 'permission_id'),
946 {'extend_existing': True}
947 )
1022 948
1023 949 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1024 950 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
951 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1025 952 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1026 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1027 953
1028 954 user = relationship('User')
955 group = relationship('RepoGroup')
1029 956 permission = relationship('Permission')
1030 group = relationship('Group')
957
958
959 class UsersGroupRepoGroupToPerm(Base, BaseModel):
960 __tablename__ = 'users_group_repo_group_to_perm'
961 __table_args__ = (
962 UniqueConstraint('users_group_id', 'group_id'),
963 {'extend_existing': True}
964 )
965
966 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
968 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
969 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
970
971 users_group = relationship('UsersGroup')
972 permission = relationship('Permission')
973 group = relationship('RepoGroup')
974
1031 975
1032 976 class Statistics(Base, BaseModel):
1033 977 __tablename__ = 'statistics'
1034 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
978 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
1035 979 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1036 980 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1037 981 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
@@ -1041,11 +985,14 b' class Statistics(Base, BaseModel):'
1041 985
1042 986 repository = relationship('Repository', single_parent=True)
1043 987
988
1044 989 class UserFollowing(Base, BaseModel):
1045 990 __tablename__ = 'user_followings'
1046 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1047 UniqueConstraint('user_id', 'follows_user_id')
1048 , {'extend_existing':True})
991 __table_args__ = (
992 UniqueConstraint('user_id', 'follows_repository_id'),
993 UniqueConstraint('user_id', 'follows_user_id'),
994 {'extend_existing': True}
995 )
1049 996
1050 997 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1051 998 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
@@ -1058,20 +1005,19 b' class UserFollowing(Base, BaseModel):'
1058 1005 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1059 1006 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1060 1007
1061
1062 1008 @classmethod
1063 1009 def get_repo_followers(cls, repo_id):
1064 1010 return cls.query().filter(cls.follows_repo_id == repo_id)
1065 1011
1012
1066 1013 class CacheInvalidation(Base, BaseModel):
1067 1014 __tablename__ = 'cache_invalidation'
1068 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1015 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1069 1016 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1070 1017 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1071 1018 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1072 1019 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1073 1020
1074
1075 1021 def __init__(self, cache_key, cache_args=''):
1076 1022 self.cache_key = cache_key
1077 1023 self.cache_args = cache_args
@@ -1081,10 +1027,177 b' class CacheInvalidation(Base, BaseModel)'
1081 1027 return "<%s('%s:%s')>" % (self.__class__.__name__,
1082 1028 self.cache_id, self.cache_key)
1083 1029
1030 @classmethod
1031 def _get_key(cls, key):
1032 """
1033 Wrapper for generating a key
1034
1035 :param key:
1036 """
1037 import rhodecode
1038 prefix = ''
1039 iid = rhodecode.CONFIG.get('instance_id')
1040 if iid:
1041 prefix = iid
1042 return "%s%s" % (prefix, key)
1043
1044 @classmethod
1045 def get_by_key(cls, key):
1046 return cls.query().filter(cls.cache_key == key).scalar()
1047
1048 @classmethod
1049 def invalidate(cls, key):
1050 """
1051 Returns Invalidation object if this given key should be invalidated
1052 None otherwise. `cache_active = False` means that this cache
1053 state is not valid and needs to be invalidated
1054
1055 :param key:
1056 """
1057 return cls.query()\
1058 .filter(CacheInvalidation.cache_key == key)\
1059 .filter(CacheInvalidation.cache_active == False)\
1060 .scalar()
1061
1062 @classmethod
1063 def set_invalidate(cls, key):
1064 """
1065 Mark this Cache key for invalidation
1066
1067 :param key:
1068 """
1069
1070 log.debug('marking %s for invalidation' % key)
1071 inv_obj = Session.query(cls)\
1072 .filter(cls.cache_key == key).scalar()
1073 if inv_obj:
1074 inv_obj.cache_active = False
1075 else:
1076 log.debug('cache key not found in invalidation db -> creating one')
1077 inv_obj = CacheInvalidation(key)
1078
1079 try:
1080 Session.add(inv_obj)
1081 Session.commit()
1082 except Exception:
1083 log.error(traceback.format_exc())
1084 Session.rollback()
1085
1086 @classmethod
1087 def set_valid(cls, key):
1088 """
1089 Mark this cache key as active and currently cached
1090
1091 :param key:
1092 """
1093 inv_obj = cls.get_by_key(key)
1094 inv_obj.cache_active = True
1095 Session.add(inv_obj)
1096 Session.commit()
1097
1098
1099 class ChangesetComment(Base, BaseModel):
1100 __tablename__ = 'changeset_comments'
1101 __table_args__ = ({'extend_existing': True},)
1102 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1103 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1104 revision = Column('revision', String(40), nullable=False)
1105 line_no = Column('line_no', Unicode(10), nullable=True)
1106 f_path = Column('f_path', Unicode(1000), nullable=True)
1107 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1108 text = Column('text', Unicode(25000), nullable=False)
1109 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1110
1111 author = relationship('User', lazy='joined')
1112 repo = relationship('Repository')
1113
1114 @classmethod
1115 def get_users(cls, revision):
1116 """
1117 Returns user associated with this changesetComment. ie those
1118 who actually commented
1119
1120 :param cls:
1121 :param revision:
1122 """
1123 return Session.query(User)\
1124 .filter(cls.revision == revision)\
1125 .join(ChangesetComment.author).all()
1126
1127
1128 class Notification(Base, BaseModel):
1129 __tablename__ = 'notifications'
1130 __table_args__ = ({'extend_existing': True},)
1131
1132 TYPE_CHANGESET_COMMENT = u'cs_comment'
1133 TYPE_MESSAGE = u'message'
1134 TYPE_MENTION = u'mention'
1135 TYPE_REGISTRATION = u'registration'
1136
1137 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1138 subject = Column('subject', Unicode(512), nullable=True)
1139 body = Column('body', Unicode(50000), nullable=True)
1140 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1142 type_ = Column('type', Unicode(256))
1143
1144 created_by_user = relationship('User')
1145 notifications_to_users = relationship('UserNotification', lazy='joined',
1146 cascade="all, delete, delete-orphan")
1147
1148 @property
1149 def recipients(self):
1150 return [x.user for x in UserNotification.query()\
1151 .filter(UserNotification.notification == self).all()]
1152
1153 @classmethod
1154 def create(cls, created_by, subject, body, recipients, type_=None):
1155 if type_ is None:
1156 type_ = Notification.TYPE_MESSAGE
1157
1158 notification = cls()
1159 notification.created_by_user = created_by
1160 notification.subject = subject
1161 notification.body = body
1162 notification.type_ = type_
1163 notification.created_on = datetime.datetime.now()
1164
1165 for u in recipients:
1166 assoc = UserNotification()
1167 assoc.notification = notification
1168 u.notifications.append(assoc)
1169 Session.add(notification)
1170 return notification
1171
1172 @property
1173 def description(self):
1174 from rhodecode.model.notification import NotificationModel
1175 return NotificationModel().make_description(self)
1176
1177
1178 class UserNotification(Base, BaseModel):
1179 __tablename__ = 'user_to_notification'
1180 __table_args__ = (
1181 UniqueConstraint('user_id', 'notification_id'),
1182 {'extend_existing': True}
1183 )
1184 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1185 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1186 read = Column('read', Boolean, default=False)
1187 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1188
1189 user = relationship('User', lazy="joined")
1190 notification = relationship('Notification', lazy="joined",
1191 order_by=lambda: Notification.created_on.desc(),)
1192
1193 def mark_as_read(self):
1194 self.read = True
1195 Session.add(self)
1196
1197
1084 1198 class DbMigrateVersion(Base, BaseModel):
1085 1199 __tablename__ = 'db_migrate_version'
1086 __table_args__ = {'extend_existing':True}
1200 __table_args__ = {'extend_existing': True}
1087 1201 repository_id = Column('repository_id', String(250), primary_key=True)
1088 1202 repository_path = Column('repository_path', Text)
1089 1203 version = Column('version', Integer)
1090
@@ -36,28 +36,33 b' from rhodecode.config.routing import ADM'
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.db import User, UsersGroup, Group, Repository
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
42 40 from rhodecode import BACKENDS
43 41
44 42 log = logging.getLogger(__name__)
45 43
44
46 45 #this is needed to translate the messages using _() in validators
47 46 class State_obj(object):
48 47 _ = staticmethod(_)
49 48
49
50 50 #==============================================================================
51 51 # VALIDATORS
52 52 #==============================================================================
53 53 class ValidAuthToken(formencode.validators.FancyValidator):
54 messages = {'invalid_token':_('Token mismatch')}
54 messages = {'invalid_token': _('Token mismatch')}
55 55
56 56 def validate_python(self, value, state):
57 57
58 58 if value != authentication_token():
59 raise formencode.Invalid(self.message('invalid_token', state,
60 search_number=value), value, state)
59 raise formencode.Invalid(
60 self.message('invalid_token',
61 state, search_number=value),
62 value,
63 state
64 )
65
61 66
62 67 def ValidUsername(edit, old_data):
63 68 class _ValidUsername(formencode.validators.FancyValidator):
@@ -68,7 +73,7 b' def ValidUsername(edit, old_data):'
68 73 #check if user is unique
69 74 old_un = None
70 75 if edit:
71 old_un = UserModel().get(old_data.get('user_id')).username
76 old_un = User.get(old_data.get('user_id')).username
72 77
73 78 if old_un != value or not edit:
74 79 if User.get_by_username(value, case_insensitive=True):
@@ -76,11 +81,13 b' def ValidUsername(edit, old_data):'
76 81 'exists') , value, state)
77 82
78 83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 raise formencode.Invalid(_('Username may only contain '
80 'alphanumeric characters '
81 'underscores, periods or dashes '
82 'and must begin with alphanumeric '
83 'character'), value, state)
84 raise formencode.Invalid(
85 _('Username may only contain alphanumeric characters '
86 'underscores, periods or dashes and must begin with '
87 'alphanumeric character'),
88 value,
89 state
90 )
84 91
85 92 return _ValidUsername
86 93
@@ -102,16 +109,17 b' def ValidUsersGroup(edit, old_data):'
102 109 if UsersGroup.get_by_group_name(value, cache=False,
103 110 case_insensitive=True):
104 111 raise formencode.Invalid(_('This users group '
105 'already exists') , value,
112 'already exists'), value,
106 113 state)
107 114
108
109 115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 raise formencode.Invalid(_('Group name may only contain '
111 'alphanumeric characters '
112 'underscores, periods or dashes '
113 'and must begin with alphanumeric '
114 'character'), value, state)
116 raise formencode.Invalid(
117 _('RepoGroup name may only contain alphanumeric characters '
118 'underscores, periods or dashes and must begin with '
119 'alphanumeric character'),
120 value,
121 state
122 )
115 123
116 124 return _ValidUsersGroup
117 125
@@ -141,15 +149,14 b' def ValidReposGroup(edit, old_data):'
141 149
142 150 old_gname = None
143 151 if edit:
144 old_gname = Group.get(
145 old_data.get('group_id')).group_name
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
146 153
147 154 if old_gname != group_name or not edit:
148 155
149 156 # check group
150 gr = Group.query()\
151 .filter(Group.group_name == slug)\
152 .filter(Group.group_parent_id == group_parent_id)\
157 gr = RepoGroup.query()\
158 .filter(RepoGroup.group_name == slug)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
153 160 .scalar()
154 161
155 162 if gr:
@@ -173,58 +180,64 b' def ValidReposGroup(edit, old_data):'
173 180
174 181 return _ValidReposGroup
175 182
183
176 184 class ValidPassword(formencode.validators.FancyValidator):
177 185
178 186 def to_python(self, value, state):
179 187
180 if value:
188 if not value:
189 return
181 190
182 if value.get('password'):
183 try:
184 value['password'] = get_crypt_password(value['password'])
185 except UnicodeEncodeError:
186 e_dict = {'password':_('Invalid characters in password')}
187 raise formencode.Invalid('', value, state, error_dict=e_dict)
191 if value.get('password'):
192 try:
193 value['password'] = get_crypt_password(value['password'])
194 except UnicodeEncodeError:
195 e_dict = {'password': _('Invalid characters in password')}
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
188 197
189 if value.get('password_confirmation'):
190 try:
191 value['password_confirmation'] = \
192 get_crypt_password(value['password_confirmation'])
193 except UnicodeEncodeError:
194 e_dict = {'password_confirmation':_('Invalid characters in password')}
195 raise formencode.Invalid('', value, state, error_dict=e_dict)
198 if value.get('password_confirmation'):
199 try:
200 value['password_confirmation'] = \
201 get_crypt_password(value['password_confirmation'])
202 except UnicodeEncodeError:
203 e_dict = {
204 'password_confirmation': _('Invalid characters in password')
205 }
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
196 207
197 if value.get('new_password'):
198 try:
199 value['new_password'] = \
200 get_crypt_password(value['new_password'])
201 except UnicodeEncodeError:
202 e_dict = {'new_password':_('Invalid characters in password')}
203 raise formencode.Invalid('', value, state, error_dict=e_dict)
208 if value.get('new_password'):
209 try:
210 value['new_password'] = \
211 get_crypt_password(value['new_password'])
212 except UnicodeEncodeError:
213 e_dict = {'new_password': _('Invalid characters in password')}
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
204 215
205 return value
216 return value
217
206 218
207 219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
208 220
209 221 def validate_python(self, value, state):
210
222
211 223 pass_val = value.get('password') or value.get('new_password')
212 224 if pass_val != value['password_confirmation']:
213 225 e_dict = {'password_confirmation':
214 226 _('Passwords do not match')}
215 227 raise formencode.Invalid('', value, state, error_dict=e_dict)
216 228
229
217 230 class ValidAuth(formencode.validators.FancyValidator):
218 231 messages = {
219 232 'invalid_password':_('invalid password'),
220 233 'invalid_login':_('invalid user name'),
221 234 'disabled_account':_('Your account is disabled')
222 235 }
223
236
224 237 # error mapping
225 e_dict = {'username':messages['invalid_login'],
226 'password':messages['invalid_password']}
227 e_dict_disable = {'username':messages['disabled_account']}
238 e_dict = {'username': messages['invalid_login'],
239 'password': messages['invalid_password']}
240 e_dict_disable = {'username': messages['disabled_account']}
228 241
229 242 def validate_python(self, value, state):
230 243 password = value['password']
@@ -235,16 +248,21 b' class ValidAuth(formencode.validators.Fa'
235 248 return value
236 249 else:
237 250 if user and user.active is False:
238 log.warning('user %s is disabled', username)
239 raise formencode.Invalid(self.message('disabled_account',
240 state=State_obj),
241 value, state,
242 error_dict=self.e_dict_disable)
251 log.warning('user %s is disabled' % username)
252 raise formencode.Invalid(
253 self.message('disabled_account',
254 state=State_obj),
255 value, state,
256 error_dict=self.e_dict_disable
257 )
243 258 else:
244 log.warning('user %s not authenticated', username)
245 raise formencode.Invalid(self.message('invalid_password',
246 state=State_obj), value, state,
247 error_dict=self.e_dict)
259 log.warning('user %s failed to authenticate' % username)
260 raise formencode.Invalid(
261 self.message('invalid_password',
262 state=State_obj), value, state,
263 error_dict=self.e_dict
264 )
265
248 266
249 267 class ValidRepoUser(formencode.validators.FancyValidator):
250 268
@@ -257,6 +275,7 b' class ValidRepoUser(formencode.validator'
257 275 value, state)
258 276 return value
259 277
278
260 279 def ValidRepoName(edit, old_data):
261 280 class _ValidRepoName(formencode.validators.FancyValidator):
262 281 def to_python(self, value, state):
@@ -268,41 +287,41 b' def ValidRepoName(edit, old_data):'
268 287 e_dict = {'repo_name': _('This repository name is disallowed')}
269 288 raise formencode.Invalid('', value, state, error_dict=e_dict)
270 289
271
272 290 if value.get('repo_group'):
273 gr = Group.get(value.get('repo_group'))
291 gr = RepoGroup.get(value.get('repo_group'))
274 292 group_path = gr.full_path
275 293 # value needs to be aware of group name in order to check
276 294 # db key This is an actual just the name to store in the
277 295 # database
278 repo_name_full = group_path + Group.url_sep() + repo_name
279
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297
280 298 else:
281 299 group_path = ''
282 300 repo_name_full = repo_name
283 301
284
285 302 value['repo_name_full'] = repo_name_full
286 303 rename = old_data.get('repo_name') != repo_name_full
287 304 create = not edit
288 305 if rename or create:
289 306
290 307 if group_path != '':
291 if RepoModel().get_by_repo_name(repo_name_full,):
292 e_dict = {'repo_name':_('This repository already '
293 'exists in a group "%s"') %
294 gr.group_name}
308 if Repository.get_by_repo_name(repo_name_full):
309 e_dict = {
310 'repo_name': _('This repository already exists in '
311 'a group "%s"') % gr.group_name
312 }
295 313 raise formencode.Invalid('', value, state,
296 314 error_dict=e_dict)
297 elif Group.get_by_group_name(repo_name_full):
298 e_dict = {'repo_name':_('There is a group with this'
299 ' name already "%s"') %
300 repo_name_full}
315 elif RepoGroup.get_by_group_name(repo_name_full):
316 e_dict = {
317 'repo_name': _('There is a group with this name '
318 'already "%s"') % repo_name_full
319 }
301 320 raise formencode.Invalid('', value, state,
302 321 error_dict=e_dict)
303 322
304 elif RepoModel().get_by_repo_name(repo_name_full):
305 e_dict = {'repo_name':_('This repository '
323 elif Repository.get_by_repo_name(repo_name_full):
324 e_dict = {'repo_name': _('This repository '
306 325 'already exists')}
307 326 raise formencode.Invalid('', value, state,
308 327 error_dict=e_dict)
@@ -311,24 +330,9 b' def ValidRepoName(edit, old_data):'
311 330
312 331 return _ValidRepoName
313 332
314 def ValidForkName():
315 class _ValidForkName(formencode.validators.FancyValidator):
316 def to_python(self, value, state):
317 333
318 repo_name = value.get('fork_name')
319
320 slug = repo_name_slug(repo_name)
321 if slug in [ADMIN_PREFIX, '']:
322 e_dict = {'repo_name': _('This repository name is disallowed')}
323 raise formencode.Invalid('', value, state, error_dict=e_dict)
324
325 if RepoModel().get_by_repo_name(repo_name):
326 e_dict = {'fork_name':_('This repository '
327 'already exists')}
328 raise formencode.Invalid('', value, state,
329 error_dict=e_dict)
330 return value
331 return _ValidForkName
334 def ValidForkName(*args, **kwargs):
335 return ValidRepoName(*args, **kwargs)
332 336
333 337
334 338 def SlugifyName():
@@ -339,6 +343,7 b' def SlugifyName():'
339 343
340 344 return _SlugifyName
341 345
346
342 347 def ValidCloneUri():
343 348 from mercurial.httprepo import httprepository, httpsrepository
344 349 from rhodecode.lib.utils import make_ui
@@ -351,14 +356,14 b' def ValidCloneUri():'
351 356 elif value.startswith('https'):
352 357 try:
353 358 httpsrepository(make_ui('db'), value).capabilities
354 except Exception, e:
359 except Exception:
355 360 log.error(traceback.format_exc())
356 361 raise formencode.Invalid(_('invalid clone url'), value,
357 362 state)
358 363 elif value.startswith('http'):
359 364 try:
360 365 httprepository(make_ui('db'), value).capabilities
361 except Exception, e:
366 except Exception:
362 367 log.error(traceback.format_exc())
363 368 raise formencode.Invalid(_('invalid clone url'), value,
364 369 state)
@@ -370,6 +375,7 b' def ValidCloneUri():'
370 375
371 376 return _ValidCloneUri
372 377
378
373 379 def ValidForkType(old_data):
374 380 class _ValidForkType(formencode.validators.FancyValidator):
375 381
@@ -381,64 +387,77 b' def ValidForkType(old_data):'
381 387 return value
382 388 return _ValidForkType
383 389
384 class ValidPerms(formencode.validators.FancyValidator):
385 messages = {'perm_new_member_name':_('This username or users group name'
386 ' is not valid')}
390
391 def ValidPerms(type_='repo'):
392 if type_ == 'group':
393 EMPTY_PERM = 'group.none'
394 elif type_ == 'repo':
395 EMPTY_PERM = 'repository.none'
387 396
388 def to_python(self, value, state):
389 perms_update = []
390 perms_new = []
391 #build a list of permission to update and new permission to create
392 for k, v in value.items():
393 #means new added member to permissions
394 if k.startswith('perm_new_member'):
395 new_perm = value.get('perm_new_member', False)
396 new_member = value.get('perm_new_member_name', False)
397 new_type = value.get('perm_new_member_type')
397 class _ValidPerms(formencode.validators.FancyValidator):
398 messages = {
399 'perm_new_member_name':
400 _('This username or users group name is not valid')
401 }
402
403 def to_python(self, value, state):
404 perms_update = []
405 perms_new = []
406 # build a list of permission to update and new permission to create
407 for k, v in value.items():
408 # means new added member to permissions
409 if k.startswith('perm_new_member'):
410 new_perm = value.get('perm_new_member', False)
411 new_member = value.get('perm_new_member_name', False)
412 new_type = value.get('perm_new_member_type')
398 413
399 if new_member and new_perm:
400 if (new_member, new_perm, new_type) not in perms_new:
401 perms_new.append((new_member, new_perm, new_type))
402 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
403 member = k[7:]
404 t = {'u':'user',
405 'g':'users_group'}[k[0]]
406 if member == 'default':
407 if value['private']:
408 #set none for default when updating to private repo
409 v = 'repository.none'
410 perms_update.append((member, v, t))
414 if new_member and new_perm:
415 if (new_member, new_perm, new_type) not in perms_new:
416 perms_new.append((new_member, new_perm, new_type))
417 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
418 member = k[7:]
419 t = {'u': 'user',
420 'g': 'users_group'
421 }[k[0]]
422 if member == 'default':
423 if value.get('private'):
424 # set none for default when updating to private repo
425 v = EMPTY_PERM
426 perms_update.append((member, v, t))
411 427
412 value['perms_updates'] = perms_update
413 value['perms_new'] = perms_new
428 value['perms_updates'] = perms_update
429 value['perms_new'] = perms_new
414 430
415 #update permissions
416 for k, v, t in perms_new:
417 try:
418 if t is 'user':
419 self.user_db = User.query()\
420 .filter(User.active == True)\
421 .filter(User.username == k).one()
422 if t is 'users_group':
423 self.user_db = UsersGroup.query()\
424 .filter(UsersGroup.users_group_active == True)\
425 .filter(UsersGroup.users_group_name == k).one()
431 # update permissions
432 for k, v, t in perms_new:
433 try:
434 if t is 'user':
435 self.user_db = User.query()\
436 .filter(User.active == True)\
437 .filter(User.username == k).one()
438 if t is 'users_group':
439 self.user_db = UsersGroup.query()\
440 .filter(UsersGroup.users_group_active == True)\
441 .filter(UsersGroup.users_group_name == k).one()
426 442
427 except Exception:
428 msg = self.message('perm_new_member_name',
429 state=State_obj)
430 raise formencode.Invalid(msg, value, state,
431 error_dict={'perm_new_member_name':msg})
432 return value
443 except Exception:
444 msg = self.message('perm_new_member_name',
445 state=State_obj)
446 raise formencode.Invalid(
447 msg, value, state, error_dict={'perm_new_member_name': msg}
448 )
449 return value
450 return _ValidPerms
451
433 452
434 453 class ValidSettings(formencode.validators.FancyValidator):
435 454
436 455 def to_python(self, value, state):
437 #settings form can't edit user
438 if value.has_key('user'):
456 # settings form can't edit user
457 if 'user' in value:
439 458 del['value']['user']
459 return value
440 460
441 return value
442 461
443 462 class ValidPath(formencode.validators.FancyValidator):
444 463 def to_python(self, value, state):
@@ -446,33 +465,37 b' class ValidPath(formencode.validators.Fa'
446 465 if not os.path.isdir(value):
447 466 msg = _('This is not a valid path')
448 467 raise formencode.Invalid(msg, value, state,
449 error_dict={'paths_root_path':msg})
468 error_dict={'paths_root_path': msg})
450 469 return value
451 470
471
452 472 def UniqSystemEmail(old_data):
453 473 class _UniqSystemEmail(formencode.validators.FancyValidator):
454 474 def to_python(self, value, state):
455 475 value = value.lower()
456 if old_data.get('email') != value:
457 user = User.query().filter(User.email == value).scalar()
476 if old_data.get('email', '').lower() != value:
477 user = User.get_by_email(value, case_insensitive=True)
458 478 if user:
459 479 raise formencode.Invalid(
460 _("This e-mail address is already taken"),
461 value, state)
480 _("This e-mail address is already taken"), value, state
481 )
462 482 return value
463 483
464 484 return _UniqSystemEmail
465 485
486
466 487 class ValidSystemEmail(formencode.validators.FancyValidator):
467 488 def to_python(self, value, state):
468 489 value = value.lower()
469 user = User.query().filter(User.email == value).scalar()
490 user = User.get_by_email(value, case_insensitive=True)
470 491 if user is None:
471 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
472 value, state)
492 raise formencode.Invalid(
493 _("This e-mail address doesn't exist."), value, state
494 )
473 495
474 496 return value
475 497
498
476 499 class LdapLibValidator(formencode.validators.FancyValidator):
477 500
478 501 def to_python(self, value, state):
@@ -483,45 +506,50 b' class LdapLibValidator(formencode.valida'
483 506 raise LdapImportError
484 507 return value
485 508
509
486 510 class AttrLoginValidator(formencode.validators.FancyValidator):
487 511
488 512 def to_python(self, value, state):
489 513
490 514 if not value or not isinstance(value, (str, unicode)):
491 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
492 "must be specified - this is the name "
493 "of the attribute that is equivalent "
494 "to 'username'"),
495 value, state)
515 raise formencode.Invalid(
516 _("The LDAP Login attribute of the CN must be specified - "
517 "this is the name of the attribute that is equivalent "
518 "to 'username'"), value, state
519 )
496 520
497 521 return value
498 522
499 #===============================================================================
523
524 #==============================================================================
500 525 # FORMS
501 #===============================================================================
526 #==============================================================================
502 527 class LoginForm(formencode.Schema):
503 528 allow_extra_fields = True
504 529 filter_extra_fields = True
505 530 username = UnicodeString(
506 strip=True,
507 min=1,
508 not_empty=True,
509 messages={
510 'empty':_('Please enter a login'),
511 'tooShort':_('Enter a value %(min)i characters long or more')}
512 )
531 strip=True,
532 min=1,
533 not_empty=True,
534 messages={
535 'empty': _('Please enter a login'),
536 'tooShort': _('Enter a value %(min)i characters long or more')}
537 )
513 538
514 539 password = UnicodeString(
515 strip=True,
516 min=3,
517 not_empty=True,
518 messages={
519 'empty':_('Please enter a password'),
520 'tooShort':_('Enter %(min)i characters or more')}
521 )
540 strip=True,
541 min=3,
542 not_empty=True,
543 messages={
544 'empty': _('Please enter a password'),
545 'tooShort': _('Enter %(min)i characters or more')}
546 )
547
548 remember = StringBoolean(if_missing=False)
522 549
523 550 chained_validators = [ValidAuth]
524 551
552
525 553 def UserForm(edit=False, old_data={}):
526 554 class _UserForm(formencode.Schema):
527 555 allow_extra_fields = True
@@ -530,15 +558,17 b' def UserForm(edit=False, old_data={}):'
530 558 ValidUsername(edit, old_data))
531 559 if edit:
532 560 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
533 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
561 password_confirmation = All(UnicodeString(strip=True, min=6,
562 not_empty=False))
534 563 admin = StringBoolean(if_missing=False)
535 564 else:
536 565 password = All(UnicodeString(strip=True, min=6, not_empty=True))
537 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
538
566 password_confirmation = All(UnicodeString(strip=True, min=6,
567 not_empty=False))
568
539 569 active = StringBoolean(if_missing=False)
540 name = UnicodeString(strip=True, min=1, not_empty=True)
541 lastname = UnicodeString(strip=True, min=1, not_empty=True)
570 name = UnicodeString(strip=True, min=1, not_empty=False)
571 lastname = UnicodeString(strip=True, min=1, not_empty=False)
542 572 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
543 573
544 574 chained_validators = [ValidPasswordsMatch, ValidPassword]
@@ -563,10 +593,11 b' def UsersGroupForm(edit=False, old_data='
563 593
564 594 return _UsersGroupForm
565 595
596
566 597 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
567 598 class _ReposGroupForm(formencode.Schema):
568 599 allow_extra_fields = True
569 filter_extra_fields = True
600 filter_extra_fields = False
570 601
571 602 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
572 603 SlugifyName())
@@ -576,10 +607,11 b' def ReposGroupForm(edit=False, old_data='
576 607 testValueList=True,
577 608 if_missing=None, not_empty=False)
578 609
579 chained_validators = [ValidReposGroup(edit, old_data)]
610 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
580 611
581 612 return _ReposGroupForm
582 613
614
583 615 def RegisterForm(edit=False, old_data={}):
584 616 class _RegisterForm(formencode.Schema):
585 617 allow_extra_fields = True
@@ -589,14 +621,15 b' def RegisterForm(edit=False, old_data={}'
589 621 password = All(UnicodeString(strip=True, min=6, not_empty=True))
590 622 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
591 623 active = StringBoolean(if_missing=False)
592 name = UnicodeString(strip=True, min=1, not_empty=True)
593 lastname = UnicodeString(strip=True, min=1, not_empty=True)
624 name = UnicodeString(strip=True, min=1, not_empty=False)
625 lastname = UnicodeString(strip=True, min=1, not_empty=False)
594 626 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
595 627
596 628 chained_validators = [ValidPasswordsMatch, ValidPassword]
597 629
598 630 return _RegisterForm
599 631
632
600 633 def PasswordResetForm():
601 634 class _PasswordResetForm(formencode.Schema):
602 635 allow_extra_fields = True
@@ -604,6 +637,7 b' def PasswordResetForm():'
604 637 email = All(ValidSystemEmail(), Email(not_empty=True))
605 638 return _PasswordResetForm
606 639
640
607 641 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
608 642 repo_groups=[]):
609 643 class _RepoForm(formencode.Schema):
@@ -624,23 +658,29 b' def RepoForm(edit=False, old_data={}, su'
624 658 #this is repo owner
625 659 user = All(UnicodeString(not_empty=True), ValidRepoUser)
626 660
627 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
661 chained_validators = [ValidRepoName(edit, old_data), ValidPerms()]
628 662 return _RepoForm
629 663
630 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
664
665 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
666 repo_groups=[]):
631 667 class _RepoForkForm(formencode.Schema):
632 668 allow_extra_fields = True
633 669 filter_extra_fields = False
634 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
670 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
635 671 SlugifyName())
672 repo_group = OneOf(repo_groups, hideList=True)
673 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
636 674 description = UnicodeString(strip=True, min=1, not_empty=True)
637 675 private = StringBoolean(if_missing=False)
638 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
639
640 chained_validators = [ValidForkName()]
676 copy_permissions = StringBoolean(if_missing=False)
677 update_after_clone = StringBoolean(if_missing=False)
678 fork_parent_id = UnicodeString()
679 chained_validators = [ValidForkName(edit, old_data)]
641 680
642 681 return _RepoForkForm
643 682
683
644 684 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
645 685 repo_groups=[]):
646 686 class _RepoForm(formencode.Schema):
@@ -652,7 +692,7 b' def RepoSettingsForm(edit=False, old_dat'
652 692 repo_group = OneOf(repo_groups, hideList=True)
653 693 private = StringBoolean(if_missing=False)
654 694
655 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
695 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
656 696 ValidSettings]
657 697 return _RepoForm
658 698
@@ -667,6 +707,7 b' def ApplicationSettingsForm():'
667 707
668 708 return _ApplicationSettingsForm
669 709
710
670 711 def ApplicationUiSettingsForm():
671 712 class _ApplicationUiSettingsForm(formencode.Schema):
672 713 allow_extra_fields = True
@@ -680,6 +721,7 b' def ApplicationUiSettingsForm():'
680 721
681 722 return _ApplicationUiSettingsForm
682 723
724
683 725 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
684 726 class _DefaultPermissionsForm(formencode.Schema):
685 727 allow_extra_fields = True
@@ -3,7 +3,7 b' from sqlalchemy.ext.declarative import d'
3 3 from sqlalchemy.orm import scoped_session, sessionmaker
4 4 from beaker import cache
5 5
6 from rhodecode.model import caching_query
6 from rhodecode.lib import caching_query
7 7
8 8
9 9 # Beaker CacheManager. A home base for cache configurations.
@@ -15,7 +15,8 b' cache_manager = cache.CacheManager()'
15 15 #
16 16 Session = scoped_session(
17 17 sessionmaker(
18 query_cls=caching_query.query_callable(cache_manager)
18 query_cls=caching_query.query_callable(cache_manager),
19 expire_on_commit=True,
19 20 )
20 21 )
21 22
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Aug 20, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -28,19 +28,22 b' import traceback'
28 28
29 29 from sqlalchemy.exc import DatabaseError
30 30
31 from rhodecode.lib.caching_query import FromCache
32
31 33 from rhodecode.model import BaseModel
32 from rhodecode.model.db import User, Permission, UserToPerm, RepoToPerm
33 from rhodecode.model.caching_query import FromCache
34 from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm
34 35
35 36 log = logging.getLogger(__name__)
36 37
37 38
38 39 class PermissionModel(BaseModel):
39 """Permissions model for RhodeCode
40 """
41 Permissions model for RhodeCode
40 42 """
41 43
42 44 def get_permission(self, permission_id, cache=False):
43 """Get's permissions by id
45 """
46 Get's permissions by id
44 47
45 48 :param permission_id: id of permission to get from database
46 49 :param cache: use Cache for this query
@@ -52,7 +55,8 b' class PermissionModel(BaseModel):'
52 55 return perm.get(permission_id)
53 56
54 57 def get_permission_by_name(self, name, cache=False):
55 """Get's permissions by given name
58 """
59 Get's permissions by given name
56 60
57 61 :param name: name to fetch
58 62 :param cache: Use cache for this query
@@ -66,8 +70,8 b' class PermissionModel(BaseModel):'
66 70
67 71 def update(self, form_result):
68 72 perm_user = self.sa.query(User)\
69 .filter(User.username ==
70 form_result['perm_user_name']).scalar()
73 .filter(User.username ==
74 form_result['perm_user_name']).scalar()
71 75 u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
72 76 perm_user).all()
73 77 if len(u2p) != 3:
@@ -76,7 +80,7 b' class PermissionModel(BaseModel):'
76 80 ' your database' % len(u2p))
77 81
78 82 try:
79 #stage 1 change defaults
83 # stage 1 change defaults
80 84 for p in u2p:
81 85 if p.permission.permission_name.startswith('repository.'):
82 86 p.permission = self.get_permission_by_name(
@@ -95,19 +99,17 b' class PermissionModel(BaseModel):'
95 99
96 100 #stage 2 update all default permissions for repos if checked
97 101 if form_result['overwrite_default'] == True:
98 for r2p in self.sa.query(RepoToPerm)\
99 .filter(RepoToPerm.user == perm_user).all():
102 for r2p in self.sa.query(UserRepoToPerm)\
103 .filter(UserRepoToPerm.user == perm_user).all():
100 104 r2p.permission = self.get_permission_by_name(
101 105 form_result['default_perm'])
102 106 self.sa.add(r2p)
103 107
104 #stage 3 set anonymous access
108 # stage 3 set anonymous access
105 109 if perm_user.username == 'default':
106 110 perm_user.active = bool(form_result['anonymous'])
107 111 self.sa.add(perm_user)
108 112
109 self.sa.commit()
110 113 except (DatabaseError,):
111 114 log.error(traceback.format_exc())
112 self.sa.rollback()
113 115 raise
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Jun 5, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -28,24 +28,46 b' import logging'
28 28 import traceback
29 29 from datetime import datetime
30 30
31 from vcs.utils.lazy import LazyProperty
32 from vcs.backends import get_backend
31 from rhodecode.lib.vcs.backends import get_backend
33 32
33 from rhodecode.lib import LazyProperty
34 34 from rhodecode.lib import safe_str, safe_unicode
35 from rhodecode.lib.caching_query import FromCache
36 from rhodecode.lib.hooks import log_create_repository
35 37
36 38 from rhodecode.model import BaseModel
37 from rhodecode.model.caching_query import FromCache
38 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
41
40 42
41 43 log = logging.getLogger(__name__)
42 44
43 45
44 46 class RepoModel(BaseModel):
45 47
48 def __get_user(self, user):
49 return self._get_instance(User, user, callback=User.get_by_username)
50
51 def __get_users_group(self, users_group):
52 return self._get_instance(UsersGroup, users_group,
53 callback=UsersGroup.get_by_group_name)
54
55 def __get_repos_group(self, repos_group):
56 return self._get_instance(RepoGroup, repos_group,
57 callback=RepoGroup.get_by_group_name)
58
59 def __get_repo(self, repository):
60 return self._get_instance(Repository, repository,
61 callback=Repository.get_by_repo_name)
62
63 def __get_perm(self, permission):
64 return self._get_instance(Permission, permission,
65 callback=Permission.get_by_key)
66
46 67 @LazyProperty
47 68 def repos_path(self):
48 """Get's the repositories root path from database
69 """
70 Get's the repositories root path from database
49 71 """
50 72
51 73 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
@@ -60,6 +82,9 b' class RepoModel(BaseModel):'
60 82 "get_repo_%s" % repo_id))
61 83 return repo.scalar()
62 84
85 def get_repo(self, repository):
86 return self.__get_repo(repository)
87
63 88 def get_by_repo_name(self, repo_name, cache=False):
64 89 repo = self.sa.query(Repository)\
65 90 .filter(Repository.repo_name == repo_name)
@@ -69,7 +94,6 b' class RepoModel(BaseModel):'
69 94 "get_repo_%s" % repo_name))
70 95 return repo.scalar()
71 96
72
73 97 def get_users_js(self):
74 98
75 99 users = self.sa.query(User).filter(User.active == True).all()
@@ -93,9 +117,9 b' class RepoModel(BaseModel):'
93 117
94 118 def _get_defaults(self, repo_name):
95 119 """
96 Get's information about repository, and returns a dict for
120 Get's information about repository, and returns a dict for
97 121 usage in forms
98
122
99 123 :param repo_name:
100 124 """
101 125
@@ -130,7 +154,6 b' class RepoModel(BaseModel):'
130 154
131 155 return defaults
132 156
133
134 157 def update(self, repo_name, form_data):
135 158 try:
136 159 cur_repo = self.get_by_repo_name(repo_name, cache=False)
@@ -138,48 +161,24 b' class RepoModel(BaseModel):'
138 161 # update permissions
139 162 for member, perm, member_type in form_data['perms_updates']:
140 163 if member_type == 'user':
141 r2p = self.sa.query(RepoToPerm)\
142 .filter(RepoToPerm.user == User.get_by_username(member))\
143 .filter(RepoToPerm.repository == cur_repo)\
144 .one()
145
146 r2p.permission = self.sa.query(Permission)\
147 .filter(Permission.permission_name ==
148 perm).scalar()
149 self.sa.add(r2p)
164 # this updates existing one
165 RepoModel().grant_user_permission(
166 repo=cur_repo, user=member, perm=perm
167 )
150 168 else:
151 g2p = self.sa.query(UsersGroupRepoToPerm)\
152 .filter(UsersGroupRepoToPerm.users_group ==
153 UsersGroup.get_by_group_name(member))\
154 .filter(UsersGroupRepoToPerm.repository ==
155 cur_repo).one()
156
157 g2p.permission = self.sa.query(Permission)\
158 .filter(Permission.permission_name ==
159 perm).scalar()
160 self.sa.add(g2p)
161
169 RepoModel().grant_users_group_permission(
170 repo=cur_repo, group_name=member, perm=perm
171 )
162 172 # set new permissions
163 173 for member, perm, member_type in form_data['perms_new']:
164 174 if member_type == 'user':
165 r2p = RepoToPerm()
166 r2p.repository = cur_repo
167 r2p.user = User.get_by_username(member)
168
169 r2p.permission = self.sa.query(Permission)\
170 .filter(Permission.
171 permission_name == perm)\
172 .scalar()
173 self.sa.add(r2p)
175 RepoModel().grant_user_permission(
176 repo=cur_repo, user=member, perm=perm
177 )
174 178 else:
175 g2p = UsersGroupRepoToPerm()
176 g2p.repository = cur_repo
177 g2p.users_group = UsersGroup.get_by_group_name(member)
178 g2p.permission = self.sa.query(Permission)\
179 .filter(Permission.
180 permission_name == perm)\
181 .scalar()
182 self.sa.add(g2p)
179 RepoModel().grant_users_group_permission(
180 repo=cur_repo, group_name=member, perm=perm
181 )
183 182
184 183 # update current repo
185 184 for k, v in form_data.items():
@@ -188,7 +187,7 b' class RepoModel(BaseModel):'
188 187 elif k == 'repo_name':
189 188 pass
190 189 elif k == 'repo_group':
191 cur_repo.group = Group.get(v)
190 cur_repo.group = RepoGroup.get(v)
192 191
193 192 else:
194 193 setattr(cur_repo, k, v)
@@ -202,133 +201,220 b' class RepoModel(BaseModel):'
202 201 # rename repository
203 202 self.__rename_repo(old=repo_name, new=new_name)
204 203
205 self.sa.commit()
206 204 return cur_repo
207 205 except:
208 206 log.error(traceback.format_exc())
209 self.sa.rollback()
210 207 raise
211 208
212 209 def create(self, form_data, cur_user, just_db=False, fork=False):
210 from rhodecode.model.scm import ScmModel
213 211
214 212 try:
215 213 if fork:
216 repo_name = form_data['fork_name']
217 org_name = form_data['repo_name']
218 org_full_name = org_name
214 fork_parent_id = form_data['fork_parent_id']
219 215
220 else:
221 org_name = repo_name = form_data['repo_name']
222 repo_name_full = form_data['repo_name_full']
216 # repo name is just a name of repository
217 # while repo_name_full is a full qualified name that is combined
218 # with name and path of group
219 repo_name = form_data['repo_name']
220 repo_name_full = form_data['repo_name_full']
223 221
224 222 new_repo = Repository()
225 223 new_repo.enable_statistics = False
224
226 225 for k, v in form_data.items():
227 226 if k == 'repo_name':
228 if fork:
229 v = repo_name
230 else:
231 v = repo_name_full
227 v = repo_name_full
232 228 if k == 'repo_group':
233 229 k = 'group_id'
234
235 230 if k == 'description':
236 v = safe_unicode(v) or repo_name
231 v = v or repo_name
237 232
238 233 setattr(new_repo, k, v)
239 234
240 235 if fork:
241 parent_repo = self.sa.query(Repository)\
242 .filter(Repository.repo_name == org_full_name).one()
236 parent_repo = Repository.get(fork_parent_id)
243 237 new_repo.fork = parent_repo
244 238
245 239 new_repo.user_id = cur_user.user_id
246 240 self.sa.add(new_repo)
247 241
248 #create default permission
249 repo_to_perm = RepoToPerm()
250 default = 'repository.read'
251 for p in User.get_by_username('default').user_perms:
252 if p.permission.permission_name.startswith('repository.'):
253 default = p.permission.permission_name
254 break
242 def _create_default_perms():
243 # create default permission
244 repo_to_perm = UserRepoToPerm()
245 default = 'repository.read'
246 for p in User.get_by_username('default').user_perms:
247 if p.permission.permission_name.startswith('repository.'):
248 default = p.permission.permission_name
249 break
250
251 default_perm = 'repository.none' if form_data['private'] else default
252
253 repo_to_perm.permission_id = self.sa.query(Permission)\
254 .filter(Permission.permission_name == default_perm)\
255 .one().permission_id
256
257 repo_to_perm.repository = new_repo
258 repo_to_perm.user_id = User.get_by_username('default').user_id
259
260 self.sa.add(repo_to_perm)
255 261
256 default_perm = 'repository.none' if form_data['private'] else default
262 if fork:
263 if form_data.get('copy_permissions'):
264 repo = Repository.get(fork_parent_id)
265 user_perms = UserRepoToPerm.query()\
266 .filter(UserRepoToPerm.repository == repo).all()
267 group_perms = UsersGroupRepoToPerm.query()\
268 .filter(UsersGroupRepoToPerm.repository == repo).all()
257 269
258 repo_to_perm.permission_id = self.sa.query(Permission)\
259 .filter(Permission.permission_name == default_perm)\
260 .one().permission_id
270 for perm in user_perms:
271 UserRepoToPerm.create(perm.user, new_repo,
272 perm.permission)
261 273
262 repo_to_perm.repository = new_repo
263 repo_to_perm.user_id = User.get_by_username('default').user_id
264
265 self.sa.add(repo_to_perm)
274 for perm in group_perms:
275 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
276 perm.permission)
277 else:
278 _create_default_perms()
279 else:
280 _create_default_perms()
266 281
267 282 if not just_db:
268 283 self.__create_repo(repo_name, form_data['repo_type'],
269 284 form_data['repo_group'],
270 285 form_data['clone_uri'])
271 286
272 self.sa.commit()
273
274 #now automatically start following this repository as owner
275 from rhodecode.model.scm import ScmModel
287 # now automatically start following this repository as owner
276 288 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
277 cur_user.user_id)
289 cur_user.user_id)
290 log_create_repository(new_repo.get_dict(),
291 created_by=cur_user.username)
278 292 return new_repo
279 293 except:
280 294 log.error(traceback.format_exc())
281 self.sa.rollback()
282 295 raise
283 296
284 297 def create_fork(self, form_data, cur_user):
298 """
299 Simple wrapper into executing celery task for fork creation
300
301 :param form_data:
302 :param cur_user:
303 """
285 304 from rhodecode.lib.celerylib import tasks, run_task
286 305 run_task(tasks.create_repo_fork, form_data, cur_user)
287 306
288 307 def delete(self, repo):
308 repo = self.__get_repo(repo)
289 309 try:
290 310 self.sa.delete(repo)
291 311 self.__delete_repo(repo)
292 self.sa.commit()
293 312 except:
294 313 log.error(traceback.format_exc())
295 self.sa.rollback()
296 raise
297
298 def delete_perm_user(self, form_data, repo_name):
299 try:
300 self.sa.query(RepoToPerm)\
301 .filter(RepoToPerm.repository \
302 == self.get_by_repo_name(repo_name))\
303 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
304 self.sa.commit()
305 except:
306 log.error(traceback.format_exc())
307 self.sa.rollback()
308 314 raise
309 315
310 def delete_perm_users_group(self, form_data, repo_name):
316 def grant_user_permission(self, repo, user, perm):
317 """
318 Grant permission for user on given repository, or update existing one
319 if found
320
321 :param repo: Instance of Repository, repository_id, or repository name
322 :param user: Instance of User, user_id or username
323 :param perm: Instance of Permission, or permission_name
324 """
325 user = self.__get_user(user)
326 repo = self.__get_repo(repo)
327 permission = self.__get_perm(perm)
328
329 # check if we have that permission already
330 obj = self.sa.query(UserRepoToPerm)\
331 .filter(UserRepoToPerm.user == user)\
332 .filter(UserRepoToPerm.repository == repo)\
333 .scalar()
334 if obj is None:
335 # create new !
336 obj = UserRepoToPerm()
337 obj.repository = repo
338 obj.user = user
339 obj.permission = permission
340 self.sa.add(obj)
341
342 def revoke_user_permission(self, repo, user):
343 """
344 Revoke permission for user on given repository
345
346 :param repo: Instance of Repository, repository_id, or repository name
347 :param user: Instance of User, user_id or username
348 """
349 user = self.__get_user(user)
350 repo = self.__get_repo(repo)
351
352 obj = self.sa.query(UserRepoToPerm)\
353 .filter(UserRepoToPerm.repository == repo)\
354 .filter(UserRepoToPerm.user == user)\
355 .one()
356 self.sa.delete(obj)
357
358 def grant_users_group_permission(self, repo, group_name, perm):
359 """
360 Grant permission for users group on given repository, or update
361 existing one if found
362
363 :param repo: Instance of Repository, repository_id, or repository name
364 :param group_name: Instance of UserGroup, users_group_id,
365 or users group name
366 :param perm: Instance of Permission, or permission_name
367 """
368 repo = self.__get_repo(repo)
369 group_name = self.__get_users_group(group_name)
370 permission = self.__get_perm(perm)
371
372 # check if we have that permission already
373 obj = self.sa.query(UsersGroupRepoToPerm)\
374 .filter(UsersGroupRepoToPerm.users_group == group_name)\
375 .filter(UsersGroupRepoToPerm.repository == repo)\
376 .scalar()
377
378 if obj is None:
379 # create new
380 obj = UsersGroupRepoToPerm()
381
382 obj.repository = repo
383 obj.users_group = group_name
384 obj.permission = permission
385 self.sa.add(obj)
386
387 def revoke_users_group_permission(self, repo, group_name):
388 """
389 Revoke permission for users group on given repository
390
391 :param repo: Instance of Repository, repository_id, or repository name
392 :param group_name: Instance of UserGroup, users_group_id,
393 or users group name
394 """
395 repo = self.__get_repo(repo)
396 group_name = self.__get_users_group(group_name)
397
398 obj = self.sa.query(UsersGroupRepoToPerm)\
399 .filter(UsersGroupRepoToPerm.repository == repo)\
400 .filter(UsersGroupRepoToPerm.users_group == group_name)\
401 .one()
402 self.sa.delete(obj)
403
404 def delete_stats(self, repo_name):
405 """
406 removes stats for given repo
407
408 :param repo_name:
409 """
311 410 try:
312 self.sa.query(UsersGroupRepoToPerm)\
313 .filter(UsersGroupRepoToPerm.repository \
314 == self.get_by_repo_name(repo_name))\
315 .filter(UsersGroupRepoToPerm.users_group_id \
316 == form_data['users_group_id']).delete()
317 self.sa.commit()
411 obj = self.sa.query(Statistics)\
412 .filter(Statistics.repository ==
413 self.get_by_repo_name(repo_name))\
414 .one()
415 self.sa.delete(obj)
318 416 except:
319 417 log.error(traceback.format_exc())
320 self.sa.rollback()
321 raise
322
323 def delete_stats(self, repo_name):
324 try:
325 self.sa.query(Statistics)\
326 .filter(Statistics.repository == \
327 self.get_by_repo_name(repo_name)).delete()
328 self.sa.commit()
329 except:
330 log.error(traceback.format_exc())
331 self.sa.rollback()
332 418 raise
333 419
334 420 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
@@ -345,15 +431,16 b' class RepoModel(BaseModel):'
345 431 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
346 432
347 433 if new_parent_id:
348 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
434 paths = RepoGroup.get(new_parent_id)\
435 .full_path.split(RepoGroup.url_sep())
349 436 new_parent_path = os.sep.join(paths)
350 437 else:
351 438 new_parent_path = ''
352 439
353 repo_path = os.path.join(*map(lambda x:safe_str(x),
440 # we need to make it str for mercurial
441 repo_path = os.path.join(*map(lambda x: safe_str(x),
354 442 [self.repos_path, new_parent_path, repo_name]))
355 443
356
357 444 # check if this path is not a repository
358 445 if is_valid_repo(repo_path, self.repos_path):
359 446 raise Exception('This path %s is a valid repository' % repo_path)
@@ -362,13 +449,14 b' class RepoModel(BaseModel):'
362 449 if is_valid_repos_group(repo_path, self.repos_path):
363 450 raise Exception('This path %s is a valid group' % repo_path)
364 451
365 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
366 clone_uri)
452 log.info('creating repo %s in %s @ %s' % (
453 repo_name, safe_unicode(repo_path), clone_uri
454 )
455 )
367 456 backend = get_backend(alias)
368 457
369 458 backend(repo_path, create=True, src_url=clone_uri)
370 459
371
372 460 def __rename_repo(self, old, new):
373 461 """
374 462 renames repository on filesystem
@@ -376,13 +464,14 b' class RepoModel(BaseModel):'
376 464 :param old: old name
377 465 :param new: new name
378 466 """
379 log.info('renaming repo from %s to %s', old, new)
467 log.info('renaming repo from %s to %s' % (old, new))
380 468
381 469 old_path = os.path.join(self.repos_path, old)
382 470 new_path = os.path.join(self.repos_path, new)
383 471 if os.path.isdir(new_path):
384 raise Exception('Was trying to rename to already existing dir %s' \
385 % new_path)
472 raise Exception(
473 'Was trying to rename to already existing dir %s' % new_path
474 )
386 475 shutil.move(old_path, new_path)
387 476
388 477 def __delete_repo(self, repo):
@@ -395,14 +484,12 b' class RepoModel(BaseModel):'
395 484 :param repo: repo object
396 485 """
397 486 rm_path = os.path.join(self.repos_path, repo.repo_name)
398 log.info("Removing %s", rm_path)
399 #disable hg/git
487 log.info("Removing %s" % (rm_path))
488 # disable hg/git
400 489 alias = repo.repo_type
401 490 shutil.move(os.path.join(rm_path, '.%s' % alias),
402 491 os.path.join(rm_path, 'rm__.%s' % alias))
403 #disable repo
404 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
405 % (datetime.today()\
406 .strftime('%Y%m%d_%H%M%S_%f'),
407 repo.repo_name)))
408
492 # disable repo
493 _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'),
494 repo.repo_name)
495 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -8,7 +8,7 b''
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot, marcink
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
@@ -25,18 +25,33 b''
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import logging
28 from rhodecode.model.db import BaseModel, RepoToPerm, Permission,\
29 UsersGroupRepoToPerm
30 from rhodecode.model.meta import Session
28 from rhodecode.model import BaseModel
29 from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission,\
30 User, Repository
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class RepositoryPermissionModel(BaseModel):
36
37 def __get_user(self, user):
38 return self._get_instance(User, user, callback=User.get_by_username)
39
40 def __get_repo(self, repository):
41 return self._get_instance(Repository, repository,
42 callback=Repository.get_by_repo_name)
43
44 def __get_perm(self, permission):
45 return self._get_instance(Permission, permission,
46 callback=Permission.get_by_key)
47
36 48 def get_user_permission(self, repository, user):
37 return RepoToPerm.query() \
38 .filter(RepoToPerm.user == user) \
39 .filter(RepoToPerm.repository == repository) \
49 repository = self.__get_repo(repository)
50 user = self.__get_user(user)
51
52 return UserRepoToPerm.query() \
53 .filter(UserRepoToPerm.user == user) \
54 .filter(UserRepoToPerm.repository == repository) \
40 55 .scalar()
41 56
42 57 def update_user_permission(self, repository, user, permission):
@@ -46,18 +61,16 b' class RepositoryPermissionModel(BaseMode'
46 61 if not current.permission is permission:
47 62 current.permission = permission
48 63 else:
49 p = RepoToPerm()
64 p = UserRepoToPerm()
50 65 p.user = user
51 66 p.repository = repository
52 67 p.permission = permission
53 Session.add(p)
54 Session.commit()
68 self.sa.add(p)
55 69
56 70 def delete_user_permission(self, repository, user):
57 71 current = self.get_user_permission(repository, user)
58 72 if current:
59 Session.delete(current)
60 Session.commit()
73 self.sa.delete(current)
61 74
62 75 def get_users_group_permission(self, repository, users_group):
63 76 return UsersGroupRepoToPerm.query() \
@@ -78,13 +91,11 b' class RepositoryPermissionModel(BaseMode'
78 91 p.repository = repository
79 92 p.permission = permission
80 93 self.sa.add(p)
81 Session.commit()
82 94
83 95 def delete_users_group_permission(self, repository, users_group):
84 96 current = self.get_users_group_permission(repository, users_group)
85 97 if current:
86 98 self.sa.delete(current)
87 Session.commit()
88 99
89 100 def update_or_delete_user_permission(self, repository, user, permission):
90 101 if permission:
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Jan 25, 2011
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -28,19 +28,32 b' import logging'
28 28 import traceback
29 29 import shutil
30 30
31 from pylons.i18n.translation import _
32
33 from vcs.utils.lazy import LazyProperty
31 from rhodecode.lib import LazyProperty
34 32
35 33 from rhodecode.model import BaseModel
36 from rhodecode.model.caching_query import FromCache
37 from rhodecode.model.db import Group, RhodeCodeUi
34 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
35 User, Permission, UsersGroupRepoGroupToPerm, UsersGroup
38 36
39 37 log = logging.getLogger(__name__)
40 38
41 39
42 40 class ReposGroupModel(BaseModel):
43 41
42 def __get_user(self, user):
43 return self._get_instance(User, user, callback=User.get_by_username)
44
45 def __get_users_group(self, users_group):
46 return self._get_instance(UsersGroup, users_group,
47 callback=UsersGroup.get_by_group_name)
48
49 def __get_repos_group(self, repos_group):
50 return self._get_instance(RepoGroup, repos_group,
51 callback=RepoGroup.get_by_group_name)
52
53 def __get_perm(self, permission):
54 return self._get_instance(Permission, permission,
55 callback=Permission.get_by_key)
56
44 57 @LazyProperty
45 58 def repos_path(self):
46 59 """
@@ -50,6 +63,24 b' class ReposGroupModel(BaseModel):'
50 63 q = RhodeCodeUi.get_by_key('/').one()
51 64 return q.ui_value
52 65
66 def _create_default_perms(self, new_group):
67 # create default permission
68 repo_group_to_perm = UserRepoGroupToPerm()
69 default_perm = 'group.read'
70 for p in User.get_by_username('default').user_perms:
71 if p.permission.permission_name.startswith('group.'):
72 default_perm = p.permission.permission_name
73 break
74
75 repo_group_to_perm.permission_id = self.sa.query(Permission)\
76 .filter(Permission.permission_name == default_perm)\
77 .one().permission_id
78
79 repo_group_to_perm.group = new_group
80 repo_group_to_perm.user_id = User.get_by_username('default').user_id
81
82 self.sa.add(repo_group_to_perm)
83
53 84 def __create_group(self, group_name):
54 85 """
55 86 makes repositories group on filesystem
@@ -59,7 +90,7 b' class ReposGroupModel(BaseModel):'
59 90 """
60 91
61 92 create_path = os.path.join(self.repos_path, group_name)
62 log.debug('creating new group in %s', create_path)
93 log.debug('creating new group in %s' % create_path)
63 94
64 95 if os.path.isdir(create_path):
65 96 raise Exception('That directory already exists !')
@@ -69,7 +100,7 b' class ReposGroupModel(BaseModel):'
69 100 def __rename_group(self, old, new):
70 101 """
71 102 Renames a group on filesystem
72
103
73 104 :param group_name:
74 105 """
75 106
@@ -77,13 +108,12 b' class ReposGroupModel(BaseModel):'
77 108 log.debug('skipping group rename')
78 109 return
79 110
80 log.debug('renaming repos group from %s to %s', old, new)
81
111 log.debug('renaming repos group from %s to %s' % (old, new))
82 112
83 113 old_path = os.path.join(self.repos_path, old)
84 114 new_path = os.path.join(self.repos_path, new)
85 115
86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
116 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
87 117
88 118 if os.path.isdir(new_path):
89 119 raise Exception('Was trying to rename to already '
@@ -93,10 +123,10 b' class ReposGroupModel(BaseModel):'
93 123 def __delete_group(self, group):
94 124 """
95 125 Deletes a group from a filesystem
96
126
97 127 :param group: instance of group from database
98 128 """
99 paths = group.full_path.split(Group.url_sep())
129 paths = group.full_path.split(RepoGroup.url_sep())
100 130 paths = os.sep.join(paths)
101 131
102 132 rm_path = os.path.join(self.repos_path, paths)
@@ -104,33 +134,59 b' class ReposGroupModel(BaseModel):'
104 134 # delete only if that path really exists
105 135 os.rmdir(rm_path)
106 136
107 def create(self, form_data):
137 def create(self, group_name, group_description, parent, just_db=False):
108 138 try:
109 new_repos_group = Group()
110 new_repos_group.group_description = form_data['group_description']
111 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
112 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
139 new_repos_group = RepoGroup()
140 new_repos_group.group_description = group_description
141 new_repos_group.parent_group = self.__get_repos_group(parent)
142 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
113 143
114 144 self.sa.add(new_repos_group)
145 self._create_default_perms(new_repos_group)
115 146
116 self.__create_group(new_repos_group.group_name)
147 if not just_db:
148 # we need to flush here, in order to check if database won't
149 # throw any exceptions, create filesystem dirs at the very end
150 self.sa.flush()
151 self.__create_group(new_repos_group.group_name)
117 152
118 self.sa.commit()
119 153 return new_repos_group
120 154 except:
121 155 log.error(traceback.format_exc())
122 self.sa.rollback()
123 156 raise
124 157
125 158 def update(self, repos_group_id, form_data):
126 159
127 160 try:
128 repos_group = Group.get(repos_group_id)
161 repos_group = RepoGroup.get(repos_group_id)
162
163 # update permissions
164 for member, perm, member_type in form_data['perms_updates']:
165 if member_type == 'user':
166 # this updates also current one if found
167 ReposGroupModel().grant_user_permission(
168 repos_group=repos_group, user=member, perm=perm
169 )
170 else:
171 ReposGroupModel().grant_users_group_permission(
172 repos_group=repos_group, group_name=member, perm=perm
173 )
174 # set new permissions
175 for member, perm, member_type in form_data['perms_new']:
176 if member_type == 'user':
177 ReposGroupModel().grant_user_permission(
178 repos_group=repos_group, user=member, perm=perm
179 )
180 else:
181 ReposGroupModel().grant_users_group_permission(
182 repos_group=repos_group, group_name=member, perm=perm
183 )
184
129 185 old_path = repos_group.full_path
130
186
131 187 # change properties
132 188 repos_group.group_description = form_data['group_description']
133 repos_group.parent_group = Group.get(form_data['group_parent_id'])
189 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
134 190 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
135 191
136 192 new_path = repos_group.full_path
@@ -139,26 +195,116 b' class ReposGroupModel(BaseModel):'
139 195
140 196 self.__rename_group(old_path, new_path)
141 197
142 # we need to get all repositories from this new group and
198 # we need to get all repositories from this new group and
143 199 # rename them accordingly to new group path
144 200 for r in repos_group.repositories:
145 201 r.repo_name = r.get_new_name(r.just_name)
146 202 self.sa.add(r)
147 203
148 self.sa.commit()
149 204 return repos_group
150 205 except:
151 206 log.error(traceback.format_exc())
152 self.sa.rollback()
153 207 raise
154 208
155 209 def delete(self, users_group_id):
156 210 try:
157 users_group = Group.get(users_group_id)
211 users_group = RepoGroup.get(users_group_id)
158 212 self.sa.delete(users_group)
159 213 self.__delete_group(users_group)
160 self.sa.commit()
161 214 except:
162 215 log.error(traceback.format_exc())
163 self.sa.rollback()
164 216 raise
217
218 def grant_user_permission(self, repos_group, user, perm):
219 """
220 Grant permission for user on given repositories group, or update
221 existing one if found
222
223 :param repos_group: Instance of ReposGroup, repositories_group_id,
224 or repositories_group name
225 :param user: Instance of User, user_id or username
226 :param perm: Instance of Permission, or permission_name
227 """
228
229 repos_group = self.__get_repos_group(repos_group)
230 user = self.__get_user(user)
231 permission = self.__get_perm(perm)
232
233 # check if we have that permission already
234 obj = self.sa.query(UserRepoGroupToPerm)\
235 .filter(UserRepoGroupToPerm.user == user)\
236 .filter(UserRepoGroupToPerm.group == repos_group)\
237 .scalar()
238 if obj is None:
239 # create new !
240 obj = UserRepoGroupToPerm()
241 obj.group = repos_group
242 obj.user = user
243 obj.permission = permission
244 self.sa.add(obj)
245
246 def revoke_user_permission(self, repos_group, user):
247 """
248 Revoke permission for user on given repositories group
249
250 :param repos_group: Instance of ReposGroup, repositories_group_id,
251 or repositories_group name
252 :param user: Instance of User, user_id or username
253 """
254
255 repos_group = self.__get_repos_group(repos_group)
256 user = self.__get_user(user)
257
258 obj = self.sa.query(UserRepoGroupToPerm)\
259 .filter(UserRepoGroupToPerm.user == user)\
260 .filter(UserRepoGroupToPerm.group == repos_group)\
261 .one()
262 self.sa.delete(obj)
263
264 def grant_users_group_permission(self, repos_group, group_name, perm):
265 """
266 Grant permission for users group on given repositories group, or update
267 existing one if found
268
269 :param repos_group: Instance of ReposGroup, repositories_group_id,
270 or repositories_group name
271 :param group_name: Instance of UserGroup, users_group_id,
272 or users group name
273 :param perm: Instance of Permission, or permission_name
274 """
275 repos_group = self.__get_repos_group(repos_group)
276 group_name = self.__get_users_group(group_name)
277 permission = self.__get_perm(perm)
278
279 # check if we have that permission already
280 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
281 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
282 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
283 .scalar()
284
285 if obj is None:
286 # create new
287 obj = UsersGroupRepoGroupToPerm()
288
289 obj.group = repos_group
290 obj.users_group = group_name
291 obj.permission = permission
292 self.sa.add(obj)
293
294 def revoke_users_group_permission(self, repos_group, group_name):
295 """
296 Revoke permission for users group on given repositories group
297
298 :param repos_group: Instance of ReposGroup, repositories_group_id,
299 or repositories_group name
300 :param group_name: Instance of UserGroup, users_group_id,
301 or users group name
302 """
303 repos_group = self.__get_repos_group(repos_group)
304 group_name = self.__get_users_group(group_name)
305
306 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
307 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
308 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
309 .one()
310 self.sa.delete(obj)
@@ -28,22 +28,20 b' import traceback'
28 28 import logging
29 29 import cStringIO
30 30
31 from sqlalchemy.exc import DatabaseError
32
33 from vcs import get_backend
34 from vcs.exceptions import RepositoryError
35 from vcs.utils.lazy import LazyProperty
36 from vcs.nodes import FileNode
31 from rhodecode.lib.vcs import get_backend
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.lib.vcs.utils.lazy import LazyProperty
34 from rhodecode.lib.vcs.nodes import FileNode
37 35
38 36 from rhodecode import BACKENDS
39 37 from rhodecode.lib import helpers as h
40 38 from rhodecode.lib import safe_str
41 from rhodecode.lib.auth import HasRepoPermissionAny
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
42 40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
43 41 action_logger, EmptyChangeset
44 42 from rhodecode.model import BaseModel
45 43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 UserFollowing, UserLog, User
44 UserFollowing, UserLog, User, RepoGroup
47 45
48 46 log = logging.getLogger(__name__)
49 47
@@ -63,6 +61,7 b' class RepoTemp(object):'
63 61 def __repr__(self):
64 62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
65 63
64
66 65 class CachedRepoList(object):
67 66
68 67 def __init__(self, db_repo_list, repos_path, order_by=None):
@@ -79,19 +78,18 b' class CachedRepoList(object):'
79 78
80 79 def __iter__(self):
81 80 for dbr in self.db_repo_list:
82
83 81 scmr = dbr.scm_instance_cached
84
85 82 # check permission at this level
86 if not HasRepoPermissionAny('repository.read', 'repository.write',
87 'repository.admin')(dbr.repo_name,
88 'get repo check'):
83 if not HasRepoPermissionAny(
84 'repository.read', 'repository.write', 'repository.admin'
85 )(dbr.repo_name, 'get repo check'):
89 86 continue
90 87
91 88 if scmr is None:
92 log.error('%s this repository is present in database but it '
93 'cannot be created as an scm instance',
94 dbr.repo_name)
89 log.error(
90 '%s this repository is present in database but it '
91 'cannot be created as an scm instance' % dbr.repo_name
92 )
95 93 continue
96 94
97 95 last_change = scmr.last_change
@@ -103,8 +101,7 b' class CachedRepoList(object):'
103 101 tmp_d['description'] = dbr.description
104 102 tmp_d['description_sort'] = tmp_d['description']
105 103 tmp_d['last_change'] = last_change
106 tmp_d['last_change_sort'] = time.mktime(last_change \
107 .timetuple())
104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
108 105 tmp_d['tip'] = tip.raw_id
109 106 tmp_d['tip_sort'] = tip.revision
110 107 tmp_d['rev'] = tip.revision
@@ -115,17 +112,53 b' class CachedRepoList(object):'
115 112 tmp_d['last_msg'] = tip.message
116 113 tmp_d['author'] = tip.author
117 114 tmp_d['dbrepo'] = dbr.get_dict()
118 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
119 else {}
115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
120 116 yield tmp_d
121 117
118
119 class GroupList(object):
120
121 def __init__(self, db_repo_group_list):
122 self.db_repo_group_list = db_repo_group_list
123
124 def __len__(self):
125 return len(self.db_repo_group_list)
126
127 def __repr__(self):
128 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
129
130 def __iter__(self):
131 for dbgr in self.db_repo_group_list:
132 # check permission at this level
133 if not HasReposGroupPermissionAny(
134 'group.read', 'group.write', 'group.admin'
135 )(dbgr.group_name, 'get group repo check'):
136 continue
137
138 yield dbgr
139
140
122 141 class ScmModel(BaseModel):
123 """Generic Scm Model
142 """
143 Generic Scm Model
124 144 """
125 145
146 def __get_repo(self, instance):
147 cls = Repository
148 if isinstance(instance, cls):
149 return instance
150 elif isinstance(instance, int) or str(instance).isdigit():
151 return cls.get(instance)
152 elif isinstance(instance, basestring):
153 return cls.get_by_repo_name(instance)
154 elif instance:
155 raise Exception('given object must be int, basestr or Instance'
156 ' of %s got %s' % (type(cls), type(instance)))
157
126 158 @LazyProperty
127 159 def repos_path(self):
128 """Get's the repositories root path from database
160 """
161 Get's the repositories root path from database
129 162 """
130 163
131 164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
@@ -133,7 +166,8 b' class ScmModel(BaseModel):'
133 166 return q.ui_value
134 167
135 168 def repo_scan(self, repos_path=None):
136 """Listing of repositories in given path. This path should not be a
169 """
170 Listing of repositories in given path. This path should not be a
137 171 repository itself. Return a dictionary of repository objects
138 172
139 173 :param repos_path: path to directory containing repositories
@@ -142,19 +176,19 b' class ScmModel(BaseModel):'
142 176 if repos_path is None:
143 177 repos_path = self.repos_path
144 178
145 log.info('scanning for repositories in %s', repos_path)
179 log.info('scanning for repositories in %s' % repos_path)
146 180
147 181 baseui = make_ui('db')
148 repos_list = {}
182 repos = {}
149 183
150 184 for name, path in get_filesystem_repos(repos_path, recursive=True):
151
185
152 186 # name need to be decomposed and put back together using the /
153 187 # since this is internal storage separator for rhodecode
154 188 name = Repository.url_sep().join(name.split(os.sep))
155
189
156 190 try:
157 if name in repos_list:
191 if name in repos:
158 192 raise RepositoryError('Duplicate repository name %s '
159 193 'found in %s' % (name, path))
160 194 else:
@@ -162,17 +196,14 b' class ScmModel(BaseModel):'
162 196 klass = get_backend(path[0])
163 197
164 198 if path[0] == 'hg' and path[0] in BACKENDS.keys():
165
166 # for mercurial we need to have an str path
167 repos_list[name] = klass(safe_str(path[1]),
168 baseui=baseui)
199 repos[name] = klass(safe_str(path[1]), baseui=baseui)
169 200
170 201 if path[0] == 'git' and path[0] in BACKENDS.keys():
171 repos_list[name] = klass(path[1])
202 repos[name] = klass(path[1])
172 203 except OSError:
173 204 continue
174 205
175 return repos_list
206 return repos
176 207
177 208 def get_repos(self, all_repos=None, sort_key=None):
178 209 """
@@ -192,30 +223,22 b' class ScmModel(BaseModel):'
192 223
193 224 return repo_iter
194 225
226 def get_repos_groups(self, all_groups=None):
227 if all_groups is None:
228 all_groups = RepoGroup.query()\
229 .filter(RepoGroup.group_parent_id == None).all()
230 group_iter = GroupList(all_groups)
231
232 return group_iter
233
195 234 def mark_for_invalidation(self, repo_name):
196 235 """Puts cache invalidation task into db for
197 236 further global cache invalidation
198 237
199 238 :param repo_name: this repo that should invalidation take place
200 239 """
201
202 log.debug('marking %s for invalidation', repo_name)
203 cache = self.sa.query(CacheInvalidation)\
204 .filter(CacheInvalidation.cache_key == repo_name).scalar()
205
206 if cache:
207 # mark this cache as inactive
208 cache.cache_active = False
209 else:
210 log.debug('cache key not found in invalidation db -> creating one')
211 cache = CacheInvalidation(repo_name)
212
213 try:
214 self.sa.add(cache)
215 self.sa.commit()
216 except (DatabaseError,):
217 log.error(traceback.format_exc())
218 self.sa.rollback()
240 CacheInvalidation.set_invalidate(repo_name)
241 CacheInvalidation.set_invalidate(repo_name + "_README")
219 242
220 243 def toggle_following_repo(self, follow_repo_id, user_id):
221 244
@@ -224,17 +247,14 b' class ScmModel(BaseModel):'
224 247 .filter(UserFollowing.user_id == user_id).scalar()
225 248
226 249 if f is not None:
227
228 250 try:
229 251 self.sa.delete(f)
230 self.sa.commit()
231 252 action_logger(UserTemp(user_id),
232 253 'stopped_following_repo',
233 254 RepoTemp(follow_repo_id))
234 255 return
235 256 except:
236 257 log.error(traceback.format_exc())
237 self.sa.rollback()
238 258 raise
239 259
240 260 try:
@@ -242,13 +262,12 b' class ScmModel(BaseModel):'
242 262 f.user_id = user_id
243 263 f.follows_repo_id = follow_repo_id
244 264 self.sa.add(f)
245 self.sa.commit()
265
246 266 action_logger(UserTemp(user_id),
247 267 'started_following_repo',
248 268 RepoTemp(follow_repo_id))
249 269 except:
250 270 log.error(traceback.format_exc())
251 self.sa.rollback()
252 271 raise
253 272
254 273 def toggle_following_user(self, follow_user_id, user_id):
@@ -259,11 +278,9 b' class ScmModel(BaseModel):'
259 278 if f is not None:
260 279 try:
261 280 self.sa.delete(f)
262 self.sa.commit()
263 281 return
264 282 except:
265 283 log.error(traceback.format_exc())
266 self.sa.rollback()
267 284 raise
268 285
269 286 try:
@@ -271,10 +288,8 b' class ScmModel(BaseModel):'
271 288 f.user_id = user_id
272 289 f.follows_user_id = follow_user_id
273 290 self.sa.add(f)
274 self.sa.commit()
275 291 except:
276 292 log.error(traceback.format_exc())
277 self.sa.rollback()
278 293 raise
279 294
280 295 def is_following_repo(self, repo_name, user_id, cache=False):
@@ -310,6 +325,13 b' class ScmModel(BaseModel):'
310 325 return self.sa.query(Repository)\
311 326 .filter(Repository.fork_id == repo_id).count()
312 327
328 def mark_as_fork(self, repo, fork, user):
329 repo = self.__get_repo(repo)
330 fork = self.__get_repo(fork)
331 repo.fork = fork
332 self.sa.add(repo)
333 return repo
334
313 335 def pull_changes(self, repo_name, username):
314 336 dbrepo = Repository.get_by_repo_name(repo_name)
315 337 clone_uri = dbrepo.clone_uri
@@ -333,13 +355,13 b' class ScmModel(BaseModel):'
333 355 log.error(traceback.format_exc())
334 356 raise
335 357
336 def commit_change(self, repo, repo_name, cs, user, author, message, content,
337 f_path):
358 def commit_change(self, repo, repo_name, cs, user, author, message,
359 content, f_path):
338 360
339 361 if repo.alias == 'hg':
340 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
362 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
341 363 elif repo.alias == 'git':
342 from vcs.backends.git import GitInMemoryChangeset as IMC
364 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
343 365
344 366 # decoding here will force that we have proper encoded values
345 367 # in any other case this will throw exceptions and deny commit
@@ -363,9 +385,9 b' class ScmModel(BaseModel):'
363 385 def create_node(self, repo, repo_name, cs, user, author, message, content,
364 386 f_path):
365 387 if repo.alias == 'hg':
366 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
388 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
367 389 elif repo.alias == 'git':
368 from vcs.backends.git import GitInMemoryChangeset as IMC
390 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
369 391 # decoding here will force that we have proper encoded values
370 392 # in any other case this will throw exceptions and deny commit
371 393
@@ -400,20 +422,35 b' class ScmModel(BaseModel):'
400 422
401 423 self.mark_for_invalidation(repo_name)
402 424
425 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
426 """
427 recursive walk in root dir and return a set of all path in that dir
428 based on repository walk function
429
430 :param repo_name: name of repository
431 :param revision: revision for which to list nodes
432 :param root_path: root path to list
433 :param flat: return as a list, if False returns a dict with decription
434
435 """
436 _files = list()
437 _dirs = list()
438 try:
439 _repo = self.__get_repo(repo_name)
440 changeset = _repo.scm_instance.get_changeset(revision)
441 root_path = root_path.lstrip('/')
442 for topnode, dirs, files in changeset.walk(root_path):
443 for f in files:
444 _files.append(f.path if flat else {"name": f.path,
445 "type": "file"})
446 for d in dirs:
447 _dirs.append(d.path if flat else {"name": d.path,
448 "type": "dir"})
449 except RepositoryError:
450 log.debug(traceback.format_exc())
451 raise
452
453 return _dirs, _files
403 454
404 455 def get_unread_journal(self):
405 456 return self.sa.query(UserLog).count()
406
407 def _should_invalidate(self, repo_name):
408 """Looks up database for invalidation signals for this repo_name
409
410 :param repo_name:
411 """
412
413 ret = self.sa.query(CacheInvalidation)\
414 .filter(CacheInvalidation.cache_key == repo_name)\
415 .filter(CacheInvalidation.cache_active == False)\
416 .scalar()
417
418 return ret
419
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Apr 9, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -26,13 +26,16 b''
26 26 import logging
27 27 import traceback
28 28
29 from pylons import url
29 30 from pylons.i18n.translation import _
30 31
31 32 from rhodecode.lib import safe_unicode
33 from rhodecode.lib.caching_query import FromCache
34
32 35 from rhodecode.model import BaseModel
33 from rhodecode.model.caching_query import FromCache
34 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
35 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
36 39 from rhodecode.lib.exceptions import DefaultUserException, \
37 40 UserOwnsReposException
38 41
@@ -42,13 +45,28 b' from sqlalchemy.orm import joinedload'
42 45
43 46 log = logging.getLogger(__name__)
44 47
45 PERM_WEIGHTS = {'repository.none': 0,
46 'repository.read': 1,
47 'repository.write': 3,
48 'repository.admin': 3}
48
49 PERM_WEIGHTS = {
50 'repository.none': 0,
51 'repository.read': 1,
52 'repository.write': 3,
53 'repository.admin': 4,
54 'group.none': 0,
55 'group.read': 1,
56 'group.write': 3,
57 'group.admin': 4,
58 }
49 59
50 60
51 61 class UserModel(BaseModel):
62
63 def __get_user(self, user):
64 return self._get_instance(User, user, callback=User.get_by_username)
65
66 def __get_perm(self, permission):
67 return self._get_instance(Permission, permission,
68 callback=Permission.get_by_key)
69
52 70 def get(self, user_id, cache=False):
53 71 user = self.sa.query(User)
54 72 if cache:
@@ -56,6 +74,9 b' class UserModel(BaseModel):'
56 74 "get_user_%s" % user_id))
57 75 return user.get(user_id)
58 76
77 def get_user(self, user):
78 return self.__get_user(user)
79
59 80 def get_by_username(self, username, cache=False, case_insensitive=False):
60 81
61 82 if case_insensitive:
@@ -69,13 +90,7 b' class UserModel(BaseModel):'
69 90 return user.scalar()
70 91
71 92 def get_by_api_key(self, api_key, cache=False):
72
73 user = self.sa.query(User)\
74 .filter(User.api_key == api_key)
75 if cache:
76 user = user.options(FromCache("sql_cache_short",
77 "get_user_%s" % api_key))
78 return user.scalar()
93 return User.get_by_api_key(api_key, cache)
79 94
80 95 def create(self, form_data):
81 96 try:
@@ -85,18 +100,91 b' class UserModel(BaseModel):'
85 100
86 101 new_user.api_key = generate_api_key(form_data['username'])
87 102 self.sa.add(new_user)
88 self.sa.commit()
89 103 return new_user
90 104 except:
91 105 log.error(traceback.format_exc())
92 self.sa.rollback()
93 106 raise
94 107
108 def create_or_update(self, username, password, email, name, lastname,
109 active=True, admin=False, ldap_dn=None):
110 """
111 Creates a new instance if not found, or updates current one
112
113 :param username:
114 :param password:
115 :param email:
116 :param active:
117 :param name:
118 :param lastname:
119 :param active:
120 :param admin:
121 :param ldap_dn:
122 """
123
124 from rhodecode.lib.auth import get_crypt_password
125
126 log.debug('Checking for %s account in RhodeCode database' % username)
127 user = User.get_by_username(username, case_insensitive=True)
128 if user is None:
129 log.debug('creating new user %s' % username)
130 new_user = User()
131 else:
132 log.debug('updating user %s' % username)
133 new_user = user
134
135 try:
136 new_user.username = username
137 new_user.admin = admin
138 new_user.password = get_crypt_password(password)
139 new_user.api_key = generate_api_key(username)
140 new_user.email = email
141 new_user.active = active
142 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
143 new_user.name = name
144 new_user.lastname = lastname
145 self.sa.add(new_user)
146 return new_user
147 except (DatabaseError,):
148 log.error(traceback.format_exc())
149 raise
150
151 def create_for_container_auth(self, username, attrs):
152 """
153 Creates the given user if it's not already in the database
154
155 :param username:
156 :param attrs:
157 """
158 if self.get_by_username(username, case_insensitive=True) is None:
159
160 # autogenerate email for container account without one
161 generate_email = lambda usr: '%s@container_auth.account' % usr
162
163 try:
164 new_user = User()
165 new_user.username = username
166 new_user.password = None
167 new_user.api_key = generate_api_key(username)
168 new_user.email = attrs['email']
169 new_user.active = attrs.get('active', True)
170 new_user.name = attrs['name'] or generate_email(username)
171 new_user.lastname = attrs['lastname']
172
173 self.sa.add(new_user)
174 return new_user
175 except (DatabaseError,):
176 log.error(traceback.format_exc())
177 self.sa.rollback()
178 raise
179 log.debug('User %s already exists. Skipping creation of account'
180 ' for container auth.', username)
181 return None
182
95 183 def create_ldap(self, username, password, user_dn, attrs):
96 184 """
97 185 Checks if user is in database, if not creates this user marked
98 186 as ldap user
99
187
100 188 :param username:
101 189 :param password:
102 190 :param user_dn:
@@ -105,31 +193,36 b' class UserModel(BaseModel):'
105 193 from rhodecode.lib.auth import get_crypt_password
106 194 log.debug('Checking for such ldap account in RhodeCode database')
107 195 if self.get_by_username(username, case_insensitive=True) is None:
196
197 # autogenerate email for ldap account without one
198 generate_email = lambda usr: '%s@ldap.account' % usr
199
108 200 try:
109 201 new_user = User()
202 username = username.lower()
110 203 # add ldap account always lowercase
111 new_user.username = username.lower()
204 new_user.username = username
112 205 new_user.password = get_crypt_password(password)
113 206 new_user.api_key = generate_api_key(username)
114 new_user.email = attrs['email']
115 new_user.active = True
207 new_user.email = attrs['email'] or generate_email(username)
208 new_user.active = attrs.get('active', True)
116 209 new_user.ldap_dn = safe_unicode(user_dn)
117 210 new_user.name = attrs['name']
118 211 new_user.lastname = attrs['lastname']
119 212
120 213 self.sa.add(new_user)
121 self.sa.commit()
122 return True
214 return new_user
123 215 except (DatabaseError,):
124 216 log.error(traceback.format_exc())
125 217 self.sa.rollback()
126 218 raise
127 219 log.debug('this %s user exists skipping creation of ldap account',
128 220 username)
129 return False
221 return None
130 222
131 223 def create_registration(self, form_data):
132 from rhodecode.lib.celerylib import tasks, run_task
224 from rhodecode.model.notification import NotificationModel
225
133 226 try:
134 227 new_user = User()
135 228 for k, v in form_data.items():
@@ -137,18 +230,26 b' class UserModel(BaseModel):'
137 230 setattr(new_user, k, v)
138 231
139 232 self.sa.add(new_user)
140 self.sa.commit()
233 self.sa.flush()
234
235 # notification to admins
236 subject = _('new user registration')
141 237 body = ('New user registration\n'
142 'username: %s\n'
143 'email: %s\n')
144 body = body % (form_data['username'], form_data['email'])
238 '---------------------\n'
239 '- Username: %s\n'
240 '- Full Name: %s\n'
241 '- Email: %s\n')
242 body = body % (new_user.username, new_user.full_name,
243 new_user.email)
244 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
245 kw = {'registered_user_url': edit_url}
246 NotificationModel().create(created_by=new_user, subject=subject,
247 body=body, recipients=None,
248 type_=Notification.TYPE_REGISTRATION,
249 email_kwargs=kw)
145 250
146 run_task(tasks.send_email, None,
147 _('[RhodeCode] New User registration'),
148 body)
149 251 except:
150 252 log.error(traceback.format_exc())
151 self.sa.rollback()
152 253 raise
153 254
154 255 def update(self, user_id, form_data):
@@ -167,10 +268,8 b' class UserModel(BaseModel):'
167 268 setattr(user, k, v)
168 269
169 270 self.sa.add(user)
170 self.sa.commit()
171 271 except:
172 272 log.error(traceback.format_exc())
173 self.sa.rollback()
174 273 raise
175 274
176 275 def update_my_account(self, user_id, form_data):
@@ -189,15 +288,14 b' class UserModel(BaseModel):'
189 288 setattr(user, k, v)
190 289
191 290 self.sa.add(user)
192 self.sa.commit()
193 291 except:
194 292 log.error(traceback.format_exc())
195 self.sa.rollback()
196 293 raise
197 294
198 def delete(self, user_id):
295 def delete(self, user):
296 user = self.__get_user(user)
297
199 298 try:
200 user = self.get(user_id, cache=False)
201 299 if user.username == 'default':
202 300 raise DefaultUserException(
203 301 _("You can't remove this user since it's"
@@ -209,10 +307,8 b' class UserModel(BaseModel):'
209 307 'remove those repositories') \
210 308 % user.repositories)
211 309 self.sa.delete(user)
212 self.sa.commit()
213 310 except:
214 311 log.error(traceback.format_exc())
215 self.sa.rollback()
216 312 raise
217 313
218 314 def reset_password_link(self, data):
@@ -243,16 +339,19 b' class UserModel(BaseModel):'
243 339 else:
244 340 dbuser = self.get(user_id)
245 341
246 if dbuser is not None:
247 log.debug('filling %s data', dbuser)
342 if dbuser is not None and dbuser.active:
343 log.debug('filling %s data' % dbuser)
248 344 for k, v in dbuser.get_dict().items():
249 345 setattr(auth_user, k, v)
346 else:
347 return False
250 348
251 349 except:
252 350 log.error(traceback.format_exc())
253 351 auth_user.is_authenticated = False
352 return False
254 353
255 return auth_user
354 return True
256 355
257 356 def fill_perms(self, user):
258 357 """
@@ -262,98 +361,109 b' class UserModel(BaseModel):'
262 361
263 362 :param user: user instance to fill his perms
264 363 """
265
266 user.permissions['repositories'] = {}
267 user.permissions['global'] = set()
364 RK = 'repositories'
365 GK = 'repositories_groups'
366 GLOBAL = 'global'
367 user.permissions[RK] = {}
368 user.permissions[GK] = {}
369 user.permissions[GLOBAL] = set()
268 370
269 371 #======================================================================
270 372 # fetch default permissions
271 373 #======================================================================
272 default_user = self.get_by_username('default', cache=True)
374 default_user = User.get_by_username('default', cache=True)
375 default_user_id = default_user.user_id
273 376
274 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
275 .join((Repository, RepoToPerm.repository_id ==
276 Repository.repo_id))\
277 .join((Permission, RepoToPerm.permission_id ==
278 Permission.permission_id))\
279 .filter(RepoToPerm.user == default_user).all()
377 default_repo_perms = Permission.get_default_perms(default_user_id)
378 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
280 379
281 380 if user.is_admin:
282 381 #==================================================================
283 # #admin have all default rights set to admin
382 # admin user have all default rights for repositories
383 # and groups set to admin
284 384 #==================================================================
285 user.permissions['global'].add('hg.admin')
385 user.permissions[GLOBAL].add('hg.admin')
286 386
287 for perm in default_perms:
387 # repositories
388 for perm in default_repo_perms:
389 r_k = perm.UserRepoToPerm.repository.repo_name
288 390 p = 'repository.admin'
289 user.permissions['repositories'][perm.RepoToPerm.
290 repository.repo_name] = p
391 user.permissions[RK][r_k] = p
392
393 # repositories groups
394 for perm in default_repo_groups_perms:
395 rg_k = perm.UserRepoGroupToPerm.group.group_name
396 p = 'group.admin'
397 user.permissions[GK][rg_k] = p
291 398
292 399 else:
293 400 #==================================================================
294 # set default permissions
401 # set default permissions first for repositories and groups
295 402 #==================================================================
296 403 uid = user.user_id
297 404
298 #default global
405 # default global permissions
299 406 default_global_perms = self.sa.query(UserToPerm)\
300 .filter(UserToPerm.user == default_user)
407 .filter(UserToPerm.user_id == default_user_id)
301 408
302 409 for perm in default_global_perms:
303 user.permissions['global'].add(perm.permission.permission_name)
410 user.permissions[GLOBAL].add(perm.permission.permission_name)
304 411
305 #default for repositories
306 for perm in default_perms:
307 if perm.Repository.private and not (perm.Repository.user_id ==
308 uid):
309 #diself.sable defaults for private repos,
412 # default for repositories
413 for perm in default_repo_perms:
414 r_k = perm.UserRepoToPerm.repository.repo_name
415 if perm.Repository.private and not (perm.Repository.user_id == uid):
416 # disable defaults for private repos,
310 417 p = 'repository.none'
311 418 elif perm.Repository.user_id == uid:
312 #set admin if owner
419 # set admin if owner
313 420 p = 'repository.admin'
314 421 else:
315 422 p = perm.Permission.permission_name
316 423
317 user.permissions['repositories'][perm.RepoToPerm.
318 repository.repo_name] = p
424 user.permissions[RK][r_k] = p
425
426 # default for repositories groups
427 for perm in default_repo_groups_perms:
428 rg_k = perm.UserRepoGroupToPerm.group.group_name
429 p = perm.Permission.permission_name
430 user.permissions[GK][rg_k] = p
319 431
320 432 #==================================================================
321 433 # overwrite default with user permissions if any
322 434 #==================================================================
323 435
324 #user global
436 # user global
325 437 user_perms = self.sa.query(UserToPerm)\
326 438 .options(joinedload(UserToPerm.permission))\
327 439 .filter(UserToPerm.user_id == uid).all()
328 440
329 441 for perm in user_perms:
330 user.permissions['global'].add(perm.permission.
331 permission_name)
442 user.permissions[GLOBAL].add(perm.permission.permission_name)
332 443
333 #user repositories
334 user_repo_perms = self.sa.query(RepoToPerm, Permission,
335 Repository)\
336 .join((Repository, RepoToPerm.repository_id ==
337 Repository.repo_id))\
338 .join((Permission, RepoToPerm.permission_id ==
339 Permission.permission_id))\
340 .filter(RepoToPerm.user_id == uid).all()
444 # user repositories
445 user_repo_perms = \
446 self.sa.query(UserRepoToPerm, Permission, Repository)\
447 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
448 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
449 .filter(UserRepoToPerm.user_id == uid)\
450 .all()
341 451
342 452 for perm in user_repo_perms:
343 453 # set admin if owner
454 r_k = perm.UserRepoToPerm.repository.repo_name
344 455 if perm.Repository.user_id == uid:
345 456 p = 'repository.admin'
346 457 else:
347 458 p = perm.Permission.permission_name
348 user.permissions['repositories'][perm.RepoToPerm.
349 repository.repo_name] = p
459 user.permissions[RK][r_k] = p
350 460
351 461 #==================================================================
352 462 # check if user is part of groups for this repository and fill in
353 463 # (or replace with higher) permissions
354 464 #==================================================================
355 465
356 #users group global
466 # users group global
357 467 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
358 468 .options(joinedload(UsersGroupToPerm.permission))\
359 469 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
@@ -361,30 +471,82 b' class UserModel(BaseModel):'
361 471 .filter(UsersGroupMember.user_id == uid).all()
362 472
363 473 for perm in user_perms_from_users_groups:
364 user.permissions['global'].add(perm.permission.permission_name)
474 user.permissions[GLOBAL].add(perm.permission.permission_name)
365 475
366 #users group repositories
367 user_repo_perms_from_users_groups = self.sa.query(
368 UsersGroupRepoToPerm,
369 Permission, Repository,)\
370 .join((Repository, UsersGroupRepoToPerm.repository_id ==
371 Repository.repo_id))\
372 .join((Permission, UsersGroupRepoToPerm.permission_id ==
373 Permission.permission_id))\
374 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
375 UsersGroupMember.users_group_id))\
376 .filter(UsersGroupMember.user_id == uid).all()
476 # users group repositories
477 user_repo_perms_from_users_groups = \
478 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
479 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
480 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
481 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
482 .filter(UsersGroupMember.user_id == uid)\
483 .all()
377 484
378 485 for perm in user_repo_perms_from_users_groups:
486 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
379 487 p = perm.Permission.permission_name
380 cur_perm = user.permissions['repositories'][perm.
381 UsersGroupRepoToPerm.
382 repository.repo_name]
383 #overwrite permission only if it's greater than permission
488 cur_perm = user.permissions[RK][r_k]
489 # overwrite permission only if it's greater than permission
384 490 # given from other sources
385 491 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
386 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
387 repository.repo_name] = p
492 user.permissions[RK][r_k] = p
493
494 #==================================================================
495 # get access for this user for repos group and override defaults
496 #==================================================================
497
498 # user repositories groups
499 user_repo_groups_perms = \
500 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
501 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
502 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
503 .filter(UserRepoToPerm.user_id == uid)\
504 .all()
505
506 for perm in user_repo_groups_perms:
507 rg_k = perm.UserRepoGroupToPerm.group.group_name
508 p = perm.Permission.permission_name
509 cur_perm = user.permissions[GK][rg_k]
510 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
511 user.permissions[GK][rg_k] = p
388 512
389 513 return user
390 514
515 def has_perm(self, user, perm):
516 if not isinstance(perm, Permission):
517 raise Exception('perm needs to be an instance of Permission class '
518 'got %s instead' % type(perm))
519
520 user = self.__get_user(user)
521
522 return UserToPerm.query().filter(UserToPerm.user == user)\
523 .filter(UserToPerm.permission == perm).scalar() is not None
524
525 def grant_perm(self, user, perm):
526 """
527 Grant user global permissions
528
529 :param user:
530 :param perm:
531 """
532 user = self.__get_user(user)
533 perm = self.__get_perm(perm)
534 new = UserToPerm()
535 new.user = user
536 new.permission = perm
537 self.sa.add(new)
538
539 def revoke_perm(self, user, perm):
540 """
541 Revoke users global permissions
542
543 :param user:
544 :param perm:
545 """
546 user = self.__get_user(user)
547 perm = self.__get_perm(perm)
548
549 obj = UserToPerm.query().filter(UserToPerm.user == user)\
550 .filter(UserToPerm.permission == perm).scalar()
551 if obj:
552 self.sa.delete(obj)
@@ -8,6 +8,7 b''
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 12 :license: GPLv3, see COPYING for more details.
12 13 """
13 14 # This program is free software: you can redistribute it and/or modify
@@ -27,50 +28,99 b' import logging'
27 28 import traceback
28 29
29 30 from rhodecode.model import BaseModel
30 from rhodecode.model.caching_query import FromCache
31 from rhodecode.model.db import UsersGroupMember, UsersGroup
31 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
32 UsersGroupRepoToPerm, Permission, UsersGroupToPerm, User
33 from rhodecode.lib.exceptions import UsersGroupsAssignedException
32 34
33 35 log = logging.getLogger(__name__)
34 36
37
35 38 class UsersGroupModel(BaseModel):
36 39
37 def get(self, users_group_id, cache = False):
38 users_group = UsersGroup.query()
39 if cache:
40 users_group = users_group.options(FromCache("sql_cache_short",
41 "get_users_group_%s" % users_group_id))
42 return users_group.get(users_group_id)
40 def __get_user(self, user):
41 return self._get_instance(User, user, callback=User.get_by_username)
42
43 def __get_users_group(self, users_group):
44 return self._get_instance(UsersGroup, users_group,
45 callback=UsersGroup.get_by_group_name)
46
47 def __get_perm(self, permission):
48 return self._get_instance(Permission, permission,
49 callback=Permission.get_by_key)
43 50
44 def get_by_name(self, name, cache = False, case_insensitive = False):
45 users_group = UsersGroup.query()
46 if case_insensitive:
47 users_group = users_group.filter(UsersGroup.users_group_name.ilike(name))
48 else:
49 users_group = users_group.filter(UsersGroup.users_group_name == name)
50 if cache:
51 users_group = users_group.options(FromCache("sql_cache_short",
52 "get_users_group_%s" % name))
53 return users_group.scalar()
51 def get(self, users_group_id, cache=False):
52 return UsersGroup.get(users_group_id)
53
54 def get_by_name(self, name, cache=False, case_insensitive=False):
55 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
54 56
55 def create(self, form_data):
57 def create(self, name, active=True):
56 58 try:
57 new_users_group = UsersGroup()
58 for k, v in form_data.items():
59 setattr(new_users_group, k, v)
60
61 self.sa.add(new_users_group)
62 self.sa.commit()
63 return new_users_group
59 new = UsersGroup()
60 new.users_group_name = name
61 new.users_group_active = active
62 self.sa.add(new)
63 return new
64 64 except:
65 65 log.error(traceback.format_exc())
66 self.sa.rollback()
66 raise
67
68 def update(self, users_group, form_data):
69
70 try:
71 users_group = self.__get_users_group(users_group)
72
73 for k, v in form_data.items():
74 if k == 'users_group_members':
75 users_group.members = []
76 self.sa.flush()
77 members_list = []
78 if v:
79 v = [v] if isinstance(v, basestring) else v
80 for u_id in set(v):
81 member = UsersGroupMember(users_group.users_group_id, u_id)
82 members_list.append(member)
83 setattr(users_group, 'members', members_list)
84 setattr(users_group, k, v)
85
86 self.sa.add(users_group)
87 except:
88 log.error(traceback.format_exc())
89 raise
90
91 def delete(self, users_group, force=False):
92 """
93 Deletes repos group, unless force flag is used
94 raises exception if there are members in that group, else deletes
95 group and users
96
97 :param users_group:
98 :param force:
99 """
100 try:
101 users_group = self.__get_users_group(users_group)
102
103 # check if this group is not assigned to repo
104 assigned_groups = UsersGroupRepoToPerm.query()\
105 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
106
107 if assigned_groups and force is False:
108 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
109 assigned_groups)
110
111 self.sa.delete(users_group)
112 except:
113 log.error(traceback.format_exc())
67 114 raise
68 115
69 116 def add_user_to_group(self, users_group, user):
117 users_group = self.__get_users_group(users_group)
118 user = self.__get_user(user)
119
70 120 for m in users_group.members:
71 121 u = m.user
72 122 if u.user_id == user.user_id:
73 return m
123 return True
74 124
75 125 try:
76 126 users_group_member = UsersGroupMember()
@@ -81,9 +131,58 b' class UsersGroupModel(BaseModel):'
81 131 user.group_member.append(users_group_member)
82 132
83 133 self.sa.add(users_group_member)
84 self.sa.commit()
85 134 return users_group_member
86 135 except:
87 136 log.error(traceback.format_exc())
88 self.sa.rollback()
89 137 raise
138
139 def remove_user_from_group(self, users_group, user):
140 users_group = self.__get_users_group(users_group)
141 user = self.__get_user(user)
142
143 users_group_member = None
144 for m in users_group.members:
145 if m.user.user_id == user.user_id:
146 # Found this user's membership row
147 users_group_member = m
148 break
149
150 if users_group_member:
151 try:
152 self.sa.delete(users_group_member)
153 return True
154 except:
155 log.error(traceback.format_exc())
156 raise
157 else:
158 # User isn't in that group
159 return False
160
161 def has_perm(self, users_group, perm):
162 users_group = self.__get_users_group(users_group)
163 perm = self.__get_perm(perm)
164
165 return UsersGroupToPerm.query()\
166 .filter(UsersGroupToPerm.users_group == users_group)\
167 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
168
169 def grant_perm(self, users_group, perm):
170 if not isinstance(perm, Permission):
171 raise Exception('perm needs to be an instance of Permission class')
172
173 users_group = self.__get_users_group(users_group)
174
175 new = UsersGroupToPerm()
176 new.users_group = users_group
177 new.permission = perm
178 self.sa.add(new)
179
180 def revoke_perm(self, users_group, perm):
181 users_group = self.__get_users_group(users_group)
182 perm = self.__get_perm(perm)
183
184 obj = UsersGroupToPerm.query()\
185 .filter(UsersGroupToPerm.users_group == users_group)\
186 .filter(UsersGroupToPerm.permission == perm).scalar()
187 if obj:
188 self.sa.delete(obj)
@@ -7,15 +7,53 b' div.codeblock {'
7 7 line-height: 100%;
8 8 /* new */
9 9 line-height: 125%;
10 -webkit-border-radius: 4px;
11 -moz-border-radius: 4px;
12 border-radius: 4px;
10 13 }
11 14 div.codeblock .code-header{
12 15 border-bottom: 1px solid #CCCCCC;
13 16 background: #EEEEEE;
14 17 padding:10px 0 10px 0;
15 18 }
16 div.codeblock .code-header .revision{
19
20 div.codeblock .code-header .stats{
21 clear: both;
22 padding: 6px 8px 6px 10px;
23 border-bottom: 1px solid rgb(204, 204, 204);
24 height: 23px;
25 margin-bottom: 6px;
26 }
27
28 div.codeblock .code-header .stats .left{
29 float:left;
30 }
31 div.codeblock .code-header .stats .left.img{
32 margin-top:-2px;
33 }
34 div.codeblock .code-header .stats .left.item{
35 float:left;
36 padding: 0 9px 0 9px;
37 border-right:1px solid #ccc;
38 }
39 div.codeblock .code-header .stats .left.item pre{
40
41 }
42 div.codeblock .code-header .stats .left.item.last{
43 border-right:none;
44 }
45 div.codeblock .code-header .stats .buttons{
46 float:right;
47 padding-right:4px;
48 }
49
50 div.codeblock .code-header .author{
17 51 margin-left:25px;
18 52 font-weight: bold;
53 height: 25px;
54 }
55 div.codeblock .code-header .author .user{
56 padding-top:3px;
19 57 }
20 58 div.codeblock .code-header .commit{
21 59 margin-left:25px;
@@ -33,10 +71,20 b' div.codeblock .code-body table td {'
33 71 div.code-body {
34 72 background-color: #FFFFFF;
35 73 }
36 div.code-body pre .match{
74
75 div.codeblock .code-header .search-path {
76 padding: 0px 0px 0px 10px;
77 }
78
79 div.search-code-body {
80 background-color: #FFFFFF;
81 padding: 5px 0px 5px 10px;
82 }
83
84 div.search-code-body pre .match{
37 85 background-color: #FAFFA6;
38 86 }
39 div.code-body pre .break{
87 div.search-code-body pre .break{
40 88 background-color: #DDE7EF;
41 89 width: 100%;
42 90 color: #747474;
@@ -64,64 +112,64 b' div.annotatediv{'
64 112 .linenos a { text-decoration: none; }
65 113
66 114 .code { display: block; }
67 .code-highlight .hll { background-color: #ffffcc }
68 .code-highlight .c { color: #408080; font-style: italic } /* Comment */
69 .code-highlight .err { border: 1px solid #FF0000 } /* Error */
70 .code-highlight .k { color: #008000; font-weight: bold } /* Keyword */
71 .code-highlight .o { color: #666666 } /* Operator */
72 .code-highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */
73 .code-highlight .cp { color: #BC7A00 } /* Comment.Preproc */
74 .code-highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */
75 .code-highlight .cs { color: #408080; font-style: italic } /* Comment.Special */
76 .code-highlight .gd { color: #A00000 } /* Generic.Deleted */
77 .code-highlight .ge { font-style: italic } /* Generic.Emph */
78 .code-highlight .gr { color: #FF0000 } /* Generic.Error */
79 .code-highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
80 .code-highlight .gi { color: #00A000 } /* Generic.Inserted */
81 .code-highlight .go { color: #808080 } /* Generic.Output */
82 .code-highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
83 .code-highlight .gs { font-weight: bold } /* Generic.Strong */
84 .code-highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
85 .code-highlight .gt { color: #0040D0 } /* Generic.Traceback */
86 .code-highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
87 .code-highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
88 .code-highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
89 .code-highlight .kp { color: #008000 } /* Keyword.Pseudo */
90 .code-highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
91 .code-highlight .kt { color: #B00040 } /* Keyword.Type */
92 .code-highlight .m { color: #666666 } /* Literal.Number */
93 .code-highlight .s { color: #BA2121 } /* Literal.String */
94 .code-highlight .na { color: #7D9029 } /* Name.Attribute */
95 .code-highlight .nb { color: #008000 } /* Name.Builtin */
96 .code-highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
97 .code-highlight .no { color: #880000 } /* Name.Constant */
98 .code-highlight .nd { color: #AA22FF } /* Name.Decorator */
99 .code-highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
100 .code-highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
101 .code-highlight .nf { color: #0000FF } /* Name.Function */
102 .code-highlight .nl { color: #A0A000 } /* Name.Label */
103 .code-highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
104 .code-highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
105 .code-highlight .nv { color: #19177C } /* Name.Variable */
106 .code-highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
107 .code-highlight .w { color: #bbbbbb } /* Text.Whitespace */
108 .code-highlight .mf { color: #666666 } /* Literal.Number.Float */
109 .code-highlight .mh { color: #666666 } /* Literal.Number.Hex */
110 .code-highlight .mi { color: #666666 } /* Literal.Number.Integer */
111 .code-highlight .mo { color: #666666 } /* Literal.Number.Oct */
112 .code-highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
113 .code-highlight .sc { color: #BA2121 } /* Literal.String.Char */
114 .code-highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
115 .code-highlight .s2 { color: #BA2121 } /* Literal.String.Double */
116 .code-highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
117 .code-highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
118 .code-highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
119 .code-highlight .sx { color: #008000 } /* Literal.String.Other */
120 .code-highlight .sr { color: #BB6688 } /* Literal.String.Regex */
121 .code-highlight .s1 { color: #BA2121 } /* Literal.String.Single */
122 .code-highlight .ss { color: #19177C } /* Literal.String.Symbol */
123 .code-highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
124 .code-highlight .vc { color: #19177C } /* Name.Variable.Class */
125 .code-highlight .vg { color: #19177C } /* Name.Variable.Global */
126 .code-highlight .vi { color: #19177C } /* Name.Variable.Instance */
127 .code-highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
115 .code-highlight .hll, .codehilite .hll { background-color: #ffffcc }
116 .code-highlight .c, .codehilite .c { color: #408080; font-style: italic } /* Comment */
117 .code-highlight .err, .codehilite .err { border: 1px solid #FF0000 } /* Error */
118 .code-highlight .k, .codehilite .k { color: #008000; font-weight: bold } /* Keyword */
119 .code-highlight .o, .codehilite .o { color: #666666 } /* Operator */
120 .code-highlight .cm, .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
121 .code-highlight .cp, .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
122 .code-highlight .c1, .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
123 .code-highlight .cs, .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
124 .code-highlight .gd, .codehilite .gd { color: #A00000 } /* Generic.Deleted */
125 .code-highlight .ge, .codehilite .ge { font-style: italic } /* Generic.Emph */
126 .code-highlight .gr, .codehilite .gr { color: #FF0000 } /* Generic.Error */
127 .code-highlight .gh, .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
128 .code-highlight .gi, .codehilite .gi { color: #00A000 } /* Generic.Inserted */
129 .code-highlight .go, .codehilite .go { color: #808080 } /* Generic.Output */
130 .code-highlight .gp, .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
131 .code-highlight .gs, .codehilite .gs { font-weight: bold } /* Generic.Strong */
132 .code-highlight .gu, .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
133 .code-highlight .gt, .codehilite .gt { color: #0040D0 } /* Generic.Traceback */
134 .code-highlight .kc, .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
135 .code-highlight .kd, .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
136 .code-highlight .kn, .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
137 .code-highlight .kp, .codehilite .kp { color: #008000 } /* Keyword.Pseudo */
138 .code-highlight .kr, .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
139 .code-highlight .kt, .codehilite .kt { color: #B00040 } /* Keyword.Type */
140 .code-highlight .m, .codehilite .m { color: #666666 } /* Literal.Number */
141 .code-highlight .s, .codehilite .s { color: #BA2121 } /* Literal.String */
142 .code-highlight .na, .codehilite .na { color: #7D9029 } /* Name.Attribute */
143 .code-highlight .nb, .codehilite .nb { color: #008000 } /* Name.Builtin */
144 .code-highlight .nc, .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
145 .code-highlight .no, .codehilite .no { color: #880000 } /* Name.Constant */
146 .code-highlight .nd, .codehilite .nd { color: #AA22FF } /* Name.Decorator */
147 .code-highlight .ni, .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
148 .code-highlight .ne, .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
149 .code-highlight .nf, .codehilite .nf { color: #0000FF } /* Name.Function */
150 .code-highlight .nl, .codehilite .nl { color: #A0A000 } /* Name.Label */
151 .code-highlight .nn, .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
152 .code-highlight .nt, .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
153 .code-highlight .nv, .codehilite .nv { color: #19177C } /* Name.Variable */
154 .code-highlight .ow, .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
155 .code-highlight .w, .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
156 .code-highlight .mf, .codehilite .mf { color: #666666 } /* Literal.Number.Float */
157 .code-highlight .mh, .codehilite .mh { color: #666666 } /* Literal.Number.Hex */
158 .code-highlight .mi, .codehilite .mi { color: #666666 } /* Literal.Number.Integer */
159 .code-highlight .mo, .codehilite .mo { color: #666666 } /* Literal.Number.Oct */
160 .code-highlight .sb, .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
161 .code-highlight .sc, .codehilite .sc { color: #BA2121 } /* Literal.String.Char */
162 .code-highlight .sd, .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
163 .code-highlight .s2, .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
164 .code-highlight .se, .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
165 .code-highlight .sh, .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
166 .code-highlight .si, .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
167 .code-highlight .sx, .codehilite .sx { color: #008000 } /* Literal.String.Other */
168 .code-highlight .sr, .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
169 .code-highlight .s1, .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
170 .code-highlight .ss, .codehilite .ss { color: #19177C } /* Literal.String.Symbol */
171 .code-highlight .bp, .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
172 .code-highlight .vc, .codehilite .vc { color: #19177C } /* Name.Variable.Class */
173 .code-highlight .vg, .codehilite .vg { color: #19177C } /* Name.Variable.Global */
174 .code-highlight .vi, .codehilite .vi { color: #19177C } /* Name.Variable.Instance */
175 .code-highlight .il, .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
This diff has been collapsed as it changes many lines, (5745 lines changed) Show them Hide them
@@ -1,2759 +1,4272 b''
1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
2 border:0;
3 outline:0;
4 font-size:100%;
5 vertical-align:baseline;
6 background:transparent;
7 margin:0;
8 padding:0;
1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
2 {
3 border: 0;
4 outline: 0;
5 font-size: 100%;
6 vertical-align: baseline;
7 background: transparent;
8 margin: 0;
9 padding: 0;
9 10 }
10 11
11 12 body {
12 line-height:1;
13 height:100%;
14 background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
15 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
16 font-size:12px;
17 color:#000;
18 margin:0;
19 padding:0;
13 line-height: 1;
14 height: 100%;
15 background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
16 font-family: Lucida Grande, Verdana, Lucida Sans Regular,
17 Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
18 color: #000;
19 margin: 0;
20 padding: 0;
21 font-size: 12px;
20 22 }
21 23
22 24 ol,ul {
23 list-style:none;
25 list-style: none;
24 26 }
25 27
26 28 blockquote,q {
27 quotes:none;
29 quotes: none;
28 30 }
29 31
30 32 blockquote:before,blockquote:after,q:before,q:after {
31 content:none;
33 content: none;
32 34 }
33 35
34 36 :focus {
35 outline:0;
37 outline: 0;
36 38 }
37 39
38 40 del {
39 text-decoration:line-through;
41 text-decoration: line-through;
40 42 }
41 43
42 44 table {
43 border-collapse:collapse;
44 border-spacing:0;
45 border-collapse: collapse;
46 border-spacing: 0;
45 47 }
46 48
47 49 html {
48 height:100%;
50 height: 100%;
49 51 }
50 52
51 53 a {
52 color:#003367;
53 text-decoration:none;
54 cursor:pointer;
54 color: #003367;
55 text-decoration: none;
56 cursor: pointer;
55 57 }
56 58
57 59 a:hover {
58 color:#316293;
59 text-decoration:underline;
60 color: #316293;
61 text-decoration: underline;
60 62 }
61 63
62 64 h1,h2,h3,h4,h5,h6 {
63 color:#292929;
64 font-weight:700;
65 color: #292929;
66 font-weight: 700;
65 67 }
66 68
67 69 h1 {
68 font-size:22px;
70 font-size: 22px;
69 71 }
70 72
71 73 h2 {
72 font-size:20px;
74 font-size: 20px;
73 75 }
74 76
75 77 h3 {
76 font-size:18px;
78 font-size: 18px;
77 79 }
78 80
79 81 h4 {
80 font-size:16px;
82 font-size: 16px;
81 83 }
82 84
83 85 h5 {
84 font-size:14px;
86 font-size: 14px;
85 87 }
86 88
87 89 h6 {
88 font-size:11px;
90 font-size: 11px;
89 91 }
90 92
91 93 ul.circle {
92 list-style-type:circle;
94 list-style-type: circle;
93 95 }
94 96
95 97 ul.disc {
96 list-style-type:disc;
98 list-style-type: disc;
97 99 }
98 100
99 101 ul.square {
100 list-style-type:square;
102 list-style-type: square;
101 103 }
102 104
103 105 ol.lower-roman {
104 list-style-type:lower-roman;
106 list-style-type: lower-roman;
105 107 }
106 108
107 109 ol.upper-roman {
108 list-style-type:upper-roman;
110 list-style-type: upper-roman;
109 111 }
110 112
111 113 ol.lower-alpha {
112 list-style-type:lower-alpha;
114 list-style-type: lower-alpha;
113 115 }
114 116
115 117 ol.upper-alpha {
116 list-style-type:upper-alpha;
118 list-style-type: upper-alpha;
117 119 }
118 120
119 121 ol.decimal {
120 list-style-type:decimal;
122 list-style-type: decimal;
121 123 }
122 124
123 125 div.color {
124 clear:both;
125 overflow:hidden;
126 position:absolute;
127 background:#FFF;
128 margin:7px 0 0 60px;
129 padding:1px 1px 1px 0;
126 clear: both;
127 overflow: hidden;
128 position: absolute;
129 background: #FFF;
130 margin: 7px 0 0 60px;
131 padding: 1px 1px 1px 0;
130 132 }
131 133
132 134 div.color a {
133 width:15px;
134 height:15px;
135 display:block;
136 float:left;
137 margin:0 0 0 1px;
138 padding:0;
135 width: 15px;
136 height: 15px;
137 display: block;
138 float: left;
139 margin: 0 0 0 1px;
140 padding: 0;
139 141 }
140 142
141 143 div.options {
142 clear:both;
143 overflow:hidden;
144 position:absolute;
145 background:#FFF;
146 margin:7px 0 0 162px;
147 padding:0;
144 clear: both;
145 overflow: hidden;
146 position: absolute;
147 background: #FFF;
148 margin: 7px 0 0 162px;
149 padding: 0;
148 150 }
149 151
150 152 div.options a {
151 height:1%;
152 display:block;
153 text-decoration:none;
154 margin:0;
155 padding:3px 8px;
153 height: 1%;
154 display: block;
155 text-decoration: none;
156 margin: 0;
157 padding: 3px 8px;
156 158 }
157 159
158 160 .top-left-rounded-corner {
159 -webkit-border-top-left-radius: 8px;
160 -khtml-border-radius-topleft: 8px;
161 -moz-border-radius-topleft: 8px;
162 border-top-left-radius: 8px;
161 -webkit-border-top-left-radius: 8px;
162 -khtml-border-radius-topleft: 8px;
163 -moz-border-radius-topleft: 8px;
164 border-top-left-radius: 8px;
163 165 }
164 166
165 167 .top-right-rounded-corner {
166 -webkit-border-top-right-radius: 8px;
167 -khtml-border-radius-topright: 8px;
168 -moz-border-radius-topright: 8px;
169 border-top-right-radius: 8px;
168 -webkit-border-top-right-radius: 8px;
169 -khtml-border-radius-topright: 8px;
170 -moz-border-radius-topright: 8px;
171 border-top-right-radius: 8px;
170 172 }
171 173
172 174 .bottom-left-rounded-corner {
173 -webkit-border-bottom-left-radius: 8px;
174 -khtml-border-radius-bottomleft: 8px;
175 -moz-border-radius-bottomleft: 8px;
176 border-bottom-left-radius: 8px;
175 -webkit-border-bottom-left-radius: 8px;
176 -khtml-border-radius-bottomleft: 8px;
177 -moz-border-radius-bottomleft: 8px;
178 border-bottom-left-radius: 8px;
177 179 }
178 180
179 181 .bottom-right-rounded-corner {
180 -webkit-border-bottom-right-radius: 8px;
181 -khtml-border-radius-bottomright: 8px;
182 -moz-border-radius-bottomright: 8px;
183 border-bottom-right-radius: 8px;
184 }
185
182 -webkit-border-bottom-right-radius: 8px;
183 -khtml-border-radius-bottomright: 8px;
184 -moz-border-radius-bottomright: 8px;
185 border-bottom-right-radius: 8px;
186 }
186 187
187 188 #header {
188 margin:0;
189 padding:0 10px;
190 }
191
192
193 #header ul#logged-user{
194 margin-bottom:5px !important;
195 -webkit-border-radius: 0px 0px 8px 8px;
196 -khtml-border-radius: 0px 0px 8px 8px;
197 -moz-border-radius: 0px 0px 8px 8px;
198 border-radius: 0px 0px 8px 8px;
199 height:37px;
200 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
201 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
189 margin: 0;
190 padding: 0 10px;
191 }
192
193 #header ul#logged-user {
194 margin-bottom: 5px !important;
195 -webkit-border-radius: 0px 0px 8px 8px;
196 -khtml-border-radius: 0px 0px 8px 8px;
197 -moz-border-radius: 0px 0px 8px 8px;
198 border-radius: 0px 0px 8px 8px;
199 height: 37px;
200 background-color: #eedc94;
201 background-repeat: repeat-x;
202 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
203 to(#eedc94) );
204 background-image: -moz-linear-gradient(top, #003b76, #00376e);
205 background-image: -ms-linear-gradient(top, #003b76, #00376e);
206 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
207 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
208 background-image: -o-linear-gradient(top, #003b76, #00376e);
209 background-image: linear-gradient(top, #003b76, #00376e);
210 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
211 endColorstr='#00376e', GradientType=0 );
212 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
202 213 }
203 214
204 215 #header ul#logged-user li {
205 list-style:none;
206 float:left;
207 margin:8px 0 0;
208 padding:4px 12px;
209 border-left: 1px solid #316293;
216 list-style: none;
217 float: left;
218 margin: 8px 0 0;
219 padding: 4px 12px;
220 border-left: 1px solid #316293;
210 221 }
211 222
212 223 #header ul#logged-user li.first {
213 border-left:none;
214 margin:4px;
224 border-left: none;
225 margin: 4px;
215 226 }
216 227
217 228 #header ul#logged-user li.first div.gravatar {
218 margin-top:-2px;
229 margin-top: -2px;
219 230 }
220 231
221 232 #header ul#logged-user li.first div.account {
222 padding-top:4px;
223 float:left;
233 padding-top: 4px;
234 float: left;
224 235 }
225 236
226 237 #header ul#logged-user li.last {
227 border-right:none;
238 border-right: none;
228 239 }
229 240
230 241 #header ul#logged-user li a {
231 color:#fff;
232 font-weight:700;
233 text-decoration:none;
242 color: #fff;
243 font-weight: 700;
244 text-decoration: none;
234 245 }
235 246
236 247 #header ul#logged-user li a:hover {
237 text-decoration:underline;
248 text-decoration: underline;
238 249 }
239 250
240 251 #header ul#logged-user li.highlight a {
241 color:#fff;
252 color: #fff;
242 253 }
243 254
244 255 #header ul#logged-user li.highlight a:hover {
245 color:#FFF;
256 color: #FFF;
246 257 }
247 258
248 259 #header #header-inner {
249 min-height:40px;
250 clear:both;
251 position:relative;
252 background:#003367 url("../images/header_inner.png") repeat-x;
253 margin:0;
254 padding:0;
255 display:block;
256 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
257 -webkit-border-radius: 4px 4px 4px 4px;
258 -khtml-border-radius: 4px 4px 4px 4px;
259 -moz-border-radius: 4px 4px 4px 4px;
260 border-radius: 4px 4px 4px 4px;
261 }
262
260 min-height: 44px;
261 clear: both;
262 position: relative;
263 background-color: #eedc94;
264 background-repeat: repeat-x;
265 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),to(#eedc94) );
266 background-image: -moz-linear-gradient(top, #003b76, #00376e);
267 background-image: -ms-linear-gradient(top, #003b76, #00376e);
268 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
269 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
270 background-image: -o-linear-gradient(top, #003b76, #00376e);
271 background-image: linear-gradient(top, #003b76, #00376e);
272 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
273 margin: 0;
274 padding: 0;
275 display: block;
276 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
277 -webkit-border-radius: 4px 4px 4px 4px;
278 -khtml-border-radius: 4px 4px 4px 4px;
279 -moz-border-radius: 4px 4px 4px 4px;
280 border-radius: 4px 4px 4px 4px;
281 }
282 #header #header-inner.hover{
283 position: fixed !important;
284 width: 100% !important;
285 margin-left: -10px !important;
286 z-index: 10000;
287 -webkit-border-radius: 0px 0px 0px 0px;
288 -khtml-border-radius: 0px 0px 0px 0px;
289 -moz-border-radius: 0px 0px 0px 0px;
290 border-radius: 0px 0px 0px 0px;
291 }
263 292 #header #header-inner #home a {
264 height:40px;
265 width:46px;
266 display:block;
267 background:url("../images/button_home.png");
268 background-position:0 0;
269 margin:0;
270 padding:0;
293 height: 40px;
294 width: 46px;
295 display: block;
296 background: url("../images/button_home.png");
297 background-position: 0 0;
298 margin: 0;
299 padding: 0;
271 300 }
272 301
273 302 #header #header-inner #home a:hover {
274 background-position:0 -40px;
275 }
303 background-position: 0 -40px;
304 }
305
276 306 #header #header-inner #logo {
277 float: left;
278 position: absolute;
279 }
307 float: left;
308 position: absolute;
309 }
310
280 311 #header #header-inner #logo h1 {
281 color:#FFF;
282 font-size:18px;
283 margin:10px 0 0 13px;
284 padding:0;
312 color: #FFF;
313 font-size: 20px;
314 margin: 12px 0 0 13px;
315 padding: 0;
285 316 }
286 317
287 318 #header #header-inner #logo a {
288 color:#fff;
289 text-decoration:none;
319 color: #fff;
320 text-decoration: none;
290 321 }
291 322
292 323 #header #header-inner #logo a:hover {
293 color:#bfe3ff;
324 color: #bfe3ff;
294 325 }
295 326
296 327 #header #header-inner #quick,#header #header-inner #quick ul {
297 position:relative;
298 float:right;
299 list-style-type:none;
300 list-style-position:outside;
301 margin:6px 5px 0 0;
302 padding:0;
328 position: relative;
329 float: right;
330 list-style-type: none;
331 list-style-position: outside;
332 margin: 8px 8px 0 0;
333 padding: 0;
303 334 }
304 335
305 336 #header #header-inner #quick li {
306 position:relative;
307 float:left;
308 margin:0 5px 0 0;
309 padding:0;
310 }
311
312 #header #header-inner #quick li a {
313 top:0;
314 left:0;
315 height:1%;
316 display:block;
317 clear:both;
318 overflow:hidden;
319 color:#FFF;
320 font-weight:700;
321 text-decoration:none;
322 background:#369;
323 padding:0;
324 -webkit-border-radius: 4px 4px 4px 4px;
325 -khtml-border-radius: 4px 4px 4px 4px;
326 -moz-border-radius: 4px 4px 4px 4px;
327 border-radius: 4px 4px 4px 4px;
337 position: relative;
338 float: left;
339 margin: 0 5px 0 0;
340 padding: 0;
341 }
342
343 #header #header-inner #quick li a.menu_link {
344 top: 0;
345 left: 0;
346 height: 1%;
347 display: block;
348 clear: both;
349 overflow: hidden;
350 color: #FFF;
351 font-weight: 700;
352 text-decoration: none;
353 background: #369;
354 padding: 0;
355 -webkit-border-radius: 4px 4px 4px 4px;
356 -khtml-border-radius: 4px 4px 4px 4px;
357 -moz-border-radius: 4px 4px 4px 4px;
358 border-radius: 4px 4px 4px 4px;
328 359 }
329 360
330 361 #header #header-inner #quick li span.short {
331 padding:9px 6px 8px 6px;
362 padding: 9px 6px 8px 6px;
332 363 }
333 364
334 365 #header #header-inner #quick li span {
335 top:0;
336 right:0;
337 height:1%;
338 display:block;
339 float:left;
340 border-left:1px solid #3f6f9f;
341 margin:0;
342 padding:10px 12px 8px 10px;
366 top: 0;
367 right: 0;
368 height: 1%;
369 display: block;
370 float: left;
371 border-left: 1px solid #3f6f9f;
372 margin: 0;
373 padding: 10px 12px 8px 10px;
343 374 }
344 375
345 376 #header #header-inner #quick li span.normal {
346 border:none;
347 padding:10px 12px 8px;
377 border: none;
378 padding: 10px 12px 8px;
348 379 }
349 380
350 381 #header #header-inner #quick li span.icon {
351 top:0;
352 left:0;
353 border-left:none;
354 border-right:1px solid #2e5c89;
355 padding:8px 6px 4px;
382 top: 0;
383 left: 0;
384 border-left: none;
385 border-right: 1px solid #2e5c89;
386 padding: 8px 6px 4px;
356 387 }
357 388
358 389 #header #header-inner #quick li span.icon_short {
359 top:0;
360 left:0;
361 border-left:none;
362 border-right:1px solid #2e5c89;
363 padding:8px 6px 4px;
364 }
365 #header #header-inner #quick li span.icon img, #header #header-inner #quick li span.icon_short img {
390 top: 0;
391 left: 0;
392 border-left: none;
393 border-right: 1px solid #2e5c89;
394 padding: 8px 6px 4px;
395 }
396
397 #header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
398 {
366 399 margin: 0px -2px 0px 0px;
367 400 }
368 401
369 402 #header #header-inner #quick li a:hover {
370 background:#4e4e4e no-repeat top left;
403 background: #4e4e4e no-repeat top left;
371 404 }
372 405
373 406 #header #header-inner #quick li a:hover span {
374 border-left:1px solid #545454;
375 }
376
377 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
378 border-left:none;
379 border-right:1px solid #464646;
407 border-left: 1px solid #545454;
408 }
409
410 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short
411 {
412 border-left: none;
413 border-right: 1px solid #464646;
380 414 }
381 415
382 416 #header #header-inner #quick ul {
383 top:29px;
384 right:0;
385 min-width:200px;
386 display:none;
387 position:absolute;
388 background:#FFF;
389 border:1px solid #666;
390 border-top:1px solid #003367;
391 z-index:100;
392 margin:0;
393 padding:0;
417 top: 29px;
418 right: 0;
419 min-width: 200px;
420 display: none;
421 position: absolute;
422 background: #FFF;
423 border: 1px solid #666;
424 border-top: 1px solid #003367;
425 z-index: 100;
426 margin: 0px 0px 0px 0px;
427 padding: 0;
394 428 }
395 429
396 430 #header #header-inner #quick ul.repo_switcher {
397 max-height:275px;
398 overflow-x:hidden;
399 overflow-y:auto;
400 }
431 max-height: 275px;
432 overflow-x: hidden;
433 overflow-y: auto;
434 }
435
401 436 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
402 float:none;
403 margin:0;
404 border-bottom:2px solid #003367;
405 }
406
407
408 #header #header-inner #quick .repo_switcher_type{
409 position:absolute;
410 left:0;
411 top:9px;
412
413 }
437 float: none;
438 margin: 0;
439 border-bottom: 2px solid #003367;
440 }
441
442 #header #header-inner #quick .repo_switcher_type {
443 position: absolute;
444 left: 0;
445 top: 9px;
446 }
447
414 448 #header #header-inner #quick li ul li {
415 border-bottom:1px solid #ddd;
449 border-bottom: 1px solid #ddd;
416 450 }
417 451
418 452 #header #header-inner #quick li ul li a {
419 width:182px;
420 height:auto;
421 display:block;
422 float:left;
423 background:#FFF;
424 color:#003367;
425 font-weight:400;
426 margin:0;
427 padding:7px 9px;
453 width: 182px;
454 height: auto;
455 display: block;
456 float: left;
457 background: #FFF;
458 color: #003367;
459 font-weight: 400;
460 margin: 0;
461 padding: 7px 9px;
428 462 }
429 463
430 464 #header #header-inner #quick li ul li a:hover {
431 color:#000;
432 background:#FFF;
465 color: #000;
466 background: #FFF;
433 467 }
434 468
435 469 #header #header-inner #quick ul ul {
436 top:auto;
470 top: auto;
437 471 }
438 472
439 473 #header #header-inner #quick li ul ul {
440 right:200px;
441 max-height:275px;
442 overflow:auto;
443 overflow-x:hidden;
444 white-space:normal;
445 }
446
447 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
448 background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
449 width:167px;
450 margin:0;
451 padding:12px 9px 7px 24px;
452 }
453
454 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
455 background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
456 min-width:167px;
457 margin:0;
458 padding:12px 9px 7px 24px;
459 }
460
461 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
462 background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
463 min-width:167px;
464 margin:0;
465 padding:12px 9px 7px 24px;
466 }
467
468 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
469 background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
470 min-width:167px;
471 margin:0 0 0 14px;
472 padding:12px 9px 7px 24px;
473 }
474
475 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
476 background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
477 min-width:167px;
478 margin:0 0 0 14px;
479 padding:12px 9px 7px 24px;
480 }
481
482 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
483 background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
484 width:167px;
485 margin:0;
486 padding:12px 9px 7px 24px;
487 }
488
489 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover {
490 background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF;
491 width:167px;
492 margin:0;
493 padding:12px 9px 7px 24px;
494 }
495
496 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
497 background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
498 width:167px;
499 margin:0;
500 padding:12px 9px 7px 24px;
501 }
502
503 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
504 background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
505 width:167px;
506 margin:0;
507 padding:12px 9px 7px 24px;
508 }
509
510 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
511 background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
512 width:167px;
513 margin:0;
514 padding:12px 9px 7px 24px;
515 }
516
517 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
518 background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
519 width:167px;
520 margin:0;
521 padding:12px 9px 7px 24px;
522 }
523
524 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
525 background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
526 width:167px;
527 margin:0;
528 padding:12px 9px 7px 24px;
529 }
530
531 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
532 background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
533 width:167px;
534 margin:0;
535 padding:12px 9px 7px 24px;
536 }
537
538 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
539 background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
540 width:167px;
541 margin:0;
542 padding:12px 9px 7px 24px;
543 }
544
545 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
546 background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
547 width:167px;
548 margin:0;
549 padding:12px 9px 7px 24px;
550 }
551
552 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
553 background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
554 width:167px;
555 margin:0;
556 padding:12px 9px 7px 24px;
557 }
558
559 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
560 background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
561 width:167px;
562 margin:0;
563 padding:12px 9px 7px 24px;
564 }
565
566 #header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
567 background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
568 width:167px;
569 margin:0;
570 padding:12px 9px 7px 24px;
571 }
572
474 right: 200px;
475 max-height: 275px;
476 overflow: auto;
477 overflow-x: hidden;
478 white-space: normal;
479 }
480
481 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
482 {
483 background: url("../images/icons/book.png") no-repeat scroll 4px 9px
484 #FFF;
485 width: 167px;
486 margin: 0;
487 padding: 12px 9px 7px 24px;
488 }
489
490 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
491 {
492 background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
493 #FFF;
494 min-width: 167px;
495 margin: 0;
496 padding: 12px 9px 7px 24px;
497 }
498
499 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
500 {
501 background: url("../images/icons/lock_open.png") no-repeat scroll 4px
502 9px #FFF;
503 min-width: 167px;
504 margin: 0;
505 padding: 12px 9px 7px 24px;
506 }
507
508 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
509 {
510 background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
511 #FFF;
512 min-width: 167px;
513 margin: 0 0 0 14px;
514 padding: 12px 9px 7px 24px;
515 }
516
517 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
518 {
519 background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
520 #FFF;
521 min-width: 167px;
522 margin: 0 0 0 14px;
523 padding: 12px 9px 7px 24px;
524 }
525
526 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
527 {
528 background: url("../images/icons/database_edit.png") no-repeat scroll
529 4px 9px #FFF;
530 width: 167px;
531 margin: 0;
532 padding: 12px 9px 7px 24px;
533 }
534
535 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
536 {
537 background: url("../images/icons/database_link.png") no-repeat scroll
538 4px 9px #FFF;
539 width: 167px;
540 margin: 0;
541 padding: 12px 9px 7px 24px;
542 }
543
544 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
545 {
546 background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
547 width: 167px;
548 margin: 0;
549 padding: 12px 9px 7px 24px;
550 }
551
552 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
553 {
554 background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
555 width: 167px;
556 margin: 0;
557 padding: 12px 9px 7px 24px;
558 }
559
560 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
561 {
562 background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
563 width: 167px;
564 margin: 0;
565 padding: 12px 9px 7px 24px;
566 }
567
568 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
569 {
570 background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
571 width: 167px;
572 margin: 0;
573 padding: 12px 9px 7px 24px;
574 }
575
576 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
577 {
578 background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
579 width: 167px;
580 margin: 0;
581 padding: 12px 9px 7px 24px;
582 }
583
584 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
585 {
586 background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
587 9px;
588 width: 167px;
589 margin: 0;
590 padding: 12px 9px 7px 24px;
591 }
592
593 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
594 {
595 background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
596 width: 167px;
597 margin: 0;
598 padding: 12px 9px 7px 24px;
599 }
600
601 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
602 {
603 background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
604 width: 167px;
605 margin: 0;
606 padding: 12px 9px 7px 24px;
607 }
608
609 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
610 {
611 background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
612 9px;
613 width: 167px;
614 margin: 0;
615 padding: 12px 9px 7px 24px;
616 }
617
618 #header #header-inner #quick li ul li a.tags,
619 #header #header-inner #quick li ul li a.tags:hover{
620 background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
621 width: 167px;
622 margin: 0;
623 padding: 12px 9px 7px 24px;
624 }
625
626 #header #header-inner #quick li ul li a.bookmarks,
627 #header #header-inner #quick li ul li a.bookmarks:hover{
628 background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
629 width: 167px;
630 margin: 0;
631 padding: 12px 9px 7px 24px;
632 }
633
634 #header #header-inner #quick li ul li a.admin,
635 #header #header-inner #quick li ul li a.admin:hover{
636 background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
637 width: 167px;
638 margin: 0;
639 padding: 12px 9px 7px 24px;
640 }
573 641
574 642 .groups_breadcrumbs a {
575 643 color: #fff;
576 644 }
645
577 646 .groups_breadcrumbs a:hover {
578 color: #bfe3ff;
579 text-decoration: none;
580 }
581
582 .quick_repo_menu{
647 color: #bfe3ff;
648 text-decoration: none;
649 }
650
651 td.quick_repo_menu {
583 652 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
584 653 cursor: pointer;
585 654 width: 8px;
586 }
587 .quick_repo_menu.active{
588 background: #FFF url("../images/horizontal-indicator.png") 4px 50% no-repeat !important;
655 border: 1px solid transparent;
656 }
657
658 td.quick_repo_menu.active {
659 background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
660 border: 1px solid #003367;
661 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
589 662 cursor: pointer;
590 663 }
591 .quick_repo_menu .menu_items{
592 margin-top:6px;
593 width:150px;
664
665 td.quick_repo_menu .menu_items {
666 margin-top: 10px;
667 margin-left:-6px;
668 width: 150px;
594 669 position: absolute;
595 background-color:#FFF;
596 background: none repeat scroll 0 0 #FFFFFF;
597 border-color: #003367 #666666 #666666;
598 border-right: 1px solid #666666;
599 border-style: solid;
600 border-width: 1px;
601 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
602 }
603 .quick_repo_menu .menu_items li{
604 padding:0 !important;
605 }
606 .quick_repo_menu .menu_items a{
670 background-color: #FFF;
671 background: none repeat scroll 0 0 #FFFFFF;
672 border-color: #003367 #666666 #666666;
673 border-right: 1px solid #666666;
674 border-style: solid;
675 border-width: 1px;
676 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
677 border-top-style: none;
678 }
679
680 td.quick_repo_menu .menu_items li {
681 padding: 0 !important;
682 }
683
684 td.quick_repo_menu .menu_items a {
607 685 display: block;
608 686 padding: 4px 12px 4px 8px;
609 687 }
610 .quick_repo_menu .menu_items a:hover{
611 background-color: #EEE;
688
689 td.quick_repo_menu .menu_items a:hover {
690 background-color: #EEE;
691 text-decoration: none;
692 }
693
694 td.quick_repo_menu .menu_items .icon img {
695 margin-bottom: -2px;
696 }
697
698 td.quick_repo_menu .menu_items.hidden {
699 display: none;
700 }
701
702 .yui-dt-first th {
703 text-align: left;
704 }
705
706 /*
707 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
708 Code licensed under the BSD License:
709 http://developer.yahoo.com/yui/license.html
710 version: 2.9.0
711 */
712 .yui-skin-sam .yui-dt-mask {
713 position: absolute;
714 z-index: 9500;
715 }
716 .yui-dt-tmp {
717 position: absolute;
718 left: -9000px;
719 }
720 .yui-dt-scrollable .yui-dt-bd { overflow: auto }
721 .yui-dt-scrollable .yui-dt-hd {
722 overflow: hidden;
723 position: relative;
724 }
725 .yui-dt-scrollable .yui-dt-bd thead tr,
726 .yui-dt-scrollable .yui-dt-bd thead th {
727 position: absolute;
728 left: -1500px;
729 }
730 .yui-dt-scrollable tbody { -moz-outline: 0 }
731 .yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
732 .yui-skin-sam thead .yui-dt-draggable { cursor: move }
733 .yui-dt-coltarget {
734 position: absolute;
735 z-index: 999;
736 }
737 .yui-dt-hd { zoom: 1 }
738 th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
739 .yui-dt-resizer {
740 position: absolute;
741 right: 0;
742 bottom: 0;
743 height: 100%;
744 cursor: e-resize;
745 cursor: col-resize;
746 background-color: #CCC;
747 opacity: 0;
748 filter: alpha(opacity=0);
749 }
750 .yui-dt-resizerproxy {
751 visibility: hidden;
752 position: absolute;
753 z-index: 9000;
754 background-color: #CCC;
755 opacity: 0;
756 filter: alpha(opacity=0);
757 }
758 th.yui-dt-hidden .yui-dt-liner,
759 td.yui-dt-hidden .yui-dt-liner,
760 th.yui-dt-hidden .yui-dt-resizer { display: none }
761 .yui-dt-editor,
762 .yui-dt-editor-shim {
763 position: absolute;
764 z-index: 9000;
765 }
766 .yui-skin-sam .yui-dt table {
767 margin: 0;
768 padding: 0;
769 font-family: arial;
770 font-size: inherit;
771 border-collapse: separate;
772 *border-collapse: collapse;
773 border-spacing: 0;
774 border: 1px solid #7f7f7f;
775 }
776 .yui-skin-sam .yui-dt thead { border-spacing: 0 }
777 .yui-skin-sam .yui-dt caption {
778 color: #000;
779 font-size: 85%;
780 font-weight: normal;
781 font-style: italic;
782 line-height: 1;
783 padding: 1em 0;
784 text-align: center;
785 }
786 .yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
787 .yui-skin-sam .yui-dt th,
788 .yui-skin-sam .yui-dt th a {
789 font-weight: normal;
612 790 text-decoration: none;
613
614 }
615 .quick_repo_menu .menu_items .icon img{
616 margin-bottom:-2px;
617 }
618 .quick_repo_menu .menu_items.hidden{
619 display: none;
791 color: #000;
792 vertical-align: bottom;
793 }
794 .yui-skin-sam .yui-dt th {
795 margin: 0;
796 padding: 0;
797 border: 0;
798 border-right: 1px solid #cbcbcb;
799 }
800 .yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
801 .yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
802 .yui-skin-sam .yui-dt-liner {
803 margin: 0;
804 padding: 0;
805 }
806 .yui-skin-sam .yui-dt-coltarget {
807 width: 5px;
808 background-color: red;
809 }
810 .yui-skin-sam .yui-dt td {
811 margin: 0;
812 padding: 0;
813 border: 0;
814 border-right: 1px solid #cbcbcb;
815 text-align: left;
816 }
817 .yui-skin-sam .yui-dt-list td { border-right: 0 }
818 .yui-skin-sam .yui-dt-resizer { width: 6px }
819 .yui-skin-sam .yui-dt-mask {
820 background-color: #000;
821 opacity: .25;
822 filter: alpha(opacity=25);
823 }
824 .yui-skin-sam .yui-dt-message { background-color: #FFF }
825 .yui-skin-sam .yui-dt-scrollable table { border: 0 }
826 .yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
827 border-left: 1px solid #7f7f7f;
828 border-top: 1px solid #7f7f7f;
829 border-right: 1px solid #7f7f7f;
830 }
831 .yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
832 border-left: 1px solid #7f7f7f;
833 border-bottom: 1px solid #7f7f7f;
834 border-right: 1px solid #7f7f7f;
835 background-color: #FFF;
836 }
837 .yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
838 .yui-skin-sam th.yui-dt-asc,
839 .yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
840 .yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
841 .yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
842 .yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
843 tbody .yui-dt-editable { cursor: pointer }
844 .yui-dt-editor {
845 text-align: left;
846 background-color: #f2f2f2;
847 border: 1px solid #808080;
848 padding: 6px;
849 }
850 .yui-dt-editor label {
851 padding-left: 4px;
852 padding-right: 6px;
853 }
854 .yui-dt-editor .yui-dt-button {
855 padding-top: 6px;
856 text-align: right;
857 }
858 .yui-dt-editor .yui-dt-button button {
859 background: url(../images/sprite.png) repeat-x 0 0;
860 border: 1px solid #999;
861 width: 4em;
862 height: 1.8em;
863 margin-left: 6px;
864 }
865 .yui-dt-editor .yui-dt-button button.yui-dt-default {
866 background: url(../images/sprite.png) repeat-x 0 -1400px;
867 background-color: #5584e0;
868 border: 1px solid #304369;
869 color: #FFF;
870 }
871 .yui-dt-editor .yui-dt-button button:hover {
872 background: url(../images/sprite.png) repeat-x 0 -1300px;
873 color: #000;
874 }
875 .yui-dt-editor .yui-dt-button button:active {
876 background: url(../images/sprite.png) repeat-x 0 -1700px;
877 color: #000;
878 }
879 .yui-skin-sam tr.yui-dt-even { background-color: #FFF }
880 .yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
881 .yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
882 .yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
883 .yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
884 .yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
885 .yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
886 .yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
887 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
888 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
889 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
890 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
891 .yui-skin-sam th.yui-dt-highlighted,
892 .yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
893 .yui-skin-sam tr.yui-dt-highlighted,
894 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
895 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
896 .yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
897 .yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
898 cursor: pointer;
899 background-color: #b2d2ff;
900 }
901 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
902 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
903 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
904 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
905 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
906 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
907 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
908 cursor: pointer;
909 background-color: #b2d2ff;
910 }
911 .yui-skin-sam th.yui-dt-selected,
912 .yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
913 .yui-skin-sam tr.yui-dt-selected td,
914 .yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
915 .yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
916 background-color: #426fd9;
917 color: #FFF;
918 }
919 .yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
920 .yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
921 background-color: #446cd7;
922 color: #FFF;
923 }
924 .yui-skin-sam .yui-dt-list th.yui-dt-selected,
925 .yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
926 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
927 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
928 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
929 background-color: #426fd9;
930 color: #FFF;
931 }
932 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
933 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
934 background-color: #446cd7;
935 color: #FFF;
936 }
937 .yui-skin-sam .yui-dt-paginator {
938 display: block;
939 margin: 6px 0;
940 white-space: nowrap;
941 }
942 .yui-skin-sam .yui-dt-paginator .yui-dt-first,
943 .yui-skin-sam .yui-dt-paginator .yui-dt-last,
944 .yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
945 .yui-skin-sam .yui-dt-paginator a.yui-dt-first,
946 .yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
947 .yui-skin-sam .yui-dt-paginator .yui-dt-previous,
948 .yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
949 .yui-skin-sam a.yui-dt-page {
950 border: 1px solid #cbcbcb;
951 padding: 2px 6px;
952 text-decoration: none;
953 background-color: #fff;
954 }
955 .yui-skin-sam .yui-dt-selected {
956 border: 1px solid #fff;
957 background-color: #fff;
620 958 }
621 959
622 960 #content #left {
623 left:0;
624 width:280px;
625 position:absolute;
961 left: 0;
962 width: 280px;
963 position: absolute;
626 964 }
627 965
628 966 #content #right {
629 margin:0 60px 10px 290px;
967 margin: 0 60px 10px 290px;
630 968 }
631 969
632 970 #content div.box {
633 clear:both;
634 overflow:hidden;
635 background:#fff;
636 margin:0 0 10px;
637 padding:0 0 10px;
638 -webkit-border-radius: 4px 4px 4px 4px;
639 -khtml-border-radius: 4px 4px 4px 4px;
640 -moz-border-radius: 4px 4px 4px 4px;
641 border-radius: 4px 4px 4px 4px;
642 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
643
971 clear: both;
972 overflow: hidden;
973 background: #fff;
974 margin: 0 0 10px;
975 padding: 0 0 10px;
976 -webkit-border-radius: 4px 4px 4px 4px;
977 -khtml-border-radius: 4px 4px 4px 4px;
978 -moz-border-radius: 4px 4px 4px 4px;
979 border-radius: 4px 4px 4px 4px;
980 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
644 981 }
645 982
646 983 #content div.box-left {
647 width:49%;
648 clear:none;
649 float:left;
650 margin:0 0 10px;
984 width: 49%;
985 clear: none;
986 float: left;
987 margin: 0 0 10px;
651 988 }
652 989
653 990 #content div.box-right {
654 width:49%;
655 clear:none;
656 float:right;
657 margin:0 0 10px;
991 width: 49%;
992 clear: none;
993 float: right;
994 margin: 0 0 10px;
658 995 }
659 996
660 997 #content div.box div.title {
661 clear:both;
662 overflow:hidden;
663 background:#369 url("../images/header_inner.png") repeat-x;
664 margin:0 0 20px;
665 padding:0;
998 clear: both;
999 overflow: hidden;
1000 background-color: #eedc94;
1001 background-repeat: repeat-x;
1002 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
1003 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1004 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1005 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1006 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1007 background-image: -o-linear-gradient(top, #003b76, #00376e);
1008 background-image: linear-gradient(top, #003b76, #00376e);
1009 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1010 margin: 0 0 20px;
1011 padding: 0;
666 1012 }
667 1013
668 1014 #content div.box div.title h5 {
669 float:left;
670 border:none;
671 color:#fff;
672 text-transform:uppercase;
673 margin:0;
674 padding:11px 0 11px 10px;
1015 float: left;
1016 border: none;
1017 color: #fff;
1018 text-transform: uppercase;
1019 margin: 0;
1020 padding: 11px 0 11px 10px;
1021 }
1022
1023 #content div.box div.title .link-white{
1024 color: #FFFFFF;
675 1025 }
676 1026
677 1027 #content div.box div.title ul.links li {
678 list-style:none;
679 float:left;
680 margin:0;
681 padding:0;
1028 list-style: none;
1029 float: left;
1030 margin: 0;
1031 padding: 0;
682 1032 }
683 1033
684 1034 #content div.box div.title ul.links li a {
685 border-left: 1px solid #316293;
686 color: #FFFFFF;
687 display: block;
688 float: left;
689 font-size: 13px;
690 font-weight: 700;
691 height: 1%;
692 margin: 0;
693 padding: 11px 22px 12px;
694 text-decoration: none;
695 }
696
697 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6 {
698 clear:both;
699 overflow:hidden;
700 border-bottom:1px solid #DDD;
701 margin:10px 20px;
702 padding:0 0 15px;
1035 border-left: 1px solid #316293;
1036 color: #FFFFFF;
1037 display: block;
1038 float: left;
1039 font-size: 13px;
1040 font-weight: 700;
1041 height: 1%;
1042 margin: 0;
1043 padding: 11px 22px 12px;
1044 text-decoration: none;
1045 }
1046
1047 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6
1048 {
1049 clear: both;
1050 overflow: hidden;
1051 border-bottom: 1px solid #DDD;
1052 margin: 10px 20px;
1053 padding: 0 0 15px;
703 1054 }
704 1055
705 1056 #content div.box p {
706 color:#5f5f5f;
707 font-size:12px;
708 line-height:150%;
709 margin:0 24px 10px;
710 padding:0;
1057 color: #5f5f5f;
1058 font-size: 12px;
1059 line-height: 150%;
1060 margin: 0 24px 10px;
1061 padding: 0;
711 1062 }
712 1063
713 1064 #content div.box blockquote {
714 border-left:4px solid #DDD;
715 color:#5f5f5f;
716 font-size:11px;
717 line-height:150%;
718 margin:0 34px;
719 padding:0 0 0 14px;
1065 border-left: 4px solid #DDD;
1066 color: #5f5f5f;
1067 font-size: 11px;
1068 line-height: 150%;
1069 margin: 0 34px;
1070 padding: 0 0 0 14px;
720 1071 }
721 1072
722 1073 #content div.box blockquote p {
723 margin:10px 0;
724 padding:0;
1074 margin: 10px 0;
1075 padding: 0;
725 1076 }
726 1077
727 1078 #content div.box dl {
728 margin:10px 24px;
1079 margin: 10px 0px;
729 1080 }
730 1081
731 1082 #content div.box dt {
732 font-size:12px;
733 margin:0;
1083 font-size: 12px;
1084 margin: 0;
734 1085 }
735 1086
736 1087 #content div.box dd {
737 font-size:12px;
738 margin:0;
739 padding:8px 0 8px 15px;
1088 font-size: 12px;
1089 margin: 0;
1090 padding: 8px 0 8px 15px;
740 1091 }
741 1092
742 1093 #content div.box li {
743 font-size:12px;
744 padding:4px 0;
1094 font-size: 12px;
1095 padding: 4px 0;
745 1096 }
746 1097
747 1098 #content div.box ul.disc,#content div.box ul.circle {
748 margin:10px 24px 10px 38px;
1099 margin: 10px 24px 10px 38px;
749 1100 }
750 1101
751 1102 #content div.box ul.square {
752 margin:10px 24px 10px 40px;
1103 margin: 10px 24px 10px 40px;
753 1104 }
754 1105
755 1106 #content div.box img.left {
756 border:none;
757 float:left;
758 margin:10px 10px 10px 0;
1107 border: none;
1108 float: left;
1109 margin: 10px 10px 10px 0;
759 1110 }
760 1111
761 1112 #content div.box img.right {
762 border:none;
763 float:right;
764 margin:10px 0 10px 10px;
1113 border: none;
1114 float: right;
1115 margin: 10px 0 10px 10px;
765 1116 }
766 1117
767 1118 #content div.box div.messages {
768 clear:both;
769 overflow:hidden;
770 margin:0 20px;
771 padding:0;
1119 clear: both;
1120 overflow: hidden;
1121 margin: 0 20px;
1122 padding: 0;
772 1123 }
773 1124
774 1125 #content div.box div.message {
775 clear:both;
776 overflow:hidden;
777 margin:0;
778 padding:10px 0;
1126 clear: both;
1127 overflow: hidden;
1128 margin: 0;
1129 padding: 5px 0;
1130 white-space: pre-wrap;
1131 }
1132 #content div.box div.expand {
1133 width: 110%;
1134 height:14px;
1135 font-size:10px;
1136 text-align:center;
1137 cursor: pointer;
1138 color:#666;
1139
1140 background:-webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
1141 background:-webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1142 background:-moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1143 background:-o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1144 background:-ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1145 background:linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1146
1147 display: none;
1148 }
1149 #content div.box div.expand .expandtext {
1150 background-color: #ffffff;
1151 padding: 2px;
1152 border-radius: 2px;
779 1153 }
780 1154
781 1155 #content div.box div.message a {
782 font-weight:400 !important;
1156 font-weight: 400 !important;
783 1157 }
784 1158
785 1159 #content div.box div.message div.image {
786 float:left;
787 margin:9px 0 0 5px;
788 padding:6px;
1160 float: left;
1161 margin: 9px 0 0 5px;
1162 padding: 6px;
789 1163 }
790 1164
791 1165 #content div.box div.message div.image img {
792 vertical-align:middle;
793 margin:0;
1166 vertical-align: middle;
1167 margin: 0;
794 1168 }
795 1169
796 1170 #content div.box div.message div.text {
797 float:left;
798 margin:0;
799 padding:9px 6px;
1171 float: left;
1172 margin: 0;
1173 padding: 9px 6px;
800 1174 }
801 1175
802 1176 #content div.box div.message div.dismiss a {
803 height:16px;
804 width:16px;
805 display:block;
806 background:url("../images/icons/cross.png") no-repeat;
807 margin:15px 14px 0 0;
808 padding:0;
809 }
810
811 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6 {
812 border:none;
813 margin:0;
814 padding:0;
1177 height: 16px;
1178 width: 16px;
1179 display: block;
1180 background: url("../images/icons/cross.png") no-repeat;
1181 margin: 15px 14px 0 0;
1182 padding: 0;
1183 }
1184
1185 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6
1186 {
1187 border: none;
1188 margin: 0;
1189 padding: 0;
815 1190 }
816 1191
817 1192 #content div.box div.message div.text span {
818 height:1%;
819 display:block;
820 margin:0;
821 padding:5px 0 0;
1193 height: 1%;
1194 display: block;
1195 margin: 0;
1196 padding: 5px 0 0;
822 1197 }
823 1198
824 1199 #content div.box div.message-error {
825 height:1%;
826 clear:both;
827 overflow:hidden;
828 background:#FBE3E4;
829 border:1px solid #FBC2C4;
830 color:#860006;
1200 height: 1%;
1201 clear: both;
1202 overflow: hidden;
1203 background: #FBE3E4;
1204 border: 1px solid #FBC2C4;
1205 color: #860006;
831 1206 }
832 1207
833 1208 #content div.box div.message-error h6 {
834 color:#860006;
1209 color: #860006;
835 1210 }
836 1211
837 1212 #content div.box div.message-warning {
838 height:1%;
839 clear:both;
840 overflow:hidden;
841 background:#FFF6BF;
842 border:1px solid #FFD324;
843 color:#5f5200;
1213 height: 1%;
1214 clear: both;
1215 overflow: hidden;
1216 background: #FFF6BF;
1217 border: 1px solid #FFD324;
1218 color: #5f5200;
844 1219 }
845 1220
846 1221 #content div.box div.message-warning h6 {
847 color:#5f5200;
1222 color: #5f5200;
848 1223 }
849 1224
850 1225 #content div.box div.message-notice {
851 height:1%;
852 clear:both;
853 overflow:hidden;
854 background:#8FBDE0;
855 border:1px solid #6BACDE;
856 color:#003863;
1226 height: 1%;
1227 clear: both;
1228 overflow: hidden;
1229 background: #8FBDE0;
1230 border: 1px solid #6BACDE;
1231 color: #003863;
857 1232 }
858 1233
859 1234 #content div.box div.message-notice h6 {
860 color:#003863;
1235 color: #003863;
861 1236 }
862 1237
863 1238 #content div.box div.message-success {
864 height:1%;
865 clear:both;
866 overflow:hidden;
867 background:#E6EFC2;
868 border:1px solid #C6D880;
869 color:#4e6100;
1239 height: 1%;
1240 clear: both;
1241 overflow: hidden;
1242 background: #E6EFC2;
1243 border: 1px solid #C6D880;
1244 color: #4e6100;
870 1245 }
871 1246
872 1247 #content div.box div.message-success h6 {
873 color:#4e6100;
1248 color: #4e6100;
874 1249 }
875 1250
876 1251 #content div.box div.form div.fields div.field {
877 height:1%;
878 border-bottom:1px solid #DDD;
879 clear:both;
880 margin:0;
881 padding:10px 0;
1252 height: 1%;
1253 border-bottom: 1px solid #DDD;
1254 clear: both;
1255 margin: 0;
1256 padding: 10px 0;
882 1257 }
883 1258
884 1259 #content div.box div.form div.fields div.field-first {
885 padding:0 0 10px;
1260 padding: 0 0 10px;
886 1261 }
887 1262
888 1263 #content div.box div.form div.fields div.field-noborder {
889 border-bottom:0 !important;
1264 border-bottom: 0 !important;
890 1265 }
891 1266
892 1267 #content div.box div.form div.fields div.field span.error-message {
893 height:1%;
894 display:inline-block;
895 color:red;
896 margin:8px 0 0 4px;
897 padding:0;
1268 height: 1%;
1269 display: inline-block;
1270 color: red;
1271 margin: 8px 0 0 4px;
1272 padding: 0;
898 1273 }
899 1274
900 1275 #content div.box div.form div.fields div.field span.success {
901 height:1%;
902 display:block;
903 color:#316309;
904 margin:8px 0 0;
905 padding:0;
1276 height: 1%;
1277 display: block;
1278 color: #316309;
1279 margin: 8px 0 0;
1280 padding: 0;
906 1281 }
907 1282
908 1283 #content div.box div.form div.fields div.field div.label {
909 left:70px;
910 width:155px;
911 position:absolute;
912 margin:0;
913 padding:8px 0 0 5px;
914 }
915
916 #content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label {
917 clear:both;
918 overflow:hidden;
919 left:0;
920 width:auto;
921 position:relative;
922 margin:0;
923 padding:0 0 8px;
1284 left: 70px;
1285 width: 155px;
1286 position: absolute;
1287 margin: 0;
1288 padding: 5px 0 0 0px;
1289 }
1290
1291 #content div.box div.form div.fields div.field div.label-summary {
1292 left: 30px;
1293 width: 155px;
1294 position: absolute;
1295 margin: 0;
1296 padding: 0px 0 0 0px;
1297 }
1298
1299 #content div.box-left div.form div.fields div.field div.label,
1300 #content div.box-right div.form div.fields div.field div.label,
1301 #content div.box-left div.form div.fields div.field div.label,
1302 #content div.box-left div.form div.fields div.field div.label-summary,
1303 #content div.box-right div.form div.fields div.field div.label-summary,
1304 #content div.box-left div.form div.fields div.field div.label-summary
1305 {
1306 clear: both;
1307 overflow: hidden;
1308 left: 0;
1309 width: auto;
1310 position: relative;
1311 margin: 0;
1312 padding: 0 0 8px;
924 1313 }
925 1314
926 1315 #content div.box div.form div.fields div.field div.label-select {
927 padding:5px 0 0 5px;
928 }
929
930 #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select {
931 padding:0 0 8px;
932 }
933
934 #content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea {
935 padding:0 0 8px !important;
936 }
937
938 #content div.box div.form div.fields div.field div.label label, div.label label{
939 color:#393939;
940 font-weight:700;
941 }
942
1316 padding: 5px 0 0 5px;
1317 }
1318
1319 #content div.box-left div.form div.fields div.field div.label-select,
1320 #content div.box-right div.form div.fields div.field div.label-select
1321 {
1322 padding: 0 0 8px;
1323 }
1324
1325 #content div.box-left div.form div.fields div.field div.label-textarea,
1326 #content div.box-right div.form div.fields div.field div.label-textarea
1327 {
1328 padding: 0 0 8px !important;
1329 }
1330
1331 #content div.box div.form div.fields div.field div.label label,div.label label
1332 {
1333 color: #393939;
1334 font-weight: 700;
1335 }
1336 #content div.box div.form div.fields div.field div.label label,div.label-summary label
1337 {
1338 color: #393939;
1339 font-weight: 700;
1340 }
943 1341 #content div.box div.form div.fields div.field div.input {
944 margin:0 0 0 200px;
1342 margin: 0 0 0 200px;
1343 }
1344
1345 #content div.box div.form div.fields div.field div.input.summary {
1346 margin: 0 0 0 110px;
1347 }
1348 #content div.box div.form div.fields div.field div.input.summary-short {
1349 margin: 0 0 0 110px;
945 1350 }
946 1351 #content div.box div.form div.fields div.field div.file {
947 margin:0 0 0 200px;
948 }
949 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input {
950 margin:0 0 0 0px;
1352 margin: 0 0 0 200px;
1353 }
1354
1355 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
1356 {
1357 margin: 0 0 0 0px;
951 1358 }
952 1359
953 1360 #content div.box div.form div.fields div.field div.input input {
954 background:#FFF;
955 border-top:1px solid #b3b3b3;
956 border-left:1px solid #b3b3b3;
957 border-right:1px solid #eaeaea;
958 border-bottom:1px solid #eaeaea;
959 color:#000;
960 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
961 font-size:11px;
962 margin:0;
963 padding:7px 7px 6px;
1361 background: #FFF;
1362 border-top: 1px solid #b3b3b3;
1363 border-left: 1px solid #b3b3b3;
1364 border-right: 1px solid #eaeaea;
1365 border-bottom: 1px solid #eaeaea;
1366 color: #000;
1367 font-size: 11px;
1368 margin: 0;
1369 padding: 7px 7px 6px;
1370 }
1371
1372 #content div.box div.form div.fields div.field div.input input#clone_url,
1373 #content div.box div.form div.fields div.field div.input input#clone_url_id
1374 {
1375 font-size: 16px;
1376 padding: 2px;
964 1377 }
965 1378
966 1379 #content div.box div.form div.fields div.field div.file input {
967 background: none repeat scroll 0 0 #FFFFFF;
968 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
969 border-style: solid;
970 border-width: 1px;
971 color: #000000;
972 font-family: Lucida Grande,Verdana,Lucida Sans Regular,Lucida Sans Unicode,Arial,sans-serif;
973 font-size: 11px;
974 margin: 0;
975 padding: 7px 7px 6px;
1380 background: none repeat scroll 0 0 #FFFFFF;
1381 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
1382 border-style: solid;
1383 border-width: 1px;
1384 color: #000000;
1385 font-size: 11px;
1386 margin: 0;
1387 padding: 7px 7px 6px;
976 1388 }
977 1389
978 1390 input.disabled {
979 1391 background-color: #F5F5F5 !important;
980 1392 }
981
982 1393 #content div.box div.form div.fields div.field div.input input.small {
983 width:30%;
1394 width: 30%;
984 1395 }
985 1396
986 1397 #content div.box div.form div.fields div.field div.input input.medium {
987 width:55%;
1398 width: 55%;
988 1399 }
989 1400
990 1401 #content div.box div.form div.fields div.field div.input input.large {
991 width:85%;
1402 width: 85%;
992 1403 }
993 1404
994 1405 #content div.box div.form div.fields div.field div.input input.date {
995 width:177px;
1406 width: 177px;
996 1407 }
997 1408
998 1409 #content div.box div.form div.fields div.field div.input input.button {
999 background:#D4D0C8;
1000 border-top:1px solid #FFF;
1001 border-left:1px solid #FFF;
1002 border-right:1px solid #404040;
1003 border-bottom:1px solid #404040;
1004 color:#000;
1005 margin:0;
1006 padding:4px 8px;
1410 background: #D4D0C8;
1411 border-top: 1px solid #FFF;
1412 border-left: 1px solid #FFF;
1413 border-right: 1px solid #404040;
1414 border-bottom: 1px solid #404040;
1415 color: #000;
1416 margin: 0;
1417 padding: 4px 8px;
1007 1418 }
1008 1419
1009 1420 #content div.box div.form div.fields div.field div.textarea {
1010 border-top:1px solid #b3b3b3;
1011 border-left:1px solid #b3b3b3;
1012 border-right:1px solid #eaeaea;
1013 border-bottom:1px solid #eaeaea;
1014 margin:0 0 0 200px;
1015 padding:10px;
1421 border-top: 1px solid #b3b3b3;
1422 border-left: 1px solid #b3b3b3;
1423 border-right: 1px solid #eaeaea;
1424 border-bottom: 1px solid #eaeaea;
1425 margin: 0 0 0 200px;
1426 padding: 10px;
1016 1427 }
1017 1428
1018 1429 #content div.box div.form div.fields div.field div.textarea-editor {
1019 border:1px solid #ddd;
1020 padding:0;
1430 border: 1px solid #ddd;
1431 padding: 0;
1021 1432 }
1022 1433
1023 1434 #content div.box div.form div.fields div.field div.textarea textarea {
1024 width:100%;
1025 height:220px;
1026 overflow:hidden;
1027 background:#FFF;
1028 color:#000;
1029 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1030 font-size:11px;
1031 outline:none;
1032 border-width:0;
1033 margin:0;
1034 padding:0;
1035 }
1036
1037 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea {
1038 width:100%;
1039 height:100px;
1435 width: 100%;
1436 height: 220px;
1437 overflow: hidden;
1438 background: #FFF;
1439 color: #000;
1440 font-size: 11px;
1441 outline: none;
1442 border-width: 0;
1443 margin: 0;
1444 padding: 0;
1445 }
1446
1447 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea
1448 {
1449 width: 100%;
1450 height: 100px;
1040 1451 }
1041 1452
1042 1453 #content div.box div.form div.fields div.field div.textarea table {
1043 width:100%;
1044 border:none;
1045 margin:0;
1046 padding:0;
1454 width: 100%;
1455 border: none;
1456 margin: 0;
1457 padding: 0;
1047 1458 }
1048 1459
1049 1460 #content div.box div.form div.fields div.field div.textarea table td {
1050 background:#DDD;
1051 border:none;
1052 padding:0;
1053 }
1054
1055 #content div.box div.form div.fields div.field div.textarea table td table {
1056 width:auto;
1057 border:none;
1058 margin:0;
1059 padding:0;
1060 }
1061
1062 #content div.box div.form div.fields div.field div.textarea table td table td {
1063 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1064 font-size:11px;
1065 padding:5px 5px 5px 0;
1066 }
1067
1068 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus {
1069 background:#f6f6f6;
1070 border-color:#666;
1461 background: #DDD;
1462 border: none;
1463 padding: 0;
1464 }
1465
1466 #content div.box div.form div.fields div.field div.textarea table td table
1467 {
1468 width: auto;
1469 border: none;
1470 margin: 0;
1471 padding: 0;
1472 }
1473
1474 #content div.box div.form div.fields div.field div.textarea table td table td
1475 {
1476 font-size: 11px;
1477 padding: 5px 5px 5px 0;
1478 }
1479
1480 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus
1481 {
1482 background: #f6f6f6;
1483 border-color: #666;
1071 1484 }
1072 1485
1073 1486 div.form div.fields div.field div.button {
1074 margin:0;
1075 padding:0 0 0 8px;
1076 }
1077
1487 margin: 0;
1488 padding: 0 0 0 8px;
1489 }
1490 #content div.box table.noborder {
1491 border: 1px solid transparent;
1492 }
1078 1493
1079 1494 #content div.box table {
1080 width:100%;
1081 border-collapse:collapse;
1082 margin:0;
1083 padding:0;
1084 border: 1px solid #eee;
1495 width: 100%;
1496 border-collapse: separate;
1497 margin: 0;
1498 padding: 0;
1499 border: 1px solid #eee;
1500 -webkit-border-radius: 4px;
1501 -moz-border-radius: 4px;
1502 border-radius: 4px;
1085 1503 }
1086 1504
1087 1505 #content div.box table th {
1088 background:#eee;
1089 border-bottom:1px solid #ddd;
1090 padding:5px 0px 5px 5px;
1506 background: #eee;
1507 border-bottom: 1px solid #ddd;
1508 padding: 5px 0px 5px 5px;
1091 1509 }
1092 1510
1093 1511 #content div.box table th.left {
1094 text-align:left;
1512 text-align: left;
1095 1513 }
1096 1514
1097 1515 #content div.box table th.right {
1098 text-align:right;
1516 text-align: right;
1099 1517 }
1100 1518
1101 1519 #content div.box table th.center {
1102 text-align:center;
1520 text-align: center;
1103 1521 }
1104 1522
1105 1523 #content div.box table th.selected {
1106 vertical-align:middle;
1107 padding:0;
1524 vertical-align: middle;
1525 padding: 0;
1108 1526 }
1109 1527
1110 1528 #content div.box table td {
1111 background:#fff;
1112 border-bottom:1px solid #cdcdcd;
1113 vertical-align:middle;
1114 padding:5px;
1529 background: #fff;
1530 border-bottom: 1px solid #cdcdcd;
1531 vertical-align: middle;
1532 padding: 5px;
1115 1533 }
1116 1534
1117 1535 #content div.box table tr.selected td {
1118 background:#FFC;
1536 background: #FFC;
1119 1537 }
1120 1538
1121 1539 #content div.box table td.selected {
1122 width:3%;
1123 text-align:center;
1124 vertical-align:middle;
1125 padding:0;
1540 width: 3%;
1541 text-align: center;
1542 vertical-align: middle;
1543 padding: 0;
1126 1544 }
1127 1545
1128 1546 #content div.box table td.action {
1129 width:45%;
1130 text-align:left;
1547 width: 45%;
1548 text-align: left;
1131 1549 }
1132 1550
1133 1551 #content div.box table td.date {
1134 width:33%;
1135 text-align:center;
1552 width: 33%;
1553 text-align: center;
1136 1554 }
1137 1555
1138 1556 #content div.box div.action {
1139 float:right;
1140 background:#FFF;
1141 text-align:right;
1142 margin:10px 0 0;
1143 padding:0;
1557 float: right;
1558 background: #FFF;
1559 text-align: right;
1560 margin: 10px 0 0;
1561 padding: 0;
1144 1562 }
1145 1563
1146 1564 #content div.box div.action select {
1147 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1148 font-size:11px;
1149 margin:0;
1565 font-size: 11px;
1566 margin: 0;
1150 1567 }
1151 1568
1152 1569 #content div.box div.action .ui-selectmenu {
1153 margin:0;
1154 padding:0;
1570 margin: 0;
1571 padding: 0;
1155 1572 }
1156 1573
1157 1574 #content div.box div.pagination {
1158 height:1%;
1159 clear:both;
1160 overflow:hidden;
1161 margin:10px 0 0;
1162 padding:0;
1575 height: 1%;
1576 clear: both;
1577 overflow: hidden;
1578 margin: 10px 0 0;
1579 padding: 0;
1163 1580 }
1164 1581
1165 1582 #content div.box div.pagination ul.pager {
1166 float:right;
1167 text-align:right;
1168 margin:0;
1169 padding:0;
1583 float: right;
1584 text-align: right;
1585 margin: 0;
1586 padding: 0;
1170 1587 }
1171 1588
1172 1589 #content div.box div.pagination ul.pager li {
1173 height:1%;
1174 float:left;
1175 list-style:none;
1176 background:#ebebeb url("../images/pager.png") repeat-x;
1177 border-top:1px solid #dedede;
1178 border-left:1px solid #cfcfcf;
1179 border-right:1px solid #c4c4c4;
1180 border-bottom:1px solid #c4c4c4;
1181 color:#4A4A4A;
1182 font-weight:700;
1183 margin:0 0 0 4px;
1184 padding:0;
1590 height: 1%;
1591 float: left;
1592 list-style: none;
1593 background: #ebebeb url("../images/pager.png") repeat-x;
1594 border-top: 1px solid #dedede;
1595 border-left: 1px solid #cfcfcf;
1596 border-right: 1px solid #c4c4c4;
1597 border-bottom: 1px solid #c4c4c4;
1598 color: #4A4A4A;
1599 font-weight: 700;
1600 margin: 0 0 0 4px;
1601 padding: 0;
1185 1602 }
1186 1603
1187 1604 #content div.box div.pagination ul.pager li.separator {
1188 padding:6px;
1605 padding: 6px;
1189 1606 }
1190 1607
1191 1608 #content div.box div.pagination ul.pager li.current {
1192 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1193 border-top:1px solid #ccc;
1194 border-left:1px solid #bebebe;
1195 border-right:1px solid #b1b1b1;
1196 border-bottom:1px solid #afafaf;
1197 color:#515151;
1198 padding:6px;
1609 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1610 border-top: 1px solid #ccc;
1611 border-left: 1px solid #bebebe;
1612 border-right: 1px solid #b1b1b1;
1613 border-bottom: 1px solid #afafaf;
1614 color: #515151;
1615 padding: 6px;
1199 1616 }
1200 1617
1201 1618 #content div.box div.pagination ul.pager li a {
1202 height:1%;
1203 display:block;
1204 float:left;
1205 color:#515151;
1206 text-decoration:none;
1207 margin:0;
1208 padding:6px;
1209 }
1210
1211 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
1212 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1213 border-top:1px solid #ccc;
1214 border-left:1px solid #bebebe;
1215 border-right:1px solid #b1b1b1;
1216 border-bottom:1px solid #afafaf;
1217 margin:-1px;
1619 height: 1%;
1620 display: block;
1621 float: left;
1622 color: #515151;
1623 text-decoration: none;
1624 margin: 0;
1625 padding: 6px;
1626 }
1627
1628 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
1629 {
1630 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1631 border-top: 1px solid #ccc;
1632 border-left: 1px solid #bebebe;
1633 border-right: 1px solid #b1b1b1;
1634 border-bottom: 1px solid #afafaf;
1635 margin: -1px;
1218 1636 }
1219 1637
1220 1638 #content div.box div.pagination-wh {
1221 height:1%;
1222 clear:both;
1223 overflow:hidden;
1224 text-align:right;
1225 margin:10px 0 0;
1226 padding:0;
1639 height: 1%;
1640 clear: both;
1641 overflow: hidden;
1642 text-align: right;
1643 margin: 10px 0 0;
1644 padding: 0;
1227 1645 }
1228 1646
1229 1647 #content div.box div.pagination-right {
1230 float:right;
1231 }
1232
1233 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
1234 height:1%;
1235 float:left;
1236 background:#ebebeb url("../images/pager.png") repeat-x;
1237 border-top:1px solid #dedede;
1238 border-left:1px solid #cfcfcf;
1239 border-right:1px solid #c4c4c4;
1240 border-bottom:1px solid #c4c4c4;
1241 color:#4A4A4A;
1242 font-weight:700;
1243 margin:0 0 0 4px;
1244 padding:6px;
1648 float: right;
1649 }
1650
1651 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot
1652 {
1653 height: 1%;
1654 float: left;
1655 background: #ebebeb url("../images/pager.png") repeat-x;
1656 border-top: 1px solid #dedede;
1657 border-left: 1px solid #cfcfcf;
1658 border-right: 1px solid #c4c4c4;
1659 border-bottom: 1px solid #c4c4c4;
1660 color: #4A4A4A;
1661 font-weight: 700;
1662 margin: 0 0 0 4px;
1663 padding: 6px;
1245 1664 }
1246 1665
1247 1666 #content div.box div.pagination-wh span.pager_curpage {
1248 height:1%;
1249 float:left;
1250 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1251 border-top:1px solid #ccc;
1252 border-left:1px solid #bebebe;
1253 border-right:1px solid #b1b1b1;
1254 border-bottom:1px solid #afafaf;
1255 color:#515151;
1256 font-weight:700;
1257 margin:0 0 0 4px;
1258 padding:6px;
1259 }
1260
1261 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
1262 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1263 border-top:1px solid #ccc;
1264 border-left:1px solid #bebebe;
1265 border-right:1px solid #b1b1b1;
1266 border-bottom:1px solid #afafaf;
1267 text-decoration:none;
1667 height: 1%;
1668 float: left;
1669 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1670 border-top: 1px solid #ccc;
1671 border-left: 1px solid #bebebe;
1672 border-right: 1px solid #b1b1b1;
1673 border-bottom: 1px solid #afafaf;
1674 color: #515151;
1675 font-weight: 700;
1676 margin: 0 0 0 4px;
1677 padding: 6px;
1678 }
1679
1680 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
1681 {
1682 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1683 border-top: 1px solid #ccc;
1684 border-left: 1px solid #bebebe;
1685 border-right: 1px solid #b1b1b1;
1686 border-bottom: 1px solid #afafaf;
1687 text-decoration: none;
1268 1688 }
1269 1689
1270 1690 #content div.box div.traffic div.legend {
1271 clear:both;
1272 overflow:hidden;
1273 border-bottom:1px solid #ddd;
1274 margin:0 0 10px;
1275 padding:0 0 10px;
1691 clear: both;
1692 overflow: hidden;
1693 border-bottom: 1px solid #ddd;
1694 margin: 0 0 10px;
1695 padding: 0 0 10px;
1276 1696 }
1277 1697
1278 1698 #content div.box div.traffic div.legend h6 {
1279 float:left;
1280 border:none;
1281 margin:0;
1282 padding:0;
1699 float: left;
1700 border: none;
1701 margin: 0;
1702 padding: 0;
1283 1703 }
1284 1704
1285 1705 #content div.box div.traffic div.legend li {
1286 list-style:none;
1287 float:left;
1288 font-size:11px;
1289 margin:0;
1290 padding:0 8px 0 4px;
1706 list-style: none;
1707 float: left;
1708 font-size: 11px;
1709 margin: 0;
1710 padding: 0 8px 0 4px;
1291 1711 }
1292 1712
1293 1713 #content div.box div.traffic div.legend li.visits {
1294 border-left:12px solid #edc240;
1714 border-left: 12px solid #edc240;
1295 1715 }
1296 1716
1297 1717 #content div.box div.traffic div.legend li.pageviews {
1298 border-left:12px solid #afd8f8;
1718 border-left: 12px solid #afd8f8;
1299 1719 }
1300 1720
1301 1721 #content div.box div.traffic table {
1302 width:auto;
1722 width: auto;
1303 1723 }
1304 1724
1305 1725 #content div.box div.traffic table td {
1306 background:transparent;
1307 border:none;
1308 padding:2px 3px 3px;
1726 background: transparent;
1727 border: none;
1728 padding: 2px 3px 3px;
1309 1729 }
1310 1730
1311 1731 #content div.box div.traffic table td.legendLabel {
1312 padding:0 3px 2px;
1313 }
1314
1315 #summary{
1316
1317 }
1318
1319 #summary .desc{
1320 white-space: pre;
1321 width: 100%;
1322 }
1323
1324 #summary .repo_name{
1325 font-size: 1.6em;
1326 font-weight: bold;
1327 vertical-align: baseline;
1328 clear:right
1329 }
1330
1732 padding: 0 3px 2px;
1733 }
1734
1735 #summary {
1736
1737 }
1738
1739 #summary .desc {
1740 white-space: pre;
1741 width: 100%;
1742 }
1743
1744 #summary .repo_name {
1745 font-size: 1.6em;
1746 font-weight: bold;
1747 vertical-align: baseline;
1748 clear: right
1749 }
1331 1750
1332 1751 #footer {
1333 clear:both;
1334 overflow:hidden;
1335 text-align:right;
1336 margin:0;
1337 padding:0 10px 4px;
1338 margin:-10px 0 0;
1752 clear: both;
1753 overflow: hidden;
1754 text-align: right;
1755 margin: 0;
1756 padding: 0 10px 4px;
1757 margin: -10px 0 0;
1339 1758 }
1340 1759
1341 1760 #footer div#footer-inner {
1342 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
1343 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1344 -webkit-border-radius: 4px 4px 4px 4px;
1345 -khtml-border-radius: 4px 4px 4px 4px;
1346 -moz-border-radius: 4px 4px 4px 4px;
1347 border-radius: 4px 4px 4px 4px;
1761 background-color: #eedc94; background-repeat : repeat-x;
1762 background-image : -khtml-gradient( linear, left top, left bottom,
1763 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1764 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1765 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1766 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1767 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1768 background-image : -o-linear-gradient( top, #003b76, #00376e));
1769 background-image : linear-gradient( top, #003b76, #00376e); filter :
1770 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1771 '#003b76', endColorstr = '#00376e', GradientType = 0);
1772 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1773 -webkit-border-radius: 4px 4px 4px 4px;
1774 -khtml-border-radius: 4px 4px 4px 4px;
1775 -moz-border-radius: 4px 4px 4px 4px;
1776 border-radius: 4px 4px 4px 4px;
1777 background-repeat: repeat-x;
1778 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1779 to(#eedc94) );
1780 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1781 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1782 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1783 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1784 background-image: -o-linear-gradient(top, #003b76, #00376e);
1785 background-image: linear-gradient(top, #003b76, #00376e);
1786 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1787 endColorstr='#00376e', GradientType=0 );
1348 1788 }
1349 1789
1350 1790 #footer div#footer-inner p {
1351 padding:15px 25px 15px 0;
1352 color:#FFF;
1353 font-weight:700;
1354 }
1791 padding: 15px 25px 15px 0;
1792 color: #FFF;
1793 font-weight: 700;
1794 }
1795
1355 1796 #footer div#footer-inner .footer-link {
1356 float:left;
1357 padding-left:10px;
1358 }
1359 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
1360 color:#FFF;
1797 float: left;
1798 padding-left: 10px;
1799 }
1800
1801 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
1802 {
1803 color: #FFF;
1361 1804 }
1362 1805
1363 1806 #login div.title {
1364 width:420px;
1365 clear:both;
1366 overflow:hidden;
1367 position:relative;
1368 background:#003367 url("../images/header_inner.png") repeat-x;
1369 margin:0 auto;
1370 padding:0;
1807 width: 420px;
1808 clear: both;
1809 overflow: hidden;
1810 position: relative;
1811 background-color: #eedc94; background-repeat : repeat-x;
1812 background-image : -khtml-gradient( linear, left top, left bottom,
1813 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1814 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1815 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1816 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1817 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1818 background-image : -o-linear-gradient( top, #003b76, #00376e));
1819 background-image : linear-gradient( top, #003b76, #00376e); filter :
1820 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1821 '#003b76', endColorstr = '#00376e', GradientType = 0);
1822 margin: 0 auto;
1823 padding: 0;
1824 background-repeat: repeat-x;
1825 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1826 to(#eedc94) );
1827 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1828 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1829 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1830 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1831 background-image: -o-linear-gradient(top, #003b76, #00376e);
1832 background-image: linear-gradient(top, #003b76, #00376e);
1833 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1834 endColorstr='#00376e', GradientType=0 );
1371 1835 }
1372 1836
1373 1837 #login div.inner {
1374 width:380px;
1375 background:#FFF url("../images/login.png") no-repeat top left;
1376 border-top:none;
1377 border-bottom:none;
1378 margin:0 auto;
1379 padding:20px;
1838 width: 380px;
1839 background: #FFF url("../images/login.png") no-repeat top left;
1840 border-top: none;
1841 border-bottom: none;
1842 margin: 0 auto;
1843 padding: 20px;
1380 1844 }
1381 1845
1382 1846 #login div.form div.fields div.field div.label {
1383 width:173px;
1384 float:left;
1385 text-align:right;
1386 margin:2px 10px 0 0;
1387 padding:5px 0 0 5px;
1847 width: 173px;
1848 float: left;
1849 text-align: right;
1850 margin: 2px 10px 0 0;
1851 padding: 5px 0 0 5px;
1388 1852 }
1389 1853
1390 1854 #login div.form div.fields div.field div.input input {
1391 width:176px;
1392 background:#FFF;
1393 border-top:1px solid #b3b3b3;
1394 border-left:1px solid #b3b3b3;
1395 border-right:1px solid #eaeaea;
1396 border-bottom:1px solid #eaeaea;
1397 color:#000;
1398 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1399 font-size:11px;
1400 margin:0;
1401 padding:7px 7px 6px;
1855 width: 176px;
1856 background: #FFF;
1857 border-top: 1px solid #b3b3b3;
1858 border-left: 1px solid #b3b3b3;
1859 border-right: 1px solid #eaeaea;
1860 border-bottom: 1px solid #eaeaea;
1861 color: #000;
1862 font-size: 11px;
1863 margin: 0;
1864 padding: 7px 7px 6px;
1402 1865 }
1403 1866
1404 1867 #login div.form div.fields div.buttons {
1405 clear:both;
1406 overflow:hidden;
1407 border-top:1px solid #DDD;
1408 text-align:right;
1409 margin:0;
1410 padding:10px 0 0;
1868 clear: both;
1869 overflow: hidden;
1870 border-top: 1px solid #DDD;
1871 text-align: right;
1872 margin: 0;
1873 padding: 10px 0 0;
1411 1874 }
1412 1875
1413 1876 #login div.form div.links {
1414 clear:both;
1415 overflow:hidden;
1416 margin:10px 0 0;
1417 padding:0 0 2px;
1418 }
1419
1877 clear: both;
1878 overflow: hidden;
1879 margin: 10px 0 0;
1880 padding: 0 0 2px;
1881 }
1882
1883 .user-menu{
1884 margin: 0px !important;
1885 float: left;
1886 }
1887
1888 .user-menu .container{
1889 padding:0px 4px 0px 4px;
1890 margin: 0px 0px 0px 0px;
1891 }
1892
1893 .user-menu .gravatar{
1894 margin: 0px 0px 0px 0px;
1895 cursor: pointer;
1896 }
1897 .user-menu .gravatar.enabled{
1898 background-color: #FDF784 !important;
1899 }
1900 .user-menu .gravatar:hover{
1901 background-color: #FDF784 !important;
1902 }
1420 1903 #quick_login{
1421 top: 31px;
1422 background-color: rgb(0, 51, 103);
1423 z-index: 999;
1424 height: 150px;
1425 position: absolute;
1426 margin-left: -16px;
1427 width: 281px;
1428 -webkit-border-radius: 0px 0px 4px 4px;
1429 -khtml-border-radius: 0px 0px 4px 4px;
1430 -moz-border-radius: 0px 0px 4px 4px;
1431 border-radius: 0px 0px 4px 4px;
1432
1433 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1434 }
1435
1436 #quick_login .password_forgoten{
1437 padding-right:10px;
1438 padding-top:0px;
1439 float:left;
1440 }
1441 #quick_login .password_forgoten a{
1442 font-size: 10px
1443 }
1444
1445 #quick_login .register{
1446 padding-right:10px;
1447 padding-top:5px;
1448 float:left;
1449 }
1450
1451 #quick_login .register a{
1452 font-size: 10px
1453 }
1454 #quick_login div.form div.fields{
1455 padding-top: 2px;
1456 padding-left:10px;
1457 }
1458
1459 #quick_login div.form div.fields div.field{
1460 padding: 5px;
1461 }
1462
1463 #quick_login div.form div.fields div.field div.label label{
1464 color:#fff;
1465 padding-bottom: 3px;
1904 min-height: 80px;
1905 margin: 37px 0 0 -251px;
1906 padding: 4px;
1907 position: absolute;
1908 width: 278px;
1909
1910 background-repeat: repeat-x;
1911 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1912 to(#eedc94) );
1913 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1914 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1915 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1916 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1917 background-image: -o-linear-gradient(top, #003b76, #00376e);
1918 background-image: linear-gradient(top, #003b76, #00376e);
1919 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1920 endColorstr='#00376e', GradientType=0 );
1921
1922 z-index: 999;
1923 -webkit-border-radius: 0px 0px 4px 4px;
1924 -khtml-border-radius: 0px 0px 4px 4px;
1925 -moz-border-radius: 0px 0px 4px 4px;
1926 border-radius: 0px 0px 4px 4px;
1927 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1928 }
1929 #quick_login h4{
1930 color: #fff;
1931 padding: 5px 0px 5px 14px;
1932 }
1933
1934 #quick_login .password_forgoten {
1935 padding-right: 10px;
1936 padding-top: 0px;
1937 text-align: left;
1938 }
1939
1940 #quick_login .password_forgoten a {
1941 font-size: 10px;
1942 color: #fff;
1943 }
1944
1945 #quick_login .register {
1946 padding-right: 10px;
1947 padding-top: 5px;
1948 text-align: left;
1949 }
1950
1951 #quick_login .register a {
1952 font-size: 10px;
1953 color: #fff;
1954 }
1955
1956 #quick_login .submit {
1957 margin: -20px 0 0 0px;
1958 position: absolute;
1959 right: 15px;
1960 }
1961
1962 #quick_login .links_left{
1963 float: left;
1964 }
1965 #quick_login .links_right{
1966 float: right;
1967 }
1968 #quick_login .full_name{
1969 color: #FFFFFF;
1970 font-weight: bold;
1971 padding: 3px;
1972 }
1973 #quick_login .big_gravatar{
1974 padding:4px 0px 0px 6px;
1975 }
1976 #quick_login .inbox{
1977 padding:4px 0px 0px 6px;
1978 color: #FFFFFF;
1979 font-weight: bold;
1980 }
1981 #quick_login .inbox a{
1982 color: #FFFFFF;
1983 }
1984 #quick_login .email,#quick_login .email a{
1985 color: #FFFFFF;
1986 padding: 3px;
1987
1988 }
1989 #quick_login .links .logout{
1990
1991 }
1992
1993 #quick_login div.form div.fields {
1994 padding-top: 2px;
1995 padding-left: 10px;
1996 }
1997
1998 #quick_login div.form div.fields div.field {
1999 padding: 5px;
2000 }
2001
2002 #quick_login div.form div.fields div.field div.label label {
2003 color: #fff;
2004 padding-bottom: 3px;
1466 2005 }
1467 2006
1468 2007 #quick_login div.form div.fields div.field div.input input {
1469 width:236px;
1470 background:#FFF;
1471 border-top:1px solid #b3b3b3;
1472 border-left:1px solid #b3b3b3;
1473 border-right:1px solid #eaeaea;
1474 border-bottom:1px solid #eaeaea;
1475 color:#000;
1476 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1477 font-size:11px;
1478 margin:0;
1479 padding:5px 7px 4px;
2008 width: 236px;
2009 background: #FFF;
2010 border-top: 1px solid #b3b3b3;
2011 border-left: 1px solid #b3b3b3;
2012 border-right: 1px solid #eaeaea;
2013 border-bottom: 1px solid #eaeaea;
2014 color: #000;
2015 font-size: 11px;
2016 margin: 0;
2017 padding: 5px 7px 4px;
1480 2018 }
1481 2019
1482 2020 #quick_login div.form div.fields div.buttons {
1483 clear:both;
1484 overflow:hidden;
1485 text-align:right;
1486 margin:0;
1487 padding:10px 14px 0px 5px;
2021 clear: both;
2022 overflow: hidden;
2023 text-align: right;
2024 margin: 0;
2025 padding: 5px 14px 0px 5px;
1488 2026 }
1489 2027
1490 2028 #quick_login div.form div.links {
1491 clear:both;
1492 overflow:hidden;
1493 margin:10px 0 0;
1494 padding:0 0 2px;
2029 clear: both;
2030 overflow: hidden;
2031 margin: 10px 0 0;
2032 padding: 0 0 2px;
2033 }
2034
2035 #quick_login ol.links{
2036 display: block;
2037 font-weight: bold;
2038 list-style: none outside none;
2039 text-align: right;
2040 }
2041 #quick_login ol.links li{
2042 line-height: 27px;
2043 margin: 0;
2044 padding: 0;
2045 color: #fff;
2046 display: block;
2047 float:none !important;
2048 }
2049
2050 #quick_login ol.links li a{
2051 color: #fff;
2052 display: block;
2053 padding: 2px;
2054 }
2055 #quick_login ol.links li a:HOVER{
2056 background-color: inherit !important;
1495 2057 }
1496 2058
1497 2059 #register div.title {
1498 clear:both;
1499 overflow:hidden;
1500 position:relative;
1501 background:#003367 url("../images/header_inner.png") repeat-x;
1502 margin:0 auto;
1503 padding:0;
2060 clear: both;
2061 overflow: hidden;
2062 position: relative;
2063 background-color: #eedc94;
2064 background-repeat: repeat-x;
2065 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
2066 to(#eedc94) );
2067 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2068 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2069 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2070 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2071 background-image: -o-linear-gradient(top, #003b76, #00376e);
2072 background-image: linear-gradient(top, #003b76, #00376e);
2073 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
2074 endColorstr='#00376e', GradientType=0 );
2075 margin: 0 auto;
2076 padding: 0;
1504 2077 }
1505 2078
1506 2079 #register div.inner {
1507 background:#FFF;
1508 border-top:none;
1509 border-bottom:none;
1510 margin:0 auto;
1511 padding:20px;
2080 background: #FFF;
2081 border-top: none;
2082 border-bottom: none;
2083 margin: 0 auto;
2084 padding: 20px;
1512 2085 }
1513 2086
1514 2087 #register div.form div.fields div.field div.label {
1515 width:135px;
1516 float:left;
1517 text-align:right;
1518 margin:2px 10px 0 0;
1519 padding:5px 0 0 5px;
2088 width: 135px;
2089 float: left;
2090 text-align: right;
2091 margin: 2px 10px 0 0;
2092 padding: 5px 0 0 5px;
1520 2093 }
1521 2094
1522 2095 #register div.form div.fields div.field div.input input {
1523 width:300px;
1524 background:#FFF;
1525 border-top:1px solid #b3b3b3;
1526 border-left:1px solid #b3b3b3;
1527 border-right:1px solid #eaeaea;
1528 border-bottom:1px solid #eaeaea;
1529 color:#000;
1530 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1531 font-size:11px;
1532 margin:0;
1533 padding:7px 7px 6px;
2096 width: 300px;
2097 background: #FFF;
2098 border-top: 1px solid #b3b3b3;
2099 border-left: 1px solid #b3b3b3;
2100 border-right: 1px solid #eaeaea;
2101 border-bottom: 1px solid #eaeaea;
2102 color: #000;
2103 font-size: 11px;
2104 margin: 0;
2105 padding: 7px 7px 6px;
1534 2106 }
1535 2107
1536 2108 #register div.form div.fields div.buttons {
1537 clear:both;
1538 overflow:hidden;
1539 border-top:1px solid #DDD;
1540 text-align:left;
1541 margin:0;
1542 padding:10px 0 0 150px;
1543 }
1544
2109 clear: both;
2110 overflow: hidden;
2111 border-top: 1px solid #DDD;
2112 text-align: left;
2113 margin: 0;
2114 padding: 10px 0 0 150px;
2115 }
1545 2116
1546 2117 #register div.form div.activation_msg {
1547 padding-top:4px;
1548 padding-bottom:4px;
1549 }
1550
1551 #journal .journal_day{
1552 font-size:20px;
1553 padding:10px 0px;
1554 border-bottom:2px solid #DDD;
1555 margin-left:10px;
1556 margin-right:10px;
1557 }
1558
1559 #journal .journal_container{
1560 padding:5px;
1561 clear:both;
1562 margin:0px 5px 0px 10px;
1563 }
1564
1565 #journal .journal_action_container{
1566 padding-left:38px;
1567 }
1568
1569 #journal .journal_user{
1570 color: #747474;
1571 font-size: 14px;
1572 font-weight: bold;
1573 height: 30px;
1574 }
1575 #journal .journal_icon{
1576 clear: both;
1577 float: left;
1578 padding-right: 4px;
1579 padding-top: 3px;
1580 }
1581 #journal .journal_action{
1582 padding-top:4px;
1583 min-height:2px;
1584 float:left
1585 }
1586 #journal .journal_action_params{
1587 clear: left;
1588 padding-left: 22px;
1589 }
1590 #journal .journal_repo{
1591 float: left;
1592 margin-left: 6px;
1593 padding-top: 3px;
1594 }
1595 #journal .date{
1596 clear: both;
1597 color: #777777;
1598 font-size: 11px;
1599 padding-left: 22px;
1600 }
1601 #journal .journal_repo .journal_repo_name{
1602 font-weight: bold;
1603 font-size: 1.1em;
1604 }
1605 #journal .compare_view{
1606 padding: 5px 0px 5px 0px;
1607 width: 95px;
1608 }
1609 .journal_highlight{
1610 font-weight: bold;
1611 padding: 0 2px;
1612 vertical-align: bottom;
1613 }
2118 padding-top: 4px;
2119 padding-bottom: 4px;
2120 }
2121
2122 #journal .journal_day {
2123 font-size: 20px;
2124 padding: 10px 0px;
2125 border-bottom: 2px solid #DDD;
2126 margin-left: 10px;
2127 margin-right: 10px;
2128 }
2129
2130 #journal .journal_container {
2131 padding: 5px;
2132 clear: both;
2133 margin: 0px 5px 0px 10px;
2134 }
2135
2136 #journal .journal_action_container {
2137 padding-left: 38px;
2138 }
2139
2140 #journal .journal_user {
2141 color: #747474;
2142 font-size: 14px;
2143 font-weight: bold;
2144 height: 30px;
2145 }
2146
2147 #journal .journal_icon {
2148 clear: both;
2149 float: left;
2150 padding-right: 4px;
2151 padding-top: 3px;
2152 }
2153
2154 #journal .journal_action {
2155 padding-top: 4px;
2156 min-height: 2px;
2157 float: left
2158 }
2159
2160 #journal .journal_action_params {
2161 clear: left;
2162 padding-left: 22px;
2163 }
2164
2165 #journal .journal_repo {
2166 float: left;
2167 margin-left: 6px;
2168 padding-top: 3px;
2169 }
2170
2171 #journal .date {
2172 clear: both;
2173 color: #777777;
2174 font-size: 11px;
2175 padding-left: 22px;
2176 }
2177
2178 #journal .journal_repo .journal_repo_name {
2179 font-weight: bold;
2180 font-size: 1.1em;
2181 }
2182
2183 #journal .compare_view {
2184 padding: 5px 0px 5px 0px;
2185 width: 95px;
2186 }
2187
2188 .journal_highlight {
2189 font-weight: bold;
2190 padding: 0 2px;
2191 vertical-align: bottom;
2192 }
2193
1614 2194 .trending_language_tbl,.trending_language_tbl td {
1615 border:0 !important;
1616 margin:0 !important;
1617 padding:0 !important;
2195 border: 0 !important;
2196 margin: 0 !important;
2197 padding: 0 !important;
2198 }
2199
2200 .trending_language_tbl,.trending_language_tbl tr {
2201 border-spacing: 1px;
1618 2202 }
1619 2203
1620 2204 .trending_language {
1621 background-color:#003367;
1622 color:#FFF;
1623 display:block;
1624 min-width:20px;
1625 text-decoration:none;
1626 height:12px;
1627 margin-bottom:4px;
1628 margin-left:5px;
1629 white-space:pre;
1630 padding:3px;
2205 background-color: #003367;
2206 color: #FFF;
2207 display: block;
2208 min-width: 20px;
2209 text-decoration: none;
2210 height: 12px;
2211 margin-bottom: 0px;
2212 margin-left: 5px;
2213 white-space: pre;
2214 padding: 3px;
1631 2215 }
1632 2216
1633 2217 h3.files_location {
1634 font-size:1.8em;
1635 font-weight:700;
1636 border-bottom:none !important;
1637 margin:10px 0 !important;
2218 font-size: 1.8em;
2219 font-weight: 700;
2220 border-bottom: none !important;
2221 margin: 10px 0 !important;
1638 2222 }
1639 2223
1640 2224 #files_data dl dt {
1641 float:left;
1642 width:115px;
1643 margin:0 !important;
1644 padding:5px;
2225 float: left;
2226 width: 60px;
2227 margin: 0 !important;
2228 padding: 5px;
1645 2229 }
1646 2230
1647 2231 #files_data dl dd {
1648 margin:0 !important;
1649 padding:5px !important;
2232 margin: 0 !important;
2233 padding: 5px !important;
2234 }
2235
2236 .tablerow0 {
2237 background-color: #F8F8F8;
2238 }
2239
2240 .tablerow1 {
2241 background-color: #FFFFFF;
2242 }
2243
2244 .changeset_id {
2245 font-family: monospace;
2246 color: #666666;
2247 }
2248
2249 .changeset_hash {
2250 color: #000000;
1650 2251 }
1651 2252
1652 2253 #changeset_content {
1653 border:1px solid #CCC;
1654 padding:5px;
1655 }
1656 #changeset_compare_view_content{
1657 border:1px solid #CCC;
1658 padding:5px;
2254 border-left: 1px solid #CCC;
2255 border-right: 1px solid #CCC;
2256 border-bottom: 1px solid #CCC;
2257 padding: 5px;
2258 }
2259
2260 #changeset_compare_view_content {
2261 border: 1px solid #CCC;
2262 padding: 5px;
1659 2263 }
1660 2264
1661 2265 #changeset_content .container {
1662 min-height:120px;
1663 font-size:1.2em;
1664 overflow:hidden;
1665 }
1666
1667 #changeset_compare_view_content .compare_view_commits{
1668 width: auto !important;
1669 }
1670
1671 #changeset_compare_view_content .compare_view_commits td{
1672 padding:0px 0px 0px 12px !important;
2266 min-height: 100px;
2267 font-size: 1.2em;
2268 overflow: hidden;
2269 }
2270
2271 #changeset_compare_view_content .compare_view_commits {
2272 width: auto !important;
2273 }
2274
2275 #changeset_compare_view_content .compare_view_commits td {
2276 padding: 0px 0px 0px 12px !important;
1673 2277 }
1674 2278
1675 2279 #changeset_content .container .right {
1676 float:right;
1677 width:25%;
1678 text-align:right;
2280 float: right;
2281 width: 20%;
2282 text-align: right;
1679 2283 }
1680 2284
1681 2285 #changeset_content .container .left .message {
1682 font-style:italic;
1683 color:#556CB5;
1684 white-space:pre-wrap;
1685 }
1686
1687 .cs_files .cur_cs{
1688 margin:10px 2px;
1689 font-weight: bold;
1690 }
1691
1692 .cs_files .node{
1693 float: left;
1694 }
1695 .cs_files .changes{
1696 float: right;
1697 }
1698 .cs_files .changes .added{
1699 background-color: #BBFFBB;
1700 float: left;
1701 text-align: center;
1702 font-size: 90%;
1703 }
1704 .cs_files .changes .deleted{
1705 background-color: #FF8888;
1706 float: left;
1707 text-align: center;
1708 font-size: 90%;
1709 }
2286 white-space: pre-wrap;
2287 }
2288 #changeset_content .container .left .message a:hover {
2289 text-decoration: none;
2290 }
2291 .cs_files .cur_cs {
2292 margin: 10px 2px;
2293 font-weight: bold;
2294 }
2295
2296 .cs_files .node {
2297 float: left;
2298 }
2299
2300 .cs_files .changes {
2301 float: right;
2302 color:#003367;
2303
2304 }
2305
2306 .cs_files .changes .added {
2307 background-color: #BBFFBB;
2308 float: left;
2309 text-align: center;
2310 font-size: 9px;
2311 padding: 2px 0px 2px 0px;
2312 }
2313
2314 .cs_files .changes .deleted {
2315 background-color: #FF8888;
2316 float: left;
2317 text-align: center;
2318 font-size: 9px;
2319 padding: 2px 0px 2px 0px;
2320 }
2321
1710 2322 .cs_files .cs_added {
1711 background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
1712 height:16px;
1713 padding-left:20px;
1714 margin-top:7px;
1715 text-align:left;
2323 background: url("../images/icons/page_white_add.png") no-repeat scroll
2324 3px;
2325 height: 16px;
2326 padding-left: 20px;
2327 margin-top: 7px;
2328 text-align: left;
1716 2329 }
1717 2330
1718 2331 .cs_files .cs_changed {
1719 background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
1720 height:16px;
1721 padding-left:20px;
1722 margin-top:7px;
1723 text-align:left;
2332 background: url("../images/icons/page_white_edit.png") no-repeat scroll
2333 3px;
2334 height: 16px;
2335 padding-left: 20px;
2336 margin-top: 7px;
2337 text-align: left;
1724 2338 }
1725 2339
1726 2340 .cs_files .cs_removed {
1727 background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
1728 height:16px;
1729 padding-left:20px;
1730 margin-top:7px;
1731 text-align:left;
2341 background: url("../images/icons/page_white_delete.png") no-repeat
2342 scroll 3px;
2343 height: 16px;
2344 padding-left: 20px;
2345 margin-top: 7px;
2346 text-align: left;
1732 2347 }
1733 2348
1734 2349 #graph {
1735 overflow:hidden;
2350 overflow: hidden;
1736 2351 }
1737 2352
1738 2353 #graph_nodes {
1739 float: left;
1740 margin-right: -6px;
1741 margin-top: -4px;
2354 float: left;
2355 margin-right: -6px;
2356 margin-top: 0px;
1742 2357 }
1743 2358
1744 2359 #graph_content {
1745 width:800px;
1746 float:left;
1747
2360 width: 80%;
2361 float: left;
1748 2362 }
1749 2363
1750 2364 #graph_content .container_header {
1751 border:1px solid #CCC;
1752 padding:10px;
1753 }
1754 #graph_content #rev_range_container{
1755 padding:10px 0px;
1756 }
2365 border-bottom: 1px solid #DDD;
2366 padding: 10px;
2367 height: 25px;
2368 }
2369
2370 #graph_content #rev_range_container {
2371 padding: 7px 20px;
2372 float: left;
2373 }
2374
1757 2375 #graph_content .container {
1758 border-bottom:1px solid #CCC;
1759 border-left:1px solid #CCC;
1760 border-right:1px solid #CCC;
1761 min-height:70px;
1762 overflow:hidden;
1763 font-size:1.2em;
2376 border-bottom: 1px solid #DDD;
2377 height: 56px;
2378 overflow: hidden;
1764 2379 }
1765 2380
1766 2381 #graph_content .container .right {
1767 float:right;
1768 width:28%;
1769 text-align:right;
1770 padding-bottom:5px;
1771 }
2382 float: right;
2383 width: 23%;
2384 text-align: right;
2385 }
2386
2387 #graph_content .container .left {
2388 float: left;
2389 width: 25%;
2390 padding-left: 5px;
2391 }
2392
2393 #graph_content .container .mid {
2394 float: left;
2395 width: 49%;
2396 }
2397
1772 2398
1773 2399 #graph_content .container .left .date {
1774 font-weight:700;
1775 padding-bottom:5px;
1776 }
1777 #graph_content .container .left .date span{
1778 vertical-align: text-top;
1779 }
1780
1781 #graph_content .container .left .author{
1782 height: 22px;
1783 }
1784 #graph_content .container .left .author .user{
1785 color: #444444;
1786 float: left;
1787 font-size: 12px;
1788 margin-left: -4px;
1789 margin-top: 4px;
1790 }
1791
1792 #graph_content .container .left .message {
1793 font-size:100%;
1794 padding-top:3px;
1795 white-space:pre-wrap;
1796 }
1797
1798 .right div {
1799 clear:both;
1800 }
1801
1802 .right .changes .changed_total{
1803 border:1px solid #DDD;
1804 display:block;
1805 float:right;
1806 text-align:center;
1807 min-width:45px;
1808 cursor: pointer;
1809 background:#FD8;
1810 font-weight: bold;
1811 }
2400 color: #666;
2401 padding-left: 22px;
2402 font-size: 10px;
2403 }
2404
2405 #graph_content .container .left .author {
2406 height: 22px;
2407 }
2408
2409 #graph_content .container .left .author .user {
2410 color: #444444;
2411 float: left;
2412 margin-left: -4px;
2413 margin-top: 4px;
2414 }
2415
2416 #graph_content .container .mid .message {
2417 white-space: pre-wrap;
2418 }
2419
2420 #graph_content .container .mid .message a:hover{
2421 text-decoration: none;
2422 }
2423 #content #graph_content .message .revision-link,
2424 #changeset_content .container .message .revision-link
2425 {
2426 color:#3F6F9F;
2427 font-weight: bold !important;
2428 }
2429
2430 #content #graph_content .message .issue-tracker-link,
2431 #changeset_content .container .message .issue-tracker-link{
2432 color:#3F6F9F;
2433 font-weight: bold !important;
2434 }
2435
2436 .right .comments-container{
2437 padding-right: 5px;
2438 margin-top:1px;
2439 float:right;
2440 height:14px;
2441 }
2442
2443 .right .comments-cnt{
2444 float: left;
2445 color: rgb(136, 136, 136);
2446 padding-right: 2px;
2447 }
2448
2449 .right .changes{
2450 clear: both;
2451 }
2452
2453 .right .changes .changed_total {
2454 display: block;
2455 float: right;
2456 text-align: center;
2457 min-width: 45px;
2458 cursor: pointer;
2459 color: #444444;
2460 background: #FEA;
2461 -webkit-border-radius: 0px 0px 0px 6px;
2462 -moz-border-radius: 0px 0px 0px 6px;
2463 border-radius: 0px 0px 0px 6px;
2464 padding: 1px;
2465 }
2466
1812 2467 .right .changes .added,.changed,.removed {
1813 border:1px solid #DDD;
1814 display:block;
1815 float:right;
1816 text-align:center;
1817 min-width:15px;
1818 cursor: help;
1819 }
1820 .right .changes .large {
1821 border:1px solid #DDD;
1822 display:block;
1823 float:right;
1824 text-align:center;
1825 min-width:45px;
1826 cursor: help;
1827 background: #54A9F7;
2468 display: block;
2469 padding: 1px;
2470 color: #444444;
2471 float: right;
2472 text-align: center;
2473 min-width: 15px;
1828 2474 }
1829 2475
1830 2476 .right .changes .added {
1831 background:#BFB;
2477 background: #CFC;
1832 2478 }
1833 2479
1834 2480 .right .changes .changed {
1835 background:#FD8;
2481 background: #FEA;
1836 2482 }
1837 2483
1838 2484 .right .changes .removed {
1839 background:#F88;
2485 background: #FAA;
1840 2486 }
1841 2487
1842 2488 .right .merge {
1843 vertical-align:top;
1844 font-size:0.75em;
1845 font-weight:700;
2489 padding: 1px 3px 1px 3px;
2490 background-color: #fca062;
2491 font-size: 10px;
2492 font-weight: bold;
2493 color: #ffffff;
2494 text-transform: uppercase;
2495 white-space: nowrap;
2496 -webkit-border-radius: 3px;
2497 -moz-border-radius: 3px;
2498 border-radius: 3px;
2499 margin-right: 2px;
1846 2500 }
1847 2501
1848 2502 .right .parent {
1849 font-size:90%;
1850 font-family:monospace;
1851 }
1852
1853 .right .logtags .branchtag {
1854 background:#FFF url("../images/icons/arrow_branch.png") no-repeat right 6px;
1855 display:block;
1856 font-size:0.8em;
1857 padding:11px 16px 0 0;
1858 }
1859
1860 .right .logtags .tagtag {
1861 background:#FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
1862 display:block;
1863 font-size:0.8em;
1864 padding:11px 16px 0 0;
1865 }
1866
2503 color: #666666;
2504 clear:both;
2505 }
2506 .right .logtags{
2507 padding: 2px 2px 2px 2px;
2508 }
2509 .right .logtags .branchtag,.logtags .branchtag {
2510 padding: 1px 3px 1px 3px;
2511 background-color: #bfbfbf;
2512 font-size: 10px;
2513 font-weight: bold;
2514 color: #ffffff;
2515 text-transform: uppercase;
2516 white-space: nowrap;
2517 -webkit-border-radius: 3px;
2518 -moz-border-radius: 3px;
2519 border-radius: 3px;
2520 }
2521 .right .logtags .branchtag a:hover,.logtags .branchtag a{
2522 color: #ffffff;
2523 }
2524 .right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
2525 text-decoration: none;
2526 color: #ffffff;
2527 }
2528 .right .logtags .tagtag,.logtags .tagtag {
2529 padding: 1px 3px 1px 3px;
2530 background-color: #62cffc;
2531 font-size: 10px;
2532 font-weight: bold;
2533 color: #ffffff;
2534 text-transform: uppercase;
2535 white-space: nowrap;
2536 -webkit-border-radius: 3px;
2537 -moz-border-radius: 3px;
2538 border-radius: 3px;
2539 }
2540 .right .logtags .tagtag a:hover,.logtags .tagtag a{
2541 color: #ffffff;
2542 }
2543 .right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
2544 text-decoration: none;
2545 color: #ffffff;
2546 }
2547 .right .logbooks .bookbook,.logbooks .bookbook {
2548 padding: 1px 3px 2px;
2549 background-color: #46A546;
2550 font-size: 9.75px;
2551 font-weight: bold;
2552 color: #ffffff;
2553 text-transform: uppercase;
2554 white-space: nowrap;
2555 -webkit-border-radius: 3px;
2556 -moz-border-radius: 3px;
2557 border-radius: 3px;
2558 }
2559 .right .logbooks .bookbook,.logbooks .bookbook a{
2560 color: #ffffff;
2561 }
2562 .right .logbooks .bookbook,.logbooks .bookbook a:hover{
2563 text-decoration: none;
2564 color: #ffffff;
2565 }
1867 2566 div.browserblock {
1868 overflow:hidden;
1869 border:1px solid #ccc;
1870 background:#f8f8f8;
1871 font-size:100%;
1872 line-height:125%;
1873 padding:0;
2567 overflow: hidden;
2568 border: 1px solid #ccc;
2569 background: #f8f8f8;
2570 font-size: 100%;
2571 line-height: 125%;
2572 padding: 0;
2573 -webkit-border-radius: 6px 6px 0px 0px;
2574 -moz-border-radius: 6px 6px 0px 0px;
2575 border-radius: 6px 6px 0px 0px;
1874 2576 }
1875 2577
1876 2578 div.browserblock .browser-header {
1877 background:#FFF;
1878 padding:10px 0px 15px 0px;
1879 width: 100%;
1880 }
2579 background: #FFF;
2580 padding: 10px 0px 15px 0px;
2581 width: 100%;
2582 }
2583
1881 2584 div.browserblock .browser-nav {
1882 float:left
2585 float: left
1883 2586 }
1884 2587
1885 2588 div.browserblock .browser-branch {
1886 float:left;
2589 float: left;
1887 2590 }
1888 2591
1889 2592 div.browserblock .browser-branch label {
1890 color:#4A4A4A;
1891 vertical-align:text-top;
2593 color: #4A4A4A;
2594 vertical-align: text-top;
1892 2595 }
1893 2596
1894 2597 div.browserblock .browser-header span {
1895 margin-left:5px;
1896 font-weight:700;
1897 }
1898
1899 div.browserblock .browser-search{
1900 clear:both;
1901 padding:8px 8px 0px 5px;
2598 margin-left: 5px;
2599 font-weight: 700;
2600 }
2601
2602 div.browserblock .browser-search {
2603 clear: both;
2604 padding: 8px 8px 0px 5px;
1902 2605 height: 20px;
1903 2606 }
2607
1904 2608 div.browserblock #node_filter_box {
1905 }
1906
1907 div.browserblock .search_activate{
1908 float: left
1909 }
1910
1911 div.browserblock .add_node{
1912 float: left;
1913 padding-left: 5px;
1914 }
1915
1916 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{
1917 text-decoration: none !important;
2609
2610 }
2611
2612 div.browserblock .search_activate {
2613 float: left
2614 }
2615
2616 div.browserblock .add_node {
2617 float: left;
2618 padding-left: 5px;
2619 }
2620
2621 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
2622 {
2623 text-decoration: none !important;
1918 2624 }
1919 2625
1920 2626 div.browserblock .browser-body {
1921 background:#EEE;
1922 border-top:1px solid #CCC;
2627 background: #EEE;
2628 border-top: 1px solid #CCC;
1923 2629 }
1924 2630
1925 2631 table.code-browser {
1926 border-collapse:collapse;
1927 width:100%;
2632 border-collapse: collapse;
2633 width: 100%;
1928 2634 }
1929 2635
1930 2636 table.code-browser tr {
1931 margin:3px;
2637 margin: 3px;
1932 2638 }
1933 2639
1934 2640 table.code-browser thead th {
1935 background-color:#EEE;
1936 height:20px;
1937 font-size:1.1em;
1938 font-weight:700;
1939 text-align:left;
1940 padding-left:10px;
2641 background-color: #EEE;
2642 height: 20px;
2643 font-size: 1.1em;
2644 font-weight: 700;
2645 text-align: left;
2646 padding-left: 10px;
1941 2647 }
1942 2648
1943 2649 table.code-browser tbody td {
1944 padding-left:10px;
1945 height:20px;
2650 padding-left: 10px;
2651 height: 20px;
1946 2652 }
1947 2653
1948 2654 table.code-browser .browser-file {
1949 background:url("../images/icons/document_16.png") no-repeat scroll 3px;
1950 height:16px;
1951 padding-left:20px;
1952 text-align:left;
1953 }
1954 .diffblock .changeset_file{
1955 background:url("../images/icons/file.png") no-repeat scroll 3px;
1956 height:16px;
1957 padding-left:22px;
1958 text-align:left;
1959 font-size: 14px;
1960 }
1961
1962 .diffblock .changeset_header{
1963 margin-left: 6px !important;
1964 }
1965
2655 background: url("../images/icons/document_16.png") no-repeat scroll 3px;
2656 height: 16px;
2657 padding-left: 20px;
2658 text-align: left;
2659 }
2660 .diffblock .changeset_header {
2661 height: 16px;
2662 }
2663 .diffblock .changeset_file {
2664 background: url("../images/icons/file.png") no-repeat scroll 3px;
2665 text-align: left;
2666 float: left;
2667 padding: 2px 0px 2px 22px;
2668 }
2669 .diffblock .diff-menu-wrapper{
2670 float: left;
2671 }
2672
2673 .diffblock .diff-menu{
2674 position: absolute;
2675 background: none repeat scroll 0 0 #FFFFFF;
2676 border-color: #003367 #666666 #666666;
2677 border-right: 1px solid #666666;
2678 border-style: solid solid solid;
2679 border-width: 1px;
2680 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
2681 margin-top:5px;
2682 margin-left:1px;
2683
2684 }
2685 .diffblock .diff-actions {
2686 padding: 2px 0px 0px 2px;
2687 float: left;
2688 }
2689 .diffblock .diff-menu ul li {
2690 padding: 0px 0px 0px 0px !important;
2691 }
2692 .diffblock .diff-menu ul li a{
2693 display: block;
2694 padding: 3px 8px 3px 8px !important;
2695 }
2696 .diffblock .diff-menu ul li a:hover{
2697 text-decoration: none;
2698 background-color: #EEEEEE;
2699 }
1966 2700 table.code-browser .browser-dir {
1967 background:url("../images/icons/folder_16.png") no-repeat scroll 3px;
1968 height:16px;
1969 padding-left:20px;
1970 text-align:left;
2701 background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
2702 height: 16px;
2703 padding-left: 20px;
2704 text-align: left;
1971 2705 }
1972 2706
1973 2707 .box .search {
2708 clear: both;
2709 overflow: hidden;
2710 margin: 0;
2711 padding: 0 20px 10px;
2712 }
2713
2714 .box .search div.search_path {
2715 background: none repeat scroll 0 0 #EEE;
2716 border: 1px solid #CCC;
2717 color: blue;
2718 margin-bottom: 10px;
2719 padding: 10px 0;
2720 }
2721
2722 .box .search div.search_path div.link {
2723 font-weight: 700;
2724 margin-left: 25px;
2725 }
2726
2727 .box .search div.search_path div.link a {
2728 color: #003367;
2729 cursor: pointer;
2730 text-decoration: none;
2731 }
2732
2733 #path_unlock {
2734 color: red;
2735 font-size: 1.2em;
2736 padding-left: 4px;
2737 }
2738
2739 .info_box span {
2740 margin-left: 3px;
2741 margin-right: 3px;
2742 }
2743
2744 .info_box .rev {
2745 color: #003367;
2746 font-size: 1.6em;
2747 font-weight: bold;
2748 vertical-align: sub;
2749 }
2750
2751 .info_box input#at_rev,.info_box input#size {
2752 background: #FFF;
2753 border-top: 1px solid #b3b3b3;
2754 border-left: 1px solid #b3b3b3;
2755 border-right: 1px solid #eaeaea;
2756 border-bottom: 1px solid #eaeaea;
2757 color: #000;
2758 font-size: 12px;
2759 margin: 0;
2760 padding: 1px 5px 1px;
2761 }
2762
2763 .info_box input#view {
2764 text-align: center;
2765 padding: 4px 3px 2px 2px;
2766 }
2767
2768 .yui-overlay,.yui-panel-container {
2769 visibility: hidden;
2770 position: absolute;
2771 z-index: 2;
2772 }
2773
2774 .yui-tt {
2775 visibility: hidden;
2776 position: absolute;
2777 color: #666;
2778 background-color: #FFF;
2779 border: 2px solid #003367;
2780 font: 100% sans-serif;
2781 width: auto;
2782 opacity: 1px;
2783 padding: 8px;
2784 white-space: pre-wrap;
2785 -webkit-border-radius: 8px 8px 8px 8px;
2786 -khtml-border-radius: 8px 8px 8px 8px;
2787 -moz-border-radius: 8px 8px 8px 8px;
2788 border-radius: 8px 8px 8px 8px;
2789 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2790 }
2791
2792 .ac {
2793 vertical-align: top;
2794 }
2795
2796 .ac .yui-ac {
2797 position: relative;
2798 font-size: 100%;
2799 }
2800
2801 .ac .perm_ac {
2802 width: 15em;
2803 }
2804
2805 .ac .yui-ac-input {
2806 width: 100%;
2807 }
2808
2809 .ac .yui-ac-container {
2810 position: absolute;
2811 top: 1.6em;
2812 width: 100%;
2813 }
2814
2815 .ac .yui-ac-content {
2816 position: absolute;
2817 width: 100%;
2818 border: 1px solid gray;
2819 background: #fff;
2820 overflow: hidden;
2821 z-index: 9050;
2822 }
2823
2824 .ac .yui-ac-shadow {
2825 position: absolute;
2826 width: 100%;
2827 background: #000;
2828 -moz-opacity: 0.1px;
2829 opacity: .10;
2830 filter: alpha(opacity = 10);
2831 z-index: 9049;
2832 margin: .3em;
2833 }
2834
2835 .ac .yui-ac-content ul {
2836 width: 100%;
2837 margin: 0;
2838 padding: 0;
2839 }
2840
2841 .ac .yui-ac-content li {
2842 cursor: default;
2843 white-space: nowrap;
2844 margin: 0;
2845 padding: 2px 5px;
2846 }
2847
2848 .ac .yui-ac-content li.yui-ac-prehighlight {
2849 background: #B3D4FF;
2850 }
2851
2852 .ac .yui-ac-content li.yui-ac-highlight {
2853 background: #556CB5;
2854 color: #FFF;
2855 }
2856
2857 .follow {
2858 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
2859 height: 16px;
2860 width: 20px;
2861 cursor: pointer;
2862 display: block;
2863 float: right;
2864 margin-top: 2px;
2865 }
2866
2867 .following {
2868 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2869 height: 16px;
2870 width: 20px;
2871 cursor: pointer;
2872 display: block;
2873 float: right;
2874 margin-top: 2px;
2875 }
2876
2877 .currently_following {
2878 padding-left: 10px;
2879 padding-bottom: 5px;
2880 }
2881
2882 .add_icon {
2883 background: url("../images/icons/add.png") no-repeat scroll 3px;
2884 padding-left: 20px;
2885 padding-top: 0px;
2886 text-align: left;
2887 }
2888
2889 .edit_icon {
2890 background: url("../images/icons/folder_edit.png") no-repeat scroll 3px;
2891 padding-left: 20px;
2892 padding-top: 0px;
2893 text-align: left;
2894 }
2895
2896 .delete_icon {
2897 background: url("../images/icons/delete.png") no-repeat scroll 3px;
2898 padding-left: 20px;
2899 padding-top: 0px;
2900 text-align: left;
2901 }
2902
2903 .refresh_icon {
2904 background: url("../images/icons/arrow_refresh.png") no-repeat scroll
2905 3px;
2906 padding-left: 20px;
2907 padding-top: 0px;
2908 text-align: left;
2909 }
2910
2911 .pull_icon {
2912 background: url("../images/icons/connect.png") no-repeat scroll 3px;
2913 padding-left: 20px;
2914 padding-top: 0px;
2915 text-align: left;
2916 }
2917
2918 .rss_icon {
2919 background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
2920 padding-left: 20px;
2921 padding-top: 4px;
2922 text-align: left;
2923 font-size: 8px
2924 }
2925
2926 .atom_icon {
2927 background: url("../images/icons/atom.png") no-repeat scroll 3px;
2928 padding-left: 20px;
2929 padding-top: 4px;
2930 text-align: left;
2931 font-size: 8px
2932 }
2933
2934 .archive_icon {
2935 background: url("../images/icons/compress.png") no-repeat scroll 3px;
2936 padding-left: 20px;
2937 text-align: left;
2938 padding-top: 1px;
2939 }
2940
2941 .start_following_icon {
2942 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
2943 padding-left: 20px;
2944 text-align: left;
2945 padding-top: 0px;
2946 }
2947
2948 .stop_following_icon {
2949 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2950 padding-left: 20px;
2951 text-align: left;
2952 padding-top: 0px;
2953 }
2954
2955 .action_button {
2956 border: 0;
2957 display: inline;
2958 }
2959
2960 .action_button:hover {
2961 border: 0;
2962 text-decoration: underline;
2963 cursor: pointer;
2964 }
2965
2966 #switch_repos {
2967 position: absolute;
2968 height: 25px;
2969 z-index: 1;
2970 }
2971
2972 #switch_repos select {
2973 min-width: 150px;
2974 max-height: 250px;
2975 z-index: 1;
2976 }
2977
2978 .breadcrumbs {
2979 border: medium none;
2980 color: #FFF;
2981 float: left;
2982 text-transform: uppercase;
2983 font-weight: 700;
2984 font-size: 14px;
2985 margin: 0;
2986 padding: 11px 0 11px 10px;
2987 }
2988
2989 .breadcrumbs .hash {
2990 text-transform: none;
2991 color: #fff;
2992 }
2993
2994 .breadcrumbs a {
2995 color: #FFF;
2996 }
2997
2998 .flash_msg {
2999
3000 }
3001
3002 .flash_msg ul {
3003
3004 }
3005
3006 .error_msg {
3007 background-color: #c43c35;
3008 background-repeat: repeat-x;
3009 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b),
3010 to(#c43c35) );
3011 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3012 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3013 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b),
3014 color-stop(100%, #c43c35) );
3015 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3016 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3017 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3018 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',
3019 endColorstr='#c43c35', GradientType=0 );
3020 border-color: #c43c35 #c43c35 #882a25;
3021 }
3022
3023 .warning_msg {
3024 color: #404040 !important;
3025 background-color: #eedc94;
3026 background-repeat: repeat-x;
3027 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
3028 to(#eedc94) );
3029 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3030 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3031 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1),
3032 color-stop(100%, #eedc94) );
3033 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3034 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3035 background-image: linear-gradient(top, #fceec1, #eedc94);
3036 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1',
3037 endColorstr='#eedc94', GradientType=0 );
3038 border-color: #eedc94 #eedc94 #e4c652;
3039 }
3040
3041 .success_msg {
3042 background-color: #57a957;
3043 background-repeat: repeat-x !important;
3044 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462),
3045 to(#57a957) );
3046 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3047 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3048 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462),
3049 color-stop(100%, #57a957) );
3050 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3051 background-image: -o-linear-gradient(top, #62c462, #57a957);
3052 background-image: linear-gradient(top, #62c462, #57a957);
3053 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462',
3054 endColorstr='#57a957', GradientType=0 );
3055 border-color: #57a957 #57a957 #3d773d;
3056 }
3057
3058 .notice_msg {
3059 background-color: #339bb9;
3060 background-repeat: repeat-x;
3061 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de),
3062 to(#339bb9) );
3063 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3064 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3065 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de),
3066 color-stop(100%, #339bb9) );
3067 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3068 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3069 background-image: linear-gradient(top, #5bc0de, #339bb9);
3070 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de',
3071 endColorstr='#339bb9', GradientType=0 );
3072 border-color: #339bb9 #339bb9 #22697d;
3073 }
3074
3075 .success_msg,.error_msg,.notice_msg,.warning_msg {
3076 font-size: 12px;
3077 font-weight: 700;
3078 min-height: 14px;
3079 line-height: 14px;
3080 margin-bottom: 10px;
3081 margin-top: 0;
3082 display: block;
3083 overflow: auto;
3084 padding: 6px 10px 6px 10px;
3085 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3086 position: relative;
3087 color: #FFF;
3088 border-width: 1px;
3089 border-style: solid;
3090 -webkit-border-radius: 4px;
3091 -moz-border-radius: 4px;
3092 border-radius: 4px;
3093 -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3094 -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3095 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3096 }
3097
3098 #msg_close {
3099 background: transparent url("../icons/cross_grey_small.png") no-repeat
3100 scroll 0 0;
3101 cursor: pointer;
3102 height: 16px;
3103 position: absolute;
3104 right: 5px;
3105 top: 5px;
3106 width: 16px;
3107 }
3108
3109 div#legend_container table,div#legend_choices table {
3110 width: auto !important;
3111 }
3112
3113 table#permissions_manage {
3114 width: 0 !important;
3115 }
3116
3117 table#permissions_manage span.private_repo_msg {
3118 font-size: 0.8em;
3119 opacity: 0.6px;
3120 }
3121
3122 table#permissions_manage td.private_repo_msg {
3123 font-size: 0.8em;
3124 }
3125
3126 table#permissions_manage tr#add_perm_input td {
3127 vertical-align: middle;
3128 }
3129
3130 div.gravatar {
3131 background-color: #FFF;
3132 float: left;
3133 margin-right: 0.7em;
3134 padding: 1px 1px 1px 1px;
3135 line-height:0;
3136 -webkit-border-radius: 3px;
3137 -khtml-border-radius: 3px;
3138 -moz-border-radius: 3px;
3139 border-radius: 3px;
3140 }
3141
3142 div.gravatar img {
3143 -webkit-border-radius: 2px;
3144 -khtml-border-radius: 2px;
3145 -moz-border-radius: 2px;
3146 border-radius: 2px;
3147 }
3148
3149 #header,#content,#footer {
3150 min-width: 978px;
3151 }
3152
3153 #content {
3154 clear: both;
3155 overflow: hidden;
3156 padding: 54px 10px 14px 10px;
3157 }
3158
3159 #content div.box div.title div.search {
3160
3161 border-left: 1px solid #316293;
3162 }
3163
3164 #content div.box div.title div.search div.input input {
3165 border: 1px solid #316293;
3166 }
3167
3168 .ui-btn{
3169 color: #515151;
3170 background-color: #DADADA;
3171 background-repeat: repeat-x;
3172 background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
3173 background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
3174 background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
3175 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
3176 background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
3177 background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
3178 background-image: linear-gradient(top, #F4F4F4, #DADADA);
3179 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
3180
3181 border-top: 1px solid #DDD;
3182 border-left: 1px solid #c6c6c6;
3183 border-right: 1px solid #DDD;
3184 border-bottom: 1px solid #c6c6c6;
3185 color: #515151;
3186 outline: none;
3187 margin: 0px 3px 3px 0px;
3188 -webkit-border-radius: 4px 4px 4px 4px !important;
3189 -khtml-border-radius: 4px 4px 4px 4px !important;
3190 -moz-border-radius: 4px 4px 4px 4px !important;
3191 border-radius: 4px 4px 4px 4px !important;
3192 cursor: pointer !important;
3193 padding: 3px 3px 3px 3px;
3194 background-position: 0 -15px;
3195
3196 }
3197 .ui-btn.xsmall{
3198 padding: 1px 2px 1px 1px;
3199 }
3200 .ui-btn.clone{
3201 padding: 5px 2px 6px 1px;
3202 margin: 0px -4px 3px 0px;
3203 -webkit-border-radius: 4px 0px 0px 4px !important;
3204 -khtml-border-radius: 4px 0px 0px 4px !important;
3205 -moz-border-radius: 4px 0px 0px 4px !important;
3206 border-radius: 4px 0px 0px 4px !important;
3207 width: 100px;
3208 text-align: center;
3209 float: left;
3210 position: absolute;
3211 }
3212 .ui-btn:focus {
3213 outline: none;
3214 }
3215 .ui-btn:hover{
3216 background-position: 0 0px;
3217 text-decoration: none;
3218 color: #515151;
3219 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
3220 }
3221
3222 .ui-btn.red{
3223 color:#fff;
3224 background-color: #c43c35;
3225 background-repeat: repeat-x;
3226 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
3227 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3228 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3229 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
3230 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3231 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3232 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3233 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
3234 border-color: #c43c35 #c43c35 #882a25;
3235 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3236 }
3237
3238
3239 .ui-btn.blue{
3240 background-color: #339bb9;
3241 background-repeat: repeat-x;
3242 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
3243 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3244 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3245 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
3246 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3247 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3248 background-image: linear-gradient(top, #5bc0de, #339bb9);
3249 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
3250 border-color: #339bb9 #339bb9 #22697d;
3251 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3252 }
3253
3254 .ui-btn.green{
3255 background-color: #57a957;
3256 background-repeat: repeat-x;
3257 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
3258 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3259 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3260 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
3261 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3262 background-image: -o-linear-gradient(top, #62c462, #57a957);
3263 background-image: linear-gradient(top, #62c462, #57a957);
3264 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
3265 border-color: #57a957 #57a957 #3d773d;
3266 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3267 }
3268
3269 ins,div.options a:hover {
3270 text-decoration: none;
3271 }
3272
3273 img,
3274 #header #header-inner #quick li a:hover span.normal,
3275 #header #header-inner #quick li ul li.last,
3276 #content div.box div.form div.fields div.field div.textarea table td table td a,
3277 #clone_url,
3278 #clone_url_id
3279 {
3280 border: none;
3281 }
3282
3283 img.icon,.right .merge img {
3284 vertical-align: bottom;
3285 }
3286
3287 #header ul#logged-user,#content div.box div.title ul.links,
3288 #content div.box div.message div.dismiss,
3289 #content div.box div.traffic div.legend ul
3290 {
3291 float: right;
3292 margin: 0;
3293 padding: 0;
3294 }
3295
3296 #header #header-inner #home,#header #header-inner #logo,
3297 #content div.box ul.left,#content div.box ol.left,
3298 #content div.box div.pagination-left,div#commit_history,
3299 div#legend_data,div#legend_container,div#legend_choices
3300 {
3301 float: left;
3302 }
3303
3304 #header #header-inner #quick li:hover ul ul,
3305 #header #header-inner #quick li:hover ul ul ul,
3306 #header #header-inner #quick li:hover ul ul ul ul,
3307 #content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
3308 {
3309 display: none;
3310 }
3311
3312 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded
3313 {
3314 display: block;
3315 }
3316
3317 #content div.graph {
3318 padding: 0 10px 10px;
3319 }
3320
3321 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
3322 {
3323 color: #bfe3ff;
3324 }
3325
3326 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal
3327 {
3328 margin: 10px 24px 10px 44px;
3329 }
3330
3331 #content div.box div.form,#content div.box div.table,#content div.box div.traffic
3332 {
3333 clear: both;
3334 overflow: hidden;
3335 margin: 0;
3336 padding: 0 20px 10px;
3337 }
3338
3339 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
3340 {
3341 clear: both;
3342 overflow: hidden;
3343 margin: 0;
3344 padding: 0;
3345 }
3346
3347 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span
3348 {
3349 height: 1%;
3350 display: block;
3351 color: #363636;
3352 margin: 0;
3353 padding: 2px 0 0;
3354 }
3355
3356 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error
3357 {
3358 background: #FBE3E4;
3359 border-top: 1px solid #e1b2b3;
3360 border-left: 1px solid #e1b2b3;
3361 border-right: 1px solid #FBC2C4;
3362 border-bottom: 1px solid #FBC2C4;
3363 }
3364
3365 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success
3366 {
3367 background: #E6EFC2;
3368 border-top: 1px solid #cebb98;
3369 border-left: 1px solid #cebb98;
3370 border-right: 1px solid #c6d880;
3371 border-bottom: 1px solid #c6d880;
3372 }
3373
3374 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input
3375 {
3376 margin: 0;
3377 }
3378
3379 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios
3380 {
3381 margin: 0 0 0 0px !important;
3382 padding: 0;
3383 }
3384
3385 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios
3386 {
3387 margin: 0 0 0 200px;
3388 padding: 0;
3389 }
3390
3391 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover
3392 {
3393 color: #000;
3394 text-decoration: none;
3395 }
3396
3397 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
3398 {
3399 border: 1px solid #666;
3400 }
3401
3402 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio
3403 {
3404 clear: both;
3405 overflow: hidden;
3406 margin: 0;
3407 padding: 8px 0 2px;
3408 }
3409
3410 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input
3411 {
3412 float: left;
3413 margin: 0;
3414 }
3415
3416 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label
3417 {
3418 height: 1%;
3419 display: block;
3420 float: left;
3421 margin: 2px 0 0 4px;
3422 }
3423
3424 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input
3425 {
3426 color: #000;
3427 font-size: 11px;
3428 font-weight: 700;
3429 margin: 0;
3430 }
3431
3432 input.ui-button {
3433 background: #e5e3e3 url("../images/button.png") repeat-x;
3434 border-top: 1px solid #DDD;
3435 border-left: 1px solid #c6c6c6;
3436 border-right: 1px solid #DDD;
3437 border-bottom: 1px solid #c6c6c6;
3438 color: #515151 !important;
3439 outline: none;
3440 margin: 0;
3441 padding: 6px 12px;
3442 -webkit-border-radius: 4px 4px 4px 4px;
3443 -khtml-border-radius: 4px 4px 4px 4px;
3444 -moz-border-radius: 4px 4px 4px 4px;
3445 border-radius: 4px 4px 4px 4px;
3446 box-shadow: 0 1px 0 #ececec;
3447 cursor: pointer;
3448 }
3449
3450 input.ui-button:hover {
3451 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3452 border-top: 1px solid #ccc;
3453 border-left: 1px solid #bebebe;
3454 border-right: 1px solid #b1b1b1;
3455 border-bottom: 1px solid #afafaf;
3456 }
3457
3458 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
3459 {
3460 display: inline;
3461 }
3462
3463 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
3464 {
3465 margin: 10px 0 0 200px;
3466 padding: 0;
3467 }
3468
3469 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons
3470 {
3471 margin: 10px 0 0;
3472 }
3473
3474 #content div.box table td.user,#content div.box table td.address {
3475 width: 10%;
3476 text-align: center;
3477 }
3478
3479 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
3480 {
3481 text-align: right;
3482 margin: 6px 0 0;
3483 padding: 0;
3484 }
3485
3486 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
3487 {
3488 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3489 border-top: 1px solid #ccc;
3490 border-left: 1px solid #bebebe;
3491 border-right: 1px solid #b1b1b1;
3492 border-bottom: 1px solid #afafaf;
3493 color: #515151;
3494 margin: 0;
3495 padding: 6px 12px;
3496 }
3497
3498 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
3499 {
3500 text-align: left;
3501 float: left;
3502 margin: 0;
3503 padding: 0;
3504 }
3505
3506 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
3507 {
3508 height: 1%;
3509 display: block;
3510 float: left;
3511 background: #ebebeb url("../images/pager.png") repeat-x;
3512 border-top: 1px solid #dedede;
3513 border-left: 1px solid #cfcfcf;
3514 border-right: 1px solid #c4c4c4;
3515 border-bottom: 1px solid #c4c4c4;
3516 color: #4A4A4A;
3517 font-weight: 700;
3518 margin: 0;
3519 padding: 6px 8px;
3520 }
3521
3522 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
3523 {
3524 color: #B4B4B4;
3525 padding: 6px;
3526 }
3527
3528 #login,#register {
3529 width: 520px;
3530 margin: 10% auto 0;
3531 padding: 0;
3532 }
3533
3534 #login div.color,#register div.color {
3535 clear: both;
3536 overflow: hidden;
3537 background: #FFF;
3538 margin: 10px auto 0;
3539 padding: 3px 3px 3px 0;
3540 }
3541
3542 #login div.color a,#register div.color a {
3543 width: 20px;
3544 height: 20px;
3545 display: block;
3546 float: left;
3547 margin: 0 0 0 3px;
3548 padding: 0;
3549 }
3550
3551 #login div.title h5,#register div.title h5 {
3552 color: #fff;
3553 margin: 10px;
3554 padding: 0;
3555 }
3556
3557 #login div.form div.fields div.field,#register div.form div.fields div.field
3558 {
3559 clear: both;
3560 overflow: hidden;
3561 margin: 0;
3562 padding: 0 0 10px;
3563 }
3564
3565 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
3566 {
3567 height: 1%;
3568 display: block;
3569 color: red;
3570 margin: 8px 0 0;
3571 padding: 0;
3572 max-width: 320px;
3573 }
3574
3575 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
3576 {
3577 color: #000;
3578 font-weight: 700;
3579 }
3580
3581 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
3582 {
3583 float: left;
3584 margin: 0;
3585 padding: 0;
3586 }
3587
3588 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
3589 {
3590 margin: 0 0 0 184px;
3591 padding: 0;
3592 }
3593
3594 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
3595 {
3596 color: #565656;
3597 font-weight: 700;
3598 }
3599
3600 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
3601 {
3602 color: #000;
3603 font-size: 1em;
3604 font-weight: 700;
3605 margin: 0;
3606 }
3607
3608 #changeset_content .container .wrapper,#graph_content .container .wrapper
3609 {
3610 width: 600px;
3611 }
3612
3613 #changeset_content .container .left {
3614 float: left;
3615 width: 75%;
3616 padding-left: 5px;
3617 }
3618
3619 #changeset_content .container .left .date,.ac .match {
3620 font-weight: 700;
3621 padding-top: 5px;
3622 padding-bottom: 5px;
3623 }
3624
3625 div#legend_container table td,div#legend_choices table td {
3626 border: none !important;
3627 height: 20px !important;
3628 padding: 0 !important;
3629 }
3630
3631 .q_filter_box {
3632 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3633 -webkit-border-radius: 4px;
3634 -moz-border-radius: 4px;
3635 border-radius: 4px;
3636 border: 0 none;
3637 color: #AAAAAA;
3638 margin-bottom: -4px;
3639 margin-top: -4px;
3640 padding-left: 3px;
3641 }
3642
3643 #node_filter {
3644 border: 0px solid #545454;
3645 color: #AAAAAA;
3646 padding-left: 3px;
3647 }
3648
3649
3650 .group_members_wrap{
3651
3652 }
3653
3654 .group_members .group_member{
3655 height: 30px;
3656 padding:0px 0px 0px 10px;
3657 }
3658
3659 /*README STYLE*/
3660
3661 div.readme {
3662 padding:0px;
3663 }
3664
3665 div.readme h2 {
3666 font-weight: normal;
3667 }
3668
3669 div.readme .readme_box {
3670 background-color: #fafafa;
3671 }
3672
3673 div.readme .readme_box {
1974 3674 clear:both;
1975 3675 overflow:hidden;
1976 3676 margin:0;
1977 3677 padding:0 20px 10px;
1978 3678 }
1979 3679
1980 .box .search div.search_path {
1981 background:none repeat scroll 0 0 #EEE;
1982 border:1px solid #CCC;
1983 color:blue;
1984 margin-bottom:10px;
1985 padding:10px 0;
1986 }
1987
1988 .box .search div.search_path div.link {
1989 font-weight:700;
1990 margin-left:25px;
1991 }
1992
1993 .box .search div.search_path div.link a {
1994 color:#003367;
1995 cursor:pointer;
1996 text-decoration:none;
1997 }
1998
1999 #path_unlock {
2000 color:red;
2001 font-size:1.2em;
2002 padding-left:4px;
2003 }
2004
2005 .info_box span {
2006 margin-left:3px;
2007 margin-right:3px;
2008 }
2009
2010 .info_box .rev {
2011 color: #003367;
2012 font-size: 1.6em;
2013 font-weight: bold;
2014 vertical-align: sub;
2015 }
2016
2017
2018 .info_box input#at_rev,.info_box input#size {
2019 background:#FFF;
2020 border-top:1px solid #b3b3b3;
2021 border-left:1px solid #b3b3b3;
2022 border-right:1px solid #eaeaea;
2023 border-bottom:1px solid #eaeaea;
2024 color:#000;
2025 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2026 font-size:12px;
2027 margin:0;
2028 padding:1px 5px 1px;
2029 }
2030
2031 .info_box input#view {
2032 text-align:center;
2033 padding:4px 3px 2px 2px;
2034 }
2035
2036 .yui-overlay,.yui-panel-container {
2037 visibility:hidden;
2038 position:absolute;
2039 z-index:2;
2040 }
2041
2042 .yui-tt {
2043 visibility:hidden;
2044 position:absolute;
2045 color:#666;
2046 background-color:#FFF;
2047 font-family:arial, helvetica, verdana, sans-serif;
2048 border:2px solid #003367;
2049 font:100% sans-serif;
2050 width:auto;
2051 opacity:1px;
2052 padding:8px;
2053 white-space: pre-wrap;
2054 -webkit-border-radius: 8px 8px 8px 8px;
2055 -khtml-border-radius: 8px 8px 8px 8px;
2056 -moz-border-radius: 8px 8px 8px 8px;
2057 border-radius: 8px 8px 8px 8px;
2058 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2059 }
2060
2061 .ac {
2062 vertical-align:top;
2063 }
2064
2065 .ac .yui-ac {
2066 position:relative;
2067 font-family:arial;
2068 font-size:100%;
2069 }
2070
2071 .ac .perm_ac {
2072 width:15em;
2073 }
2074
2075 .ac .yui-ac-input {
2076 width:100%;
2077 }
2078
2079 .ac .yui-ac-container {
2080 position:absolute;
2081 top:1.6em;
2082 width:100%;
2083 }
2084
2085 .ac .yui-ac-content {
2086 position:absolute;
2087 width:100%;
2088 border:1px solid gray;
2089 background:#fff;
2090 overflow:hidden;
2091 z-index:9050;
2092 }
2093
2094 .ac .yui-ac-shadow {
2095 position:absolute;
2096 width:100%;
2097 background:#000;
2098 -moz-opacity:0.1px;
2099 opacity:.10;
2100 filter:alpha(opacity = 10);
2101 z-index:9049;
2102 margin:.3em;
2103 }
2104
2105 .ac .yui-ac-content ul {
2106 width:100%;
2107 margin:0;
2108 padding:0;
2109 }
2110
2111 .ac .yui-ac-content li {
2112 cursor:default;
2113 white-space:nowrap;
2114 margin:0;
2115 padding:2px 5px;
2116 }
2117
2118 .ac .yui-ac-content li.yui-ac-prehighlight {
2119 background:#B3D4FF;
2120 }
2121
2122 .ac .yui-ac-content li.yui-ac-highlight {
2123 background:#556CB5;
2124 color:#FFF;
2125 }
2126
2127
2128 .follow{
2129 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2130 height: 16px;
2131 width: 20px;
2132 cursor: pointer;
2133 display: block;
2134 float: right;
2135 margin-top: 2px;
2136 }
2137
2138 .following{
2139 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2140 height: 16px;
2141 width: 20px;
2142 cursor: pointer;
2143 display: block;
2144 float: right;
2145 margin-top: 2px;
2146 }
2147
2148 .currently_following{
2149 padding-left: 10px;
2150 padding-bottom:5px;
2151 }
2152
2153 .add_icon {
2154 background:url("../images/icons/add.png") no-repeat scroll 3px;
2155 padding-left:20px;
2156 padding-top:0px;
2157 text-align:left;
2158 }
2159
2160 .edit_icon {
2161 background:url("../images/icons/folder_edit.png") no-repeat scroll 3px;
2162 padding-left:20px;
2163 padding-top:0px;
2164 text-align:left;
2165 }
2166
2167 .delete_icon {
2168 background:url("../images/icons/delete.png") no-repeat scroll 3px;
2169 padding-left:20px;
2170 padding-top:0px;
2171 text-align:left;
2172 }
2173
2174 .refresh_icon {
2175 background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px;
2176 padding-left:20px;
2177 padding-top:0px;
2178 text-align:left;
2179 }
2180
2181 .pull_icon {
2182 background:url("../images/icons/connect.png") no-repeat scroll 3px;
2183 padding-left:20px;
2184 padding-top:0px;
2185 text-align:left;
2186 }
2187
2188 .rss_icon {
2189 background:url("../images/icons/rss_16.png") no-repeat scroll 3px;
2190 padding-left:20px;
2191 padding-top:0px;
2192 text-align:left;
2193 }
2194
2195 .atom_icon {
2196 background:url("../images/icons/atom.png") no-repeat scroll 3px;
2197 padding-left:20px;
2198 padding-top:0px;
2199 text-align:left;
2200 }
2201
2202 .archive_icon {
2203 background:url("../images/icons/compress.png") no-repeat scroll 3px;
2204 padding-left:20px;
2205 text-align:left;
2206 padding-top:1px;
2207 }
2208
2209 .start_following_icon {
2210 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2211 padding-left:20px;
2212 text-align:left;
2213 padding-top:0px;
2214 }
2215
2216 .stop_following_icon {
2217 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2218 padding-left:20px;
2219 text-align:left;
2220 padding-top:0px;
2221 }
2222
2223 .action_button {
2224 border:0;
2225 display:inline;
2226 }
2227
2228 .action_button:hover {
2229 border:0;
2230 text-decoration:underline;
2231 cursor:pointer;
2232 }
2233
2234 #switch_repos {
2235 position:absolute;
2236 height:25px;
2237 z-index:1;
2238 }
2239
2240 #switch_repos select {
2241 min-width:150px;
2242 max-height:250px;
2243 z-index:1;
2244 }
2245
2246 .breadcrumbs {
2247 border:medium none;
2248 color:#FFF;
2249 float:left;
2250 text-transform:uppercase;
2251 font-weight:700;
2252 font-size:14px;
2253 margin:0;
2254 padding:11px 0 11px 10px;
2255 }
2256
2257 .breadcrumbs a {
2258 color:#FFF;
2259 }
2260
2261 .flash_msg ul {
2262 margin:0;
2263 padding:0 0 10px;
2264 }
2265
2266 .error_msg {
2267 background-color:#FFCFCF;
2268 background-image:url("../images/icons/error_msg.png");
2269 border:1px solid #FF9595;
2270 color:#C30;
2271 }
2272
2273 .warning_msg {
2274 background-color:#FFFBCC;
2275 background-image:url("../images/icons/warning_msg.png");
2276 border:1px solid #FFF35E;
2277 color:#C69E00;
2278 }
2279
2280 .success_msg {
2281 background-color:#D5FFCF;
2282 background-image:url("../images/icons/success_msg.png");
2283 border:1px solid #97FF88;
2284 color:#090;
2285 }
2286
2287 .notice_msg {
2288 background-color:#DCE3FF;
2289 background-image:url("../images/icons/notice_msg.png");
2290 border:1px solid #93A8FF;
2291 color:#556CB5;
2292 }
2293
2294 .success_msg,.error_msg,.notice_msg,.warning_msg {
2295 background-position:10px center;
2296 background-repeat:no-repeat;
2297 font-size:12px;
2298 font-weight:700;
2299 min-height:14px;
2300 line-height:14px;
2301 margin-bottom:0;
2302 margin-top:0;
2303 display:block;
2304 overflow:auto;
2305 padding:6px 10px 6px 40px;
2306 }
2307
2308 #msg_close {
2309 background:transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
2310 cursor:pointer;
2311 height:16px;
2312 position:absolute;
2313 right:5px;
2314 top:5px;
2315 width:16px;
2316 }
2317
2318 div#legend_container table,div#legend_choices table {
2319 width:auto !important;
2320 }
2321
2322 table#permissions_manage {
2323 width:0 !important;
2324 }
2325
2326 table#permissions_manage span.private_repo_msg {
2327 font-size:0.8em;
2328 opacity:0.6px;
2329 }
2330
2331 table#permissions_manage td.private_repo_msg {
2332 font-size:0.8em;
2333 }
2334
2335 table#permissions_manage tr#add_perm_input td {
2336 vertical-align:middle;
2337 }
2338
2339 div.gravatar {
2340 background-color:#FFF;
2341 border:1px solid #D0D0D0;
2342 float:left;
2343 margin-right:0.7em;
2344 padding:2px 2px 0;
2345
2346 -webkit-border-radius: 6px;
2347 -khtml-border-radius: 6px;
2348 -moz-border-radius: 6px;
2349 border-radius: 6px;
2350
2351 }
2352
2353 div.gravatar img {
2354 -webkit-border-radius: 4px;
2355 -khtml-border-radius: 4px;
2356 -moz-border-radius: 4px;
2357 border-radius: 4px;
2358 }
2359
2360 #header,#content,#footer {
2361 min-width:978px;
2362 }
2363
2364 #content {
2365 clear:both;
2366 overflow:hidden;
2367 padding:14px 10px;
2368 }
2369
2370 #content div.box div.title div.search {
2371 background:url("../images/title_link.png") no-repeat top left;
2372 border-left:1px solid #316293;
2373 }
2374
2375 #content div.box div.title div.search div.input input {
2376 border:1px solid #316293;
2377 }
2378
2379 .ui-button-small a:hover {
2380
2381 }
2382 input.ui-button-small,.ui-button-small {
2383 background:#e5e3e3 url("../images/button.png") repeat-x !important;
2384 border-top:1px solid #DDD !important;
2385 border-left:1px solid #c6c6c6 !important;
2386 border-right:1px solid #DDD !important;
2387 border-bottom:1px solid #c6c6c6 !important;
2388 color:#515151 !important;
2389 outline:none !important;
2390 margin:0 !important;
2391 -webkit-border-radius: 4px 4px 4px 4px !important;
2392 -khtml-border-radius: 4px 4px 4px 4px !important;
2393 -moz-border-radius: 4px 4px 4px 4px !important;
2394 border-radius: 4px 4px 4px 4px !important;
2395 box-shadow: 0 1px 0 #ececec !important;
2396 cursor: pointer !important;
2397 padding:0px 2px 1px 2px;
2398 }
2399
2400 input.ui-button-small:hover,.ui-button-small:hover {
2401 background:#b4b4b4 url("../images/button_selected.png") repeat-x !important;
2402 border-top:1px solid #ccc !important;
2403 border-left:1px solid #bebebe !important;
2404 border-right:1px solid #b1b1b1 !important;
2405 border-bottom:1px solid #afafaf !important;
2406 text-decoration: none;
2407 }
2408
2409 input.ui-button-small-blue,.ui-button-small-blue {
2410 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2411 border-top:1px solid #5c91a4;
2412 border-left:1px solid #2a6f89;
2413 border-right:1px solid #2b7089;
2414 border-bottom:1px solid #1a6480;
2415 color:#fff;
2416 -webkit-border-radius: 4px 4px 4px 4px;
2417 -khtml-border-radius: 4px 4px 4px 4px;
2418 -moz-border-radius: 4px 4px 4px 4px;
2419 border-radius: 4px 4px 4px 4px;
2420 box-shadow: 0 1px 0 #ececec;
2421 cursor: pointer;
2422 padding:0px 2px 1px 2px;
2423 }
2424
2425 input.ui-button-small-blue:hover {
2426
2427 }
2428
2429
2430 ins,div.options a:hover {
2431 text-decoration:none;
2432 }
2433
2434 img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url {
2435 border:none;
2436 }
2437
2438 img.icon,.right .merge img {
2439 vertical-align:bottom;
2440 }
2441
2442 #header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul {
2443 float:right;
2444 margin:0;
2445 padding:0;
2446 }
2447
2448
2449 #header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices {
2450 float:left;
2451 }
2452
2453 #header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow {
2454 display:none;
2455 }
2456
2457 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded {
2458 display:block;
2459 }
2460
2461 #content div.graph{
2462 padding:0 10px 10px;
2463 }
2464
2465 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a {
2466 color:#bfe3ff;
2467 }
2468
2469 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal {
2470 margin:10px 24px 10px 44px;
2471 }
2472
2473 #content div.box div.form,#content div.box div.table,#content div.box div.traffic {
3680 div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
3681 border-bottom: 0 !important;
3682 margin: 0 !important;
3683 padding: 0 !important;
3684 line-height: 1.5em !important;
3685 }
3686
3687
3688 div.readme .readme_box h1:first-child {
3689 padding-top: .25em !important;
3690 }
3691
3692 div.readme .readme_box h2, div.readme .readme_box h3 {
3693 margin: 1em 0 !important;
3694 }
3695
3696 div.readme .readme_box h2 {
3697 margin-top: 1.5em !important;
3698 border-top: 4px solid #e0e0e0 !important;
3699 padding-top: .5em !important;
3700 }
3701
3702 div.readme .readme_box p {
3703 color: black !important;
3704 margin: 1em 0 !important;
3705 line-height: 1.5em !important;
3706 }
3707
3708 div.readme .readme_box ul {
3709 list-style: disc !important;
3710 margin: 1em 0 1em 2em !important;
3711 }
3712
3713 div.readme .readme_box ol {
3714 list-style: decimal;
3715 margin: 1em 0 1em 2em !important;
3716 }
3717
3718 div.readme .readme_box pre, code {
3719 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
3720 }
3721
3722 div.readme .readme_box code {
3723 font-size: 12px !important;
3724 background-color: ghostWhite !important;
3725 color: #444 !important;
3726 padding: 0 .2em !important;
3727 border: 1px solid #dedede !important;
3728 }
3729
3730 div.readme .readme_box pre code {
3731 padding: 0 !important;
3732 font-size: 12px !important;
3733 background-color: #eee !important;
3734 border: none !important;
3735 }
3736
3737 div.readme .readme_box pre {
3738 margin: 1em 0;
3739 font-size: 12px;
3740 background-color: #eee;
3741 border: 1px solid #ddd;
3742 padding: 5px;
3743 color: #444;
3744 overflow: auto;
3745 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3746 -webkit-border-radius: 3px;
3747 -moz-border-radius: 3px;
3748 border-radius: 3px;
3749 }
3750
3751
3752 /** RST STYLE **/
3753
3754
3755 div.rst-block {
3756 padding:0px;
3757 }
3758
3759 div.rst-block h2 {
3760 font-weight: normal;
3761 }
3762
3763 div.rst-block {
3764 background-color: #fafafa;
3765 }
3766
3767 div.rst-block {
2474 3768 clear:both;
2475 3769 overflow:hidden;
2476 3770 margin:0;
2477 3771 padding:0 20px 10px;
2478 3772 }
2479 3773
2480 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields {
2481 clear:both;
2482 overflow:hidden;
2483 margin:0;
2484 padding:0;
2485 }
2486
2487 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span {
2488 height:1%;
2489 display:block;
2490 color:#363636;
2491 margin:0;
2492 padding:2px 0 0;
2493 }
2494
2495 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error {
2496 background:#FBE3E4;
2497 border-top:1px solid #e1b2b3;
2498 border-left:1px solid #e1b2b3;
2499 border-right:1px solid #FBC2C4;
2500 border-bottom:1px solid #FBC2C4;
2501 }
2502
2503 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success {
2504 background:#E6EFC2;
2505 border-top:1px solid #cebb98;
2506 border-left:1px solid #cebb98;
2507 border-right:1px solid #c6d880;
2508 border-bottom:1px solid #c6d880;
2509 }
2510
2511 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input {
2512 margin:0;
2513 }
2514
2515 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios{
2516 margin:0 0 0 0px !important;
2517 padding:0;
2518 }
2519
2520 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios {
2521 margin:0 0 0 200px;
2522 padding:0;
2523 }
2524
2525
2526 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover {
2527 color:#000;
2528 text-decoration:none;
2529 }
2530
2531 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus {
2532 border:1px solid #666;
2533 }
2534
2535 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio {
2536 clear:both;
2537 overflow:hidden;
2538 margin:0;
2539 padding:8px 0 2px;
2540 }
2541
2542 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input {
2543 float:left;
2544 margin:0;
2545 }
2546
2547 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label {
2548 height:1%;
2549 display:block;
2550 float:left;
2551 margin:2px 0 0 4px;
2552 }
2553
2554 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input {
2555 color:#000;
2556 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2557 font-size:11px;
2558 font-weight:700;
2559 margin:0;
2560 }
2561
2562 input.ui-button {
2563 background:#e5e3e3 url("../images/button.png") repeat-x;
2564 border-top:1px solid #DDD;
2565 border-left:1px solid #c6c6c6;
2566 border-right:1px solid #DDD;
2567 border-bottom:1px solid #c6c6c6;
2568 color:#515151 !important;
2569 outline:none;
2570 margin:0;
2571 padding:6px 12px;
2572 -webkit-border-radius: 4px 4px 4px 4px;
2573 -khtml-border-radius: 4px 4px 4px 4px;
2574 -moz-border-radius: 4px 4px 4px 4px;
2575 border-radius: 4px 4px 4px 4px;
2576 box-shadow: 0 1px 0 #ececec;
2577 cursor: pointer;
2578 }
2579
2580 input.ui-button:hover {
2581 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2582 border-top:1px solid #ccc;
2583 border-left:1px solid #bebebe;
2584 border-right:1px solid #b1b1b1;
2585 border-bottom:1px solid #afafaf;
2586 }
2587
2588 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight {
2589 display:inline;
2590 }
2591
2592 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons {
2593 margin:10px 0 0 200px;
2594 padding:0;
2595 }
2596
2597 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons {
2598 margin:10px 0 0;
2599 }
2600
2601 #content div.box table td.user,#content div.box table td.address {
2602 width:10%;
2603 text-align:center;
2604 }
2605
2606 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link {
2607 text-align:right;
2608 margin:6px 0 0;
2609 padding:0;
2610 }
2611
2612
2613 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover {
2614 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2615 border-top:1px solid #ccc;
2616 border-left:1px solid #bebebe;
2617 border-right:1px solid #b1b1b1;
2618 border-bottom:1px solid #afafaf;
2619 color:#515151;
2620 margin:0;
2621 padding:6px 12px;
2622 }
2623
2624 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results {
2625 text-align:left;
2626 float:left;
2627 margin:0;
2628 padding:0;
2629 }
2630
2631 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span {
2632 height:1%;
2633 display:block;
2634 float:left;
2635 background:#ebebeb url("../images/pager.png") repeat-x;
2636 border-top:1px solid #dedede;
2637 border-left:1px solid #cfcfcf;
2638 border-right:1px solid #c4c4c4;
2639 border-bottom:1px solid #c4c4c4;
2640 color:#4A4A4A;
2641 font-weight:700;
2642 margin:0;
2643 padding:6px 8px;
2644 }
2645
2646 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled {
2647 color:#B4B4B4;
2648 padding:6px;
2649 }
2650
2651 #login,#register {
2652 width:520px;
2653 margin:10% auto 0;
2654 padding:0;
2655 }
2656
2657 #login div.color,#register div.color {
2658 clear:both;
2659 overflow:hidden;
2660 background:#FFF;
2661 margin:10px auto 0;
2662 padding:3px 3px 3px 0;
2663 }
2664
2665 #login div.color a,#register div.color a {
2666 width:20px;
2667 height:20px;
2668 display:block;
2669 float:left;
2670 margin:0 0 0 3px;
2671 padding:0;
2672 }
2673
2674 #login div.title h5,#register div.title h5 {
2675 color:#fff;
2676 margin:10px;
2677 padding:0;
2678 }
2679
2680 #login div.form div.fields div.field,#register div.form div.fields div.field {
2681 clear:both;
2682 overflow:hidden;
2683 margin:0;
2684 padding:0 0 10px;
2685 }
2686
2687 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message {
2688 height:1%;
2689 display:block;
2690 color:red;
2691 margin:8px 0 0;
2692 padding:0;
2693 max-width: 320px;
2694 }
2695
2696 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label {
2697 color:#000;
2698 font-weight:700;
2699 }
2700
2701 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input {
2702 float:left;
2703 margin:0;
2704 padding:0;
2705 }
2706
2707 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox {
2708 margin:0 0 0 184px;
2709 padding:0;
2710 }
2711
2712 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label {
2713 color:#565656;
2714 font-weight:700;
2715 }
2716
2717 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input {
2718 color:#000;
2719 font-size:1em;
2720 font-weight:700;
2721 font-family:Verdana, Helvetica, Sans-Serif;
2722 margin:0;
2723 }
2724
2725 #changeset_content .container .wrapper,#graph_content .container .wrapper {
2726 width:600px;
2727 }
2728
2729 #changeset_content .container .left,#graph_content .container .left {
2730 float:left;
2731 width:70%;
2732 padding-left:5px;
2733 }
2734
2735 #changeset_content .container .left .date,.ac .match {
2736 font-weight:700;
2737 padding-top: 5px;
2738 padding-bottom:5px;
2739 }
2740
2741 div#legend_container table td,div#legend_choices table td {
2742 border:none !important;
2743 height:20px !important;
2744 padding:0 !important;
2745 }
2746
2747 #q_filter{
2748 border:0 none;
2749 color:#AAAAAA;
2750 margin-bottom:-4px;
2751 margin-top:-4px;
2752 padding-left:3px;
2753 }
2754
2755 #node_filter{
2756 border:0px solid #545454;
2757 color:#AAAAAA;
2758 padding-left:3px;
2759 }
3774 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
3775 border-bottom: 0 !important;
3776 margin: 0 !important;
3777 padding: 0 !important;
3778 line-height: 1.5em !important;
3779 }
3780
3781
3782 div.rst-block h1:first-child {
3783 padding-top: .25em !important;
3784 }
3785
3786 div.rst-block h2, div.rst-block h3 {
3787 margin: 1em 0 !important;
3788 }
3789
3790 div.rst-block h2 {
3791 margin-top: 1.5em !important;
3792 border-top: 4px solid #e0e0e0 !important;
3793 padding-top: .5em !important;
3794 }
3795
3796 div.rst-block p {
3797 color: black !important;
3798 margin: 1em 0 !important;
3799 line-height: 1.5em !important;
3800 }
3801
3802 div.rst-block ul {
3803 list-style: disc !important;
3804 margin: 1em 0 1em 2em !important;
3805 }
3806
3807 div.rst-block ol {
3808 list-style: decimal;
3809 margin: 1em 0 1em 2em !important;
3810 }
3811
3812 div.rst-block pre, code {
3813 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
3814 }
3815
3816 div.rst-block code {
3817 font-size: 12px !important;
3818 background-color: ghostWhite !important;
3819 color: #444 !important;
3820 padding: 0 .2em !important;
3821 border: 1px solid #dedede !important;
3822 }
3823
3824 div.rst-block pre code {
3825 padding: 0 !important;
3826 font-size: 12px !important;
3827 background-color: #eee !important;
3828 border: none !important;
3829 }
3830
3831 div.rst-block pre {
3832 margin: 1em 0;
3833 font-size: 12px;
3834 background-color: #eee;
3835 border: 1px solid #ddd;
3836 padding: 5px;
3837 color: #444;
3838 overflow: auto;
3839 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3840 -webkit-border-radius: 3px;
3841 -moz-border-radius: 3px;
3842 border-radius: 3px;
3843 }
3844
3845
3846 /** comment main **/
3847 .comments {
3848 padding:10px 20px;
3849 }
3850
3851 .comments .comment {
3852 border: 1px solid #ddd;
3853 margin-top: 10px;
3854 -webkit-border-radius: 4px;
3855 -moz-border-radius: 4px;
3856 border-radius: 4px;
3857 }
3858
3859 .comments .comment .meta {
3860 background: #f8f8f8;
3861 padding: 4px;
3862 border-bottom: 1px solid #ddd;
3863 }
3864
3865 .comments .comment .meta img {
3866 vertical-align: middle;
3867 }
3868
3869 .comments .comment .meta .user {
3870 font-weight: bold;
3871 }
3872
3873 .comments .comment .meta .date {
3874 }
3875
3876 .comments .comment .text {
3877 background-color: #FAFAFA;
3878 }
3879 .comment .text div.rst-block p {
3880 margin: 0.5em 0px !important;
3881 }
3882
3883 .comments .comments-number{
3884 padding:0px 0px 10px 0px;
3885 font-weight: bold;
3886 color: #666;
3887 font-size: 16px;
3888 }
3889
3890 /** comment form **/
3891
3892 .comment-form .clearfix{
3893 background: #EEE;
3894 -webkit-border-radius: 4px;
3895 -moz-border-radius: 4px;
3896 border-radius: 4px;
3897 padding: 10px;
3898 }
3899
3900 div.comment-form {
3901 margin-top: 20px;
3902 }
3903
3904 .comment-form strong {
3905 display: block;
3906 margin-bottom: 15px;
3907 }
3908
3909 .comment-form textarea {
3910 width: 100%;
3911 height: 100px;
3912 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
3913 }
3914
3915 form.comment-form {
3916 margin-top: 10px;
3917 margin-left: 10px;
3918 }
3919
3920 .comment-form-submit {
3921 margin-top: 5px;
3922 margin-left: 525px;
3923 }
3924
3925 .file-comments {
3926 display: none;
3927 }
3928
3929 .comment-form .comment {
3930 margin-left: 10px;
3931 }
3932
3933 .comment-form .comment-help{
3934 padding: 0px 0px 5px 0px;
3935 color: #666;
3936 }
3937
3938 .comment-form .comment-button{
3939 padding-top:5px;
3940 }
3941
3942 .add-another-button {
3943 margin-left: 10px;
3944 margin-top: 10px;
3945 margin-bottom: 10px;
3946 }
3947
3948 .comment .buttons {
3949 float: right;
3950 }
3951
3952
3953 .show-inline-comments{
3954 position: relative;
3955 top:1px
3956 }
3957
3958 /** comment inline form **/
3959
3960 .comment-inline-form .clearfix{
3961 background: #EEE;
3962 -webkit-border-radius: 4px;
3963 -moz-border-radius: 4px;
3964 border-radius: 4px;
3965 padding: 5px;
3966 }
3967
3968 div.comment-inline-form {
3969 margin-top: 5px;
3970 padding:2px 6px 8px 6px;
3971 }
3972
3973 .comment-inline-form strong {
3974 display: block;
3975 margin-bottom: 15px;
3976 }
3977
3978 .comment-inline-form textarea {
3979 width: 100%;
3980 height: 100px;
3981 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
3982 }
3983
3984 form.comment-inline-form {
3985 margin-top: 10px;
3986 margin-left: 10px;
3987 }
3988
3989 .comment-inline-form-submit {
3990 margin-top: 5px;
3991 margin-left: 525px;
3992 }
3993
3994 .file-comments {
3995 display: none;
3996 }
3997
3998 .comment-inline-form .comment {
3999 margin-left: 10px;
4000 }
4001
4002 .comment-inline-form .comment-help{
4003 padding: 0px 0px 2px 0px;
4004 color: #666666;
4005 font-size: 10px;
4006 }
4007
4008 .comment-inline-form .comment-button{
4009 padding-top:5px;
4010 }
4011
4012 /** comment inline **/
4013 .inline-comments {
4014 padding:10px 20px;
4015 }
4016
4017 .inline-comments div.rst-block {
4018 clear:both;
4019 overflow:hidden;
4020 margin:0;
4021 padding:0 20px 0px;
4022 }
4023 .inline-comments .comment {
4024 border: 1px solid #ddd;
4025 -webkit-border-radius: 4px;
4026 -moz-border-radius: 4px;
4027 border-radius: 4px;
4028 margin: 3px 3px 5px 5px;
4029 background-color: #FAFAFA;
4030 }
4031 .inline-comments .comment-wrapp{
4032 padding:1px;
4033 }
4034 .inline-comments .comment .meta {
4035 background: #f8f8f8;
4036 padding: 4px;
4037 border-bottom: 1px solid #ddd;
4038 }
4039
4040 .inline-comments .comment .meta img {
4041 vertical-align: middle;
4042 }
4043
4044 .inline-comments .comment .meta .user {
4045 font-weight: bold;
4046 }
4047
4048 .inline-comments .comment .meta .date {
4049 }
4050
4051 .inline-comments .comment .text {
4052 background-color: #FAFAFA;
4053 }
4054
4055 .inline-comments .comments-number{
4056 padding:0px 0px 10px 0px;
4057 font-weight: bold;
4058 color: #666;
4059 font-size: 16px;
4060 }
4061 .inline-comments-button .add-comment{
4062 margin:10px 5px !important;
4063 }
4064 .notifications{
4065 border-radius: 4px 4px 4px 4px;
4066 -webkit-border-radius: 4px;
4067 -moz-border-radius: 4px;
4068 float: right;
4069 margin: 20px 0px 0px 0px;
4070 position: absolute;
4071 text-align: center;
4072 width: 26px;
4073 z-index: 1000;
4074 }
4075 .notifications a{
4076 color:#888 !important;
4077 display: block;
4078 font-size: 10px;
4079 background-color: #DEDEDE !important;
4080 border-radius: 2px !important;
4081 -webkit-border-radius: 2px !important;
4082 -moz-border-radius: 2px !important;
4083 }
4084 .notifications a:hover{
4085 text-decoration: none !important;
4086 background-color: #EEEFFF !important;
4087 }
4088 .notification-header{
4089 padding-top:6px;
4090 }
4091 .notification-header .desc{
4092 font-size: 16px;
4093 height: 24px;
4094 float: left
4095 }
4096 .notification-list .container.unread{
4097
4098 }
4099 .notification-header .gravatar{
4100
4101 }
4102 .notification-header .desc.unread{
4103 font-weight: bold;
4104 font-size: 17px;
4105 }
4106
4107 .notification-header .delete-notifications{
4108 float: right;
4109 padding-top: 8px;
4110 cursor: pointer;
4111 }
4112 .notification-subject{
4113 clear:both;
4114 border-bottom: 1px solid #eee;
4115 padding:5px 0px 5px 38px;
4116 }
4117
4118
4119 /*****************************************************************************
4120 DIFFS CSS
4121 ******************************************************************************/
4122
4123 div.diffblock {
4124 overflow: auto;
4125 padding: 0px;
4126 border: 1px solid #ccc;
4127 background: #f8f8f8;
4128 font-size: 100%;
4129 line-height: 100%;
4130 /* new */
4131 line-height: 125%;
4132 -webkit-border-radius: 6px 6px 0px 0px;
4133 -moz-border-radius: 6px 6px 0px 0px;
4134 border-radius: 6px 6px 0px 0px;
4135 }
4136 div.diffblock.margined{
4137 margin: 0px 20px 0px 20px;
4138 }
4139 div.diffblock .code-header{
4140 border-bottom: 1px solid #CCCCCC;
4141 background: #EEEEEE;
4142 padding:10px 0 10px 0;
4143 height: 14px;
4144 }
4145 div.diffblock .code-header.cv{
4146 height: 34px;
4147 }
4148 div.diffblock .code-header-title{
4149 padding: 0px 0px 10px 5px !important;
4150 margin: 0 !important;
4151 }
4152 div.diffblock .code-header .hash{
4153 float: left;
4154 padding: 2px 0 0 2px;
4155 }
4156 div.diffblock .code-header .date{
4157 float:left;
4158 text-transform: uppercase;
4159 padding: 2px 0px 0px 2px;
4160 }
4161 div.diffblock .code-header div{
4162 margin-left:4px;
4163 font-weight: bold;
4164 font-size: 14px;
4165 }
4166 div.diffblock .code-body{
4167 background: #FFFFFF;
4168 }
4169 div.diffblock pre.raw{
4170 background: #FFFFFF;
4171 color:#000000;
4172 }
4173 table.code-difftable{
4174 border-collapse: collapse;
4175 width: 99%;
4176 }
4177 table.code-difftable td {
4178 padding: 0 !important;
4179 background: none !important;
4180 border:0 !important;
4181 vertical-align: none !important;
4182 }
4183 table.code-difftable .context{
4184 background:none repeat scroll 0 0 #DDE7EF;
4185 }
4186 table.code-difftable .add{
4187 background:none repeat scroll 0 0 #DDFFDD;
4188 }
4189 table.code-difftable .add ins{
4190 background:none repeat scroll 0 0 #AAFFAA;
4191 text-decoration:none;
4192 }
4193 table.code-difftable .del{
4194 background:none repeat scroll 0 0 #FFDDDD;
4195 }
4196 table.code-difftable .del del{
4197 background:none repeat scroll 0 0 #FFAAAA;
4198 text-decoration:none;
4199 }
4200
4201 /** LINE NUMBERS **/
4202 table.code-difftable .lineno{
4203
4204 padding-left:2px;
4205 padding-right:2px;
4206 text-align:right;
4207 width:32px;
4208 -moz-user-select:none;
4209 -webkit-user-select: none;
4210 border-right: 1px solid #CCC !important;
4211 border-left: 0px solid #CCC !important;
4212 border-top: 0px solid #CCC !important;
4213 border-bottom: none !important;
4214 vertical-align: middle !important;
4215
4216 }
4217 table.code-difftable .lineno.new {
4218 }
4219 table.code-difftable .lineno.old {
4220 }
4221 table.code-difftable .lineno a{
4222 color:#747474 !important;
4223 font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
4224 letter-spacing:-1px;
4225 text-align:right;
4226 padding-right: 2px;
4227 cursor: pointer;
4228 display: block;
4229 width: 32px;
4230 }
4231
4232 table.code-difftable .lineno-inline{
4233 background:none repeat scroll 0 0 #FFF !important;
4234 padding-left:2px;
4235 padding-right:2px;
4236 text-align:right;
4237 width:30px;
4238 -moz-user-select:none;
4239 -webkit-user-select: none;
4240 }
4241
4242 /** CODE **/
4243 table.code-difftable .code {
4244 display: block;
4245 width: 100%;
4246 }
4247 table.code-difftable .code td{
4248 margin:0;
4249 padding:0;
4250 }
4251 table.code-difftable .code pre{
4252 margin:0;
4253 padding:0;
4254 height: 17px;
4255 line-height: 17px;
4256 }
4257
4258
4259 .diffblock.margined.comm .line .code:hover{
4260 background-color:#FFFFCC !important;
4261 cursor: pointer !important;
4262 background-image:url("../images/icons/comment_add.png") !important;
4263 background-repeat:no-repeat !important;
4264 background-position: right !important;
4265 background-position: 0% 50% !important;
4266 }
4267 .diffblock.margined.comm .line .code.no-comment:hover{
4268 background-image: none !important;
4269 cursor: auto !important;
4270 background-color: inherit !important;
4271
4272 }
This diff has been collapsed as it changes many lines, (1093 lines changed) Show them Hide them
@@ -13,32 +13,45 b' var CodeMirror = (function() {'
13 13 if (defaults.hasOwnProperty(opt))
14 14 options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
15 15
16 // The element in which the editor lives. Takes care of scrolling
17 // (if enabled).
18 var wrapper = document.createElement("div");
16 var targetDocument = options["document"];
17 // The element in which the editor lives.
18 var wrapper = targetDocument.createElement("div");
19 19 wrapper.className = "CodeMirror";
20 20 // This mess creates the base DOM structure for the editor.
21 21 wrapper.innerHTML =
22 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
23 '<pre style="position: relative; height: 0; visibility: hidden; overflow: hidden;">' + // To measure line/char size
24 '<span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></pre>' +
25 '<div style="position: relative">' + // Moved around its parent to cover visible view
26 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
27 '<div style="overflow: hidden; position: absolute; width: 0; left: 0">' + // Wraps and hides input textarea
28 '<textarea style="height: 1px; position: absolute; width: 1px;" wrap="off"></textarea></div>' +
29 // Provides positioning relative to (visible) text origin
30 '<div class="CodeMirror-lines"><div style="position: relative">' +
31 '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
32 '<div></div></div></div></div></div>'; // This DIV contains the actual code
22 '<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
23 '<textarea style="position: absolute; width: 10000px;" wrap="off" ' +
24 'autocorrect="off" autocapitalize="off"></textarea></div>' +
25 '<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
26 '<div style="position: relative">' + // Set to the height of the text, causes scrolling
27 '<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
28 '<div style="position: relative">' + // Moved around its parent to cover visible view
29 '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
30 // Provides positioning relative to (visible) text origin
31 '<div class="CodeMirror-lines"><div style="position: relative" draggable="true">' +
32 '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
33 '<div></div>' + // This DIV contains the actual code
34 '</div></div></div></div></div>';
33 35 if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
34 36 // I've never seen more elegant code in my life.
35 var code = wrapper.firstChild, measure = code.firstChild, mover = measure.nextSibling,
37 var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
38 scroller = wrapper.lastChild, code = scroller.firstChild,
39 measure = code.firstChild, mover = measure.nextSibling,
36 40 gutter = mover.firstChild, gutterText = gutter.firstChild,
37 inputDiv = gutter.nextSibling, input = inputDiv.firstChild,
38 lineSpace = inputDiv.nextSibling.firstChild, cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
41 lineSpace = gutter.nextSibling.firstChild,
42 cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
39 43 if (options.tabindex != null) input.tabindex = options.tabindex;
40 44 if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
41 45
46 // Check for problem with IE innerHTML not working when we have a
47 // P (or similar) parent node.
48 try { stringWidth("x"); }
49 catch (e) {
50 if (e.message.match(/unknown runtime/i))
51 e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
52 throw e;
53 }
54
42 55 // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
43 56 var poll = new Delayed(), highlight = new Delayed(), blinker;
44 57
@@ -46,7 +59,7 b' var CodeMirror = (function() {'
46 59 // (see Line constructor), work an array of lines that should be
47 60 // parsed, and history the undo history (instance of History
48 61 // constructor).
49 var mode, lines = [new Line("")], work, history = new History(), focused;
62 var mode, lines = [new Line("")], work, focused;
50 63 loadMode();
51 64 // The selection. These are always maintained to point at valid
52 65 // positions. Inverted is used to remember that the user is
@@ -56,10 +69,10 b' var CodeMirror = (function() {'
56 69 // whether the user is holding shift. reducedSelection is a hack
57 70 // to get around the fact that we can't create inverted
58 71 // selections. See below.
59 var shiftSelecting, reducedSelection;
72 var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText;
60 73 // Variables used by startOperation/endOperation to track what
61 74 // happened during the operation.
62 var updateInput, changes, textChanged, selectionChanged, leaveInputAlone;
75 var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
63 76 // Current visible range (may be bigger than the view window).
64 77 var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
65 78 // editing will hold an object describing the things we put in the
@@ -67,35 +80,46 b' var CodeMirror = (function() {'
67 80 // bracketHighlighted is used to remember that a backet has been
68 81 // marked.
69 82 var editing, bracketHighlighted;
83 // Tracks the maximum line length so that the horizontal scrollbar
84 // can be kept static when scrolling.
85 var maxLine = "", maxWidth;
70 86
71 // Initialize the content. Somewhat hacky (delayed prepareInput)
72 // to work around browser issues.
87 // Initialize the content.
73 88 operation(function(){setValue(options.value || ""); updateInput = false;})();
74 setTimeout(prepareInput, 20);
89 var history = new History();
75 90
76 91 // Register our event handlers.
77 connect(wrapper, "mousedown", operation(onMouseDown));
92 connect(scroller, "mousedown", operation(onMouseDown));
93 connect(scroller, "dblclick", operation(onDoubleClick));
94 connect(lineSpace, "dragstart", onDragStart);
78 95 // Gecko browsers fire contextmenu *after* opening the menu, at
79 96 // which point we can't mess with it anymore. Context menu is
80 97 // handled in onMouseDown for Gecko.
81 if (!gecko) connect(wrapper, "contextmenu", operation(onContextMenu));
82 connect(code, "dblclick", operation(onDblClick));
83 connect(wrapper, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);});
98 if (!gecko) connect(scroller, "contextmenu", onContextMenu);
99 connect(scroller, "scroll", function() {
100 updateDisplay([]);
101 if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
102 if (options.onScroll) options.onScroll(instance);
103 });
84 104 connect(window, "resize", function() {updateDisplay(true);});
85 105 connect(input, "keyup", operation(onKeyUp));
106 connect(input, "input", function() {fastPoll(curKeyId);});
86 107 connect(input, "keydown", operation(onKeyDown));
87 108 connect(input, "keypress", operation(onKeyPress));
88 109 connect(input, "focus", onFocus);
89 110 connect(input, "blur", onBlur);
90 111
91 connect(wrapper, "dragenter", function(e){e.stop();});
92 connect(wrapper, "dragover", function(e){e.stop();});
93 connect(wrapper, "drop", operation(onDrop));
94 connect(wrapper, "paste", function(){input.focus(); fastPoll();});
112 connect(scroller, "dragenter", e_stop);
113 connect(scroller, "dragover", e_stop);
114 connect(scroller, "drop", operation(onDrop));
115 connect(scroller, "paste", function(){focusInput(); fastPoll();});
95 116 connect(input, "paste", function(){fastPoll();});
96 117 connect(input, "cut", function(){fastPoll();});
97 118
98 if (document.activeElement == input) onFocus();
119 // IE throws unspecified error in certain cases, when
120 // trying to access activeElement before onload
121 var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
122 if (hasFocus) setTimeout(onFocus, 20);
99 123 else onBlur();
100 124
101 125 function isLine(l) {return l >= 0 && l < lines.length;}
@@ -104,27 +128,37 b' var CodeMirror = (function() {'
104 128 // range checking and/or clipping. operation is used to wrap the
105 129 // call so that changes it makes are tracked, and the display is
106 130 // updated afterwards.
107 var instance = {
131 var instance = wrapper.CodeMirror = {
108 132 getValue: getValue,
109 133 setValue: operation(setValue),
110 134 getSelection: getSelection,
111 135 replaceSelection: operation(replaceSelection),
112 focus: function(){input.focus(); onFocus(); fastPoll();},
136 focus: function(){focusInput(); onFocus(); fastPoll();},
113 137 setOption: function(option, value) {
114 138 options[option] = value;
115 if (option == "lineNumbers" || option == "gutter") gutterChanged();
139 if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber")
140 operation(gutterChanged)();
116 141 else if (option == "mode" || option == "indentUnit") loadMode();
142 else if (option == "readOnly" && value == "nocursor") input.blur();
143 else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
117 144 },
118 145 getOption: function(option) {return options[option];},
119 146 undo: operation(undo),
120 147 redo: operation(redo),
121 indentLine: operation(function(n) {if (isLine(n)) indentLine(n, "smart");}),
148 indentLine: operation(function(n, dir) {
149 if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
150 }),
122 151 historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
152 clearHistory: function() {history = new History();},
123 153 matchBrackets: operation(function(){matchBrackets(true);}),
124 154 getTokenAt: function(pos) {
125 155 pos = clipPos(pos);
126 156 return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
127 157 },
158 getStateAfter: function(line) {
159 line = clipLine(line == null ? lines.length - 1: line);
160 return getStateBefore(line + 1);
161 },
128 162 cursorCoords: function(start){
129 163 if (start == null) start = sel.inverted;
130 164 return pageCoords(start ? sel.from : sel.to);
@@ -132,22 +166,41 b' var CodeMirror = (function() {'
132 166 charCoords: function(pos){return pageCoords(clipPos(pos));},
133 167 coordsChar: function(coords) {
134 168 var off = eltOffset(lineSpace);
135 var line = Math.min(showingTo - 1, showingFrom + Math.floor(coords.y / lineHeight()));
136 return clipPos({line: line, ch: charFromX(clipLine(line), coords.x)});
169 var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
170 return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
137 171 },
138 172 getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
139 markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
140 setMarker: addGutterMarker,
141 clearMarker: removeGutterMarker,
173 markText: operation(markText),
174 setMarker: operation(addGutterMarker),
175 clearMarker: operation(removeGutterMarker),
142 176 setLineClass: operation(setLineClass),
143 177 lineInfo: lineInfo,
144 addWidget: function(pos, node, scroll) {
145 var pos = localCoords(clipPos(pos), true);
146 node.style.top = (showingFrom * lineHeight() + pos.yBot + paddingTop()) + "px";
147 node.style.left = (pos.x + paddingLeft()) + "px";
178 addWidget: function(pos, node, scroll, vert, horiz) {
179 pos = localCoords(clipPos(pos));
180 var top = pos.yBot, left = pos.x;
181 node.style.position = "absolute";
148 182 code.appendChild(node);
183 if (vert == "over") top = pos.y;
184 else if (vert == "near") {
185 var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
186 hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
187 if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
188 top = pos.y - node.offsetHeight;
189 if (left + node.offsetWidth > hspace)
190 left = hspace - node.offsetWidth;
191 }
192 node.style.top = (top + paddingTop()) + "px";
193 node.style.left = node.style.right = "";
194 if (horiz == "right") {
195 left = code.clientWidth - node.offsetWidth;
196 node.style.right = "0px";
197 } else {
198 if (horiz == "left") left = 0;
199 else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
200 node.style.left = (left + paddingLeft()) + "px";
201 }
149 202 if (scroll)
150 scrollIntoView(pos.x, pos.yBot, pos.x + node.offsetWidth, pos.yBot + node.offsetHeight);
203 scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
151 204 },
152 205
153 206 lineCount: function() {return lines.length;},
@@ -171,18 +224,30 b' var CodeMirror = (function() {'
171 224 replaceRange: operation(replaceRange),
172 225 getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
173 226
227 coordsFromIndex: function(index) {
228 var total = lines.length, pos = 0, line, ch, len;
229
230 for (line = 0; line < total; line++) {
231 len = lines[line].text.length + 1;
232 if (pos + len > index) { ch = index - pos; break; }
233 pos += len;
234 }
235 return clipPos({line: line, ch: ch});
236 },
237
174 238 operation: function(f){return operation(f)();},
175 239 refresh: function(){updateDisplay(true);},
176 240 getInputField: function(){return input;},
177 getWrapperElement: function(){return wrapper;}
241 getWrapperElement: function(){return wrapper;},
242 getScrollerElement: function(){return scroller;},
243 getGutterElement: function(){return gutter;}
178 244 };
179 245
180 246 function setValue(code) {
181 history = null;
182 247 var top = {line: 0, ch: 0};
183 248 updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
184 249 splitLines(code), top, top);
185 history = new History();
250 updateInput = true;
186 251 }
187 252 function getValue(code) {
188 253 var text = [];
@@ -192,38 +257,70 b' var CodeMirror = (function() {'
192 257 }
193 258
194 259 function onMouseDown(e) {
260 // Check whether this is a click in a widget
261 for (var n = e_target(e); n != wrapper; n = n.parentNode)
262 if (n.parentNode == code && n != mover) return;
263
195 264 // First, see if this is a click in the gutter
196 for (var n = e.target(); n != wrapper; n = n.parentNode)
265 for (var n = e_target(e); n != wrapper; n = n.parentNode)
197 266 if (n.parentNode == gutterText) {
198 267 if (options.onGutterClick)
199 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom);
200 return e.stop();
268 options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
269 return e_preventDefault(e);
201 270 }
202 271
203 if (gecko && e.button() == 3) onContextMenu(e);
204 if (e.button() != 1) return;
272 var start = posFromMouse(e);
273
274 switch (e_button(e)) {
275 case 3:
276 if (gecko && !mac) onContextMenu(e);
277 return;
278 case 2:
279 if (start) setCursor(start.line, start.ch, true);
280 return;
281 }
205 282 // For button 1, if it was clicked inside the editor
206 283 // (posFromMouse returning non-null), we have to adjust the
207 284 // selection.
208 var start = posFromMouse(e), last = start, going;
209 if (!start) {if (e.target() == wrapper) e.stop(); return;}
210 setCursor(start.line, start.ch, false);
285 if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
211 286
212 287 if (!focused) onFocus();
213 e.stop();
214 // And then we have to see if it's a drag event, in which case
215 // the dragged-over text must be selected.
216 function end() {
217 input.focus();
218 updateInput = true;
219 move(); up();
288
289 var now = +new Date;
290 if (lastDoubleClick > now - 400) {
291 e_preventDefault(e);
292 return selectLine(start.line);
293 } else if (lastClick > now - 400) {
294 lastDoubleClick = now;
295 e_preventDefault(e);
296 return selectWordAt(start);
297 } else { lastClick = now; }
298
299 var last = start, going;
300 if (dragAndDrop && !posEq(sel.from, sel.to) &&
301 !posLess(start, sel.from) && !posLess(sel.to, start)) {
302 // Let the drag handler handle this.
303 var up = connect(targetDocument, "mouseup", operation(function(e2) {
304 draggingText = false;
305 up();
306 if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
307 e_preventDefault(e2);
308 setCursor(start.line, start.ch, true);
309 focusInput();
310 }
311 }), true);
312 draggingText = true;
313 return;
220 314 }
315 e_preventDefault(e);
316 setCursor(start.line, start.ch, true);
317
221 318 function extend(e) {
222 319 var cur = posFromMouse(e, true);
223 320 if (cur && !posEq(cur, last)) {
224 321 if (!focused) onFocus();
225 322 last = cur;
226 setSelection(start, cur);
323 setSelectionUser(start, cur);
227 324 updateInput = false;
228 325 var visible = visibleLines();
229 326 if (cur.line >= visible.to || cur.line < visible.from)
@@ -231,68 +328,95 b' var CodeMirror = (function() {'
231 328 }
232 329 }
233 330
234 var move = connect(document, "mousemove", operation(function(e) {
331 var move = connect(targetDocument, "mousemove", operation(function(e) {
235 332 clearTimeout(going);
236 e.stop();
333 e_preventDefault(e);
237 334 extend(e);
238 335 }), true);
239 var up = connect(document, "mouseup", operation(function(e) {
336 var up = connect(targetDocument, "mouseup", operation(function(e) {
240 337 clearTimeout(going);
241 338 var cur = posFromMouse(e);
242 if (cur) setSelection(start, cur);
243 e.stop();
244 end();
339 if (cur) setSelectionUser(start, cur);
340 e_preventDefault(e);
341 focusInput();
342 updateInput = true;
343 move(); up();
245 344 }), true);
246 345 }
247 function onDblClick(e) {
248 var pos = posFromMouse(e);
249 if (!pos) return;
250 selectWordAt(pos);
251 e.stop();
346 function onDoubleClick(e) {
347 var start = posFromMouse(e);
348 if (!start) return;
349 lastDoubleClick = +new Date;
350 e_preventDefault(e);
351 selectWordAt(start);
252 352 }
253 353 function onDrop(e) {
254 var pos = posFromMouse(e, true), files = e.e.dataTransfer.files;
354 e.preventDefault();
355 var pos = posFromMouse(e, true), files = e.dataTransfer.files;
255 356 if (!pos || options.readOnly) return;
256 357 if (files && files.length && window.FileReader && window.File) {
257 var n = files.length, text = Array(n), read = 0;
258 for (var i = 0; i < n; ++i) loadFile(files[i], i);
259 358 function loadFile(file, i) {
260 359 var reader = new FileReader;
261 360 reader.onload = function() {
262 361 text[i] = reader.result;
263 if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
362 if (++read == n) {
363 pos = clipPos(pos);
364 var end = replaceRange(text.join(""), pos, pos);
365 setSelectionUser(pos, end);
366 }
264 367 };
265 368 reader.readAsText(file);
266 369 }
370 var n = files.length, text = Array(n), read = 0;
371 for (var i = 0; i < n; ++i) loadFile(files[i], i);
267 372 }
268 373 else {
269 374 try {
270 var text = e.e.dataTransfer.getData("Text");
271 if (text) replaceRange(text, pos, pos);
375 var text = e.dataTransfer.getData("Text");
376 if (text) {
377 var end = replaceRange(text, pos, pos);
378 var curFrom = sel.from, curTo = sel.to;
379 setSelectionUser(pos, end);
380 if (draggingText) replaceRange("", curFrom, curTo);
381 focusInput();
382 }
272 383 }
273 384 catch(e){}
274 385 }
275 386 }
387 function onDragStart(e) {
388 var txt = getSelection();
389 // This will reset escapeElement
390 htmlEscape(txt);
391 e.dataTransfer.setDragImage(escapeElement, 0, 0);
392 e.dataTransfer.setData("Text", txt);
393 }
276 394 function onKeyDown(e) {
277 395 if (!focused) onFocus();
278 396
279 var code = e.e.keyCode;
397 var code = e.keyCode;
398 // IE does strange things with escape.
399 if (ie && code == 27) { e.returnValue = false; }
280 400 // Tries to detect ctrl on non-mac, cmd on mac.
281 var mod = (mac ? e.e.metaKey : e.e.ctrlKey) && !e.e.altKey, anyMod = e.e.ctrlKey || e.e.altKey || e.e.metaKey;
282 if (code == 16 || e.e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
401 var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey;
402 if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
283 403 else shiftSelecting = null;
284 404 // First give onKeyEvent option a chance to handle this.
285 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
405 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
286 406
287 if (code == 33 || code == 34) {scrollPage(code == 34); return e.stop();} // page up/down
288 if (mod && (code == 36 || code == 35)) {scrollEnd(code == 36); return e.stop();} // ctrl-home/end
289 if (mod && code == 65) {selectAll(); return e.stop();} // ctrl-a
407 if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down
408 if (mod && ((code == 36 || code == 35) || // ctrl-home/end
409 mac && (code == 38 || code == 40))) { // cmd-up/down
410 scrollEnd(code == 36 || code == 38); return e_preventDefault(e);
411 }
412 if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a
290 413 if (!options.readOnly) {
291 414 if (!anyMod && code == 13) {return;} // enter
292 if (!anyMod && code == 9 && handleTab(e.e.shiftKey)) return e.stop(); // tab
293 if (mod && code == 90) {undo(); return e.stop();} // ctrl-z
294 if (mod && ((e.e.shiftKey && code == 90) || code == 89)) {redo(); return e.stop();} // ctrl-shift-z, ctrl-y
415 if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab
416 if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
417 if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
295 418 }
419 if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
296 420
297 421 // Key id to use in the movementKeys map. We also pass it to
298 422 // fastPoll in order to 'self learn'. We need this because
@@ -300,51 +424,60 b' var CodeMirror = (function() {'
300 424 // its start when it is inverted and a movement key is pressed
301 425 // (and later restore it again), shouldn't be used for
302 426 // non-movement keys.
303 curKeyId = (mod ? "c" : "") + code;
304 if (sel.inverted && movementKeys.hasOwnProperty(curKeyId)) {
427 curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
428 if (sel.inverted && movementKeys[curKeyId] === true) {
305 429 var range = selRange(input);
306 430 if (range) {
307 431 reducedSelection = {anchor: range.start};
308 432 setSelRange(input, range.start, range.start);
309 433 }
310 434 }
435 // Don't save the key as a movementkey unless it had a modifier
436 if (!mod && !e.altKey) curKeyId = null;
311 437 fastPoll(curKeyId);
312 438 }
313 439 function onKeyUp(e) {
440 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
314 441 if (reducedSelection) {
315 442 reducedSelection = null;
316 443 updateInput = true;
317 444 }
318 if (e.e.keyCode == 16) shiftSelecting = null;
445 if (e.keyCode == 16) shiftSelecting = null;
319 446 }
320 447 function onKeyPress(e) {
321 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
448 if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
322 449 if (options.electricChars && mode.electricChars) {
323 var ch = String.fromCharCode(e.e.charCode == null ? e.e.keyCode : e.e.charCode);
450 var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
324 451 if (mode.electricChars.indexOf(ch) > -1)
325 452 setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
326 453 }
327 var code = e.e.keyCode;
454 var code = e.keyCode;
328 455 // Re-stop tab and enter. Necessary on some browsers.
329 if (code == 13) {handleEnter(); e.stop();}
330 else if (code == 9 && options.tabMode != "default") e.stop();
456 if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);}
457 else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e);
331 458 else fastPoll(curKeyId);
332 459 }
333 460
334 461 function onFocus() {
335 if (!focused && options.onFocus) options.onFocus(instance);
336 focused = true;
462 if (options.readOnly == "nocursor") return;
463 if (!focused) {
464 if (options.onFocus) options.onFocus(instance);
465 focused = true;
466 if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
467 wrapper.className += " CodeMirror-focused";
468 if (!leaveInputAlone) prepareInput();
469 }
337 470 slowPoll();
338 if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
339 wrapper.className += " CodeMirror-focused";
340 471 restartBlink();
341 472 }
342 473 function onBlur() {
343 if (focused && options.onBlur) options.onBlur(instance);
474 if (focused) {
475 if (options.onBlur) options.onBlur(instance);
476 focused = false;
477 wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
478 }
344 479 clearInterval(blinker);
345 shiftSelecting = null;
346 focused = false;
347 wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
480 setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
348 481 }
349 482
350 483 // Replace the range from from to to by the strings in newText.
@@ -367,12 +500,18 b' var CodeMirror = (function() {'
367 500 var pos = clipPos({line: change.start + change.old.length - 1,
368 501 ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
369 502 updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
503 updateInput = true;
370 504 }
371 505 }
372 506 function undo() {unredoHelper(history.done, history.undone);}
373 507 function redo() {unredoHelper(history.undone, history.done);}
374 508
375 509 function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
510 var recomputeMaxLength = false, maxLineLength = maxLine.length;
511 for (var i = from.line; i <= to.line; ++i) {
512 if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
513 }
514
376 515 var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
377 516 // First adjust the line structure, taking some care to leave highlighting intact.
378 517 if (firstLine == lastLine) {
@@ -381,24 +520,46 b' var CodeMirror = (function() {'
381 520 else {
382 521 lastLine = firstLine.split(to.ch, newText[newText.length-1]);
383 522 var spliceargs = [from.line + 1, nlines];
384 firstLine.replace(from.ch, firstLine.text.length, newText[0]);
385 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
523 firstLine.replace(from.ch, null, newText[0]);
524 for (var i = 1, e = newText.length - 1; i < e; ++i)
525 spliceargs.push(Line.inheritMarks(newText[i], firstLine));
386 526 spliceargs.push(lastLine);
387 527 lines.splice.apply(lines, spliceargs);
388 528 }
389 529 }
390 530 else if (newText.length == 1) {
391 firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
531 firstLine.replace(from.ch, null, newText[0]);
532 lastLine.replace(null, to.ch, "");
533 firstLine.append(lastLine);
392 534 lines.splice(from.line + 1, nlines);
393 535 }
394 536 else {
395 537 var spliceargs = [from.line + 1, nlines - 1];
396 firstLine.replace(from.ch, firstLine.text.length, newText[0]);
397 lastLine.replace(0, to.ch, newText[newText.length-1]);
398 for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
538 firstLine.replace(from.ch, null, newText[0]);
539 lastLine.replace(null, to.ch, newText[newText.length-1]);
540 for (var i = 1, e = newText.length - 1; i < e; ++i)
541 spliceargs.push(Line.inheritMarks(newText[i], firstLine));
399 542 lines.splice.apply(lines, spliceargs);
400 543 }
401 544
545
546 for (var i = from.line, e = i + newText.length; i < e; ++i) {
547 var l = lines[i].text;
548 if (l.length > maxLineLength) {
549 maxLine = l; maxLineLength = l.length; maxWidth = null;
550 recomputeMaxLength = false;
551 }
552 }
553 if (recomputeMaxLength) {
554 maxLineLength = 0; maxLine = ""; maxWidth = null;
555 for (var i = 0, e = lines.length; i < e; ++i) {
556 var l = lines[i].text;
557 if (l.length > maxLineLength) {
558 maxLineLength = l.length; maxLine = l;
559 }
560 }
561 }
562
402 563 // Add these lines to the work array, so that they will be
403 564 // highlighted. Adjust work lines if lines were added/removed.
404 565 var newWork = [], lendiff = newText.length - nlines - 1;
@@ -407,12 +568,17 b' var CodeMirror = (function() {'
407 568 if (task < from.line) newWork.push(task);
408 569 else if (task > to.line) newWork.push(task + lendiff);
409 570 }
410 if (newText.length) newWork.push(from.line);
571 if (newText.length < 5) {
572 highlightLines(from.line, from.line + newText.length);
573 newWork.push(from.line + newText.length);
574 } else {
575 newWork.push(from.line);
576 }
411 577 work = newWork;
412 578 startWorker(100);
413 579 // Remember that these lines changed, for updating the display
414 580 changes.push({from: from.line, to: to.line + 1, diff: lendiff});
415 textChanged = true;
581 textChanged = {from: from, to: to, text: newText};
416 582
417 583 // Update the selection
418 584 function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
@@ -483,7 +649,10 b' var CodeMirror = (function() {'
483 649 function p() {
484 650 startOperation();
485 651 var changed = readInput();
486 if (changed == "moved" && keyId) movementKeys[keyId] = true;
652 if (changed && keyId) {
653 if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
654 if (changed == "changed") movementKeys[keyId] = false;
655 }
487 656 if (!changed && !missed) {missed = true; poll.set(80, p);}
488 657 else {pollingFast = false; slowPoll();}
489 658 endOperation();
@@ -495,13 +664,12 b' var CodeMirror = (function() {'
495 664 // to the data in the editing variable, and updates the editor
496 665 // content or cursor if something changed.
497 666 function readInput() {
667 if (leaveInputAlone || !focused) return;
498 668 var changed = false, text = input.value, sr = selRange(input);
499 669 if (!sr) return false;
500 670 var changed = editing.text != text, rs = reducedSelection;
501 671 var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end);
502 if (reducedSelection && !moved && sel.from.line == 0 && sel.from.ch == 0)
503 reducedSelection = null;
504 else if (!moved) return false;
672 if (!moved && !rs) return false;
505 673 if (changed) {
506 674 shiftSelecting = reducedSelection = null;
507 675 if (options.readOnly) {updateInput = true; return "changed";}
@@ -524,13 +692,10 b' var CodeMirror = (function() {'
524 692 // so that you can, for example, press shift-up at the start of
525 693 // your selection and have the right thing happen.
526 694 if (rs) {
527 from = sr.start == rs.anchor ? to : from;
528 to = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
529 if (!posLess(from, to)) {
530 reducedSelection = null;
531 sel.inverted = false;
532 var tmp = from; from = to; to = tmp;
533 }
695 var head = sr.start == rs.anchor ? to : from;
696 var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
697 if (sel.inverted = posLess(head, tail)) { from = head; to = tail; }
698 else { reducedSelection = null; from = tail; to = head; }
534 699 }
535 700
536 701 // In some cases (cursor on same line as before), we don't have
@@ -550,8 +715,8 b' var CodeMirror = (function() {'
550 715 var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length;
551 716 for (;;) {
552 717 c = editing.text.charAt(edend);
718 if (text.charAt(end) != c) {++end; ++edend; break;}
553 719 if (c == "\n") endline--;
554 if (text.charAt(end) != c) {++end; ++edend; break;}
555 720 if (edend <= start || end <= start) break;
556 721 --end; --edend;
557 722 }
@@ -580,22 +745,36 b' var CodeMirror = (function() {'
580 745 editing = {text: text, from: from, to: to, start: startch, end: endch};
581 746 setSelRange(input, startch, reducedSelection ? startch : endch);
582 747 }
748 function focusInput() {
749 if (options.readOnly != "nocursor") input.focus();
750 }
583 751
752 function scrollEditorIntoView() {
753 if (!cursor.getBoundingClientRect) return;
754 var rect = cursor.getBoundingClientRect();
755 var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
756 if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
757 }
584 758 function scrollCursorIntoView() {
585 759 var cursor = localCoords(sel.inverted ? sel.from : sel.to);
586 760 return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
587 761 }
588 762 function scrollIntoView(x1, y1, x2, y2) {
589 var pl = paddingLeft(), pt = paddingTop();
763 var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
590 764 y1 += pt; y2 += pt; x1 += pl; x2 += pl;
591 var screen = wrapper.clientHeight, screentop = wrapper.scrollTop, scrolled = false, result = true;
592 if (y1 < screentop) {wrapper.scrollTop = Math.max(0, y1 - 10); scrolled = true;}
593 else if (y2 > screentop + screen) {wrapper.scrollTop = y2 + 10 - screen; scrolled = true;}
765 var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
766 if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
767 else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
594 768
595 var screenw = wrapper.clientWidth, screenleft = wrapper.scrollLeft;
596 if (x1 < screenleft) {wrapper.scrollLeft = Math.max(0, x1 - 10); scrolled = true;}
769 var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
770 var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
771 if (x1 < screenleft + gutterw) {
772 if (x1 < 50) x1 = 0;
773 scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
774 scrolled = true;
775 }
597 776 else if (x2 > screenw + screenleft) {
598 wrapper.scrollLeft = x2 + 10 - screenw;
777 scroller.scrollLeft = x2 + 10 - screenw;
599 778 scrolled = true;
600 779 if (x2 > code.clientWidth) result = false;
601 780 }
@@ -604,15 +783,15 b' var CodeMirror = (function() {'
604 783 }
605 784
606 785 function visibleLines() {
607 var lh = lineHeight(), top = wrapper.scrollTop - paddingTop();
786 var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
608 787 return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
609 to: Math.min(lines.length, Math.ceil((top + wrapper.clientHeight) / lh))};
788 to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
610 789 }
611 790 // Uses a set of changes plus the current scroll position to
612 791 // determine which DOM updates have to be made, and makes the
613 792 // updates.
614 793 function updateDisplay(changes) {
615 if (!wrapper.clientWidth) {
794 if (!scroller.clientWidth) {
616 795 showingFrom = showingTo = 0;
617 796 return;
618 797 }
@@ -629,7 +808,7 b' var CodeMirror = (function() {'
629 808 intact2.push(range);
630 809 else {
631 810 if (change.from > range.from)
632 intact2.push({from: range.from, to: change.from, domStart: range.domStart})
811 intact2.push({from: range.from, to: change.from, domStart: range.domStart});
633 812 if (change.to < range.to)
634 813 intact2.push({from: change.to + diff, to: range.to + diff,
635 814 domStart: range.domStart + (change.to - range.from)});
@@ -659,6 +838,7 b' var CodeMirror = (function() {'
659 838 if (domPos != domEnd || pos != to) {
660 839 changedLines += Math.abs(to - pos);
661 840 updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
841 if (to - pos != domEnd - domPos) gutterDirty = true;
662 842 }
663 843
664 844 if (!updates.length) return;
@@ -674,13 +854,23 b' var CodeMirror = (function() {'
674 854
675 855 // Position the mover div to align with the lines it's supposed
676 856 // to be showing (which will cover the visible display)
677 var different = from != showingFrom || to != showingTo || lastHeight != wrapper.clientHeight;
857 var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
678 858 showingFrom = from; showingTo = to;
679 859 mover.style.top = (from * lineHeight()) + "px";
680 860 if (different) {
681 lastHeight = wrapper.clientHeight;
861 lastHeight = scroller.clientHeight;
682 862 code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
683 updateGutter();
863 }
864 if (different || gutterDirty) updateGutter();
865
866 if (maxWidth == null) maxWidth = stringWidth(maxLine);
867 if (maxWidth > scroller.clientWidth) {
868 lineSpace.style.width = maxWidth + "px";
869 // Needed to prevent odd wrapping/hiding of widgets placed in here.
870 code.style.width = "";
871 code.style.width = scroller.scrollWidth + "px";
872 } else {
873 lineSpace.style.width = code.style.width = "";
684 874 }
685 875
686 876 // Since this is all rather error prone, it is honoured with the
@@ -712,7 +902,7 b' var CodeMirror = (function() {'
712 902 // there .innerHTML on PRE nodes is dumb, and discards
713 903 // whitespace.
714 904 var sfrom = sel.from.line, sto = sel.to.line, off = 0,
715 scratch = badInnerHTML && document.createElement("div");
905 scratch = badInnerHTML && targetDocument.createElement("div");
716 906 for (var i = 0, e = updates.length; i < e; ++i) {
717 907 var rec = updates[i];
718 908 var extra = (rec.to - rec.from) - rec.domSize;
@@ -722,7 +912,7 b' var CodeMirror = (function() {'
722 912 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
723 913 else if (extra) {
724 914 for (var j = Math.max(0, extra); j > 0; --j)
725 lineDiv.insertBefore(document.createElement("pre"), nodeAfter);
915 lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
726 916 for (var j = Math.max(0, -extra); j > 0; --j)
727 917 lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
728 918 }
@@ -753,10 +943,10 b' var CodeMirror = (function() {'
753 943
754 944 function updateGutter() {
755 945 if (!options.gutter && !options.lineNumbers) return;
756 var hText = mover.offsetHeight, hEditor = wrapper.clientHeight;
946 var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
757 947 gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
758 948 var html = [];
759 for (var i = showingFrom; i < showingTo; ++i) {
949 for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
760 950 var marker = lines[i].gutterMarker;
761 951 var text = options.lineNumbers ? i + options.firstLineNumber : null;
762 952 if (marker && marker.text)
@@ -769,37 +959,43 b' var CodeMirror = (function() {'
769 959 gutterText.innerHTML = html.join("");
770 960 var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
771 961 while (val.length + pad.length < minwidth) pad += "\u00a0";
772 if (pad) firstNode.insertBefore(document.createTextNode(pad), firstNode.firstChild);
962 if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
773 963 gutter.style.display = "";
774 964 lineSpace.style.marginLeft = gutter.offsetWidth + "px";
965 gutterDirty = false;
775 966 }
776 967 function updateCursor() {
777 var head = sel.inverted ? sel.from : sel.to;
778 var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lineHeight() + "px";
779 inputDiv.style.top = y; inputDiv.style.left = x;
968 var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
969 var x = charX(head.line, head.ch);
970 var top = head.line * lh - scroller.scrollTop;
971 inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px";
972 inputDiv.style.left = (x - scroller.scrollLeft) + "px";
780 973 if (posEq(sel.from, sel.to)) {
781 cursor.style.top = y; cursor.style.left = x;
974 cursor.style.top = (head.line - showingFrom) * lh + "px";
975 cursor.style.left = x + "px";
782 976 cursor.style.display = "";
783 977 }
784 978 else cursor.style.display = "none";
785 979 }
786 980
981 function setSelectionUser(from, to) {
982 var sh = shiftSelecting && clipPos(shiftSelecting);
983 if (sh) {
984 if (posLess(sh, from)) from = sh;
985 else if (posLess(to, sh)) to = sh;
986 }
987 setSelection(from, to);
988 }
787 989 // Update the selection. Last two args are only used by
788 990 // updateLines, since they have to be expressed in the line
789 991 // numbers before the update.
790 992 function setSelection(from, to, oldFrom, oldTo) {
791 993 if (posEq(sel.from, from) && posEq(sel.to, to)) return;
792 var sh = shiftSelecting && clipPos(shiftSelecting);
793 994 if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
794 if (sh) {
795 if (posLess(sh, from)) from = sh;
796 else if (posLess(to, sh)) to = sh;
797 }
798 995
799 var startEq = posEq(sel.to, to), endEq = posEq(sel.from, from);
800 996 if (posEq(from, to)) sel.inverted = false;
801 else if (startEq && !endEq) sel.inverted = true;
802 else if (endEq && !startEq) sel.inverted = false;
997 else if (posEq(from, sel.to)) sel.inverted = false;
998 else if (posEq(to, sel.from)) sel.inverted = true;
803 999
804 1000 // Some ugly logic used to only mark the lines that actually did
805 1001 // see a change in selection as changed, rather than the whole
@@ -829,9 +1025,9 b' var CodeMirror = (function() {'
829 1025 sel.from = from; sel.to = to;
830 1026 selectionChanged = true;
831 1027 }
832 function setCursor(line, ch) {
1028 function setCursor(line, ch, user) {
833 1029 var pos = clipPos({line: line, ch: ch || 0});
834 setSelection(pos, pos);
1030 (user ? setSelectionUser : setSelection)(pos, pos);
835 1031 }
836 1032
837 1033 function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
@@ -845,11 +1041,12 b' var CodeMirror = (function() {'
845 1041 }
846 1042
847 1043 function scrollPage(down) {
848 var linesPerPage = Math.floor(wrapper.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
849 setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch);
1044 var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
1045 setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
850 1046 }
851 1047 function scrollEnd(top) {
852 setCursor(top ? 0 : lines.length - 1);
1048 var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
1049 setSelectionUser(pos, pos);
853 1050 }
854 1051 function selectAll() {
855 1052 var endLine = lines.length - 1;
@@ -859,8 +1056,11 b' var CodeMirror = (function() {'
859 1056 var line = lines[pos.line].text;
860 1057 var start = pos.ch, end = pos.ch;
861 1058 while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
862 while (end < line.length - 1 && /\w/.test(line.charAt(end))) ++end;
863 setSelection({line: pos.line, ch: start}, {line: pos.line, ch: end});
1059 while (end < line.length && /\w/.test(line.charAt(end))) ++end;
1060 setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
1061 }
1062 function selectLine(line) {
1063 setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
864 1064 }
865 1065 function handleEnter() {
866 1066 replaceSelection("\n", "end");
@@ -868,12 +1068,17 b' var CodeMirror = (function() {'
868 1068 indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
869 1069 }
870 1070 function handleTab(shift) {
1071 function indentSelected(mode) {
1072 if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
1073 var e = sel.to.line - (sel.to.ch ? 0 : 1);
1074 for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
1075 }
871 1076 shiftSelecting = null;
872 1077 switch (options.tabMode) {
873 1078 case "default":
874 1079 return false;
875 1080 case "indent":
876 for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, "smart");
1081 indentSelected("smart");
877 1082 break;
878 1083 case "classic":
879 1084 if (posEq(sel.from, sel.to)) {
@@ -882,11 +1087,15 b' var CodeMirror = (function() {'
882 1087 break;
883 1088 }
884 1089 case "shift":
885 for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, shift ? "subtract" : "add");
1090 indentSelected(shift ? "subtract" : "add");
886 1091 break;
887 1092 }
888 1093 return true;
889 1094 }
1095 function smartHome() {
1096 var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/));
1097 setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
1098 }
890 1099
891 1100 function indentLine(n, how) {
892 1101 if (how == "smart") {
@@ -924,21 +1133,20 b' var CodeMirror = (function() {'
924 1133 for (var i = 0, l = lines.length; i < l; ++i)
925 1134 lines[i].stateAfter = null;
926 1135 work = [0];
1136 startWorker();
927 1137 }
928 1138 function gutterChanged() {
929 1139 var visible = options.gutter || options.lineNumbers;
930 1140 gutter.style.display = visible ? "" : "none";
931 if (visible) updateGutter();
1141 if (visible) gutterDirty = true;
932 1142 else lineDiv.parentNode.style.marginLeft = 0;
933 1143 }
934 1144
935 1145 function markText(from, to, className) {
936 1146 from = clipPos(from); to = clipPos(to);
937 var accum = [];
1147 var set = [];
938 1148 function add(line, from, to, className) {
939 var line = lines[line], mark = line.addMark(from, to, className);
940 mark.line = line;
941 accum.push(mark);
1149 mark = lines[line].addMark(from, to, className, set);
942 1150 }
943 1151 if (from.line == to.line) add(from.line, from.ch, to.ch, className);
944 1152 else {
@@ -948,30 +1156,51 b' var CodeMirror = (function() {'
948 1156 add(to.line, 0, to.ch, className);
949 1157 }
950 1158 changes.push({from: from.line, to: to.line + 1});
951 return function() {
952 var start, end;
953 for (var i = 0; i < accum.length; ++i) {
954 var mark = accum[i], found = indexOf(lines, mark.line);
955 mark.line.removeMark(mark);
956 if (found > -1) {
957 if (start == null) start = found;
958 end = found;
1159 return new TextMarker(set);
1160 }
1161
1162 function TextMarker(set) { this.set = set; }
1163 TextMarker.prototype.clear = operation(function() {
1164 for (var i = 0, e = this.set.length; i < e; ++i) {
1165 var mk = this.set[i].marked;
1166 for (var j = 0; j < mk.length; ++j) {
1167 if (mk[j].set == this.set) mk.splice(j--, 1);
1168 }
1169 }
1170 // We don't know the exact lines that changed. Refreshing is
1171 // cheaper than finding them.
1172 changes.push({from: 0, to: lines.length});
1173 });
1174 TextMarker.prototype.find = function() {
1175 var from, to;
1176 for (var i = 0, e = this.set.length; i < e; ++i) {
1177 var line = this.set[i], mk = line.marked;
1178 for (var j = 0; j < mk.length; ++j) {
1179 var mark = mk[j];
1180 if (mark.set == this.set) {
1181 if (mark.from != null || mark.to != null) {
1182 var found = indexOf(lines, line);
1183 if (found > -1) {
1184 if (mark.from != null) from = {line: found, ch: mark.from};
1185 if (mark.to != null) to = {line: found, ch: mark.to};
1186 }
1187 }
959 1188 }
960 1189 }
961 if (start != null) changes.push({from: start, to: end + 1});
962 };
963 }
1190 }
1191 return {from: from, to: to};
1192 };
964 1193
965 1194 function addGutterMarker(line, text, className) {
966 1195 if (typeof line == "number") line = lines[clipLine(line)];
967 1196 line.gutterMarker = {text: text, style: className};
968 updateGutter();
1197 gutterDirty = true;
969 1198 return line;
970 1199 }
971 1200 function removeGutterMarker(line) {
972 1201 if (typeof line == "number") line = lines[clipLine(line)];
973 1202 line.gutterMarker = null;
974 updateGutter();
1203 gutterDirty = true;
975 1204 }
976 1205 function setLineClass(line, className) {
977 1206 if (typeof line == "number") {
@@ -982,8 +1211,10 b' var CodeMirror = (function() {'
982 1211 var no = indexOf(lines, line);
983 1212 if (no == -1) return null;
984 1213 }
985 line.className = className;
986 changes.push({from: no, to: no + 1});
1214 if (line.className != className) {
1215 line.className = className;
1216 changes.push({from: no, to: no + 1});
1217 }
987 1218 return line;
988 1219 }
989 1220
@@ -1001,35 +1232,44 b' var CodeMirror = (function() {'
1001 1232 return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
1002 1233 }
1003 1234
1235 function stringWidth(str) {
1236 measure.innerHTML = "<pre><span>x</span></pre>";
1237 measure.firstChild.firstChild.firstChild.nodeValue = str;
1238 return measure.firstChild.firstChild.offsetWidth || 10;
1239 }
1004 1240 // These are used to go from pixel positions to character
1005 // positions, taking tabs into account.
1241 // positions, taking varying character widths into account.
1006 1242 function charX(line, pos) {
1007 var text = lines[line].text, span = measure.firstChild;
1008 if (text.lastIndexOf("\t", pos) == -1) return pos * charWidth();
1009 var old = span.firstChild.nodeValue;
1010 try {
1011 span.firstChild.nodeValue = text.slice(0, pos);
1012 return span.offsetWidth;
1013 } finally {span.firstChild.nodeValue = old;}
1243 if (pos == 0) return 0;
1244 measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
1245 return measure.firstChild.firstChild.offsetWidth;
1014 1246 }
1015 1247 function charFromX(line, x) {
1016 var text = lines[line].text, cw = charWidth();
1017 1248 if (x <= 0) return 0;
1018 if (text.indexOf("\t") == -1) return Math.min(text.length, Math.round(x / cw));
1019 var mspan = measure.firstChild, mtext = mspan.firstChild, old = mtext.nodeValue;
1020 try {
1021 mtext.nodeValue = text;
1022 var from = 0, fromX = 0, to = text.length, toX = mspan.offsetWidth;
1023 if (x > toX) return to;
1024 for (;;) {
1025 if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1026 var middle = Math.ceil((from + to) / 2);
1027 mtext.nodeValue = text.slice(0, middle);
1028 var curX = mspan.offsetWidth;
1029 if (curX > x) {to = middle; toX = curX;}
1030 else {from = middle; fromX = curX;}
1031 }
1032 } finally {mtext.nodeValue = old;}
1249 var lineObj = lines[line], text = lineObj.text;
1250 function getX(len) {
1251 measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
1252 return measure.firstChild.firstChild.offsetWidth;
1253 }
1254 var from = 0, fromX = 0, to = text.length, toX;
1255 // Guess a suitable upper bound for our search.
1256 var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
1257 for (;;) {
1258 var estX = getX(estimated);
1259 if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
1260 else {toX = estX; to = estimated; break;}
1261 }
1262 if (x > toX) return to;
1263 // Try to guess a suitable lower bound as well.
1264 estimated = Math.floor(to * 0.8); estX = getX(estimated);
1265 if (estX < x) {from = estimated; fromX = estX;}
1266 // Do a binary search between these bounds.
1267 for (;;) {
1268 if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
1269 var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
1270 if (middleX > x) {to = middle; toX = middleX;}
1271 else {from = middle; fromX = middleX;}
1272 }
1033 1273 }
1034 1274
1035 1275 function localCoords(pos, inLineWrap) {
@@ -1043,45 +1283,61 b' var CodeMirror = (function() {'
1043 1283
1044 1284 function lineHeight() {
1045 1285 var nlines = lineDiv.childNodes.length;
1046 if (nlines) return lineDiv.offsetHeight / nlines;
1047 else return measure.firstChild.offsetHeight || 1;
1286 if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
1287 measure.innerHTML = "<pre>x</pre>";
1288 return measure.firstChild.offsetHeight || 1;
1048 1289 }
1049 function charWidth() {return (measure.firstChild.offsetWidth || 320) / 40;}
1050 1290 function paddingTop() {return lineSpace.offsetTop;}
1051 1291 function paddingLeft() {return lineSpace.offsetLeft;}
1052 1292
1053 1293 function posFromMouse(e, liberal) {
1054 var off = eltOffset(lineSpace),
1055 x = e.pageX() - off.left,
1056 y = e.pageY() - off.top;
1057 if (!liberal && e.target() != lineSpace.parentNode && !(e.target() == wrapper && y > (lines.length * lineHeight())))
1058 for (var n = e.target(); n != lineDiv && n != cursor; n = n.parentNode)
1059 if (!n || n == wrapper) return null;
1060 var line = showingFrom + Math.floor(y / lineHeight());
1061 return clipPos({line: line, ch: charFromX(clipLine(line), x)});
1294 var offW = eltOffset(scroller, true), x, y;
1295 // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1296 try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1297 // This is a mess of a heuristic to try and determine whether a
1298 // scroll-bar was clicked or not, and to return null if one was
1299 // (and !liberal).
1300 if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
1301 return null;
1302 var offL = eltOffset(lineSpace, true);
1303 var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
1304 return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
1062 1305 }
1063 1306 function onContextMenu(e) {
1064 1307 var pos = posFromMouse(e);
1065 1308 if (!pos || window.opera) return; // Opera is difficult.
1066 1309 if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1067 setCursor(pos.line, pos.ch);
1310 operation(setCursor)(pos.line, pos.ch);
1068 1311
1069 1312 var oldCSS = input.style.cssText;
1070 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.pageY() - 1) +
1071 "px; left: " + (e.pageX() - 1) + "px; z-index: 1000; background: white; " +
1072 "border-width: 0; outline: none; overflow: hidden;";
1313 inputDiv.style.position = "absolute";
1314 input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1315 "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
1316 "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
1317 leaveInputAlone = true;
1073 1318 var val = input.value = getSelection();
1074 input.focus();
1075 setSelRange(input, 0, val.length);
1076 if (gecko) e.stop();
1077 leaveInputAlone = true;
1078 setTimeout(function() {
1079 if (input.value != val) operation(replaceSelection)(input.value, "end");
1319 focusInput();
1320 setSelRange(input, 0, input.value.length);
1321 function rehide() {
1322 var newVal = splitLines(input.value).join("\n");
1323 if (newVal != val) operation(replaceSelection)(newVal, "end");
1324 inputDiv.style.position = "relative";
1080 1325 input.style.cssText = oldCSS;
1081 1326 leaveInputAlone = false;
1082 1327 prepareInput();
1083 1328 slowPoll();
1084 }, 50);
1329 }
1330
1331 if (gecko) {
1332 e_stop(e);
1333 var mouseup = connect(window, "mouseup", function() {
1334 mouseup();
1335 setTimeout(rehide, 20);
1336 }, true);
1337 }
1338 else {
1339 setTimeout(rehide, 50);
1340 }
1085 1341 }
1086 1342
1087 1343 // Cursor-blinking
@@ -1120,19 +1376,18 b' var CodeMirror = (function() {'
1120 1376 }
1121 1377 }
1122 1378 }
1123 for (var i = head.line, e = forward ? Math.min(i + 50, lines.length) : Math.max(0, i - 50); i != e; i+=d) {
1379 for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) {
1124 1380 var line = lines[i], first = i == head.line;
1125 1381 var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
1126 if (found) {
1127 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1128 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1129 two = markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1130 var clear = operation(function(){one(); two();});
1131 if (autoclear) setTimeout(clear, 800);
1132 else bracketHighlighted = clear;
1133 break;
1134 }
1382 if (found) break;
1135 1383 }
1384 if (!found) found = {pos: null, match: false};
1385 var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
1386 var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
1387 two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
1388 var clear = operation(function(){one.clear(); two && two.clear();});
1389 if (autoclear) setTimeout(clear, 800);
1390 else bracketHighlighted = clear;
1136 1391 }
1137 1392
1138 1393 // Finds the line to start with when starting a parse. Tries to
@@ -1148,7 +1403,7 b' var CodeMirror = (function() {'
1148 1403 if (line.stateAfter) return search;
1149 1404 var indented = line.indentation();
1150 1405 if (minline == null || minindent > indented) {
1151 minline = search;
1406 minline = search - 1;
1152 1407 minindent = indented;
1153 1408 }
1154 1409 }
@@ -1163,11 +1418,21 b' var CodeMirror = (function() {'
1163 1418 line.highlight(mode, state);
1164 1419 line.stateAfter = copyState(mode, state);
1165 1420 }
1166 if (!lines[n].stateAfter) work.push(n);
1421 changes.push({from: start, to: n});
1422 if (n < lines.length && !lines[n].stateAfter) work.push(n);
1167 1423 return state;
1168 1424 }
1425 function highlightLines(start, end) {
1426 var state = getStateBefore(start);
1427 for (var i = start; i < end; ++i) {
1428 var line = lines[i];
1429 line.highlight(mode, state);
1430 line.stateAfter = copyState(mode, state);
1431 }
1432 }
1169 1433 function highlightWorker() {
1170 1434 var end = +new Date + options.workTime;
1435 var foundWork = work.length;
1171 1436 while (work.length) {
1172 1437 if (!lines[showingFrom].stateAfter) var task = showingFrom;
1173 1438 else var task = work.pop();
@@ -1176,20 +1441,29 b' var CodeMirror = (function() {'
1176 1441 if (state) state = copyState(mode, state);
1177 1442 else state = startState(mode);
1178 1443
1444 var unchanged = 0, compare = mode.compareStates, realChange = false;
1179 1445 for (var i = start, l = lines.length; i < l; ++i) {
1180 1446 var line = lines[i], hadState = line.stateAfter;
1181 1447 if (+new Date > end) {
1182 1448 work.push(i);
1183 1449 startWorker(options.workDelay);
1184 changes.push({from: task, to: i});
1450 if (realChange) changes.push({from: task, to: i + 1});
1185 1451 return;
1186 1452 }
1187 1453 var changed = line.highlight(mode, state);
1454 if (changed) realChange = true;
1188 1455 line.stateAfter = copyState(mode, state);
1189 if (hadState && !changed && line.text) break;
1456 if (compare) {
1457 if (hadState && compare(hadState, state)) break;
1458 } else {
1459 if (changed !== false || !hadState) unchanged = 0;
1460 else if (++unchanged > 3) break;
1461 }
1190 1462 }
1191 changes.push({from: task, to: i});
1463 if (realChange) changes.push({from: task, to: i + 1});
1192 1464 }
1465 if (foundWork && options.onHighlightComplete)
1466 options.onHighlightComplete(instance);
1193 1467 }
1194 1468 function startWorker(time) {
1195 1469 if (!work.length) return;
@@ -1207,24 +1481,29 b' var CodeMirror = (function() {'
1207 1481 var reScroll = false;
1208 1482 if (selectionChanged) reScroll = !scrollCursorIntoView();
1209 1483 if (changes.length) updateDisplay(changes);
1210 else if (selectionChanged) updateCursor();
1484 else {
1485 if (selectionChanged) updateCursor();
1486 if (gutterDirty) updateGutter();
1487 }
1211 1488 if (reScroll) scrollCursorIntoView();
1212 if (selectionChanged) restartBlink();
1489 if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
1213 1490
1214 1491 // updateInput can be set to a boolean value to force/prevent an
1215 1492 // update.
1216 if (!leaveInputAlone && (updateInput === true || (updateInput !== false && selectionChanged)))
1493 if (focused && !leaveInputAlone &&
1494 (updateInput === true || (updateInput !== false && selectionChanged)))
1217 1495 prepareInput();
1218 1496
1219 if (selectionChanged && options.onCursorActivity)
1220 options.onCursorActivity(instance);
1221 if (textChanged && options.onChange)
1222 options.onChange(instance);
1223 1497 if (selectionChanged && options.matchBrackets)
1224 1498 setTimeout(operation(function() {
1225 1499 if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
1226 1500 matchBrackets(false);
1227 1501 }), 20);
1502 var tc = textChanged; // textChanged can be reset by cursoractivity callback
1503 if (selectionChanged && options.onCursorActivity)
1504 options.onCursorActivity(instance);
1505 if (tc && options.onChange && instance)
1506 options.onChange(instance, tc);
1228 1507 }
1229 1508 var nestedOperation = 0;
1230 1509 function operation(f) {
@@ -1259,6 +1538,7 b' var CodeMirror = (function() {'
1259 1538 var newmatch = line.match(query);
1260 1539 if (newmatch) match = newmatch;
1261 1540 else break;
1541 start++;
1262 1542 }
1263 1543 }
1264 1544 else {
@@ -1338,9 +1618,21 b' var CodeMirror = (function() {'
1338 1618 },
1339 1619
1340 1620 from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
1341 to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}
1621 to: function() {if (this.atOccurrence) return copyPos(this.pos.to);},
1622
1623 replace: function(newText) {
1624 var self = this;
1625 if (this.atOccurrence)
1626 operation(function() {
1627 self.pos.to = replaceRange(newText, self.pos.from, self.pos.to);
1628 })();
1629 }
1342 1630 };
1343 1631
1632 for (var ext in extensions)
1633 if (extensions.propertyIsEnumerable(ext) &&
1634 !instance.propertyIsEnumerable(ext))
1635 instance[ext] = extensions[ext];
1344 1636 return instance;
1345 1637 } // (end of function CodeMirror)
1346 1638
@@ -1348,6 +1640,7 b' var CodeMirror = (function() {'
1348 1640 CodeMirror.defaults = {
1349 1641 value: "",
1350 1642 mode: null,
1643 theme: "default",
1351 1644 indentUnit: 2,
1352 1645 indentWithTabs: false,
1353 1646 tabMode: "classic",
@@ -1356,17 +1649,21 b' var CodeMirror = (function() {'
1356 1649 onKeyEvent: null,
1357 1650 lineNumbers: false,
1358 1651 gutter: false,
1652 fixedGutter: false,
1359 1653 firstLineNumber: 1,
1360 1654 readOnly: false,
1655 smartHome: true,
1361 1656 onChange: null,
1362 1657 onCursorActivity: null,
1363 1658 onGutterClick: null,
1659 onHighlightComplete: null,
1364 1660 onFocus: null, onBlur: null, onScroll: null,
1365 1661 matchBrackets: false,
1366 1662 workTime: 100,
1367 1663 workDelay: 200,
1368 1664 undoDepth: 40,
1369 tabindex: null
1665 tabindex: null,
1666 document: window.document
1370 1667 };
1371 1668
1372 1669 // Known modes, by name and by MIME
@@ -1383,15 +1680,15 b' var CodeMirror = (function() {'
1383 1680 spec = mimeModes[spec];
1384 1681 if (typeof spec == "string")
1385 1682 var mname = spec, config = {};
1386 else
1683 else if (spec != null)
1387 1684 var mname = spec.name, config = spec;
1388 1685 var mfactory = modes[mname];
1389 1686 if (!mfactory) {
1390 1687 if (window.console) console.warn("No mode " + mname + " found, falling back to plain text.");
1391 1688 return CodeMirror.getMode(options, "text/plain");
1392 1689 }
1393 return mfactory(options, config);
1394 }
1690 return mfactory(options, config || {});
1691 };
1395 1692 CodeMirror.listModes = function() {
1396 1693 var list = [];
1397 1694 for (var m in modes)
@@ -1401,10 +1698,15 b' var CodeMirror = (function() {'
1401 1698 CodeMirror.listMIMEs = function() {
1402 1699 var list = [];
1403 1700 for (var m in mimeModes)
1404 if (mimeModes.propertyIsEnumerable(m)) list.push(m);
1701 if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
1405 1702 return list;
1406 1703 };
1407 1704
1705 var extensions = {};
1706 CodeMirror.defineExtension = function(name, func) {
1707 extensions[name] = func;
1708 };
1709
1408 1710 CodeMirror.fromTextArea = function(textarea, options) {
1409 1711 if (!options) options = {};
1410 1712 options.value = textarea.value;
@@ -1484,7 +1786,7 b' var CodeMirror = (function() {'
1484 1786 if (ok) {++this.pos; return ch;}
1485 1787 },
1486 1788 eatWhile: function(match) {
1487 var start = this.start;
1789 var start = this.pos;
1488 1790 while (this.eat(match)){}
1489 1791 return this.pos > start;
1490 1792 },
@@ -1517,6 +1819,7 b' var CodeMirror = (function() {'
1517 1819 },
1518 1820 current: function(){return this.string.slice(this.start, this.pos);}
1519 1821 };
1822 CodeMirror.StringStream = StringStream;
1520 1823
1521 1824 // Line objects. These hold state related to a line, including
1522 1825 // highlighting info (the styles array).
@@ -1526,10 +1829,23 b' var CodeMirror = (function() {'
1526 1829 this.text = text;
1527 1830 this.marked = this.gutterMarker = this.className = null;
1528 1831 }
1832 Line.inheritMarks = function(text, orig) {
1833 var ln = new Line(text), mk = orig.marked;
1834 if (mk) {
1835 for (var i = 0; i < mk.length; ++i) {
1836 if (mk[i].to == null) {
1837 var newmk = ln.marked || (ln.marked = []), mark = mk[i];
1838 newmk.push({from: null, to: null, style: mark.style, set: mark.set});
1839 mark.set.push(ln);
1840 }
1841 }
1842 }
1843 return ln;
1844 }
1529 1845 Line.prototype = {
1530 1846 // Replace a piece of a line, keeping the styles around it intact.
1531 replace: function(from, to, text) {
1532 var st = [], mk = this.marked;
1847 replace: function(from, to_, text) {
1848 var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
1533 1849 copyStyles(0, from, this.styles, st);
1534 1850 if (text) st.push(text, null);
1535 1851 copyStyles(to, this.text.length, this.styles, st);
@@ -1538,39 +1854,86 b' var CodeMirror = (function() {'
1538 1854 this.stateAfter = null;
1539 1855 if (mk) {
1540 1856 var diff = text.length - (to - from), end = this.text.length;
1541 function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
1857 var changeStart = Math.min(from, from + diff);
1542 1858 for (var i = 0; i < mk.length; ++i) {
1543 1859 var mark = mk[i], del = false;
1544 if (mark.from >= end) del = true;
1545 else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
1546 if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
1860 if (mark.from != null && mark.from >= end) del = true;
1861 else {
1862 if (mark.from != null && mark.from >= from) {
1863 mark.from += diff;
1864 if (mark.from <= 0) mark.from = from == null ? null : 0;
1865 }
1866 else if (to_ == null) mark.to = null;
1867 if (mark.to != null && mark.to > from) {
1868 mark.to += diff;
1869 if (mark.to < 0) del = true;
1870 }
1871 }
1872 if (del || (mark.from != null && mark.to != null && mark.from >= mark.to)) mk.splice(i--, 1);
1547 1873 }
1548 1874 }
1549 1875 },
1550 // Split a line in two, again keeping styles intact.
1876 // Split a part off a line, keeping styles and markers intact.
1551 1877 split: function(pos, textBefore) {
1552 var st = [textBefore, null];
1878 var st = [textBefore, null], mk = this.marked;
1553 1879 copyStyles(pos, this.text.length, this.styles, st);
1554 return new Line(textBefore + this.text.slice(pos), st);
1880 var taken = new Line(textBefore + this.text.slice(pos), st);
1881 if (mk) {
1882 for (var i = 0; i < mk.length; ++i) {
1883 var mark = mk[i];
1884 if (mark.to > pos || mark.to == null) {
1885 if (!taken.marked) taken.marked = [];
1886 taken.marked.push({
1887 from: mark.from < pos || mark.from == null ? null : mark.from - pos + textBefore.length,
1888 to: mark.to == null ? null : mark.to - pos + textBefore.length,
1889 style: mark.style, set: mark.set
1890 });
1891 mark.set.push(taken);
1892 }
1893 }
1894 }
1895 return taken;
1555 1896 },
1556 addMark: function(from, to, style) {
1557 var mk = this.marked, mark = {from: from, to: to, style: style};
1897 append: function(line) {
1898 if (!line.text.length) return;
1899 var mylen = this.text.length, mk = line.marked;
1900 this.text += line.text;
1901 copyStyles(0, line.text.length, line.styles, this.styles);
1902 if (mk && mk.length) {
1903 var mymk = this.marked || (this.marked = []);
1904 for (var i = 0; i < mymk.length; ++i)
1905 if (mymk[i].to == null) mymk[i].to = mylen;
1906 outer: for (var i = 0; i < mk.length; ++i) {
1907 var mark = mk[i];
1908 if (!mark.from) {
1909 for (var j = 0; j < mymk.length; ++j) {
1910 var mymark = mymk[j];
1911 if (mymark.to == mylen && mymark.set == mark.set) {
1912 mymark.to = mark.to == null ? null : mark.to + mylen;
1913 continue outer;
1914 }
1915 }
1916 }
1917 mymk.push(mark);
1918 mark.set.push(this);
1919 mark.from += mylen;
1920 if (mark.to != null) mark.to += mylen;
1921 }
1922 }
1923 },
1924 addMark: function(from, to, style, set) {
1925 set.push(this);
1558 1926 if (this.marked == null) this.marked = [];
1559 this.marked.push(mark);
1560 this.marked.sort(function(a, b){return a.from - b.from;});
1561 return mark;
1562 },
1563 removeMark: function(mark) {
1564 var mk = this.marked;
1565 if (!mk) return;
1566 for (var i = 0; i < mk.length; ++i)
1567 if (mk[i] == mark) {mk.splice(i, 1); break;}
1927 this.marked.push({from: from, to: to, style: style, set: set});
1928 this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
1568 1929 },
1569 1930 // Run the given mode's parser over a line, update the styles
1570 1931 // array, which contains alternating fragments of text and CSS
1571 1932 // classes.
1572 1933 highlight: function(mode, state) {
1573 var stream = new StringStream(this.text), st = this.styles, pos = 0, changed = false;
1934 var stream = new StringStream(this.text), st = this.styles, pos = 0;
1935 var changed = false, curWord = st[0], prevWord;
1936 if (this.text == "" && mode.blankLine) mode.blankLine(state);
1574 1937 while (!stream.eol()) {
1575 1938 var style = mode.token(stream, state);
1576 1939 var substr = this.text.slice(stream.start, stream.pos);
@@ -1578,8 +1941,9 b' var CodeMirror = (function() {'
1578 1941 if (pos && st[pos-1] == style)
1579 1942 st[pos-2] += substr;
1580 1943 else if (substr) {
1581 if (!changed && st[pos] != substr || st[pos+1] != style) changed = true;
1944 if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
1582 1945 st[pos++] = substr; st[pos++] = style;
1946 prevWord = curWord; curWord = st[pos];
1583 1947 }
1584 1948 // Give up when line is ridiculously long
1585 1949 if (stream.pos > 5000) {
@@ -1588,7 +1952,11 b' var CodeMirror = (function() {'
1588 1952 }
1589 1953 }
1590 1954 if (st.length != pos) {st.length = pos; changed = true;}
1591 return changed;
1955 if (pos && st[pos-2] != prevWord) changed = true;
1956 // Short lines with simple highlights return null, and are
1957 // counted as changed by the driver because they are likely to
1958 // highlight the same way in various contexts.
1959 return changed || (st.length < 5 && this.text.length < 10 ? null : false);
1592 1960 },
1593 1961 // Fetch the parser token for a given character. Useful for hacks
1594 1962 // that want to inspect the mode state (say, for completion).
@@ -1607,7 +1975,7 b' var CodeMirror = (function() {'
1607 1975 indentation: function() {return countColumn(this.text);},
1608 1976 // Produces an HTML fragment for the line, taking selection,
1609 1977 // marking, and highlighting into account.
1610 getHTML: function(sfrom, sto, includePre) {
1978 getHTML: function(sfrom, sto, includePre, endAt) {
1611 1979 var html = [];
1612 1980 if (includePre)
1613 1981 html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
@@ -1618,11 +1986,18 b' var CodeMirror = (function() {'
1618 1986 }
1619 1987 var st = this.styles, allText = this.text, marked = this.marked;
1620 1988 if (sfrom == sto) sfrom = null;
1989 var len = allText.length;
1990 if (endAt != null) len = Math.min(endAt, len);
1621 1991
1622 if (!allText)
1992 if (!allText && endAt == null)
1623 1993 span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
1624 1994 else if (!marked && sfrom == null)
1625 for (var i = 0, e = st.length; i < e; i+=2) span(st[i], st[i+1]);
1995 for (var i = 0, ch = 0; ch < len; i+=2) {
1996 var str = st[i], style = st[i+1], l = str.length;
1997 if (ch + l > len) str = str.slice(0, len - ch);
1998 ch += l;
1999 span(str, style && "cm-" + style);
2000 }
1626 2001 else {
1627 2002 var pos = 0, i = 0, text = "", style, sg = 0;
1628 2003 var markpos = -1, mark = null;
@@ -1632,9 +2007,9 b' var CodeMirror = (function() {'
1632 2007 mark = (markpos < marked.length) ? marked[markpos] : null;
1633 2008 }
1634 2009 }
1635 nextMark();
1636 while (pos < allText.length) {
1637 var upto = allText.length;
2010 nextMark();
2011 while (pos < len) {
2012 var upto = len;
1638 2013 var extraStyle = "";
1639 2014 if (sfrom != null) {
1640 2015 if (sfrom > pos) upto = sfrom;
@@ -1653,12 +2028,12 b' var CodeMirror = (function() {'
1653 2028 }
1654 2029 for (;;) {
1655 2030 var end = pos + text.length;
1656 var apliedStyle = style;
1657 if (extraStyle) apliedStyle = style ? style + extraStyle : extraStyle;
1658 span(end > upto ? text.slice(0, upto - pos) : text, apliedStyle);
2031 var appliedStyle = style;
2032 if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
2033 span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
1659 2034 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
1660 2035 pos = end;
1661 text = st[i++]; style = st[i++];
2036 text = st[i++]; style = "cm-" + st[i++];
1662 2037 }
1663 2038 }
1664 2039 if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
@@ -1716,42 +2091,34 b' var CodeMirror = (function() {'
1716 2091 }
1717 2092 };
1718 2093
1719 // Event stopping compatibility wrapper.
1720 function stopEvent() {
1721 if (this.preventDefault) {this.preventDefault(); this.stopPropagation();}
1722 else {this.returnValue = false; this.cancelBubble = true;}
1723 }
2094 function stopMethod() {e_stop(this);}
1724 2095 // Ensure an event has a stop method.
1725 2096 function addStop(event) {
1726 if (!event.stop) event.stop = stopEvent;
2097 if (!event.stop) event.stop = stopMethod;
1727 2098 return event;
1728 2099 }
1729 2100
1730 // Event wrapper, exposing the few operations we need.
1731 function Event(orig) {this.e = orig;}
1732 Event.prototype = {
1733 stop: function() {stopEvent.call(this.e);},
1734 target: function() {return this.e.target || this.e.srcElement;},
1735 button: function() {
1736 if (this.e.which) return this.e.which;
1737 else if (this.e.button & 1) return 1;
1738 else if (this.e.button & 2) return 3;
1739 else if (this.e.button & 4) return 2;
1740 },
1741 pageX: function() {
1742 if (this.e.pageX != null) return this.e.pageX;
1743 else return this.e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
1744 },
1745 pageY: function() {
1746 if (this.e.pageY != null) return this.e.pageY;
1747 else return this.e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
1748 }
1749 };
2101 function e_preventDefault(e) {
2102 if (e.preventDefault) e.preventDefault();
2103 else e.returnValue = false;
2104 }
2105 function e_stopPropagation(e) {
2106 if (e.stopPropagation) e.stopPropagation();
2107 else e.cancelBubble = true;
2108 }
2109 function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
2110 function e_target(e) {return e.target || e.srcElement;}
2111 function e_button(e) {
2112 if (e.which) return e.which;
2113 else if (e.button & 1) return 1;
2114 else if (e.button & 2) return 3;
2115 else if (e.button & 4) return 2;
2116 }
1750 2117
1751 2118 // Event handler registration. If disconnect is true, it'll return a
1752 2119 // function that unregisters the handler.
1753 2120 function connect(node, type, handler, disconnect) {
1754 function wrapHandler(event) {handler(new Event(event || window.event));}
2121 function wrapHandler(event) {handler(event || window.event);}
1755 2122 if (typeof node.addEventListener == "function") {
1756 2123 node.addEventListener(type, wrapHandler, false);
1757 2124 if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
@@ -1772,7 +2139,18 b' var CodeMirror = (function() {'
1772 2139 pre.innerHTML = " "; return !pre.innerHTML;
1773 2140 })();
1774 2141
2142 // Detect drag-and-drop
2143 var dragAndDrop = (function() {
2144 // IE8 has ondragstart and ondrop properties, but doesn't seem to
2145 // actually support ondragstart the way it's supposed to work.
2146 if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
2147 var div = document.createElement('div');
2148 return "ondragstart" in div && "ondrop" in div;
2149 })();
2150
1775 2151 var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
2152 var ie = /MSIE \d/.test(navigator.userAgent);
2153 var safari = /Apple Computer/.test(navigator.vendor);
1776 2154
1777 2155 var lineSep = "\n";
1778 2156 // Feature-detect whether newlines in textareas are converted to \r\n
@@ -1802,11 +2180,23 b' var CodeMirror = (function() {'
1802 2180 return n;
1803 2181 }
1804 2182
2183 function computedStyle(elt) {
2184 if (elt.currentStyle) return elt.currentStyle;
2185 return window.getComputedStyle(elt, null);
2186 }
1805 2187 // Find the position of an element by following the offsetParent chain.
1806 function eltOffset(node) {
1807 var x = 0, y = 0, n2 = node;
1808 for (var n = node; n; n = n.offsetParent) {x += n.offsetLeft; y += n.offsetTop;}
1809 for (var n = node; n != document.body; n = n.parentNode) {x -= n.scrollLeft; y -= n.scrollTop;}
2188 // If screen==true, it returns screen (rather than page) coordinates.
2189 function eltOffset(node, screen) {
2190 var doc = node.ownerDocument.body;
2191 var x = 0, y = 0, skipDoc = false;
2192 for (var n = node; n; n = n.offsetParent) {
2193 x += n.offsetLeft; y += n.offsetTop;
2194 if (screen && computedStyle(n).position == "fixed")
2195 skipDoc = true;
2196 }
2197 var e = screen && !skipDoc ? null : doc;
2198 for (var n = node.parentNode; n != e; n = n.parentNode)
2199 if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
1810 2200 return {left: x, top: y};
1811 2201 }
1812 2202 // Get a node's text content.
@@ -1819,9 +2209,18 b' var CodeMirror = (function() {'
1819 2209 function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
1820 2210 function copyPos(x) {return {line: x.line, ch: x.ch};}
1821 2211
2212 var escapeElement = document.createElement("pre");
1822 2213 function htmlEscape(str) {
1823 return str.replace(/[<&]/g, function(str) {return str == "&" ? "&amp;" : "&lt;";});
2214 if (badTextContent) {
2215 escapeElement.innerHTML = "";
2216 escapeElement.appendChild(document.createTextNode(str));
2217 } else {
2218 escapeElement.textContent = str;
2219 }
2220 return escapeElement.innerHTML;
1824 2221 }
2222 var badTextContent = htmlEscape("\t") != "\t";
2223 CodeMirror.htmlEscape = htmlEscape;
1825 2224
1826 2225 // Used to position the cursor after an undo/redo by finding the
1827 2226 // last edited character.
@@ -1842,8 +2241,9 b' var CodeMirror = (function() {'
1842 2241
1843 2242 // See if "".split is the broken IE version, if so, provide an
1844 2243 // alternative way to split lines.
2244 var splitLines, selRange, setSelRange;
1845 2245 if ("\n\nb".split(/\n/).length != 3)
1846 var splitLines = function(string) {
2246 splitLines = function(string) {
1847 2247 var pos = 0, nl, result = [];
1848 2248 while ((nl = string.indexOf("\n", pos)) > -1) {
1849 2249 result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
@@ -1853,23 +2253,40 b' var CodeMirror = (function() {'
1853 2253 return result;
1854 2254 };
1855 2255 else
1856 var splitLines = function(string){return string.split(/\r?\n/);};
2256 splitLines = function(string){return string.split(/\r?\n/);};
2257 CodeMirror.splitLines = splitLines;
1857 2258
1858 2259 // Sane model of finding and setting the selection in a textarea
1859 2260 if (window.getSelection) {
1860 var selRange = function(te) {
2261 selRange = function(te) {
1861 2262 try {return {start: te.selectionStart, end: te.selectionEnd};}
1862 2263 catch(e) {return null;}
1863 2264 };
1864 var setSelRange = function(te, start, end) {
1865 try {te.setSelectionRange(start, end);}
1866 catch(e) {} // Fails on Firefox when textarea isn't part of the document
1867 };
2265 if (safari)
2266 // On Safari, selection set with setSelectionRange are in a sort
2267 // of limbo wrt their anchor. If you press shift-left in them,
2268 // the anchor is put at the end, and the selection expanded to
2269 // the left. If you press shift-right, the anchor ends up at the
2270 // front. This is not what CodeMirror wants, so it does a
2271 // spurious modify() call to get out of limbo.
2272 setSelRange = function(te, start, end) {
2273 if (start == end)
2274 te.setSelectionRange(start, end);
2275 else {
2276 te.setSelectionRange(start, end - 1);
2277 window.getSelection().modify("extend", "forward", "character");
2278 }
2279 };
2280 else
2281 setSelRange = function(te, start, end) {
2282 try {te.setSelectionRange(start, end);}
2283 catch(e) {} // Fails on Firefox when textarea isn't part of the document
2284 };
1868 2285 }
1869 2286 // IE model. Don't ask.
1870 2287 else {
1871 var selRange = function(te) {
1872 try {var range = document.selection.createRange();}
2288 selRange = function(te) {
2289 try {var range = te.ownerDocument.selection.createRange();}
1873 2290 catch(e) {return null;}
1874 2291 if (!range || range.parentElement() != te) return null;
1875 2292 var val = te.value, len = val.length, localRange = te.createTextRange();
@@ -1890,7 +2307,7 b' var CodeMirror = (function() {'
1890 2307 for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
1891 2308 return {start: start, end: end};
1892 2309 };
1893 var setSelRange = function(te, start, end) {
2310 setSelRange = function(te, start, end) {
1894 2311 var range = te.createTextRange();
1895 2312 range.collapse(true);
1896 2313 var endrange = range.duplicate();
@@ -63,18 +63,16 b' function BranchRenderer() {'
63 63 var rela = document.getElementById('graph');
64 64 var pad = pad;
65 65 var scale = 22;
66
66
67 67 for (var i in data) {
68 68 this.scale(scale);
69
69 70 var row = document.getElementById("chg_"+idx);
70 var next = document.getElementById("chg_"+idx+1);
71 if (row == null)
72 continue;
73 var next = document.getElementById("chg_"+(idx+1));
71 74 var extra = 0;
72 75
73 //skip this since i don't have DATE in my app
74 //if (next.is('.changesets-date')) {
75 // extra = next.outerHeight();
76 //}
77
78 76 this.cell[1] += row.clientWidth;
79 77 this.bg[1] += this.bg_height;
80 78
@@ -82,7 +80,10 b' function BranchRenderer() {'
82 80 nodeid = cur[0];
83 81 node = cur[1];
84 82 in_l = cur[2];
85
83
84 var rowY = row.offsetTop + row.offsetHeight/2 - rela.offsetTop;
85 var nextY = (next == null) ? rowY + row.offsetHeight/2 : next.offsetTop + next.offsetHeight/2 - rela.offsetTop;
86
86 87 for (var j in in_l) {
87 88
88 89 line = in_l[j];
@@ -99,17 +100,26 b' function BranchRenderer() {'
99 100 }
100 101
101 102 this.setColor(color, 0.0, 0.65);
103
102 104
103 y = row.offsetTop-rela.offsetTop+4;
104 105 x = pad-((this.cell[0] + this.box_size * start - 1) + this.bg_height-2);
105 106
106 107 this.ctx.lineWidth=this.line_width;
107 108 this.ctx.beginPath();
108 this.ctx.moveTo(x, y);
109 this.ctx.moveTo(x, rowY);
109 110
110 y += row.offsetHeight;
111 x = pad-((1 + this.box_size * end) + this.bg_height-2);
112 this.ctx.lineTo(x,y+extra,3);
111
112 if (start == end)
113 {
114 x = pad-((1 + this.box_size * end) + this.bg_height-2);
115 this.ctx.lineTo(x,nextY+extra,3);
116 }
117 else
118 {
119 var x2 = pad-((1 + this.box_size * end) + this.bg_height-2);
120 var ymid = (rowY+nextY) / 2;
121 this.ctx.bezierCurveTo (x,ymid,x2,ymid,x2,nextY);
122 }
113 123 this.ctx.stroke();
114 124 }
115 125
@@ -117,12 +127,12 b' function BranchRenderer() {'
117 127 color = node[1]
118 128
119 129 radius = this.dot_radius;
120 y = row.offsetTop-rela.offsetTop+4;
130
121 131 x = pad-(Math.round(this.cell[0] * scale/2 * column + radius) + 15 - (column*4));
122 132
123 133 this.ctx.beginPath();
124 134 this.setColor(color, 0.25, 0.75);
125 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
135 this.ctx.arc(x, rowY, radius, 0, Math.PI * 2, true);
126 136 this.ctx.fill();
127 137
128 138 idx++;
This diff has been collapsed as it changes many lines, (518 lines changed) Show them Hide them
@@ -7,10 +7,10 b' if (typeof console == "undefined" || typ'
7 7 }
8 8
9 9
10 function str_repeat(i, m) {
10 var str_repeat = function(i, m) {
11 11 for (var o = []; m > 0; o[--m] = i);
12 12 return o.join('');
13 }
13 };
14 14
15 15 /**
16 16 * INJECT .format function into String
@@ -55,7 +55,7 b' String.prototype.format = function() {'
55 55 *
56 56 * @returns {ColorGenerator}
57 57 */
58 function ColorGenerator(){
58 var ColorGenerator = function(){
59 59 this.GOLDEN_RATIO = 0.618033988749895;
60 60 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
61 61 this.HSV_1 = 0.75;//saturation
@@ -129,7 +129,21 b' var push_state_enabled = Boolean('
129 129 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
130 130 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
131 131 )
132 )
132 );
133
134 var _run_callbacks = function(callbacks){
135 if (callbacks !== undefined){
136 var _l = callbacks.length;
137 for (var i=0;i<_l;i++){
138 var func = callbacks[i];
139 if(typeof(func)=='function'){
140 try{
141 func();
142 }catch (err){};
143 }
144 }
145 }
146 }
133 147
134 148 /**
135 149 * Partial Ajax Implementation
@@ -172,11 +186,14 b' function ypjax(url,container,s_call,f_ca'
172 186 YUC.asyncRequest(method,url,{
173 187 success:s_wrapper,
174 188 failure:function(o){
175 console.log(o)
189 console.log(o);
190 YUD.get(container).innerHTML='ERROR';
191 YUD.setStyle(container,'opacity','1.0');
192 YUD.setStyle(container,'color','red');
176 193 }
177 194 },args);
178 195
179 }
196 };
180 197
181 198 /**
182 199 * tooltip activate
@@ -203,7 +220,7 b' var tooltip_activate = function(){'
203 220 hidedelay:5,
204 221 showdelay:20,
205 222 });
206 }
223 };
207 224
208 225 /**
209 226 * show more
@@ -214,5 +231,492 b' var show_more_event = function(){'
214 231 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
215 232 YUD.setStyle(el.parentNode,'display','none');
216 233 });
234 };
235
236
237 /**
238 * Quick filter widget
239 *
240 * @param target: filter input target
241 * @param nodes: list of nodes in html we want to filter.
242 * @param display_element function that takes current node from nodes and
243 * does hide or show based on the node
244 *
245 */
246 var q_filter = function(target,nodes,display_element){
247
248 var nodes = nodes;
249 var q_filter_field = YUD.get(target);
250 var F = YAHOO.namespace(target);
251
252 YUE.on(q_filter_field,'click',function(){
253 q_filter_field.value = '';
254 });
255
256 YUE.on(q_filter_field,'keyup',function(e){
257 clearTimeout(F.filterTimeout);
258 F.filterTimeout = setTimeout(F.updateFilter,600);
259 });
260
261 F.filterTimeout = null;
262
263 var show_node = function(node){
264 YUD.setStyle(node,'display','')
265 }
266 var hide_node = function(node){
267 YUD.setStyle(node,'display','none');
268 }
269
270 F.updateFilter = function() {
271 // Reset timeout
272 F.filterTimeout = null;
273
274 var obsolete = [];
275
276 var req = q_filter_field.value.toLowerCase();
277
278 var l = nodes.length;
279 var i;
280 var showing = 0;
281
282 for (i=0;i<l;i++ ){
283 var n = nodes[i];
284 var target_element = display_element(n)
285 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
286 hide_node(target_element);
287 }
288 else{
289 show_node(target_element);
290 showing+=1;
291 }
292 }
293
294 // if repo_count is set update the number
295 var cnt = YUD.get('repo_count');
296 if(cnt){
297 YUD.get('repo_count').innerHTML = showing;
298 }
299
300 }
301 };
302
303 var ajaxPOST = function(url,postData,success) {
304 var sUrl = url;
305 var callback = {
306 success: success,
307 failure: function (o) {
308 alert("error");
309 },
310 };
311 var postData = postData;
312 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
313 };
314
315
316 /** comments **/
317 var removeInlineForm = function(form) {
318 form.parentNode.removeChild(form);
319 };
320
321 var tableTr = function(cls,body){
322 var form = document.createElement('tr');
323 YUD.addClass(form, cls);
324 form.innerHTML = '<td class="lineno-inline new-inline"></td>'+
325 '<td class="lineno-inline old-inline"></td>'+
326 '<td>{0}</td>'.format(body);
327 return form;
328 };
329
330 var createInlineForm = function(parent_tr, f_path, line) {
331 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
332 tmpl = tmpl.format(f_path, line);
333 var form = tableTr('comment-form-inline',tmpl)
334
335 // create event for hide button
336 form = new YAHOO.util.Element(form);
337 var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
338 form_hide_button.on('click', function(e) {
339 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
340 removeInlineForm(newtr);
341 YUD.removeClass(parent_tr, 'form-open');
342 });
343 return form
344 };
345 var injectInlineForm = function(tr){
346 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
347 return
348 }
349 YUD.addClass(tr,'form-open');
350 var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
351 var f_path = YUD.getAttribute(node,'path');
352 var lineno = getLineNo(tr);
353 var form = createInlineForm(tr, f_path, lineno);
354 var target_tr = tr;
355 if(YUD.hasClass(YUD.getNextSibling(tr),'inline-comments')){
356 target_tr = YUD.getNextSibling(tr);
357 }
358 YUD.insertAfter(form,target_tr);
359 YUD.get('text_'+lineno).focus();
360 tooltip_activate();
361 };
362
363 var createInlineAddButton = function(tr,label){
364 var html = '<div class="add-comment"><span class="ui-btn">{0}</span></div>'.format(label);
365
366 var add = new YAHOO.util.Element(tableTr('inline-comments-button',html));
367 add.on('click', function(e) {
368 injectInlineForm(tr);
369 });
370 return add;
371 };
372
373 var getLineNo = function(tr) {
374 var line;
375 var o = tr.children[0].id.split('_');
376 var n = tr.children[1].id.split('_');
377
378 if (n.length >= 2) {
379 line = n[n.length-1];
380 } else if (o.length >= 2) {
381 line = o[o.length-1];
382 }
383
384 return line
385 };
386
387
388 var fileBrowserListeners = function(current_url, node_list_url, url_base,
389 truncated_lbl, nomatch_lbl){
390 var current_url_branch = +"?branch=__BRANCH__";
391 var url = url_base;
392 var node_url = node_list_url;
393
394 YUE.on('stay_at_branch','click',function(e){
395 if(e.target.checked){
396 var uri = current_url_branch;
397 uri = uri.replace('__BRANCH__',e.target.value);
398 window.location = uri;
399 }
400 else{
401 window.location = current_url;
402 }
403 })
404
405 var n_filter = YUD.get('node_filter');
406 var F = YAHOO.namespace('node_filter');
407
408 F.filterTimeout = null;
409 var nodes = null;
410
411 F.initFilter = function(){
412 YUD.setStyle('node_filter_box_loading','display','');
413 YUD.setStyle('search_activate_id','display','none');
414 YUD.setStyle('add_node_id','display','none');
415 YUC.initHeader('X-PARTIAL-XHR',true);
416 YUC.asyncRequest('GET',url,{
417 success:function(o){
418 nodes = JSON.parse(o.responseText);
419 YUD.setStyle('node_filter_box_loading','display','none');
420 YUD.setStyle('node_filter_box','display','');
421 n_filter.focus();
422 if(YUD.hasClass(n_filter,'init')){
423 n_filter.value = '';
424 YUD.removeClass(n_filter,'init');
425 }
426 },
427 failure:function(o){
428 console.log('failed to load');
429 }
430 },null);
431 }
432
433 F.updateFilter = function(e) {
434
435 return function(){
436 // Reset timeout
437 F.filterTimeout = null;
438 var query = e.target.value.toLowerCase();
439 var match = [];
440 var matches = 0;
441 var matches_max = 20;
442 if (query != ""){
443 for(var i=0;i<nodes.length;i++){
444
445 var pos = nodes[i].name.toLowerCase().indexOf(query)
446 if(query && pos != -1){
447
448 matches++
449 //show only certain amount to not kill browser
450 if (matches > matches_max){
451 break;
452 }
453
454 var n = nodes[i].name;
455 var t = nodes[i].type;
456 var n_hl = n.substring(0,pos)
457 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
458 +n.substring(pos+query.length)
459 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url.replace('__FPATH__',n),n_hl));
460 }
461 if(match.length >= matches_max){
462 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(truncated_lbl));
463 }
464
465 }
466 }
467 if(query != ""){
468 YUD.setStyle('tbody','display','none');
469 YUD.setStyle('tbody_filtered','display','');
470
471 if (match.length==0){
472 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(nomatch_lbl));
473 }
474
475 YUD.get('tbody_filtered').innerHTML = match.join("");
476 }
477 else{
478 YUD.setStyle('tbody','display','');
479 YUD.setStyle('tbody_filtered','display','none');
480 }
481
482 }
483 };
484
485 YUE.on(YUD.get('filter_activate'),'click',function(){
486 F.initFilter();
487 })
488 YUE.on(n_filter,'click',function(){
489 if(YUD.hasClass(n_filter,'init')){
490 n_filter.value = '';
491 YUD.removeClass(n_filter,'init');
492 }
493 });
494 YUE.on(n_filter,'keyup',function(e){
495 clearTimeout(F.filterTimeout);
496 F.filterTimeout = setTimeout(F.updateFilter(e),600);
497 });
498 };
499
500
501 var initCodeMirror = function(textAreadId,resetUrl){
502 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
503 mode: "null",
504 lineNumbers:true
505 });
506 YUE.on('reset','click',function(e){
507 window.location=resetUrl
508 });
509
510 YUE.on('file_enable','click',function(){
511 YUD.setStyle('editor_container','display','');
512 YUD.setStyle('upload_file_container','display','none');
513 YUD.setStyle('filename_container','display','');
514 });
515
516 YUE.on('upload_file_enable','click',function(){
517 YUD.setStyle('editor_container','display','none');
518 YUD.setStyle('upload_file_container','display','');
519 YUD.setStyle('filename_container','display','none');
520 });
521 };
522
523
524
525 var getIdentNode = function(n){
526 //iterate thru nodes untill matched interesting node !
527
528 if (typeof n == 'undefined'){
529 return -1
530 }
531
532 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
533 return n
534 }
535 else{
536 return getIdentNode(n.parentNode);
537 }
538 };
539
540 var getSelectionLink = function(selection_link_label) {
541 return function(){
542 //get selection from start/to nodes
543 if (typeof window.getSelection != "undefined") {
544 s = window.getSelection();
545
546 from = getIdentNode(s.anchorNode);
547 till = getIdentNode(s.focusNode);
548
549 f_int = parseInt(from.id.replace('L',''));
550 t_int = parseInt(till.id.replace('L',''));
551
552 if (f_int > t_int){
553 //highlight from bottom
554 offset = -35;
555 ranges = [t_int,f_int];
556
557 }
558 else{
559 //highligth from top
560 offset = 35;
561 ranges = [f_int,t_int];
562 }
563
564 if (ranges[0] != ranges[1]){
565 if(YUD.get('linktt') == null){
566 hl_div = document.createElement('div');
567 hl_div.id = 'linktt';
568 }
569 anchor = '#L'+ranges[0]+'-'+ranges[1];
570 hl_div.innerHTML = '';
571 l = document.createElement('a');
572 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
573 l.innerHTML = selection_link_label;
574 hl_div.appendChild(l);
575
576 YUD.get('body').appendChild(hl_div);
577
578 xy = YUD.getXY(till.id);
579
580 YUD.addClass('linktt','yui-tt');
581 YUD.setStyle('linktt','top',xy[1]+offset+'px');
582 YUD.setStyle('linktt','left',xy[0]+'px');
583 YUD.setStyle('linktt','visibility','visible');
584 }
585 else{
586 YUD.setStyle('linktt','visibility','hidden');
587 }
588 }
589 }
590 };
591
592 var deleteNotification = function(url, notification_id,callbacks){
593 var callback = {
594 success:function(o){
595 var obj = YUD.get(String("notification_"+notification_id));
596 if(obj.parentNode !== undefined){
597 obj.parentNode.removeChild(obj);
598 }
599 _run_callbacks(callbacks);
600 },
601 failure:function(o){
602 alert("error");
603 },
604 };
605 var postData = '_method=delete';
606 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
607 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
608 callback, postData);
609 };
610
611
612 /**
613 * QUICK REPO MENU
614 */
615 var quick_repo_menu = function(){
616 YUE.on(YUQ('.quick_repo_menu'),'click',function(e){
617 var menu = e.currentTarget.firstElementChild.firstElementChild;
618 if(YUD.hasClass(menu,'hidden')){
619 YUD.addClass(e.currentTarget,'active');
620 YUD.removeClass(menu,'hidden');
621 }else{
622 YUD.removeClass(e.currentTarget,'active');
623 YUD.addClass(menu,'hidden');
624 }
625 })
626 };
627
628
629 /**
630 * TABLE SORTING
631 */
632
633 // returns a node from given html;
634 var fromHTML = function(html){
635 var _html = document.createElement('element');
636 _html.innerHTML = html;
637 return _html;
638 }
639 var get_rev = function(node){
640 var n = node.firstElementChild.firstElementChild;
641
642 if (n===null){
643 return -1
644 }
645 else{
646 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
647 return parseInt(out);
648 }
217 649 }
218 650
651 var get_name = function(node){
652 var name = node.firstElementChild.children[2].innerHTML;
653 return name
654 }
655 var get_group_name = function(node){
656 var name = node.firstElementChild.children[1].innerHTML;
657 return name
658 }
659 var get_date = function(node){
660 var date_ = node.firstElementChild.innerHTML;
661 return date_
662 }
663
664 var revisionSort = function(a, b, desc, field) {
665
666 var a_ = fromHTML(a.getData(field));
667 var b_ = fromHTML(b.getData(field));
668
669 // extract revisions from string nodes
670 a_ = get_rev(a_)
671 b_ = get_rev(b_)
672
673 var comp = YAHOO.util.Sort.compare;
674 var compState = comp(a_, b_, desc);
675 return compState;
676 };
677 var ageSort = function(a, b, desc, field) {
678 var a_ = a.getData(field);
679 var b_ = b.getData(field);
680
681 var comp = YAHOO.util.Sort.compare;
682 var compState = comp(a_, b_, desc);
683 return compState;
684 };
685
686 var nameSort = function(a, b, desc, field) {
687 var a_ = fromHTML(a.getData(field));
688 var b_ = fromHTML(b.getData(field));
689
690 // extract name from table
691 a_ = get_name(a_)
692 b_ = get_name(b_)
693
694 var comp = YAHOO.util.Sort.compare;
695 var compState = comp(a_, b_, desc);
696 return compState;
697 };
698
699 var groupNameSort = function(a, b, desc, field) {
700 var a_ = fromHTML(a.getData(field));
701 var b_ = fromHTML(b.getData(field));
702
703 // extract name from table
704 a_ = get_group_name(a_)
705 b_ = get_group_name(b_)
706
707 var comp = YAHOO.util.Sort.compare;
708 var compState = comp(a_, b_, desc);
709 return compState;
710 };
711 var dateSort = function(a, b, desc, field) {
712 var a_ = fromHTML(a.getData(field));
713 var b_ = fromHTML(b.getData(field));
714
715 // extract name from table
716 a_ = get_date(a_)
717 b_ = get_date(b_)
718
719 var comp = YAHOO.util.Sort.compare;
720 var compState = comp(a_, b_, desc);
721 return compState;
722 }; No newline at end of file
@@ -101,4 +101,50 b' Code licensed under the BSD License:'
101 101 http://developer.yahoo.com/yui/license.html
102 102 version: 2.9.0
103 103 */
104 (function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"}); No newline at end of file
104 (function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});
105 /*
106 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
107 Code licensed under the BSD License:
108 http://developer.yahoo.com/yui/license.html
109 version: 2.9.0
110 */
111 (function(){var lang=YAHOO.lang,util=YAHOO.util,Ev=util.Event;util.DataSourceBase=function(oLiveData,oConfigs){if(oLiveData===null||oLiveData===undefined){return;}this.liveData=oLiveData;this._oQueue={interval:null,conn:null,requests:[]};this.responseSchema={};if(oConfigs&&(oConfigs.constructor==Object)){for(var sConfig in oConfigs){if(sConfig){this[sConfig]=oConfigs[sConfig];}}}var maxCacheEntries=this.maxCacheEntries;if(!lang.isNumber(maxCacheEntries)||(maxCacheEntries<0)){maxCacheEntries=0;}this._aIntervals=[];this.createEvent("cacheRequestEvent");this.createEvent("cacheResponseEvent");this.createEvent("requestEvent");this.createEvent("responseEvent");this.createEvent("responseParseEvent");this.createEvent("responseCacheEvent");this.createEvent("dataErrorEvent");this.createEvent("cacheFlushEvent");var DS=util.DataSourceBase;this._sName="DataSource instance"+DS._nIndex;DS._nIndex++;};var DS=util.DataSourceBase;lang.augmentObject(DS,{TYPE_UNKNOWN:-1,TYPE_JSARRAY:0,TYPE_JSFUNCTION:1,TYPE_XHR:2,TYPE_JSON:3,TYPE_XML:4,TYPE_TEXT:5,TYPE_HTMLTABLE:6,TYPE_SCRIPTNODE:7,TYPE_LOCAL:8,ERROR_DATAINVALID:"Invalid data",ERROR_DATANULL:"Null data",_nIndex:0,_nTransactionId:0,_cloneObject:function(o){if(!lang.isValue(o)){return o;}var copy={};if(Object.prototype.toString.apply(o)==="[object RegExp]"){copy=o;}else{if(lang.isFunction(o)){copy=o;}else{if(lang.isArray(o)){var array=[];for(var i=0,len=o.length;i<len;i++){array[i]=DS._cloneObject(o[i]);}copy=array;}else{if(lang.isObject(o)){for(var x in o){if(lang.hasOwnProperty(o,x)){if(lang.isValue(o[x])&&lang.isObject(o[x])||lang.isArray(o[x])){copy[x]=DS._cloneObject(o[x]);}else{copy[x]=o[x];}}}}else{copy=o;}}}}return copy;},_getLocationValue:function(field,context){var locator=field.locator||field.key||field,xmldoc=context.ownerDocument||context,result,res,value=null;try{if(!lang.isUndefined(xmldoc.evaluate)){result=xmldoc.evaluate(locator,context,xmldoc.createNSResolver(!context.ownerDocument?context.documentElement:context.ownerDocument.documentElement),0,null);while(res=result.iterateNext()){value=res.textContent;}}else{xmldoc.setProperty("SelectionLanguage","XPath");result=context.selectNodes(locator)[0];value=result.value||result.text||null;}return value;}catch(e){}},issueCallback:function(callback,params,error,scope){if(lang.isFunction(callback)){callback.apply(scope,params);}else{if(lang.isObject(callback)){scope=callback.scope||scope||window;var callbackFunc=callback.success;if(error){callbackFunc=callback.failure;}if(callbackFunc){callbackFunc.apply(scope,params.concat([callback.argument]));}}}},parseString:function(oData){if(!lang.isValue(oData)){return null;}var string=oData+"";if(lang.isString(string)){return string;}else{return null;}},parseNumber:function(oData){if(!lang.isValue(oData)||(oData==="")){return null;}var number=oData*1;if(lang.isNumber(number)){return number;}else{return null;}},convertNumber:function(oData){return DS.parseNumber(oData);},parseDate:function(oData){var date=null;if(lang.isValue(oData)&&!(oData instanceof Date)){date=new Date(oData);}else{return oData;}if(date instanceof Date){return date;}else{return null;}},convertDate:function(oData){return DS.parseDate(oData);}});DS.Parser={string:DS.parseString,number:DS.parseNumber,date:DS.parseDate};DS.prototype={_sName:null,_aCache:null,_oQueue:null,_aIntervals:null,maxCacheEntries:0,liveData:null,dataType:DS.TYPE_UNKNOWN,responseType:DS.TYPE_UNKNOWN,responseSchema:null,useXPath:false,cloneBeforeCaching:false,toString:function(){return this._sName;},getCachedResponse:function(oRequest,oCallback,oCaller){var aCache=this._aCache;if(this.maxCacheEntries>0){if(!aCache){this._aCache=[];}else{var nCacheLength=aCache.length;if(nCacheLength>0){var oResponse=null;this.fireEvent("cacheRequestEvent",{request:oRequest,callback:oCallback,caller:oCaller});for(var i=nCacheLength-1;i>=0;i--){var oCacheElem=aCache[i];if(this.isCacheHit(oRequest,oCacheElem.request)){oResponse=oCacheElem.response;this.fireEvent("cacheResponseEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});if(i<nCacheLength-1){aCache.splice(i,1);this.addToCache(oRequest,oResponse);}oResponse.cached=true;break;}}return oResponse;}}}else{if(aCache){this._aCache=null;}}return null;},isCacheHit:function(oRequest,oCachedRequest){return(oRequest===oCachedRequest);},addToCache:function(oRequest,oResponse){var aCache=this._aCache;if(!aCache){return;}while(aCache.length>=this.maxCacheEntries){aCache.shift();}oResponse=(this.cloneBeforeCaching)?DS._cloneObject(oResponse):oResponse;var oCacheElem={request:oRequest,response:oResponse};aCache[aCache.length]=oCacheElem;this.fireEvent("responseCacheEvent",{request:oRequest,response:oResponse});},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent");}},setInterval:function(nMsec,oRequest,oCallback,oCaller){if(lang.isNumber(nMsec)&&(nMsec>=0)){var oSelf=this;var nId=setInterval(function(){oSelf.makeConnection(oRequest,oCallback,oCaller);},nMsec);this._aIntervals.push(nId);return nId;}else{}},clearInterval:function(nId){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){if(tracker[i]===nId){tracker.splice(i,1);clearInterval(nId);}}},clearAllIntervals:function(){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){clearInterval(tracker[i]);}tracker=[];},sendRequest:function(oRequest,oCallback,oCaller){var oCachedResponse=this.getCachedResponse(oRequest,oCallback,oCaller);if(oCachedResponse){DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);return null;}return this.makeConnection(oRequest,oCallback,oCaller);},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=this.liveData;this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;},handleResponse:function(oRequest,oRawResponse,oCallback,oCaller,tId){this.fireEvent("responseEvent",{tId:tId,request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller});
112 var xhr=(this.dataType==DS.TYPE_XHR)?true:false;var oParsedResponse=null;var oFullResponse=oRawResponse;if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oRawResponse&&oRawResponse.getResponseHeader)?oRawResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}else{if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&(oRawResponse.nodeType===9||oRawResponse.nodeType===1||oRawResponse.nodeType===11)){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}}switch(this.responseType){case DS.TYPE_JSARRAY:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var arrayEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,arrayEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e1){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseArrayData(oRequest,oFullResponse);break;case DS.TYPE_JSON:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var objEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,objEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseJSONData(oRequest,oFullResponse);break;case DS.TYPE_HTMLTABLE:if(xhr&&oRawResponse.responseText){var el=document.createElement("div");el.innerHTML=oRawResponse.responseText;oFullResponse=el.getElementsByTagName("table")[0];}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseHTMLTableData(oRequest,oFullResponse);break;case DS.TYPE_XML:if(xhr&&oRawResponse.responseXML){oFullResponse=oRawResponse.responseXML;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseXMLData(oRequest,oFullResponse);break;case DS.TYPE_TEXT:if(xhr&&lang.isString(oRawResponse.responseText)){oFullResponse=oRawResponse.responseText;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseTextData(oRequest,oFullResponse);break;default:oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseData(oRequest,oFullResponse);break;}oParsedResponse=oParsedResponse||{};if(!oParsedResponse.results){oParsedResponse.results=[];}if(!oParsedResponse.meta){oParsedResponse.meta={};}if(!oParsedResponse.error){oParsedResponse=this.doBeforeCallback(oRequest,oFullResponse,oParsedResponse,oCallback);this.fireEvent("responseParseEvent",{request:oRequest,response:oParsedResponse,callback:oCallback,caller:oCaller});this.addToCache(oRequest,oParsedResponse);}else{oParsedResponse.error=true;this.fireEvent("dataErrorEvent",{request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});}oParsedResponse.tId=tId;DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);},doBeforeParseData:function(oRequest,oFullResponse,oCallback){return oFullResponse;},doBeforeCallback:function(oRequest,oFullResponse,oParsedResponse,oCallback){return oParsedResponse;},parseData:function(oRequest,oFullResponse){if(lang.isValue(oFullResponse)){var oParsedResponse={results:oFullResponse,meta:{}};return oParsedResponse;}return null;},parseArrayData:function(oRequest,oFullResponse){if(lang.isArray(oFullResponse)){var results=[],i,j,rec,field,data;if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(i=fields.length-1;i>=0;--i){if(typeof fields[i]!=="object"){fields[i]={key:fields[i]};}}var parsers={},p;for(i=fields.length-1;i>=0;--i){p=(typeof fields[i].parser==="function"?fields[i].parser:DS.Parser[fields[i].parser+""])||fields[i].converter;if(p){parsers[fields[i].key]=p;}}var arrType=lang.isArray(oFullResponse[0]);for(i=oFullResponse.length-1;i>-1;i--){var oResult={};rec=oFullResponse[i];if(typeof rec==="object"){for(j=fields.length-1;j>-1;j--){field=fields[j];data=arrType?rec[j]:rec[field.key];if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}else{if(lang.isString(rec)){for(j=fields.length-1;j>-1;j--){field=fields[j];data=rec;if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;
113 }}}results[i]=oResult;}}else{results=oFullResponse;}var oParsedResponse={results:results};return oParsedResponse;}return null;},parseTextData:function(oRequest,oFullResponse){if(lang.isString(oFullResponse)){if(lang.isString(this.responseSchema.recordDelim)&&lang.isString(this.responseSchema.fieldDelim)){var oParsedResponse={results:[]};var recDelim=this.responseSchema.recordDelim;var fieldDelim=this.responseSchema.fieldDelim;if(oFullResponse.length>0){var newLength=oFullResponse.length-recDelim.length;if(oFullResponse.substr(newLength)==recDelim){oFullResponse=oFullResponse.substr(0,newLength);}if(oFullResponse.length>0){var recordsarray=oFullResponse.split(recDelim);for(var i=0,len=recordsarray.length,recIdx=0;i<len;++i){var bError=false,sRecord=recordsarray[i];if(lang.isString(sRecord)&&(sRecord.length>0)){var fielddataarray=recordsarray[i].split(fieldDelim);var oResult={};if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(var j=fields.length-1;j>-1;j--){try{var data=fielddataarray[j];if(lang.isString(data)){if(data.charAt(0)=='"'){data=data.substr(1);}if(data.charAt(data.length-1)=='"'){data=data.substr(0,data.length-1);}var field=fields[j];var key=(lang.isValue(field.key))?field.key:field;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}else{bError=true;}}catch(e){bError=true;}}}else{oResult=fielddataarray;}if(!bError){oParsedResponse.results[recIdx++]=oResult;}}}}}return oParsedResponse;}}return null;},parseXMLResult:function(result){var oResult={},schema=this.responseSchema;try{for(var m=schema.fields.length-1;m>=0;m--){var field=schema.fields[m];var key=(lang.isValue(field.key))?field.key:field;var data=null;if(this.useXPath){data=YAHOO.util.DataSource._getLocationValue(field,result);}else{var xmlAttr=result.attributes.getNamedItem(key);if(xmlAttr){data=xmlAttr.value;}else{var xmlNode=result.getElementsByTagName(key);if(xmlNode&&xmlNode.item(0)){var item=xmlNode.item(0);data=(item)?((item.text)?item.text:(item.textContent)?item.textContent:null):null;if(!data){var datapieces=[];for(var j=0,len=item.childNodes.length;j<len;j++){if(item.childNodes[j].nodeValue){datapieces[datapieces.length]=item.childNodes[j].nodeValue;}}if(datapieces.length>0){data=datapieces.join("");}}}}}if(data===null){data="";}if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}}catch(e){}return oResult;},parseXMLData:function(oRequest,oFullResponse){var bError=false,schema=this.responseSchema,oParsedResponse={meta:{}},xmlList=null,metaNode=schema.metaNode,metaLocators=schema.metaFields||{},i,k,loc,v;try{if(this.useXPath){for(k in metaLocators){oParsedResponse.meta[k]=YAHOO.util.DataSource._getLocationValue(metaLocators[k],oFullResponse);}}else{metaNode=metaNode?oFullResponse.getElementsByTagName(metaNode)[0]:oFullResponse;if(metaNode){for(k in metaLocators){if(lang.hasOwnProperty(metaLocators,k)){loc=metaLocators[k];v=metaNode.getElementsByTagName(loc)[0];if(v){v=v.firstChild.nodeValue;}else{v=metaNode.attributes.getNamedItem(loc);if(v){v=v.value;}}if(lang.isValue(v)){oParsedResponse.meta[k]=v;}}}}}xmlList=(schema.resultNode)?oFullResponse.getElementsByTagName(schema.resultNode):null;}catch(e){}if(!xmlList||!lang.isArray(schema.fields)){bError=true;}else{oParsedResponse.results=[];for(i=xmlList.length-1;i>=0;--i){var oResult=this.parseXMLResult(xmlList.item(i));oParsedResponse.results[i]=oResult;}}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;},parseJSONData:function(oRequest,oFullResponse){var oParsedResponse={results:[],meta:{}};if(lang.isObject(oFullResponse)&&this.responseSchema.resultsList){var schema=this.responseSchema,fields=schema.fields,resultsList=oFullResponse,results=[],metaFields=schema.metaFields||{},fieldParsers=[],fieldPaths=[],simpleFields=[],bError=false,i,len,j,v,key,parser,path;var buildPath=function(needle){var path=null,keys=[],i=0;if(needle){needle=needle.replace(/\[(['"])(.*?)\1\]/g,function(x,$1,$2){keys[i]=$2;return".@"+(i++);}).replace(/\[(\d+)\]/g,function(x,$1){keys[i]=parseInt($1,10)|0;return".@"+(i++);}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(needle)){path=needle.split(".");for(i=path.length-1;i>=0;--i){if(path[i].charAt(0)==="@"){path[i]=keys[parseInt(path[i].substr(1),10)];}}}else{}}return path;};var walkPath=function(path,origin){var v=origin,i=0,len=path.length;for(;i<len&&v;++i){v=v[path[i]];}return v;};path=buildPath(schema.resultsList);if(path){resultsList=walkPath(path,oFullResponse);if(resultsList===undefined){bError=true;}}else{bError=true;}if(!resultsList){resultsList=[];}if(!lang.isArray(resultsList)){resultsList=[resultsList];}if(!bError){if(schema.fields){var field;for(i=0,len=fields.length;i<len;i++){field=fields[i];key=field.key||field;parser=((typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""])||field.converter;path=buildPath(key);if(parser){fieldParsers[fieldParsers.length]={key:key,parser:parser};}if(path){if(path.length>1){fieldPaths[fieldPaths.length]={key:key,path:path};}else{simpleFields[simpleFields.length]={key:key,path:path[0]};}}else{}}for(i=resultsList.length-1;i>=0;--i){var r=resultsList[i],rec={};if(r){for(j=simpleFields.length-1;j>=0;--j){rec[simpleFields[j].key]=(r[simpleFields[j].path]!==undefined)?r[simpleFields[j].path]:r[j];}for(j=fieldPaths.length-1;j>=0;--j){rec[fieldPaths[j].key]=walkPath(fieldPaths[j].path,r);}for(j=fieldParsers.length-1;j>=0;--j){var p=fieldParsers[j].key;rec[p]=fieldParsers[j].parser.call(this,rec[p]);if(rec[p]===undefined){rec[p]=null;}}}results[i]=rec;}}else{results=resultsList;}for(key in metaFields){if(lang.hasOwnProperty(metaFields,key)){path=buildPath(metaFields[key]);
114 if(path){v=walkPath(path,oFullResponse);oParsedResponse.meta[key]=v;}}}}else{oParsedResponse.error=true;}oParsedResponse.results=results;}else{oParsedResponse.error=true;}return oParsedResponse;},parseHTMLTableData:function(oRequest,oFullResponse){var bError=false;var elTable=oFullResponse;var fields=this.responseSchema.fields;var oParsedResponse={results:[]};if(lang.isArray(fields)){for(var i=0;i<elTable.tBodies.length;i++){var elTbody=elTable.tBodies[i];for(var j=elTbody.rows.length-1;j>-1;j--){var elRow=elTbody.rows[j];var oResult={};for(var k=fields.length-1;k>-1;k--){var field=fields[k];var key=(lang.isValue(field.key))?field.key:field;var data=elRow.cells[k].innerHTML;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}oParsedResponse.results[j]=oResult;}}}else{bError=true;}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;}};lang.augmentProto(DS,util.EventProvider);util.LocalDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_LOCAL;if(oLiveData){if(YAHOO.lang.isArray(oLiveData)){this.responseType=DS.TYPE_JSARRAY;}else{if(oLiveData.nodeType&&oLiveData.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oLiveData.nodeName&&(oLiveData.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;oLiveData=oLiveData.cloneNode(true);}else{if(YAHOO.lang.isString(oLiveData)){this.responseType=DS.TYPE_TEXT;}else{if(YAHOO.lang.isObject(oLiveData)){this.responseType=DS.TYPE_JSON;}}}}}}else{oLiveData=[];this.responseType=DS.TYPE_JSARRAY;}util.LocalDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.LocalDataSource,DS);lang.augmentObject(util.LocalDataSource,DS);util.FunctionDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_JSFUNCTION;oLiveData=oLiveData||function(){};util.FunctionDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.FunctionDataSource,DS,{scope:null,makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=(this.scope)?this.liveData.call(this.scope,oRequest,this,oCallback):this.liveData(oRequest,oCallback);if(this.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&oRawResponse.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;}});lang.augmentObject(util.FunctionDataSource,DS);util.ScriptNodeDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_SCRIPTNODE;oLiveData=oLiveData||"";util.ScriptNodeDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.ScriptNodeDataSource,DS,{getUtility:util.Get,asyncMode:"allowAll",scriptCallbackParam:"callback",generateRequestCallback:function(id){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]";},doBeforeGetScriptNode:function(sUri){return sUri;},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});if(util.ScriptNodeDataSource._nPending===0){util.ScriptNodeDataSource.callbacks=[];util.ScriptNodeDataSource._nId=0;}var id=util.ScriptNodeDataSource._nId;util.ScriptNodeDataSource._nId++;var oSelf=this;util.ScriptNodeDataSource.callbacks[id]=function(oRawResponse){if((oSelf.asyncMode!=="ignoreStaleResponses")||(id===util.ScriptNodeDataSource.callbacks.length-1)){if(oSelf.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){oSelf.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse.nodeType&&oRawResponse.nodeType==9){oSelf.responseType=DS.TYPE_XML;}else{if(oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){oSelf.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){oSelf.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){oSelf.responseType=DS.TYPE_TEXT;}}}}}}oSelf.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);}else{}delete util.ScriptNodeDataSource.callbacks[id];};util.ScriptNodeDataSource._nPending++;var sUri=this.liveData+oRequest+this.generateRequestCallback(id);sUri=this.doBeforeGetScriptNode(sUri);this.getUtility.script(sUri,{autopurge:true,onsuccess:util.ScriptNodeDataSource._bumpPendingDown,onfail:util.ScriptNodeDataSource._bumpPendingDown});return tId;}});lang.augmentObject(util.ScriptNodeDataSource,DS);lang.augmentObject(util.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});util.XHRDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_XHR;this.connMgr=this.connMgr||util.Connect;oLiveData=oLiveData||"";util.XHRDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.XHRDataSource,DS,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(oRequest,oCallback,oCaller){var oRawResponse=null;var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oSelf=this;var oConnMgr=this.connMgr;var oQueue=this._oQueue;var _xhrSuccess=function(oResponse){if(oResponse&&(this.connXhrMode=="ignoreStaleResponses")&&(oResponse.tId!=oQueue.conn.tId)){return null;}else{if(!oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:null,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);return null;
115 }else{if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oResponse.getResponseHeader)?oResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}}this.handleResponse(oRequest,oResponse,oCallback,oCaller,tId);}}};var _xhrFailure=function(oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATAINVALID});if(lang.isString(this.liveData)&&lang.isString(oRequest)&&(this.liveData.lastIndexOf("?")!==this.liveData.length-1)&&(oRequest.indexOf("?")!==0)){}oResponse=oResponse||{};oResponse.error=true;DS.issueCallback(oCallback,[oRequest,oResponse],true,oCaller);return null;};var _xhrCallback={success:_xhrSuccess,failure:_xhrFailure,scope:this};if(lang.isNumber(this.connTimeout)){_xhrCallback.timeout=this.connTimeout;}if(this.connXhrMode=="cancelStaleRequests"){if(oQueue.conn){if(oConnMgr.abort){oConnMgr.abort(oQueue.conn);oQueue.conn=null;}else{}}}if(oConnMgr&&oConnMgr.asyncRequest){var sLiveData=this.liveData;var isPost=this.connMethodPost;var sMethod=(isPost)?"POST":"GET";var sUri=(isPost||!lang.isValue(oRequest))?sLiveData:sLiveData+oRequest;var sRequest=(isPost)?oRequest:null;if(this.connXhrMode!="queueRequests"){oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}else{if(oQueue.conn){var allRequests=oQueue.requests;allRequests.push({request:oRequest,callback:_xhrCallback});if(!oQueue.interval){oQueue.interval=setInterval(function(){if(oConnMgr.isCallInProgress(oQueue.conn)){return;}else{if(allRequests.length>0){sUri=(isPost||!lang.isValue(allRequests[0].request))?sLiveData:sLiveData+allRequests[0].request;sRequest=(isPost)?allRequests[0].request:null;oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,allRequests[0].callback,sRequest);allRequests.shift();}else{clearInterval(oQueue.interval);oQueue.interval=null;}}},50);}}else{oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}}}else{DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);}return tId;}});lang.augmentObject(util.XHRDataSource,DS);util.DataSource=function(oLiveData,oConfigs){oConfigs=oConfigs||{};var dataType=oConfigs.dataType;if(dataType){if(dataType==DS.TYPE_LOCAL){return new util.LocalDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_XHR){return new util.XHRDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_SCRIPTNODE){return new util.ScriptNodeDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_JSFUNCTION){return new util.FunctionDataSource(oLiveData,oConfigs);}}}}}if(YAHOO.lang.isString(oLiveData)){return new util.XHRDataSource(oLiveData,oConfigs);}else{if(YAHOO.lang.isFunction(oLiveData)){return new util.FunctionDataSource(oLiveData,oConfigs);}else{return new util.LocalDataSource(oLiveData,oConfigs);}}};lang.augmentObject(util.DataSource,DS);})();YAHOO.util.Number={format:function(e,k){if(e===""||e===null||!isFinite(e)){return"";}e=+e;k=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,(k||{}));var j=e+"",l=Math.abs(e),b=k.decimalPlaces||0,r=k.thousandsSeparator,f=k.negativeFormat||("-"+k.format),q,p,g,h;if(f.indexOf("#")>-1){f=f.replace(/#/,k.format);}if(b<0){q=l-(l%1)+"";g=q.length+b;if(g>0){q=Number("."+q).toFixed(g).slice(2)+new Array(q.length-g+1).join("0");}else{q="0";}}else{var a=l+"";if(b>0||a.indexOf(".")>0){var d=Math.pow(10,b);q=Math.round(l*d)/d+"";var c=q.indexOf("."),m,o;if(c<0){m=b;o=(Math.pow(10,m)+"").substring(1);if(b>0){q=q+"."+o;}}else{m=b-(q.length-c-1);o=(Math.pow(10,m)+"").substring(1);q=q+o;}}else{q=l.toFixed(b)+"";}}p=q.split(/\D/);if(l>=1000){g=p[0].length%3||3;p[0]=p[0].slice(0,g)+p[0].slice(g).replace(/(\d{3})/g,r+"$1");}return YAHOO.util.Number.format._applyFormat((e<0?f:k.format),p.join(k.decimalSeparator),k);}};YAHOO.util.Number.format.defaults={format:"{prefix}{number}{suffix}",negativeFormat:null,decimalSeparator:".",decimalPlaces:null,thousandsSeparator:""};YAHOO.util.Number.format._applyFormat=function(a,b,c){return a.replace(/\{(\w+)\}/g,function(d,e){return e==="number"?b:e in c?c[e]:"";});};(function(){var a=function(c,e,d){if(typeof d==="undefined"){d=10;}for(;parseInt(c,10)<d&&d>1;d/=10){c=e.toString()+c;}return c.toString();};var b={formats:{a:function(e,c){return c.a[e.getDay()];},A:function(e,c){return c.A[e.getDay()];},b:function(e,c){return c.b[e.getMonth()];},B:function(e,c){return c.B[e.getMonth()];},C:function(c){return a(parseInt(c.getFullYear()/100,10),0);},d:["getDate","0"],e:["getDate"," "],g:function(c){return a(parseInt(b.formats.G(c)%100,10),0);},G:function(f){var g=f.getFullYear();var e=parseInt(b.formats.V(f),10);var c=parseInt(b.formats.W(f),10);if(c>e){g++;}else{if(c===0&&e>=52){g--;}}return g;},H:["getHours","0"],I:function(e){var c=e.getHours()%12;return a(c===0?12:c,0);},j:function(h){var g=new Date(""+h.getFullYear()+"/1/1 GMT");var e=new Date(""+h.getFullYear()+"/"+(h.getMonth()+1)+"/"+h.getDate()+" GMT");var c=e-g;var f=parseInt(c/60000/60/24,10)+1;return a(f,0,100);},k:["getHours"," "],l:function(e){var c=e.getHours()%12;return a(c===0?12:c," ");},m:function(c){return a(c.getMonth()+1,0);},M:["getMinutes","0"],p:function(e,c){return c.p[e.getHours()>=12?1:0];},P:function(e,c){return c.P[e.getHours()>=12?1:0];},s:function(e,c){return parseInt(e.getTime()/1000,10);},S:["getSeconds","0"],u:function(c){var e=c.getDay();return e===0?7:e;},U:function(g){var c=parseInt(b.formats.j(g),10);var f=6-g.getDay();var e=parseInt((c+f)/7,10);return a(e,0);},V:function(g){var f=parseInt(b.formats.W(g),10);var c=(new Date(""+g.getFullYear()+"/1/1")).getDay();var e=f+(c>4||c<=1?0:1);if(e===53&&(new Date(""+g.getFullYear()+"/12/31")).getDay()<4){e=1;}else{if(e===0){e=b.formats.V(new Date(""+(g.getFullYear()-1)+"/12/31"));}}return a(e,0);},w:"getDay",W:function(g){var c=parseInt(b.formats.j(g),10);var f=7-b.formats.u(g);var e=parseInt((c+f)/7,10);
116 return a(e,0,10);},y:function(c){return a(c.getFullYear()%100,0);},Y:"getFullYear",z:function(f){var e=f.getTimezoneOffset();var c=a(parseInt(Math.abs(e/60),10),0);var g=a(Math.abs(e%60),0);return(e>0?"-":"+")+c+g;},Z:function(c){var e=c.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");if(e.length>4){e=b.formats.z(c);}return e;},"%":function(c){return"%";}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(g,f,d){f=f||{};if(!(g instanceof Date)){return YAHOO.lang.isValue(g)?g:"";}var h=f.format||"%m/%d/%Y";if(h==="YYYY/MM/DD"){h="%Y/%m/%d";}else{if(h==="DD/MM/YYYY"){h="%d/%m/%Y";}else{if(h==="MM/DD/YYYY"){h="%m/%d/%Y";}}}d=d||"en";if(!(d in YAHOO.util.DateLocale)){if(d.replace(/-[a-zA-Z]+$/,"") in YAHOO.util.DateLocale){d=d.replace(/-[a-zA-Z]+$/,"");}else{d="en";}}var j=YAHOO.util.DateLocale[d];var c=function(l,k){var m=b.aggregates[k];return(m==="locale"?j[k]:m);};var e=function(l,k){var m=b.formats[k];if(typeof m==="string"){return g[m]();}else{if(typeof m==="function"){return m.call(g,g,j);}else{if(typeof m==="object"&&typeof m[0]==="string"){return a(g[m[0]](),m[1]);}else{return k;}}}};while(h.match(/%[cDFhnrRtTxX]/)){h=h.replace(/%([cDFhnrRtTxX])/g,c);}var i=h.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,e);c=e=undefined;return i;}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=b;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale["en"]=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"]);})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.9.0",build:"2800"});/*
117 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
118 Code licensed under the BSD License:
119 http://developer.yahoo.com/yui/license.html
120 version: 2.9.0
121 */
122 YAHOO.util.Chain=function(){this.q=[].slice.call(arguments);this.createEvent("end");};YAHOO.util.Chain.prototype={id:0,run:function(){var g=this.q[0],d;if(!g){this.fireEvent("end");return this;}else{if(this.id){return this;}}d=g.method||g;if(typeof d==="function"){var f=g.scope||{},b=g.argument||[],a=g.timeout||0,e=this;if(!(b instanceof Array)){b=[b];}if(a<0){this.id=a;if(g.until){for(;!g.until();){d.apply(f,b);}}else{if(g.iterations){for(;g.iterations-->0;){d.apply(f,b);}}else{d.apply(f,b);}}this.q.shift();this.id=0;return this.run();}else{if(g.until){if(g.until()){this.q.shift();return this.run();}}else{if(!g.iterations||!--g.iterations){this.q.shift();}}this.id=setTimeout(function(){d.apply(f,b);if(e.id){e.id=0;e.run();}},a);}}return this;},add:function(a){this.q.push(a);return this;},pause:function(){if(this.id>0){clearTimeout(this.id);}this.id=0;return this;},stop:function(){this.pause();this.q=[];return this;}};YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);(function(){var a=YAHOO.util.Event,c=YAHOO.lang,b=[],d=function(h,e,f){var g;if(!h||h===f){g=false;}else{g=YAHOO.util.Selector.test(h,e)?h:d(h.parentNode,e,f);}return g;};c.augmentObject(a,{_createDelegate:function(f,e,g,h){return function(i){var j=this,n=a.getTarget(i),l=e,p=(j.nodeType===9),q,k,o,m;if(c.isFunction(e)){q=e(n);}else{if(c.isString(e)){if(!p){o=j.id;if(!o){o=a.generateId(j);}m=("#"+o+" ");l=(m+e).replace(/,/gi,(","+m));}if(YAHOO.util.Selector.test(n,l)){q=n;}else{if(YAHOO.util.Selector.test(n,((l.replace(/,/gi," *,"))+" *"))){q=d(n,l,j);}}}}if(q){k=q;if(h){if(h===true){k=g;}else{k=h;}}return f.call(k,i,q,j,g);}};},delegate:function(f,j,l,g,h,i){var e=j,k,m;if(c.isString(g)&&!YAHOO.util.Selector){return false;}if(j=="mouseenter"||j=="mouseleave"){if(!a._createMouseDelegate){return false;}e=a._getType(j);k=a._createMouseDelegate(l,h,i);m=a._createDelegate(function(p,o,n){return k.call(o,p,n);},g,h,i);}else{m=a._createDelegate(l,g,h,i);}b.push([f,e,l,m]);return a.on(f,e,m);},removeDelegate:function(f,j,i){var k=j,h=false,g,e;if(j=="mouseenter"||j=="mouseleave"){k=a._getType(j);}g=a._getCacheIndex(b,f,k,i);if(g>=0){e=b[g];}if(f&&e){h=a.removeListener(e[0],e[1],e[3]);if(h){delete b[g][2];delete b[g][3];b.splice(g,1);}}return h;}});}());(function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j];}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1);}}return k;};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j;}else{o=k;}}n=[q,j];if(m){n.splice(1,0,p,m);}return i.apply(o,n);}};},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i);}else{j=e.apply(b,arguments);}return j;},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments);}else{i=f.apply(b,arguments);}return i;},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j);}}}}else{n=c.apply(b,arguments);}return(n&&n.length)?n:null;}},true);b.on=b.addListener;}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});var Y=YAHOO,Y_DOM=YAHOO.util.Dom,EMPTY_ARRAY=[],Y_UA=Y.env.ua,Y_Lang=Y.lang,Y_DOC=document,Y_DOCUMENT_ELEMENT=Y_DOC.documentElement,Y_DOM_inDoc=Y_DOM.inDocument,Y_mix=Y_Lang.augmentObject,Y_guid=Y_DOM.generateId,Y_getDoc=function(a){var b=Y_DOC;if(a){b=(a.nodeType===9)?a:a.ownerDocument||a.document||Y_DOC;}return b;},Y_Array=function(g,d){var c,b,h=d||0;try{return Array.prototype.slice.call(g,h);}catch(f){b=[];c=g.length;for(;h<c;h++){b.push(g[h]);}return b;}},Y_DOM_allById=function(f,a){a=a||Y_DOC;var b=[],c=[],d,e;if(a.querySelectorAll){c=a.querySelectorAll('[id="'+f+'"]');}else{if(a.all){b=a.all(f);if(b){if(b.nodeName){if(b.id===f){c.push(b);b=EMPTY_ARRAY;}else{b=[b];}}if(b.length){for(d=0;e=b[d++];){if(e.id===f||(e.attributes&&e.attributes.id&&e.attributes.id.value===f)){c.push(e);}}}}}else{c=[Y_getDoc(a).getElementById(f)];}}return c;};var COMPARE_DOCUMENT_POSITION="compareDocumentPosition",OWNER_DOCUMENT="ownerDocument",Selector={_foundCache:[],useNative:true,_compare:("sourceIndex" in Y_DOCUMENT_ELEMENT)?function(f,e){var d=f.sourceIndex,c=e.sourceIndex;if(d===c){return 0;}else{if(d>c){return 1;}}return -1;}:(Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION]?function(b,a){if(b[COMPARE_DOCUMENT_POSITION](a)&4){return -1;}else{return 1;}}:function(e,d){var c,a,b;if(e&&d){c=e[OWNER_DOCUMENT].createRange();c.setStart(e,0);a=d[OWNER_DOCUMENT].createRange();a.setStart(d,0);b=c.compareBoundaryPoints(1,a);}return b;}),_sort:function(a){if(a){a=Y_Array(a,0,true);if(a.sort){a.sort(Selector._compare);}}return a;},_deDupe:function(a){var b=[],c,d;for(c=0;(d=a[c++]);){if(!d._found){b[b.length]=d;d._found=true;}}for(c=0;(d=b[c++]);){d._found=null;d.removeAttribute("_found");}return b;},query:function(b,j,k,a){if(typeof j=="string"){j=Y_DOM.get(j);if(!j){return(k)?null:[];}}else{j=j||Y_DOC;}var f=[],c=(Selector.useNative&&Y_DOC.querySelector&&!a),e=[[b,j]],g,l,d,h=(c)?Selector._nativeQuery:Selector._bruteQuery;if(b&&h){if(!a&&(!c||j.tagName)){e=Selector._splitQueries(b,j);}for(d=0;(g=e[d++]);){l=h(g[0],g[1],k);if(!k){l=Y_Array(l,0,true);}if(l){f=f.concat(l);}}if(e.length>1){f=Selector._sort(Selector._deDupe(f));}}Y.log("query: "+b+" returning: "+f.length,"info","Selector");return(k)?(f[0]||null):f;},_splitQueries:function(c,f){var b=c.split(","),d=[],g="",e,a;if(f){if(f.tagName){f.id=f.id||Y_guid();g='[id="'+f.id+'"] ';}for(e=0,a=b.length;e<a;++e){c=g+b[e];d.push([c,f]);}}return d;},_nativeQuery:function(a,b,c){if(Y_UA.webkit&&a.indexOf(":checked")>-1&&(Selector.pseudos&&Selector.pseudos.checked)){return Selector.query(a,b,c,true);
123 }try{return b["querySelector"+(c?"":"All")](a);}catch(d){return Selector.query(a,b,c,true);}},filter:function(b,a){var c=[],d,e;if(b&&a){for(d=0;(e=b[d++]);){if(Selector.test(e,a)){c[c.length]=e;}}}else{Y.log("invalid filter input (nodes: "+b+", selector: "+a+")","warn","Selector");}return c;},test:function(c,d,k){var g=false,b=d.split(","),a=false,l,o,h,n,f,e,m;if(c&&c.tagName){if(!k&&!Y_DOM_inDoc(c)){l=c.parentNode;if(l){k=l;}else{n=c[OWNER_DOCUMENT].createDocumentFragment();n.appendChild(c);k=n;a=true;}}k=k||c[OWNER_DOCUMENT];if(!c.id){c.id=Y_guid();}for(f=0;(m=b[f++]);){m+='[id="'+c.id+'"]';h=Selector.query(m,k);for(e=0;o=h[e++];){if(o===c){g=true;break;}}if(g){break;}}if(a){n.removeChild(c);}}return g;}};YAHOO.util.Selector=Selector;var PARENT_NODE="parentNode",TAG_NAME="tagName",ATTRIBUTES="attributes",COMBINATOR="combinator",PSEUDOS="pseudos",SelectorCSS2={_reRegExpTokens:/([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,SORT_RESULTS:true,_children:function(e,a){var b=e.children,d,c=[],f,g;if(e.children&&a&&e.children.tags){c=e.children.tags(a);}else{if((!b&&e[TAG_NAME])||(b&&a)){f=b||e.childNodes;b=[];for(d=0;(g=f[d++]);){if(g.tagName){if(!a||a===g.tagName){b.push(g);}}}}}return b||[];},_re:{attr:/(\[[^\]]*\])/g,esc:/\\[:\[\]\(\)#\.\'\>+~"]/gi,pseudos:/(\([^\)]*\))/g},shorthand:{"\\#(-?[_a-z]+[-\\w\\uE000]*)":"[id=$1]","\\.(-?[_a-z]+[-\\w\\uE000]*)":"[className~=$1]"},operators:{"":function(b,a){return !!b.getAttribute(a);},"~=":"(?:^|\\s+){val}(?:\\s+|$)","|=":"^{val}(?:-|$)"},pseudos:{"first-child":function(a){return Selector._children(a[PARENT_NODE])[0]===a;}},_bruteQuery:function(f,j,l){var g=[],a=[],i=Selector._tokenize(f),e=i[i.length-1],k=Y_getDoc(j),c,b,h,d;if(e){b=e.id;h=e.className;d=e.tagName||"*";if(j.getElementsByTagName){if(b&&(j.all||(j.nodeType===9||Y_DOM_inDoc(j)))){a=Y_DOM_allById(b,j);}else{if(h){a=j.getElementsByClassName(h);}else{a=j.getElementsByTagName(d);}}}else{c=j.firstChild;while(c){if(c.tagName){a.push(c);}c=c.nextSilbing||c.firstChild;}}if(a.length){g=Selector._filterNodes(a,i,l);}}return g;},_filterNodes:function(l,f,h){var r=0,q,s=f.length,k=s-1,e=[],o=l[0],v=o,t=Selector.getters,d,p,c,g,a,m,b,u;for(r=0;(v=o=l[r++]);){k=s-1;g=null;testLoop:while(v&&v.tagName){c=f[k];b=c.tests;q=b.length;if(q&&!a){while((u=b[--q])){d=u[1];if(t[u[0]]){m=t[u[0]](v,u[0]);}else{m=v[u[0]];if(m===undefined&&v.getAttribute){m=v.getAttribute(u[0]);}}if((d==="="&&m!==u[2])||(typeof d!=="string"&&d.test&&!d.test(m))||(!d.test&&typeof d==="function"&&!d(v,u[0],u[2]))){if((v=v[g])){while(v&&(!v.tagName||(c.tagName&&c.tagName!==v.tagName))){v=v[g];}}continue testLoop;}}}k--;if(!a&&(p=c.combinator)){g=p.axis;v=v[g];while(v&&!v.tagName){v=v[g];}if(p.direct){g=null;}}else{e.push(o);if(h){return e;}break;}}}o=v=null;return e;},combinators:{" ":{axis:"parentNode"},">":{axis:"parentNode",direct:true},"+":{axis:"previousSibling",direct:true}},_parsers:[{name:ATTRIBUTES,re:/^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,fn:function(d,e){var c=d[2]||"",a=Selector.operators,b=(d[3])?d[3].replace(/\\/g,""):"",f;if((d[1]==="id"&&c==="=")||(d[1]==="className"&&Y_DOCUMENT_ELEMENT.getElementsByClassName&&(c==="~="||c==="="))){e.prefilter=d[1];d[3]=b;e[d[1]]=(d[1]==="id")?d[3]:b;}if(c in a){f=a[c];if(typeof f==="string"){d[3]=b.replace(Selector._reRegExpTokens,"\\$1");f=new RegExp(f.replace("{val}",d[3]));}d[2]=f;}if(!e.last||e.prefilter!==d[1]){return d.slice(1);}}},{name:TAG_NAME,re:/^((?:-?[_a-z]+[\w-]*)|\*)/i,fn:function(b,c){var a=b[1].toUpperCase();c.tagName=a;if(a!=="*"&&(!c.last||c.prefilter)){return[TAG_NAME,"=",a];}if(!c.prefilter){c.prefilter="tagName";}}},{name:COMBINATOR,re:/^\s*([>+~]|\s)\s*/,fn:function(a,b){}},{name:PSEUDOS,re:/^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,fn:function(a,b){var c=Selector[PSEUDOS][a[1]];if(c){if(a[2]){a[2]=a[2].replace(/\\/g,"");}return[a[2],c];}else{return false;}}}],_getToken:function(a){return{tagName:null,id:null,className:null,attributes:{},combinator:null,tests:[]};},_tokenize:function(c){c=c||"";c=Selector._replaceShorthand(Y_Lang.trim(c));var b=Selector._getToken(),h=c,g=[],j=false,e,f,d,a;outer:do{j=false;for(d=0;(a=Selector._parsers[d++]);){if((e=a.re.exec(c))){if(a.name!==COMBINATOR){b.selector=c;}c=c.replace(e[0],"");if(!c.length){b.last=true;}if(Selector._attrFilters[e[1]]){e[1]=Selector._attrFilters[e[1]];}f=a.fn(e,b);if(f===false){j=false;break outer;}else{if(f){b.tests.push(f);}}if(!c.length||a.name===COMBINATOR){g.push(b);b=Selector._getToken(b);if(a.name===COMBINATOR){b.combinator=Selector.combinators[e[1]];}}j=true;}}}while(j&&c.length);if(!j||c.length){Y.log("query: "+h+" contains unsupported token in: "+c,"warn","Selector");g=[];}return g;},_replaceShorthand:function(b){var d=Selector.shorthand,c=b.match(Selector._re.esc),e,h,g,f,a;if(c){b=b.replace(Selector._re.esc,"\uE000");}e=b.match(Selector._re.attr);h=b.match(Selector._re.pseudos);if(e){b=b.replace(Selector._re.attr,"\uE001");}if(h){b=b.replace(Selector._re.pseudos,"\uE002");}for(g in d){if(d.hasOwnProperty(g)){b=b.replace(new RegExp(g,"gi"),d[g]);}}if(e){for(f=0,a=e.length;f<a;++f){b=b.replace(/\uE001/,e[f]);}}if(h){for(f=0,a=h.length;f<a;++f){b=b.replace(/\uE002/,h[f]);}}b=b.replace(/\[/g,"\uE003");b=b.replace(/\]/g,"\uE004");b=b.replace(/\(/g,"\uE005");b=b.replace(/\)/g,"\uE006");if(c){for(f=0,a=c.length;f<a;++f){b=b.replace("\uE000",c[f]);}}return b;},_attrFilters:{"class":"className","for":"htmlFor"},getters:{href:function(b,a){return Y_DOM.getAttribute(b,a);}}};Y_mix(Selector,SelectorCSS2,true);Selector.getters.src=Selector.getters.rel=Selector.getters.href;if(Selector.useNative&&Y_DOC.querySelector){Selector.shorthand["\\.([^\\s\\\\(\\[:]*)"]="[class~=$1]";}Selector._reNth=/^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;Selector._getNth=function(d,o,q,h){Selector._reNth.test(o);var m=parseInt(RegExp.$1,10),c=RegExp.$2,j=RegExp.$3,k=parseInt(RegExp.$4,10)||0,p=[],l=Selector._children(d.parentNode,q),f;if(j){m=2;f="+";c="n";k=(j==="odd")?1:0;}else{if(isNaN(m)){m=(c)?1:0;
124 }}if(m===0){if(h){k=l.length-k+1;}if(l[k-1]===d){return true;}else{return false;}}else{if(m<0){h=!!h;m=Math.abs(m);}}if(!h){for(var e=k-1,g=l.length;e<g;e+=m){if(e>=0&&l[e]===d){return true;}}}else{for(var e=l.length-k,g=l.length;e>=0;e-=m){if(e<g&&l[e]===d){return true;}}}return false;};Y_mix(Selector.pseudos,{"root":function(a){return a===a.ownerDocument.documentElement;},"nth-child":function(a,b){return Selector._getNth(a,b);},"nth-last-child":function(a,b){return Selector._getNth(a,b,null,true);},"nth-of-type":function(a,b){return Selector._getNth(a,b,a.tagName);},"nth-last-of-type":function(a,b){return Selector._getNth(a,b,a.tagName,true);},"last-child":function(b){var a=Selector._children(b.parentNode);return a[a.length-1]===b;},"first-of-type":function(a){return Selector._children(a.parentNode,a.tagName)[0]===a;},"last-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a[a.length-1]===b;},"only-child":function(b){var a=Selector._children(b.parentNode);return a.length===1&&a[0]===b;},"only-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a.length===1&&a[0]===b;},"empty":function(a){return a.childNodes.length===0;},"not":function(a,b){return !Selector.test(a,b);},"contains":function(a,b){var c=a.innerText||a.textContent||"";return c.indexOf(b)>-1;},"checked":function(a){return(a.checked===true||a.selected===true);},enabled:function(a){return(a.disabled!==undefined&&!a.disabled);},disabled:function(a){return(a.disabled);}});Y_mix(Selector.operators,{"^=":"^{val}","!=":function(b,a,c){return b[a]!==c;},"$=":"{val}$","*=":"{val}"});Selector.combinators["~"]={axis:"previousSibling"};YAHOO.register("selector",YAHOO.util.Selector,{version:"2.9.0",build:"2800"});var Dom=YAHOO.util.Dom;YAHOO.widget.ColumnSet=function(a){this._sId=Dom.generateId(null,"yui-cs");a=YAHOO.widget.DataTable._cloneObject(a);this._init(a);YAHOO.widget.ColumnSet._nCount++;};YAHOO.widget.ColumnSet._nCount=0;YAHOO.widget.ColumnSet.prototype={_sId:null,_aDefinitions:null,tree:null,flat:null,keys:null,headers:null,_init:function(j){var k=[];var a=[];var g=[];var e=[];var c=-1;var b=function(m,s){c++;if(!k[c]){k[c]=[];}for(var o=0;o<m.length;o++){var i=m[o];var q=new YAHOO.widget.Column(i);i.yuiColumnId=q._sId;a.push(q);if(s){q._oParent=s;}if(YAHOO.lang.isArray(i.children)){q.children=i.children;var r=0;var p=function(v){var w=v.children;for(var u=0;u<w.length;u++){if(YAHOO.lang.isArray(w[u].children)){p(w[u]);}else{r++;}}};p(i);q._nColspan=r;var t=i.children;for(var n=0;n<t.length;n++){var l=t[n];if(q.className&&(l.className===undefined)){l.className=q.className;}if(q.editor&&(l.editor===undefined)){l.editor=q.editor;}if(q.editorOptions&&(l.editorOptions===undefined)){l.editorOptions=q.editorOptions;}if(q.formatter&&(l.formatter===undefined)){l.formatter=q.formatter;}if(q.resizeable&&(l.resizeable===undefined)){l.resizeable=q.resizeable;}if(q.sortable&&(l.sortable===undefined)){l.sortable=q.sortable;}if(q.hidden){l.hidden=true;}if(q.width&&(l.width===undefined)){l.width=q.width;}if(q.minWidth&&(l.minWidth===undefined)){l.minWidth=q.minWidth;}if(q.maxAutoWidth&&(l.maxAutoWidth===undefined)){l.maxAutoWidth=q.maxAutoWidth;}if(q.type&&(l.type===undefined)){l.type=q.type;}if(q.type&&!q.formatter){q.formatter=q.type;}if(q.text&&!YAHOO.lang.isValue(q.label)){q.label=q.text;}if(q.parser){}if(q.sortOptions&&((q.sortOptions.ascFunction)||(q.sortOptions.descFunction))){}}if(!k[c+1]){k[c+1]=[];}b(t,q);}else{q._nKeyIndex=g.length;q._nColspan=1;g.push(q);}k[c].push(q);}c--;};if(YAHOO.lang.isArray(j)){b(j);this._aDefinitions=j;}else{return null;}var f;var d=function(l){var n=1;var q;var o;var r=function(t,p){p=p||1;for(var u=0;u<t.length;u++){var m=t[u];if(YAHOO.lang.isArray(m.children)){p++;r(m.children,p);p--;}else{if(p>n){n=p;}}}};for(var i=0;i<l.length;i++){q=l[i];r(q);for(var s=0;s<q.length;s++){o=q[s];if(!YAHOO.lang.isArray(o.children)){o._nRowspan=n;}else{o._nRowspan=1;}}n=1;}};d(k);for(f=0;f<k[0].length;f++){k[0][f]._nTreeIndex=f;}var h=function(l,m){e[l].push(m.getSanitizedKey());if(m._oParent){h(l,m._oParent);}};for(f=0;f<g.length;f++){e[f]=[];h(f,g[f]);e[f]=e[f].reverse();}this.tree=k;this.flat=a;this.keys=g;this.headers=e;},getId:function(){return this._sId;},toString:function(){return"ColumnSet instance "+this._sId;},getDefinitions:function(){var a=this._aDefinitions;var b=function(e,g){for(var d=0;d<e.length;d++){var f=e[d];var i=g.getColumnById(f.yuiColumnId);if(i){var h=i.getDefinition();for(var c in h){if(YAHOO.lang.hasOwnProperty(h,c)){f[c]=h[c];}}}if(YAHOO.lang.isArray(f.children)){b(f.children,g);}}};b(a,this);this._aDefinitions=a;return a;},getColumnById:function(c){if(YAHOO.lang.isString(c)){var a=this.flat;for(var b=a.length-1;b>-1;b--){if(a[b]._sId===c){return a[b];}}}return null;},getColumn:function(c){if(YAHOO.lang.isNumber(c)&&this.keys[c]){return this.keys[c];}else{if(YAHOO.lang.isString(c)){var a=this.flat;var d=[];for(var b=0;b<a.length;b++){if(a[b].key===c){d.push(a[b]);}}if(d.length===1){return d[0];}else{if(d.length>1){return d;}}}}return null;},getDescendants:function(d){var b=this;var c=[];var a;var e=function(f){c.push(f);if(f.children){for(a=0;a<f.children.length;a++){e(b.getColumn(f.children[a].key));}}};e(d);return c;}};YAHOO.widget.Column=function(b){this._sId=Dom.generateId(null,"yui-col");if(b&&YAHOO.lang.isObject(b)){for(var a in b){if(a){this[a]=b[a];}}}if(!YAHOO.lang.isValue(this.key)){this.key=Dom.generateId(null,"yui-dt-col");}if(!YAHOO.lang.isValue(this.field)){this.field=this.key;}YAHOO.widget.Column._nCount++;if(this.width&&!YAHOO.lang.isNumber(this.width)){this.width=null;}if(this.editor&&YAHOO.lang.isString(this.editor)){this.editor=new YAHOO.widget.CellEditor(this.editor,this.editorOptions);}};YAHOO.lang.augmentObject(YAHOO.widget.Column,{_nCount:0,formatCheckbox:function(b,a,c,d){YAHOO.widget.DataTable.formatCheckbox(b,a,c,d);},formatCurrency:function(b,a,c,d){YAHOO.widget.DataTable.formatCurrency(b,a,c,d);},formatDate:function(b,a,c,d){YAHOO.widget.DataTable.formatDate(b,a,c,d);
125 },formatEmail:function(b,a,c,d){YAHOO.widget.DataTable.formatEmail(b,a,c,d);},formatLink:function(b,a,c,d){YAHOO.widget.DataTable.formatLink(b,a,c,d);},formatNumber:function(b,a,c,d){YAHOO.widget.DataTable.formatNumber(b,a,c,d);},formatSelect:function(b,a,c,d){YAHOO.widget.DataTable.formatDropdown(b,a,c,d);}});YAHOO.widget.Column.prototype={_sId:null,_nKeyIndex:null,_nTreeIndex:null,_nColspan:1,_nRowspan:1,_oParent:null,_elTh:null,_elThLiner:null,_elThLabel:null,_elResizer:null,_nWidth:null,_dd:null,_ddResizer:null,key:null,field:null,label:null,abbr:null,children:null,width:null,minWidth:null,maxAutoWidth:null,hidden:false,selected:false,className:null,formatter:null,currencyOptions:null,dateOptions:null,dropdownOptions:null,editor:null,resizeable:false,sortable:false,sortOptions:null,getId:function(){return this._sId;},toString:function(){return"Column instance "+this._sId;},getDefinition:function(){var a={};a.abbr=this.abbr;a.className=this.className;a.editor=this.editor;a.editorOptions=this.editorOptions;a.field=this.field;a.formatter=this.formatter;a.hidden=this.hidden;a.key=this.key;a.label=this.label;a.minWidth=this.minWidth;a.maxAutoWidth=this.maxAutoWidth;a.resizeable=this.resizeable;a.selected=this.selected;a.sortable=this.sortable;a.sortOptions=this.sortOptions;a.width=this.width;a._calculatedWidth=this._calculatedWidth;return a;},getKey:function(){return this.key;},getField:function(){return this.field;},getSanitizedKey:function(){return this.getKey().replace(/[^\w\-]/g,"");},getKeyIndex:function(){return this._nKeyIndex;},getTreeIndex:function(){return this._nTreeIndex;},getParent:function(){return this._oParent;},getColspan:function(){return this._nColspan;},getColSpan:function(){return this.getColspan();},getRowspan:function(){return this._nRowspan;},getThEl:function(){return this._elTh;},getThLinerEl:function(){return this._elThLiner;},getResizerEl:function(){return this._elResizer;},getColEl:function(){return this.getThEl();},getIndex:function(){return this.getKeyIndex();},format:function(){}};YAHOO.util.Sort={compare:function(d,c,e){if((d===null)||(typeof d=="undefined")){if((c===null)||(typeof c=="undefined")){return 0;}else{return 1;}}else{if((c===null)||(typeof c=="undefined")){return -1;}}if(d.constructor==String){d=d.toLowerCase();}if(c.constructor==String){c=c.toLowerCase();}if(d<c){return(e)?1:-1;}else{if(d>c){return(e)?-1:1;}else{return 0;}}}};YAHOO.widget.ColumnDD=function(d,a,c,b){if(d&&a&&c&&b){this.datatable=d;this.table=d.getTableEl();this.column=a;this.headCell=c;this.pointer=b;this.newIndex=null;this.init(c);this.initFrame();this.invalidHandleTypes={};this.setPadding(10,0,(this.datatable.getTheadEl().offsetHeight+10),0);YAHOO.util.Event.on(window,"resize",function(){this.initConstraints();},this,true);}else{}};if(YAHOO.util.DDProxy){YAHOO.extend(YAHOO.widget.ColumnDD,YAHOO.util.DDProxy,{initConstraints:function(){var g=YAHOO.util.Dom.getRegion(this.table),d=this.getEl(),f=YAHOO.util.Dom.getXY(d),c=parseInt(YAHOO.util.Dom.getStyle(d,"width"),10),a=parseInt(YAHOO.util.Dom.getStyle(d,"height"),10),e=((f[0]-g.left)+15),b=((g.right-f[0]-c)+15);this.setXConstraint(e,b);this.setYConstraint(10,10);},_resizeProxy:function(){YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this,arguments);var a=this.getDragEl(),b=this.getEl();YAHOO.util.Dom.setStyle(this.pointer,"height",(this.table.parentNode.offsetHeight+10)+"px");YAHOO.util.Dom.setStyle(this.pointer,"display","block");var c=YAHOO.util.Dom.getXY(b);YAHOO.util.Dom.setXY(this.pointer,[c[0],(c[1]-5)]);YAHOO.util.Dom.setStyle(a,"height",this.datatable.getContainerEl().offsetHeight+"px");YAHOO.util.Dom.setStyle(a,"width",(parseInt(YAHOO.util.Dom.getStyle(a,"width"),10)+4)+"px");YAHOO.util.Dom.setXY(this.dragEl,c);},onMouseDown:function(){this.initConstraints();this.resetConstraints();},clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)));}},onDragOver:function(h,a){var f=this.datatable.getColumn(a);if(f){var c=f.getTreeIndex();while((c===null)&&f.getParent()){f=f.getParent();c=f.getTreeIndex();}if(c!==null){var b=f.getThEl();var k=c;var d=YAHOO.util.Event.getPageX(h),i=YAHOO.util.Dom.getX(b),j=i+((YAHOO.util.Dom.get(b).offsetWidth)/2),e=this.column.getTreeIndex();if(d<j){YAHOO.util.Dom.setX(this.pointer,i);}else{var g=parseInt(b.offsetWidth,10);YAHOO.util.Dom.setX(this.pointer,(i+g));k++;}if(c>e){k--;}if(k<0){k=0;}else{if(k>this.datatable.getColumnSet().tree[0].length){k=this.datatable.getColumnSet().tree[0].length;}}this.newIndex=k;}}},onDragDrop:function(){this.datatable.reorderColumn(this.column,this.newIndex);},endDrag:function(){this.newIndex=null;YAHOO.util.Dom.setStyle(this.pointer,"display","none");}});}YAHOO.util.ColumnResizer=function(e,c,d,a,b){if(e&&c&&d&&a){this.datatable=e;this.column=c;this.headCell=d;this.headCellLiner=c.getThLinerEl();this.resizerLiner=d.firstChild;this.init(a,a,{dragOnly:true,dragElId:b.id});this.initFrame();this.resetResizerEl();this.setPadding(0,1,0,0);}else{}};if(YAHOO.util.DD){YAHOO.extend(YAHOO.util.ColumnResizer,YAHOO.util.DDProxy,{resetResizerEl:function(){var a=YAHOO.util.Dom.get(this.handleElId).style;a.left="auto";a.right=0;a.top="auto";a.bottom=0;a.height=this.headCell.offsetHeight+"px";},onMouseUp:function(h){var f=this.datatable.getColumnSet().keys,b;for(var c=0,a=f.length;c<a;c++){b=f[c];if(b._ddResizer){b._ddResizer.resetResizerEl();}}this.resetResizerEl();var d=this.headCellLiner;var g=d.offsetWidth-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingLeft"),10)|0)-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingRight"),10)|0);this.datatable.fireEvent("columnResizeEvent",{column:this.column,target:this.headCell,width:g});},onMouseDown:function(a){this.startWidth=this.headCellLiner.offsetWidth;this.startX=YAHOO.util.Event.getXY(a)[0];this.nLinerPadding=(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0)+(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
126 },clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)));}},startDrag:function(){var e=this.datatable.getColumnSet().keys,d=this.column.getKeyIndex(),b;for(var c=0,a=e.length;c<a;c++){b=e[c];if(b._ddResizer){YAHOO.util.Dom.get(b._ddResizer.handleElId).style.height="1em";}}},onDrag:function(c){var d=YAHOO.util.Event.getXY(c)[0];if(d>YAHOO.util.Dom.getX(this.headCellLiner)){var a=d-this.startX;var b=this.startWidth+a-this.nLinerPadding;if(b>0){this.datatable.setColumnWidth(this.column,b);}}}});}(function(){var g=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,c=a.Dom,f=a.Event,d=e.DataTable;YAHOO.widget.RecordSet=function(h){this._init(h);};var b=e.RecordSet;b._nCount=0;b.prototype={_sId:null,_init:function(h){this._sId=c.generateId(null,"yui-rs");e.RecordSet._nCount++;this._records=[];this._initEvents();if(h){if(g.isArray(h)){this.addRecords(h);}else{if(g.isObject(h)){this.addRecord(h);}}}},_initEvents:function(){this.createEvent("recordAddEvent");this.createEvent("recordsAddEvent");this.createEvent("recordSetEvent");this.createEvent("recordsSetEvent");this.createEvent("recordUpdateEvent");this.createEvent("recordDeleteEvent");this.createEvent("recordsDeleteEvent");this.createEvent("resetEvent");this.createEvent("recordValueUpdateEvent");},_addRecord:function(j,h){var i=new YAHOO.widget.Record(j);if(YAHOO.lang.isNumber(h)&&(h>-1)){this._records.splice(h,0,i);}else{this._records[this._records.length]=i;}return i;},_setRecord:function(i,h){if(!g.isNumber(h)||h<0){h=this._records.length;}return(this._records[h]=new e.Record(i));},_deleteRecord:function(i,h){if(!g.isNumber(h)||(h<0)){h=1;}this._records.splice(i,h);},getId:function(){return this._sId;},toString:function(){return"RecordSet instance "+this._sId;},getLength:function(){return this._records.length;},getRecord:function(h){var j;if(h instanceof e.Record){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h._sId)){return h;}}}else{if(g.isNumber(h)){if((h>-1)&&(h<this.getLength())){return this._records[h];}}else{if(g.isString(h)){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h)){return this._records[j];}}}}}return null;},getRecords:function(i,h){if(!g.isNumber(i)){return this._records;}if(!g.isNumber(h)){return this._records.slice(i);}return this._records.slice(i,i+h);},hasRecords:function(j,h){var l=this.getRecords(j,h);for(var k=0;k<h;++k){if(typeof l[k]==="undefined"){return false;}}return true;},getRecordIndex:function(j){if(j){for(var h=this._records.length-1;h>-1;h--){if(this._records[h]&&j.getId()===this._records[h].getId()){return h;}}}return null;},addRecord:function(j,h){if(g.isObject(j)){var i=this._addRecord(j,h);this.fireEvent("recordAddEvent",{record:i,data:j});return i;}else{return null;}},addRecords:function(m,l){if(g.isArray(m)){var p=[],j,n,h;l=g.isNumber(l)?l:this._records.length;j=l;for(n=0,h=m.length;n<h;++n){if(g.isObject(m[n])){var k=this._addRecord(m[n],j++);p.push(k);}}this.fireEvent("recordsAddEvent",{records:p,data:m});return p;}else{if(g.isObject(m)){var o=this._addRecord(m);this.fireEvent("recordsAddEvent",{records:[o],data:m});return o;}else{return null;}}},setRecord:function(j,h){if(g.isObject(j)){var i=this._setRecord(j,h);this.fireEvent("recordSetEvent",{record:i,data:j});return i;}else{return null;}},setRecords:function(o,n){var r=e.Record,k=g.isArray(o)?o:[o],q=[],p=0,h=k.length,m=0;n=parseInt(n,10)|0;for(;p<h;++p){if(typeof k[p]==="object"&&k[p]){q[m++]=this._records[n+p]=new r(k[p]);}}this.fireEvent("recordsSetEvent",{records:q,data:o});this.fireEvent("recordsSet",{records:q,data:o});if(k.length&&!q.length){}return q;},updateRecord:function(h,l){var j=this.getRecord(h);if(j&&g.isObject(l)){var k={};for(var i in j._oData){if(g.hasOwnProperty(j._oData,i)){k[i]=j._oData[i];}}j._oData=l;this.fireEvent("recordUpdateEvent",{record:j,newData:l,oldData:k});return j;}else{return null;}},updateKey:function(h,i,j){this.updateRecordValue(h,i,j);},updateRecordValue:function(h,k,n){var j=this.getRecord(h);if(j){var m=null;var l=j._oData[k];if(l&&g.isObject(l)){m={};for(var i in l){if(g.hasOwnProperty(l,i)){m[i]=l[i];}}}else{m=l;}j._oData[k]=n;this.fireEvent("keyUpdateEvent",{record:j,key:k,newData:n,oldData:m});this.fireEvent("recordValueUpdateEvent",{record:j,key:k,newData:n,oldData:m});}else{}},replaceRecords:function(h){this.reset();return this.addRecords(h);},sortRecords:function(h,j,i){return this._records.sort(function(l,k){return h(l,k,j,i);});},reverseRecords:function(){return this._records.reverse();},deleteRecord:function(h){if(g.isNumber(h)&&(h>-1)&&(h<this.getLength())){var i=this.getRecord(h).getData();this._deleteRecord(h);this.fireEvent("recordDeleteEvent",{data:i,index:h});return i;}else{return null;}},deleteRecords:function(k,h){if(!g.isNumber(h)){h=1;}if(g.isNumber(k)&&(k>-1)&&(k<this.getLength())){var m=this.getRecords(k,h);var j=[],n=[];for(var l=0;l<m.length;l++){j[j.length]=m[l];n[n.length]=m[l].getData();}this._deleteRecord(k,h);this.fireEvent("recordsDeleteEvent",{data:j,deletedData:n,index:k});return j;}else{return null;}},reset:function(){this._records=[];this.fireEvent("resetEvent");}};g.augmentProto(b,a.EventProvider);YAHOO.widget.Record=function(h){this._nCount=e.Record._nCount;this._sId=c.generateId(null,"yui-rec");e.Record._nCount++;this._oData={};if(g.isObject(h)){for(var i in h){if(g.hasOwnProperty(h,i)){this._oData[i]=h[i];}}}};YAHOO.widget.Record._nCount=0;YAHOO.widget.Record.prototype={_nCount:null,_sId:null,_oData:null,getCount:function(){return this._nCount;},getId:function(){return this._sId;},getData:function(h){if(g.isString(h)){return this._oData[h];}else{return this._oData;}},setData:function(h,i){this._oData[h]=i;}};})();(function(){var h=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,c=a.Dom,g=a.Event,f=a.DataSourceBase;YAHOO.widget.DataTable=function(i,m,o,k){var l=e.DataTable;
127 if(k&&k.scrollable){return new YAHOO.widget.ScrollingDataTable(i,m,o,k);}this._nIndex=l._nCount;this._sId=c.generateId(null,"yui-dt");this._oChainRender=new YAHOO.util.Chain();this._oChainRender.subscribe("end",this._onRenderChainEnd,this,true);this._initConfigs(k);this._initDataSource(o);if(!this._oDataSource){return;}this._initColumnSet(m);if(!this._oColumnSet){return;}this._initRecordSet();if(!this._oRecordSet){}l.superclass.constructor.call(this,i,this.configs);var q=this._initDomElements(i);if(!q){return;}this.showTableMessage(this.get("MSG_LOADING"),l.CLASS_LOADING);this._initEvents();l._nCount++;l._nCurrentCount++;var n={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,scope:this,argument:this.getState()};var p=this.get("initialLoad");if(p===true){this._oDataSource.sendRequest(this.get("initialRequest"),n);}else{if(p===false){this.showTableMessage(this.get("MSG_EMPTY"),l.CLASS_EMPTY);}else{var j=p||{};n.argument=j.argument||{};this._oDataSource.sendRequest(j.request,n);}}};var d=e.DataTable;h.augmentObject(d,{CLASS_DATATABLE:"yui-dt",CLASS_LINER:"yui-dt-liner",CLASS_LABEL:"yui-dt-label",CLASS_MESSAGE:"yui-dt-message",CLASS_MASK:"yui-dt-mask",CLASS_DATA:"yui-dt-data",CLASS_COLTARGET:"yui-dt-coltarget",CLASS_RESIZER:"yui-dt-resizer",CLASS_RESIZERLINER:"yui-dt-resizerliner",CLASS_RESIZERPROXY:"yui-dt-resizerproxy",CLASS_EDITOR:"yui-dt-editor",CLASS_EDITOR_SHIM:"yui-dt-editor-shim",CLASS_PAGINATOR:"yui-dt-paginator",CLASS_PAGE:"yui-dt-page",CLASS_DEFAULT:"yui-dt-default",CLASS_PREVIOUS:"yui-dt-previous",CLASS_NEXT:"yui-dt-next",CLASS_FIRST:"yui-dt-first",CLASS_LAST:"yui-dt-last",CLASS_REC:"yui-dt-rec",CLASS_EVEN:"yui-dt-even",CLASS_ODD:"yui-dt-odd",CLASS_SELECTED:"yui-dt-selected",CLASS_HIGHLIGHTED:"yui-dt-highlighted",CLASS_HIDDEN:"yui-dt-hidden",CLASS_DISABLED:"yui-dt-disabled",CLASS_EMPTY:"yui-dt-empty",CLASS_LOADING:"yui-dt-loading",CLASS_ERROR:"yui-dt-error",CLASS_EDITABLE:"yui-dt-editable",CLASS_DRAGGABLE:"yui-dt-draggable",CLASS_RESIZEABLE:"yui-dt-resizeable",CLASS_SCROLLABLE:"yui-dt-scrollable",CLASS_SORTABLE:"yui-dt-sortable",CLASS_ASC:"yui-dt-asc",CLASS_DESC:"yui-dt-desc",CLASS_BUTTON:"yui-dt-button",CLASS_CHECKBOX:"yui-dt-checkbox",CLASS_DROPDOWN:"yui-dt-dropdown",CLASS_RADIO:"yui-dt-radio",_nCount:0,_nCurrentCount:0,_elDynStyleNode:null,_bDynStylesFallback:(b.ie)?true:false,_oDynStyles:{},_cloneObject:function(m){if(!h.isValue(m)){return m;}var p={};if(m instanceof YAHOO.widget.BaseCellEditor){p=m;}else{if(Object.prototype.toString.apply(m)==="[object RegExp]"){p=m;}else{if(h.isFunction(m)){p=m;}else{if(h.isArray(m)){var n=[];for(var l=0,k=m.length;l<k;l++){n[l]=d._cloneObject(m[l]);}p=n;}else{if(h.isObject(m)){for(var j in m){if(h.hasOwnProperty(m,j)){if(h.isValue(m[j])&&h.isObject(m[j])||h.isArray(m[j])){p[j]=d._cloneObject(m[j]);}else{p[j]=m[j];}}}}else{p=m;}}}}}return p;},formatButton:function(i,j,k,n,m){var l=h.isValue(n)?n:"Click";i.innerHTML='<button type="button" class="'+d.CLASS_BUTTON+'">'+l+"</button>";},formatCheckbox:function(i,j,k,n,m){var l=n;l=(l)?' checked="checked"':"";i.innerHTML='<input type="checkbox"'+l+' class="'+d.CLASS_CHECKBOX+'" />';},formatCurrency:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.currencyOptions||i.get("currencyOptions"));},formatDate:function(j,l,m,o,n){var i=n||this,k=m.dateOptions||i.get("dateOptions");j.innerHTML=a.Date.format(o,k,k.locale);},formatDropdown:function(l,u,q,j,t){var s=t||this,r=(h.isValue(j))?j:u.getData(q.field),v=(h.isArray(q.dropdownOptions))?q.dropdownOptions:null,k,p=l.getElementsByTagName("select");if(p.length===0){k=document.createElement("select");k.className=d.CLASS_DROPDOWN;k=l.appendChild(k);g.addListener(k,"change",s._onDropdownChange,s);}k=p[0];if(k){k.innerHTML="";if(v){for(var n=0;n<v.length;n++){var o=v[n];var m=document.createElement("option");m.value=(h.isValue(o.value))?o.value:o;m.innerHTML=(h.isValue(o.text))?o.text:(h.isValue(o.label))?o.label:o;m=k.appendChild(m);if(m.value==r){m.selected=true;}}}else{k.innerHTML='<option selected value="'+r+'">'+r+"</option>";}}else{l.innerHTML=h.isValue(j)?j:"";}},formatEmail:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="mailto:'+m+'">'+m+"</a>";}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):"";}},formatLink:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="'+m+'">'+m+"</a>";}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):"";}},formatNumber:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.numberOptions||i.get("numberOptions"));},formatRadio:function(j,k,l,o,n){var i=n||this,m=o;m=(m)?' checked="checked"':"";j.innerHTML='<input type="radio"'+m+' name="'+i.getId()+"-col-"+l.getSanitizedKey()+'"'+' class="'+d.CLASS_RADIO+'" />';},formatText:function(i,j,l,n,m){var k=(h.isValue(n))?n:"";i.innerHTML=h.escapeHTML(k.toString());},formatTextarea:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i="<textarea>"+l+"</textarea>";j.innerHTML=i;},formatTextbox:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i='<input type="text" value="'+l+'" />';j.innerHTML=i;},formatDefault:function(i,j,k,m,l){i.innerHTML=(h.isValue(m)&&m!=="")?m.toString():"&#160;";},validateNumber:function(j){var i=j*1;if(h.isNumber(i)){return i;}else{return undefined;}}});d.Formatter={button:d.formatButton,checkbox:d.formatCheckbox,currency:d.formatCurrency,"date":d.formatDate,dropdown:d.formatDropdown,email:d.formatEmail,link:d.formatLink,"number":d.formatNumber,radio:d.formatRadio,text:d.formatText,textarea:d.formatTextarea,textbox:d.formatTextbox,defaultFormatter:d.formatDefault};h.extend(d,a.Element,{initAttributes:function(i){i=i||{};d.superclass.initAttributes.call(this,i);this.setAttributeConfig("summary",{value:"",validator:h.isString,method:function(j){if(this._elTable){this._elTable.summary=j;}}});this.setAttributeConfig("selectionMode",{value:"standard",validator:h.isString});this.setAttributeConfig("sortedBy",{value:null,validator:function(j){if(j){return(h.isObject(j)&&j.key);
128 }else{return(j===null);}},method:function(k){var r=this.get("sortedBy");this._configs.sortedBy.value=k;var j,o,m,q;if(this._elThead){if(r&&r.key&&r.dir){j=this._oColumnSet.getColumn(r.key);o=j.getKeyIndex();var u=j.getThEl();c.removeClass(u,r.dir);this.formatTheadCell(j.getThLinerEl().firstChild,j,k);}if(k){m=(k.column)?k.column:this._oColumnSet.getColumn(k.key);q=m.getKeyIndex();var v=m.getThEl();if(k.dir&&((k.dir=="asc")||(k.dir=="desc"))){var p=(k.dir=="desc")?d.CLASS_DESC:d.CLASS_ASC;c.addClass(v,p);}else{var l=k.dir||d.CLASS_ASC;c.addClass(v,l);}this.formatTheadCell(m.getThLinerEl().firstChild,m,k);}}if(this._elTbody){this._elTbody.style.display="none";var s=this._elTbody.rows,t;for(var n=s.length-1;n>-1;n--){t=s[n].childNodes;if(t[o]){c.removeClass(t[o],r.dir);}if(t[q]){c.addClass(t[q],k.dir);}}this._elTbody.style.display="";}this._clearTrTemplateEl();}});this.setAttributeConfig("paginator",{value:null,validator:function(j){return j===null||j instanceof e.Paginator;},method:function(){this._updatePaginator.apply(this,arguments);}});this.setAttributeConfig("caption",{value:null,validator:h.isString,method:function(j){this._initCaptionEl(j);}});this.setAttributeConfig("draggableColumns",{value:false,validator:h.isBoolean,method:function(j){if(this._elThead){if(j){this._initDraggableColumns();}else{this._destroyDraggableColumns();}}}});this.setAttributeConfig("renderLoopSize",{value:0,validator:h.isNumber});this.setAttributeConfig("sortFunction",{value:function(k,j,o,n){var m=YAHOO.util.Sort.compare,l=m(k.getData(n),j.getData(n),o);if(l===0){return m(k.getCount(),j.getCount(),o);}else{return l;}}});this.setAttributeConfig("formatRow",{value:null,validator:h.isFunction});this.setAttributeConfig("generateRequest",{value:function(k,n){k=k||{pagination:null,sortedBy:null};var m=encodeURIComponent((k.sortedBy)?k.sortedBy.key:n.getColumnSet().keys[0].getKey());var j=(k.sortedBy&&k.sortedBy.dir===YAHOO.widget.DataTable.CLASS_DESC)?"desc":"asc";var o=(k.pagination)?k.pagination.recordOffset:0;var l=(k.pagination)?k.pagination.rowsPerPage:null;return"sort="+m+"&dir="+j+"&startIndex="+o+((l!==null)?"&results="+l:"");},validator:h.isFunction});this.setAttributeConfig("initialRequest",{value:null});this.setAttributeConfig("initialLoad",{value:true});this.setAttributeConfig("dynamicData",{value:false,validator:h.isBoolean});this.setAttributeConfig("MSG_EMPTY",{value:"No records found.",validator:h.isString});this.setAttributeConfig("MSG_LOADING",{value:"Loading...",validator:h.isString});this.setAttributeConfig("MSG_ERROR",{value:"Data error.",validator:h.isString});this.setAttributeConfig("MSG_SORTASC",{value:"Click to sort ascending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_ASC){m[l]._elThLabel.firstChild.title=k;}}}}});this.setAttributeConfig("MSG_SORTDESC",{value:"Click to sort descending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_DESC){m[l]._elThLabel.firstChild.title=k;}}}}});this.setAttributeConfig("currencySymbol",{value:"$",validator:h.isString});this.setAttributeConfig("currencyOptions",{value:{prefix:this.get("currencySymbol"),decimalPlaces:2,decimalSeparator:".",thousandsSeparator:","}});this.setAttributeConfig("dateOptions",{value:{format:"%m/%d/%Y",locale:"en"}});this.setAttributeConfig("numberOptions",{value:{decimalPlaces:0,thousandsSeparator:","}});},_bInit:true,_nIndex:null,_nTrCount:0,_nTdCount:0,_sId:null,_oChainRender:null,_elContainer:null,_elMask:null,_elTable:null,_elCaption:null,_elColgroup:null,_elThead:null,_elTbody:null,_elMsgTbody:null,_elMsgTr:null,_elMsgTd:null,_elColumnDragTarget:null,_elColumnResizerProxy:null,_oDataSource:null,_oColumnSet:null,_oRecordSet:null,_oCellEditor:null,_sFirstTrId:null,_sLastTrId:null,_elTrTemplate:null,_aDynFunctions:[],_disabled:false,clearTextSelection:function(){var i;if(window.getSelection){i=window.getSelection();}else{if(document.getSelection){i=document.getSelection();}else{if(document.selection){i=document.selection;}}}if(i){if(i.empty){i.empty();}else{if(i.removeAllRanges){i.removeAllRanges();}else{if(i.collapse){i.collapse();}}}}},_focusEl:function(i){i=i||this._elTbody;setTimeout(function(){try{i.focus();}catch(j){}},0);},_repaintGecko:(b.gecko)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k);}:function(){},_repaintOpera:(b.opera)?function(){if(b.opera){document.documentElement.className+=" ";document.documentElement.className=YAHOO.lang.trim(document.documentElement.className);}}:function(){},_repaintWebkit:(b.webkit)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k);}:function(){},_initConfigs:function(i){if(!i||!h.isObject(i)){i={};}this.configs=i;},_initColumnSet:function(n){var m,k,j;if(this._oColumnSet){for(k=0,j=this._oColumnSet.keys.length;k<j;k++){m=this._oColumnSet.keys[k];d._oDynStyles["."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER]=undefined;if(m.editor&&m.editor.unsubscribeAll){m.editor.unsubscribeAll();}}this._oColumnSet=null;this._clearTrTemplateEl();}if(h.isArray(n)){this._oColumnSet=new YAHOO.widget.ColumnSet(n);}else{if(n instanceof YAHOO.widget.ColumnSet){this._oColumnSet=n;}}var l=this._oColumnSet.keys;for(k=0,j=l.length;k<j;k++){m=l[k];if(m.editor&&m.editor.subscribe){m.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);m.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);m.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);m.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);m.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);m.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);m.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);
129 m.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true);}}},_initDataSource:function(j){this._oDataSource=null;if(j&&(h.isFunction(j.sendRequest))){this._oDataSource=j;}else{var k=null;var o=this._elContainer;var l=0;if(o.hasChildNodes()){var n=o.childNodes;for(l=0;l<n.length;l++){if(n[l].nodeName&&n[l].nodeName.toLowerCase()=="table"){k=n[l];break;}}if(k){var m=[];for(;l<this._oColumnSet.keys.length;l++){m.push({key:this._oColumnSet.keys[l].key});}this._oDataSource=new f(k);this._oDataSource.responseType=f.TYPE_HTMLTABLE;this._oDataSource.responseSchema={fields:m};}}}},_initRecordSet:function(){if(this._oRecordSet){this._oRecordSet.reset();}else{this._oRecordSet=new YAHOO.widget.RecordSet();}},_initDomElements:function(i){this._initContainerEl(i);this._initTableEl(this._elContainer);this._initColgroupEl(this._elTable);this._initTheadEl(this._elTable);this._initMsgTbodyEl(this._elTable);this._initTbodyEl(this._elTable);if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody){return false;}else{return true;}},_destroyContainerEl:function(m){var k=this._oColumnSet.keys,l,j;c.removeClass(m,d.CLASS_DATATABLE);g.purgeElement(m);g.purgeElement(this._elThead,true);g.purgeElement(this._elTbody);g.purgeElement(this._elMsgTbody);l=m.getElementsByTagName("select");if(l.length){g.detachListener(l,"change");}for(j=k.length-1;j>=0;--j){if(k[j].editor){g.purgeElement(k[j].editor._elContainer);}}m.innerHTML="";this._elContainer=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;},_initContainerEl:function(j){j=c.get(j);if(j&&j.nodeName&&(j.nodeName.toLowerCase()=="div")){this._destroyContainerEl(j);c.addClass(j,d.CLASS_DATATABLE);g.addListener(j,"focus",this._onTableFocus,this);g.addListener(j,"dblclick",this._onTableDblclick,this);this._elContainer=j;var i=document.createElement("div");i.className=d.CLASS_MASK;i.style.display="none";this._elMask=j.appendChild(i);}},_destroyTableEl:function(){var i=this._elTable;if(i){g.purgeElement(i,true);i.parentNode.removeChild(i);this._elCaption=null;this._elColgroup=null;this._elThead=null;this._elTbody=null;}},_initCaptionEl:function(i){if(this._elTable&&i){if(!this._elCaption){this._elCaption=this._elTable.createCaption();}this._elCaption.innerHTML=i;}else{if(this._elCaption){this._elCaption.parentNode.removeChild(this._elCaption);}}},_initTableEl:function(i){if(i){this._destroyTableEl();this._elTable=i.appendChild(document.createElement("table"));this._elTable.summary=this.get("summary");if(this.get("caption")){this._initCaptionEl(this.get("caption"));}g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-message>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-message>tr>td",this);}},_destroyColgroupEl:function(){var i=this._elColgroup;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elColgroup=null;}},_initColgroupEl:function(s){if(s){this._destroyColgroupEl();var l=this._aColIds||[],r=this._oColumnSet.keys,m=0,p=l.length,j,o,q=document.createDocumentFragment(),n=document.createElement("col");for(m=0,p=r.length;m<p;m++){o=r[m];j=q.appendChild(n.cloneNode(false));}var k=s.insertBefore(document.createElement("colgroup"),s.firstChild);k.appendChild(q);this._elColgroup=k;}},_insertColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup){var j=this._elColgroup.childNodes[i]||null;this._elColgroup.insertBefore(document.createElement("col"),j);}},_removeColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup&&this._elColgroup.childNodes[i]){this._elColgroup.removeChild(this._elColgroup.childNodes[i]);}},_reorderColgroupColEl:function(l,k){if(h.isArray(l)&&h.isNumber(k)&&this._elColgroup&&(this._elColgroup.childNodes.length>l[l.length-1])){var j,n=[];for(j=l.length-1;j>-1;j--){n.push(this._elColgroup.removeChild(this._elColgroup.childNodes[l[j]]));}var m=this._elColgroup.childNodes[k]||null;for(j=n.length-1;j>-1;j--){this._elColgroup.insertBefore(n[j],m);}}},_destroyTheadEl:function(){var j=this._elThead;if(j){var i=j.parentNode;g.purgeElement(j,true);this._destroyColumnHelpers();i.removeChild(j);this._elThead=null;}},_initTheadEl:function(v){v=v||this._elTable;if(v){this._destroyTheadEl();var q=(this._elColgroup)?v.insertBefore(document.createElement("thead"),this._elColgroup.nextSibling):v.appendChild(document.createElement("thead"));g.addListener(q,"focus",this._onTheadFocus,this);g.addListener(q,"keydown",this._onTheadKeydown,this);g.addListener(q,"mousedown",this._onTableMousedown,this);g.addListener(q,"mouseup",this._onTableMouseup,this);g.addListener(q,"click",this._onTheadClick,this);var x=this._oColumnSet,t,r,p,n;var w=x.tree;var o;for(r=0;r<w.length;r++){var m=q.appendChild(document.createElement("tr"));for(p=0;p<w[r].length;p++){t=w[r][p];o=m.appendChild(document.createElement("th"));this._initThEl(o,t);}if(r===0){c.addClass(m,d.CLASS_FIRST);}if(r===(w.length-1)){c.addClass(m,d.CLASS_LAST);}}var k=x.headers[0]||[];for(r=0;r<k.length;r++){c.addClass(c.get(this.getId()+"-th-"+k[r]),d.CLASS_FIRST);}var s=x.headers[x.headers.length-1]||[];for(r=0;r<s.length;r++){c.addClass(c.get(this.getId()+"-th-"+s[r]),d.CLASS_LAST);}if(b.webkit&&b.webkit<420){var u=this;setTimeout(function(){q.style.display="";},0);q.style.display="none";}this._elThead=q;this._initColumnHelpers();}},_initThEl:function(m,l){m.id=this.getId()+"-th-"+l.getSanitizedKey();m.innerHTML="";m.rowSpan=l.getRowspan();m.colSpan=l.getColspan();l._elTh=m;var i=m.appendChild(document.createElement("div"));i.id=m.id+"-liner";i.className=d.CLASS_LINER;l._elThLiner=i;var j=i.appendChild(document.createElement("span"));
130 j.className=d.CLASS_LABEL;if(l.abbr){m.abbr=l.abbr;}if(l.hidden){this._clearMinWidth(l);}m.className=this._getColumnClassNames(l);if(l.width){var k=(l.minWidth&&(l.width<l.minWidth))?l.minWidth:l.width;if(d._bDynStylesFallback){m.firstChild.style.overflow="hidden";m.firstChild.style.width=k+"px";}else{this._setColumnWidthDynStyles(l,k+"px","hidden");}}this.formatTheadCell(j,l,this.get("sortedBy"));l._elThLabel=j;},formatTheadCell:function(i,m,k){var q=m.getKey();var p=h.isValue(m.label)?m.label:q;if(m.sortable){var n=this.getColumnSortDir(m,k);var j=(n===d.CLASS_DESC);if(k&&(m.key===k.key)){j=!(k.dir===d.CLASS_DESC);}var l=this.getId()+"-href-"+m.getSanitizedKey();var o=(j)?this.get("MSG_SORTDESC"):this.get("MSG_SORTASC");i.innerHTML='<a href="'+l+'" title="'+o+'" class="'+d.CLASS_SORTABLE+'">'+p+"</a>";}else{i.innerHTML=p;}},_destroyDraggableColumns:function(){var l,m;for(var k=0,j=this._oColumnSet.tree[0].length;k<j;k++){l=this._oColumnSet.tree[0][k];if(l._dd){l._dd=l._dd.unreg();c.removeClass(l.getThEl(),d.CLASS_DRAGGABLE);}}this._destroyColumnDragTargetEl();},_initDraggableColumns:function(){this._destroyDraggableColumns();if(a.DD){var m,n,k;for(var l=0,j=this._oColumnSet.tree[0].length;l<j;l++){m=this._oColumnSet.tree[0][l];n=m.getThEl();c.addClass(n,d.CLASS_DRAGGABLE);k=this._initColumnDragTargetEl();m._dd=new YAHOO.widget.ColumnDD(this,m,n,k);}}else{}},_destroyColumnDragTargetEl:function(){if(this._elColumnDragTarget){var i=this._elColumnDragTarget;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnDragTarget=null;}},_initColumnDragTargetEl:function(){if(!this._elColumnDragTarget){var i=document.createElement("div");i.id=this.getId()+"-coltarget";i.className=d.CLASS_COLTARGET;i.style.display="none";document.body.insertBefore(i,document.body.firstChild);this._elColumnDragTarget=i;}return this._elColumnDragTarget;},_destroyResizeableColumns:function(){var k=this._oColumnSet.keys;for(var l=0,j=k.length;l<j;l++){if(k[l]._ddResizer){k[l]._ddResizer=k[l]._ddResizer.unreg();c.removeClass(k[l].getThEl(),d.CLASS_RESIZEABLE);}}this._destroyColumnResizerProxyEl();},_initResizeableColumns:function(){this._destroyResizeableColumns();if(a.DD){var p,k,n,q,j,r,m;for(var l=0,o=this._oColumnSet.keys.length;l<o;l++){p=this._oColumnSet.keys[l];if(p.resizeable){k=p.getThEl();c.addClass(k,d.CLASS_RESIZEABLE);n=p.getThLinerEl();q=k.appendChild(document.createElement("div"));q.className=d.CLASS_RESIZERLINER;q.appendChild(n);j=q.appendChild(document.createElement("div"));j.id=k.id+"-resizer";j.className=d.CLASS_RESIZER;p._elResizer=j;r=this._initColumnResizerProxyEl();p._ddResizer=new YAHOO.util.ColumnResizer(this,p,k,j,r);m=function(i){g.stopPropagation(i);};g.addListener(j,"click",m);}}}else{}},_destroyColumnResizerProxyEl:function(){if(this._elColumnResizerProxy){var i=this._elColumnResizerProxy;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnResizerProxy=null;}},_initColumnResizerProxyEl:function(){if(!this._elColumnResizerProxy){var i=document.createElement("div");i.id=this.getId()+"-colresizerproxy";i.className=d.CLASS_RESIZERPROXY;document.body.insertBefore(i,document.body.firstChild);this._elColumnResizerProxy=i;}return this._elColumnResizerProxy;},_destroyColumnHelpers:function(){this._destroyDraggableColumns();this._destroyResizeableColumns();},_initColumnHelpers:function(){if(this.get("draggableColumns")){this._initDraggableColumns();}this._initResizeableColumns();},_destroyTbodyEl:function(){var i=this._elTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null;}},_initTbodyEl:function(j){if(j){this._destroyTbodyEl();var i=j.appendChild(document.createElement("tbody"));i.tabIndex=0;i.className=d.CLASS_DATA;g.addListener(i,"focus",this._onTbodyFocus,this);g.addListener(i,"mousedown",this._onTableMousedown,this);g.addListener(i,"mouseup",this._onTableMouseup,this);g.addListener(i,"keydown",this._onTbodyKeydown,this);g.addListener(i,"click",this._onTbodyClick,this);if(b.ie){i.hideFocus=true;}this._elTbody=i;}},_destroyMsgTbodyEl:function(){var i=this._elMsgTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null;}},_initMsgTbodyEl:function(l){if(l){var k=document.createElement("tbody");k.className=d.CLASS_MESSAGE;var j=k.appendChild(document.createElement("tr"));j.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTr=j;var m=j.appendChild(document.createElement("td"));m.colSpan=this._oColumnSet.keys.length||1;m.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTd=m;k=l.insertBefore(k,this._elTbody);var i=m.appendChild(document.createElement("div"));i.className=d.CLASS_LINER;this._elMsgTbody=k;g.addListener(k,"focus",this._onTbodyFocus,this);g.addListener(k,"mousedown",this._onTableMousedown,this);g.addListener(k,"mouseup",this._onTableMouseup,this);g.addListener(k,"keydown",this._onTbodyKeydown,this);g.addListener(k,"click",this._onTbodyClick,this);}},_initEvents:function(){this._initColumnSort();YAHOO.util.Event.addListener(document,"click",this._onDocumentClick,this);this.subscribe("paginatorChange",function(){this._handlePaginatorChange.apply(this,arguments);});this.subscribe("initEvent",function(){this.renderPaginator();});this._initCellEditing();},_initColumnSort:function(){this.subscribe("theadCellClickEvent",this.onEventSortColumn);var i=this.get("sortedBy");if(i){if(i.dir=="desc"){this._configs.sortedBy.value.dir=d.CLASS_DESC;}else{if(i.dir=="asc"){this._configs.sortedBy.value.dir=d.CLASS_ASC;}}}},_initCellEditing:function(){this.subscribe("editorBlurEvent",function(){this.onEditorBlurEvent.apply(this,arguments);});this.subscribe("editorBlockEvent",function(){this.onEditorBlockEvent.apply(this,arguments);});this.subscribe("editorUnblockEvent",function(){this.onEditorUnblockEvent.apply(this,arguments);});},_getColumnClassNames:function(l,k){var i;if(h.isString(l.className)){i=[l.className];}else{if(h.isArray(l.className)){i=l.className;}else{i=[];}}i[i.length]=this.getId()+"-col-"+l.getSanitizedKey();
131 i[i.length]="yui-dt-col-"+l.getSanitizedKey();var j=this.get("sortedBy")||{};if(l.key===j.key){i[i.length]=j.dir||"";}if(l.hidden){i[i.length]=d.CLASS_HIDDEN;}if(l.selected){i[i.length]=d.CLASS_SELECTED;}if(l.sortable){i[i.length]=d.CLASS_SORTABLE;}if(l.resizeable){i[i.length]=d.CLASS_RESIZEABLE;}if(l.editor){i[i.length]=d.CLASS_EDITABLE;}if(k){i=i.concat(k);}return i.join(" ");},_clearTrTemplateEl:function(){this._elTrTemplate=null;},_getTrTemplateEl:function(u,o){if(this._elTrTemplate){return this._elTrTemplate;}else{var q=document,s=q.createElement("tr"),l=q.createElement("td"),k=q.createElement("div");l.appendChild(k);var t=document.createDocumentFragment(),r=this._oColumnSet.keys,n;var p;for(var m=0,j=r.length;m<j;m++){n=l.cloneNode(true);n=this._formatTdEl(r[m],n,m,(m===j-1));t.appendChild(n);}s.appendChild(t);s.className=d.CLASS_REC;this._elTrTemplate=s;return s;}},_formatTdEl:function(n,p,q,m){var t=this._oColumnSet;var i=t.headers,k=i[q],o="",v;for(var l=0,u=k.length;l<u;l++){v=this._sId+"-th-"+k[l]+" ";o+=v;}p.headers=o;var s=[];if(q===0){s[s.length]=d.CLASS_FIRST;}if(m){s[s.length]=d.CLASS_LAST;}p.className=this._getColumnClassNames(n,s);p.firstChild.className=d.CLASS_LINER;if(n.width&&d._bDynStylesFallback){var r=(n.minWidth&&(n.width<n.minWidth))?n.minWidth:n.width;p.firstChild.style.overflow="hidden";p.firstChild.style.width=r+"px";}return p;},_addTrEl:function(k){var j=this._getTrTemplateEl();var i=j.cloneNode(true);return this._updateTrEl(i,k);},_updateTrEl:function(q,r){var p=this.get("formatRow")?this.get("formatRow").call(this,q,r):true;if(p){q.style.display="none";var o=q.childNodes,m;for(var l=0,n=o.length;l<n;++l){m=o[l];this.formatCell(o[l].firstChild,r,this._oColumnSet.keys[l]);}q.style.display="";}var j=q.id,k=r.getId();if(this._sFirstTrId===j){this._sFirstTrId=k;}if(this._sLastTrId===j){this._sLastTrId=k;}q.id=k;return q;},_deleteTrEl:function(i){var j;if(!h.isNumber(i)){j=c.get(i).sectionRowIndex;}else{j=i;}if(h.isNumber(j)&&(j>-2)&&(j<this._elTbody.rows.length)){return this._elTbody.removeChild(this._elTbody.rows[i]);}else{return null;}},_unsetFirstRow:function(){if(this._sFirstTrId){c.removeClass(this._sFirstTrId,d.CLASS_FIRST);this._sFirstTrId=null;}},_setFirstRow:function(){this._unsetFirstRow();var i=this.getFirstTrEl();if(i){c.addClass(i,d.CLASS_FIRST);this._sFirstTrId=i.id;}},_unsetLastRow:function(){if(this._sLastTrId){c.removeClass(this._sLastTrId,d.CLASS_LAST);this._sLastTrId=null;}},_setLastRow:function(){this._unsetLastRow();var i=this.getLastTrEl();if(i){c.addClass(i,d.CLASS_LAST);this._sLastTrId=i.id;}},_setRowStripes:function(t,l){var m=this._elTbody.rows,q=0,s=m.length,p=[],r=0,n=[],j=0;if((t!==null)&&(t!==undefined)){var o=this.getTrEl(t);if(o){q=o.sectionRowIndex;if(h.isNumber(l)&&(l>1)){s=q+l;}}}for(var k=q;k<s;k++){if(k%2){p[r++]=m[k];}else{n[j++]=m[k];}}if(p.length){c.replaceClass(p,d.CLASS_EVEN,d.CLASS_ODD);}if(n.length){c.replaceClass(n,d.CLASS_ODD,d.CLASS_EVEN);}},_setSelections:function(){var l=this.getSelectedRows();var n=this.getSelectedCells();if((l.length>0)||(n.length>0)){var m=this._oColumnSet,k;for(var j=0;j<l.length;j++){k=c.get(l[j]);if(k){c.addClass(k,d.CLASS_SELECTED);}}for(j=0;j<n.length;j++){k=c.get(n[j].recordId);if(k){c.addClass(k.childNodes[m.getColumn(n[j].columnKey).getKeyIndex()],d.CLASS_SELECTED);}}}},_onRenderChainEnd:function(){this.hideTableMessage();if(this._elTbody.rows.length===0){this.showTableMessage(this.get("MSG_EMPTY"),d.CLASS_EMPTY);}var i=this;setTimeout(function(){if((i instanceof d)&&i._sId){if(i._bInit){i._bInit=false;i.fireEvent("initEvent");}i.fireEvent("renderEvent");i.fireEvent("refreshEvent");i.validateColumnWidths();i.fireEvent("postRenderEvent");}},0);},_onDocumentClick:function(l,j){var m=g.getTarget(l);var i=m.nodeName.toLowerCase();if(!c.isAncestor(j._elContainer,m)){j.fireEvent("tableBlurEvent");if(j._oCellEditor){if(j._oCellEditor.getContainerEl){var k=j._oCellEditor.getContainerEl();if(!c.isAncestor(k,m)&&(k.id!==m.id)){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});}}else{if(j._oCellEditor.isActive){if(!c.isAncestor(j._oCellEditor.container,m)&&(j._oCellEditor.container.id!==m.id)){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}}}},_onTableFocus:function(j,i){i.fireEvent("tableFocusEvent");},_onTheadFocus:function(j,i){i.fireEvent("theadFocusEvent");i.fireEvent("tableFocusEvent");},_onTbodyFocus:function(j,i){i.fireEvent("tbodyFocusEvent");i.fireEvent("tableFocusEvent");},_onTableMouseover:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoverEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoverEvent",{target:o,event:n});}break;case"th":l=k.fireEvent("theadCellMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoverEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerRowMouseoverEvent",{target:o,event:n});}else{l=k.fireEvent("rowMouseoverEvent",{target:o,event:n});}break;default:break;}if(l===false){return;}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase();}}}k.fireEvent("tableMouseoverEvent",{target:(o||k._elContainer),event:n});},_onTableMouseout:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoutEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoutEvent",{target:o,event:n});}break;case"th":l=k.fireEvent("theadCellMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoutEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoutEvent",{target:o,event:n});
132 l=k.fireEvent("headerRowMouseoutEvent",{target:o,event:n});}else{l=k.fireEvent("rowMouseoutEvent",{target:o,event:n});}break;default:break;}if(l===false){return;}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase();}}}k.fireEvent("tableMouseoutEvent",{target:(o||k._elContainer),event:n});},_onTableMousedown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMousedownEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMousedownEvent",{target:m,event:l});k=j.fireEvent("headerLabelMousedownEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellMousedownEvent",{target:m,event:l});k=j.fireEvent("headerCellMousedownEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMousedownEvent",{target:m,event:l});k=j.fireEvent("headerRowMousedownEvent",{target:m,event:l});}else{k=j.fireEvent("rowMousedownEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableMousedownEvent",{target:(m||j._elContainer),event:l});},_onTableMouseup:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMouseupEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMouseupEvent",{target:m,event:l});k=j.fireEvent("headerLabelMouseupEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellMouseupEvent",{target:m,event:l});k=j.fireEvent("headerCellMouseupEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMouseupEvent",{target:m,event:l});k=j.fireEvent("headerRowMouseupEvent",{target:m,event:l});}else{k=j.fireEvent("rowMouseupEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableMouseupEvent",{target:(m||j._elContainer),event:l});},_onTableDblclick:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"td":k=j.fireEvent("cellDblclickEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelDblclickEvent",{target:m,event:l});k=j.fireEvent("headerLabelDblclickEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellDblclickEvent",{target:m,event:l});k=j.fireEvent("headerCellDblclickEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowDblclickEvent",{target:m,event:l});k=j.fireEvent("headerRowDblclickEvent",{target:m,event:l});}else{k=j.fireEvent("rowDblclickEvent",{target:m,event:l});}break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableDblclickEvent",{target:(m||j._elContainer),event:l});},_onTheadKeydown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":case"textarea":break;case"thead":k=j.fireEvent("theadKeyEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableKeyEvent",{target:(m||j._elContainer),event:l});},_onTbodyKeydown:function(m,k){var j=k.get("selectionMode");if(j=="standard"){k._handleStandardSelectionByKey(m);}else{if(j=="single"){k._handleSingleSelectionByKey(m);}else{if(j=="cellblock"){k._handleCellBlockSelectionByKey(m);}else{if(j=="cellrange"){k._handleCellRangeSelectionByKey(m);}else{if(j=="singlecell"){k._handleSingleCellSelectionByKey(m);}}}}}if(k._oCellEditor){if(k._oCellEditor.fireEvent){k._oCellEditor.fireEvent("blurEvent",{editor:k._oCellEditor});}else{if(k._oCellEditor.isActive){k.fireEvent("editorBlurEvent",{editor:k._oCellEditor});}}}var n=g.getTarget(m);var i=n.nodeName&&n.nodeName.toLowerCase();var l=true;while(n&&(i!="table")){switch(i){case"body":return;case"tbody":l=k.fireEvent("tbodyKeyEvent",{target:n,event:m});break;default:break;}if(l===false){return;}else{n=n.parentNode;if(n){i=n.nodeName.toLowerCase();}}}k.fireEvent("tableKeyEvent",{target:(n||k._elContainer),event:m});},_onTheadClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});}else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("theadCheckboxClickEvent",{target:m,event:l});}else{if(n=="radio"){k=j.fireEvent("theadRadioClickEvent",{target:m,event:l});}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l});}else{k=false;}}else{if(m.disabled){k=false;}}}}break;case"a":k=j.fireEvent("theadLinkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l});}else{k=false;}break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelClickEvent",{target:m,event:l});k=j.fireEvent("headerLabelClickEvent",{target:m,event:l});}break;case"th":k=j.fireEvent("theadCellClickEvent",{target:m,event:l});k=j.fireEvent("headerCellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("theadRowClickEvent",{target:m,event:l});k=j.fireEvent("headerRowClickEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l});},_onTbodyClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor});
133 }else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor});}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("checkboxClickEvent",{target:m,event:l});}else{if(n=="radio"){k=j.fireEvent("radioClickEvent",{target:m,event:l});}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l});}else{k=false;}}else{if(m.disabled){k=false;}}}}break;case"a":k=j.fireEvent("linkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l});}else{k=false;}break;case"td":k=j.fireEvent("cellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("rowClickEvent",{target:m,event:l});break;default:break;}if(k===false){return;}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase();}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l});},_onDropdownChange:function(j,i){var k=g.getTarget(j);i.fireEvent("dropdownChangeEvent",{event:j,target:k});},configs:null,getId:function(){return this._sId;},toString:function(){return"DataTable instance "+this._sId;},getDataSource:function(){return this._oDataSource;},getColumnSet:function(){return this._oColumnSet;},getRecordSet:function(){return this._oRecordSet;},getState:function(){return{totalRecords:this.get("paginator")?this.get("paginator").get("totalRecords"):this._oRecordSet.getLength(),pagination:this.get("paginator")?this.get("paginator").getState():null,sortedBy:this.get("sortedBy"),selectedRows:this.getSelectedRows(),selectedCells:this.getSelectedCells()};},getContainerEl:function(){return this._elContainer;},getTableEl:function(){return this._elTable;},getTheadEl:function(){return this._elThead;},getTbodyEl:function(){return this._elTbody;},getMsgTbodyEl:function(){return this._elMsgTbody;},getMsgTdEl:function(){return this._elMsgTd;},getTrEl:function(k){if(k instanceof YAHOO.widget.Record){return document.getElementById(k.getId());}else{if(h.isNumber(k)){var j=c.getElementsByClassName(d.CLASS_REC,"tr",this._elTbody);return j&&j[k]?j[k]:null;}else{if(k){var i=(h.isString(k))?document.getElementById(k):k;if(i&&i.ownerDocument==document){if(i.nodeName.toLowerCase()!="tr"){i=c.getAncestorByTagName(i,"tr");}return i;}}}}return null;},getFirstTrEl:function(){var k=this._elTbody.rows,j=0;while(k[j]){if(this.getRecord(k[j])){return k[j];}j++;}return null;},getLastTrEl:function(){var k=this._elTbody.rows,j=k.length-1;while(j>-1){if(this.getRecord(k[j])){return k[j];}j--;}return null;},getNextTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j<k.length-1){l=k[j+1];if(this.getRecord(l)){return l;}j++;}}else{if(j<k.length-1){return k[j+1];}}}return null;},getPreviousTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j>0){l=k[j-1];if(this.getRecord(l)){return l;}j--;}}else{if(j>0){return k[j-1];}}}return null;},getCellIndex:function(k){k=this.getTdEl(k);if(k){if(b.ie>0){var l=0,n=k.parentNode,m=n.childNodes,j=m.length;for(;l<j;l++){if(m[l]==k){return l;}}}else{return k.cellIndex;}}},getTdLinerEl:function(i){var j=this.getTdEl(i);return j.firstChild||null;},getTdEl:function(i){var n;var l=c.get(i);if(l&&(l.ownerDocument==document)){if(l.nodeName.toLowerCase()!="td"){n=c.getAncestorByTagName(l,"td");}else{n=l;}if(n&&((n.parentNode.parentNode==this._elTbody)||(n.parentNode.parentNode===null)||(n.parentNode.parentNode.nodeType===11))){return n;}}else{if(i){var m,k;if(h.isString(i.columnKey)&&h.isString(i.recordId)){m=this.getRecord(i.recordId);var o=this.getColumn(i.columnKey);if(o){k=o.getKeyIndex();}}if(i.record&&i.column&&i.column.getKeyIndex){m=i.record;k=i.column.getKeyIndex();}var j=this.getTrEl(m);if((k!==null)&&j&&j.cells&&j.cells.length>0){return j.cells[k]||null;}}}return null;},getFirstTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getFirstTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[0];}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[0];}}}return null;},getLastTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getLastTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[i.cells.length-1];}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[i.childNodes.length-1];}}}return null;},getNextTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(j.cells&&(j.cells.length)>0&&(k<j.cells.length-1)){return j.cells[k+1];}else{if(j.childNodes&&(j.childNodes.length)>0&&(k<j.childNodes.length-1)){return j.childNodes[k+1];}else{var l=this.getNextTrEl(j);if(l){return l.cells[0];}}}}return null;},getPreviousTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(k>0){if(j.cells&&j.cells.length>0){return j.cells[k-1];}else{if(j.childNodes&&j.childNodes.length>0){return j.childNodes[k-1];}}}else{var l=this.getPreviousTrEl(j);if(l){return this.getLastTdEl(l);}}}return null;},getAboveTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getPreviousTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null;}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null;}}}}return null;},getBelowTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getNextTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null;}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null;}}}}return null;},getThLinerEl:function(j){var i=this.getColumn(j);return(i)?i.getThLinerEl():null;},getThEl:function(k){var l;if(k instanceof YAHOO.widget.Column){var j=k;l=j.getThEl();if(l){return l;}}else{var i=c.get(k);if(i&&(i.ownerDocument==document)){if(i.nodeName.toLowerCase()!="th"){l=c.getAncestorByTagName(i,"th");
134 }else{l=i;}return l;}}return null;},getTrIndex:function(m){var i=this.getRecord(m),k=this.getRecordIndex(i),l;if(i){l=this.getTrEl(i);if(l){return l.sectionRowIndex;}else{var j=this.get("paginator");if(j){return j.get("recordOffset")+k;}else{return k;}}}return null;},load:function(i){i=i||{};(i.datasource||this._oDataSource).sendRequest(i.request||this.get("initialRequest"),i.callback||{success:this.onDataReturnInitializeTable,failure:this.onDataReturnInitializeTable,scope:this,argument:this.getState()});},initializeTable:function(){this._bInit=true;this._oRecordSet.reset();var i=this.get("paginator");if(i){i.set("totalRecords",0);}this._unselectAllTrEls();this._unselectAllTdEls();this._aSelections=null;this._oAnchorRecord=null;this._oAnchorCell=null;this.set("sortedBy",null);},_runRenderChain:function(){this._oChainRender.run();},_getViewRecords:function(){var i=this.get("paginator");if(i){return this._oRecordSet.getRecords(i.getStartIndex(),i.getRowsPerPage());}else{return this._oRecordSet.getRecords();}},render:function(){this._oChainRender.stop();this.fireEvent("beforeRenderEvent");var r,p,o,s,l=this._getViewRecords();var m=this._elTbody,q=this.get("renderLoopSize"),t=l.length;if(t>0){m.style.display="none";while(m.lastChild){m.removeChild(m.lastChild);}m.style.display="";this._oChainRender.add({method:function(u){if((this instanceof d)&&this._sId){var k=u.nCurrentRecord,w=((u.nCurrentRecord+u.nLoopLength)>t)?t:(u.nCurrentRecord+u.nLoopLength),j,v;m.style.display="none";for(;k<w;k++){j=c.get(l[k].getId());j=j||this._addTrEl(l[k]);v=m.childNodes[k]||null;m.insertBefore(j,v);}m.style.display="";u.nCurrentRecord=k;}},scope:this,iterations:(q>0)?Math.ceil(t/q):1,argument:{nCurrentRecord:0,nLoopLength:(q>0)?q:t},timeout:(q>0)?0:-1});this._oChainRender.add({method:function(i){if((this instanceof d)&&this._sId){while(m.rows.length>t){m.removeChild(m.lastChild);}this._setFirstRow();this._setLastRow();this._setRowStripes();this._setSelections();}},scope:this,timeout:(q>0)?0:-1});}else{var n=m.rows.length;if(n>0){this._oChainRender.add({method:function(k){if((this instanceof d)&&this._sId){var j=k.nCurrent,v=k.nLoopLength,u=(j-v<0)?0:j-v;m.style.display="none";for(;j>u;j--){m.deleteRow(-1);}m.style.display="";k.nCurrent=j;}},scope:this,iterations:(q>0)?Math.ceil(n/q):1,argument:{nCurrent:n,nLoopLength:(q>0)?q:n},timeout:(q>0)?0:-1});}}this._runRenderChain();},disable:function(){this._disabled=true;var i=this._elTable;var j=this._elMask;j.style.width=i.offsetWidth+"px";j.style.height=i.offsetHeight+"px";j.style.left=i.offsetLeft+"px";j.style.display="";this.fireEvent("disableEvent");},undisable:function(){this._disabled=false;this._elMask.style.display="none";this.fireEvent("undisableEvent");},isDisabled:function(){return this._disabled;},destroy:function(){var k=this.toString();this._oChainRender.stop();this._destroyColumnHelpers();var m;for(var l=0,j=this._oColumnSet.flat.length;l<j;l++){m=this._oColumnSet.flat[l].editor;if(m&&m.destroy){m.destroy();this._oColumnSet.flat[l].editor=null;}}this._destroyPaginator();this._oRecordSet.unsubscribeAll();this.unsubscribeAll();g.removeListener(document,"click",this._onDocumentClick);this._destroyContainerEl(this._elContainer);for(var n in this){if(h.hasOwnProperty(this,n)){this[n]=null;}}d._nCurrentCount--;if(d._nCurrentCount<1){if(d._elDynStyleNode){document.getElementsByTagName("head")[0].removeChild(d._elDynStyleNode);d._elDynStyleNode=null;}}},showTableMessage:function(j,i){var k=this._elMsgTd;if(h.isString(j)){k.firstChild.innerHTML=j;}if(h.isString(i)){k.className=i;}this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:j,className:i});},hideTableMessage:function(){if(this._elMsgTbody.style.display!="none"){this._elMsgTbody.style.display="none";this._elMsgTbody.parentNode.style.width="";this.fireEvent("tableMsgHideEvent");}},focus:function(){this.focusTbodyEl();},focusTheadEl:function(){this._focusEl(this._elThead);},focusTbodyEl:function(){this._focusEl(this._elTbody);},onShow:function(){this.validateColumnWidths();for(var m=this._oColumnSet.keys,l=0,j=m.length,k;l<j;l++){k=m[l];if(k._ddResizer){k._ddResizer.resetResizerEl();}}},getRecordIndex:function(l){var k;if(!h.isNumber(l)){if(l instanceof YAHOO.widget.Record){return this._oRecordSet.getRecordIndex(l);}else{var j=this.getTrEl(l);if(j){k=j.sectionRowIndex;}}}else{k=l;}if(h.isNumber(k)){var i=this.get("paginator");if(i){return i.get("recordOffset")+k;}else{return k;}}return null;},getRecord:function(k){var j=this._oRecordSet.getRecord(k);if(!j){var i=this.getTrEl(k);if(i){j=this._oRecordSet.getRecord(i.id);}}if(j instanceof YAHOO.widget.Record){return this._oRecordSet.getRecord(j);}else{return null;}},getColumn:function(m){var o=this._oColumnSet.getColumn(m);if(!o){var n=this.getTdEl(m);if(n){o=this._oColumnSet.getColumn(this.getCellIndex(n));}else{n=this.getThEl(m);if(n){var k=this._oColumnSet.flat;for(var l=0,j=k.length;l<j;l++){if(k[l].getThEl().id===n.id){o=k[l];}}}}}if(!o){}return o;},getColumnById:function(i){return this._oColumnSet.getColumnById(i);},getColumnSortDir:function(k,l){if(k.sortOptions&&k.sortOptions.defaultDir){if(k.sortOptions.defaultDir=="asc"){k.sortOptions.defaultDir=d.CLASS_ASC;}else{if(k.sortOptions.defaultDir=="desc"){k.sortOptions.defaultDir=d.CLASS_DESC;}}}var j=(k.sortOptions&&k.sortOptions.defaultDir)?k.sortOptions.defaultDir:d.CLASS_ASC;var i=false;l=l||this.get("sortedBy");if(l&&(l.key===k.key)){i=true;if(l.dir){j=(l.dir===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC;}else{j=(j===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC;}}return j;},doBeforeSortColumn:function(j,i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true;},sortColumn:function(m,j){if(m&&(m instanceof YAHOO.widget.Column)){if(!m.sortable){c.addClass(this.getThEl(m),d.CLASS_SORTABLE);}if(j&&(j!==d.CLASS_ASC)&&(j!==d.CLASS_DESC)){j=null;}var n=j||this.getColumnSortDir(m);var l=this.get("sortedBy")||{};var t=(l.key===m.key)?true:false;var p=this.doBeforeSortColumn(m,n);
135 if(p){if(this.get("dynamicData")){var s=this.getState();if(s.pagination){s.pagination.recordOffset=0;}s.sortedBy={key:m.key,dir:n};var k=this.get("generateRequest")(s,this);this.unselectAllRows();this.unselectAllCells();var r={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:s,scope:this};this._oDataSource.sendRequest(k,r);}else{var i=(m.sortOptions&&h.isFunction(m.sortOptions.sortFunction))?m.sortOptions.sortFunction:null;if(!t||j||i){i=i||this.get("sortFunction");var q=(m.sortOptions&&m.sortOptions.field)?m.sortOptions.field:m.field;this._oRecordSet.sortRecords(i,((n==d.CLASS_DESC)?true:false),q);}else{this._oRecordSet.reverseRecords();}var o=this.get("paginator");if(o){o.setPage(1,true);}this.render();this.set("sortedBy",{key:m.key,dir:n,column:m});}this.fireEvent("columnSortEvent",{column:m,dir:n});return;}}},setColumnWidth:function(j,i){if(!(j instanceof YAHOO.widget.Column)){j=this.getColumn(j);}if(j){if(h.isNumber(i)){i=(i>j.minWidth)?i:j.minWidth;j.width=i;this._setColumnWidth(j,i+"px");this.fireEvent("columnSetWidthEvent",{column:j,width:i});}else{if(i===null){j.width=i;this._setColumnWidth(j,"auto");this.validateColumnWidths(j);this.fireEvent("columnUnsetWidthEvent",{column:j});}}this._clearTrTemplateEl();}else{}},_setColumnWidth:function(j,i,k){if(j&&(j.getKeyIndex()!==null)){k=k||(((i==="")||(i==="auto"))?"visible":"hidden");if(!d._bDynStylesFallback){this._setColumnWidthDynStyles(j,i,k);}else{this._setColumnWidthDynFunction(j,i,k);}}else{}},_setColumnWidthDynStyles:function(m,l,n){var j=d._elDynStyleNode,k;if(!j){j=document.createElement("style");j.type="text/css";j=document.getElementsByTagName("head").item(0).appendChild(j);d._elDynStyleNode=j;}if(j){var i="."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER;if(this._elTbody){this._elTbody.style.display="none";}k=d._oDynStyles[i];if(!k){if(j.styleSheet&&j.styleSheet.addRule){j.styleSheet.addRule(i,"overflow:"+n);j.styleSheet.addRule(i,"width:"+l);k=j.styleSheet.rules[j.styleSheet.rules.length-1];d._oDynStyles[i]=k;}else{if(j.sheet&&j.sheet.insertRule){j.sheet.insertRule(i+" {overflow:"+n+";width:"+l+";}",j.sheet.cssRules.length);k=j.sheet.cssRules[j.sheet.cssRules.length-1];d._oDynStyles[i]=k;}}}else{k.style.overflow=n;k.style.width=l;}if(this._elTbody){this._elTbody.style.display="";}}if(!k){d._bDynStylesFallback=true;this._setColumnWidthDynFunction(m,l);}},_setColumnWidthDynFunction:function(r,m,s){if(m=="auto"){m="";}var l=this._elTbody?this._elTbody.rows.length:0;if(!this._aDynFunctions[l]){var q,p,o;var t=["var colIdx=oColumn.getKeyIndex();","oColumn.getThLinerEl().style.overflow="];for(q=l-1,p=2;q>=0;--q){t[p++]="this._elTbody.rows[";t[p++]=q;t[p++]="].cells[colIdx].firstChild.style.overflow=";}t[p]="sOverflow;";t[p+1]="oColumn.getThLinerEl().style.width=";for(q=l-1,o=p+2;q>=0;--q){t[o++]="this._elTbody.rows[";t[o++]=q;t[o++]="].cells[colIdx].firstChild.style.width=";}t[o]="sWidth;";this._aDynFunctions[l]=new Function("oColumn","sWidth","sOverflow",t.join(""));}var n=this._aDynFunctions[l];if(n){n.call(this,r,m,s);}},validateColumnWidths:function(o){var l=this._elColgroup;var q=l.cloneNode(true);var p=false;var n=this._oColumnSet.keys;var k;if(o&&!o.hidden&&!o.width&&(o.getKeyIndex()!==null)){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[o.getKeyIndex()].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true;}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden");}}}else{for(var m=0,j=n.length;m<j;m++){o=n[m];if(!o.hidden&&!o.width){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[m].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true;}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden");}}}}}if(p){l.parentNode.replaceChild(q,l);this._elColgroup=q;}},_clearMinWidth:function(i){if(i.getKeyIndex()!==null){this._elColgroup.childNodes[i.getKeyIndex()].style.width="";}},_restoreMinWidth:function(i){if(i.minWidth&&(i.getKeyIndex()!==null)){this._elColgroup.childNodes[i.getKeyIndex()].style.width=i.minWidth+"px";}},hideColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r);}if(r&&!r.hidden&&r.getTreeIndex()!==null){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=true;c.addClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._clearMinWidth(r);for(var p=0;p<n;p++){c.addClass(o[p].cells[k],d.CLASS_HIDDEN);}}this.fireEvent("columnHideEvent",{column:t});}this._repaintOpera();this._clearTrTemplateEl();}else{}},showColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r);}if(r&&r.hidden&&(r.getTreeIndex()!==null)){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=false;c.removeClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._restoreMinWidth(r);for(var p=0;p<n;p++){c.removeClass(o[p].cells[k],d.CLASS_HIDDEN);}}this.fireEvent("columnShowEvent",{column:t});}this._clearTrTemplateEl();}else{}},removeColumn:function(p){if(!(p instanceof YAHOO.widget.Column)){p=this.getColumn(p);}if(p){var m=p.getTreeIndex();if(m!==null){var o,r,q=p.getKeyIndex();if(q===null){var u=[];var j=this._oColumnSet.getDescendants(p);for(o=0,r=j.length;o<r;o++){var s=j[o].getKeyIndex();if(s!==null){u[u.length]=s;}}if(u.length>0){q=u;}}else{q=[q];}if(q!==null){q.sort(function(v,i){return YAHOO.util.Sort.compare(v,i);});this._destroyTheadEl();var k=this._oColumnSet.getDefinitions();p=k.splice(m,1)[0];this._initColumnSet(k);this._initTheadEl();for(o=q.length-1;o>-1;o--){this._removeColgroupColEl(q[o]);}var t=this._elTbody.rows;if(t.length>0){var n=this.get("renderLoopSize"),l=t.length;
136 this._oChainRender.add({method:function(y){if((this instanceof d)&&this._sId){var x=y.nCurrentRow,v=n>0?Math.min(x+n,t.length):t.length,z=y.aIndexes,w;for(;x<v;++x){for(w=z.length-1;w>-1;w--){t[x].removeChild(t[x].childNodes[z[w]]);}}y.nCurrentRow=x;}},iterations:(n>0)?Math.ceil(l/n):1,argument:{nCurrentRow:0,aIndexes:q},scope:this,timeout:(n>0)?0:-1});this._runRenderChain();}this.fireEvent("columnRemoveEvent",{column:p});return p;}}}},insertColumn:function(r,s){if(r instanceof YAHOO.widget.Column){r=r.getDefinition();}else{if(r.constructor!==Object){return;}}var x=this._oColumnSet;if(!h.isValue(s)||!h.isNumber(s)){s=x.tree[0].length;}this._destroyTheadEl();var z=this._oColumnSet.getDefinitions();z.splice(s,0,r);this._initColumnSet(z);this._initTheadEl();x=this._oColumnSet;var n=x.tree[0][s];var p,t,w=[];var l=x.getDescendants(n);for(p=0,t=l.length;p<t;p++){var u=l[p].getKeyIndex();if(u!==null){w[w.length]=u;}}if(w.length>0){var y=w.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);})[0];for(p=w.length-1;p>-1;p--){this._insertColgroupColEl(w[p]);}var v=this._elTbody.rows;if(v.length>0){var o=this.get("renderLoopSize"),m=v.length;var k=[],q;for(p=0,t=w.length;p<t;p++){var j=w[p];q=this._getTrTemplateEl().childNodes[p].cloneNode(true);q=this._formatTdEl(this._oColumnSet.keys[j],q,j,(j===this._oColumnSet.keys.length-1));k[j]=q;}this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F=D.descKeyIndexes,A=o>0?Math.min(C+o,v.length):v.length,E;for(;C<A;++C){E=v[C].childNodes[y]||null;for(B=F.length-1;B>-1;B--){v[C].insertBefore(D.aTdTemplates[F[B]].cloneNode(true),E);}}D.nCurrentRow=C;}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aTdTemplates:k,descKeyIndexes:w},scope:this,timeout:(o>0)?0:-1});this._runRenderChain();}this.fireEvent("columnInsertEvent",{column:r,index:s});return n;}},reorderColumn:function(q,r){if(!(q instanceof YAHOO.widget.Column)){q=this.getColumn(q);}if(q&&YAHOO.lang.isNumber(r)){var z=q.getTreeIndex();if((z!==null)&&(z!==r)){var p,s,l=q.getKeyIndex(),k,v=[],t;if(l===null){k=this._oColumnSet.getDescendants(q);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t;}}if(v.length>0){l=v;}}else{l=[l];}if(l!==null){l.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);});this._destroyTheadEl();var w=this._oColumnSet.getDefinitions();var j=w.splice(z,1)[0];w.splice(r,0,j);this._initColumnSet(w);this._initTheadEl();var n=this._oColumnSet.tree[0][r];var y=n.getKeyIndex();if(y===null){v=[];k=this._oColumnSet.getDescendants(n);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t;}}if(v.length>0){y=v;}}else{y=[y];}var x=y.sort(function(A,i){return YAHOO.util.Sort.compare(A,i);})[0];this._reorderColgroupColEl(l,x);var u=this._elTbody.rows;if(u.length>0){var o=this.get("renderLoopSize"),m=u.length;this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F,E,A=o>0?Math.min(C+o,u.length):u.length,H=D.aIndexes,G;for(;C<A;++C){F=[];G=u[C];for(B=H.length-1;B>-1;B--){F.push(G.removeChild(G.childNodes[H[B]]));}E=G.childNodes[x]||null;for(B=F.length-1;B>-1;B--){G.insertBefore(F[B],E);}}D.nCurrentRow=C;}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aIndexes:l},scope:this,timeout:(o>0)?0:-1});this._runRenderChain();}this.fireEvent("columnReorderEvent",{column:n,oldIndex:z});return n;}}}},selectColumn:function(k){k=this.getColumn(k);if(k&&!k.selected){if(k.getKeyIndex()!==null){k.selected=true;var l=k.getThEl();c.addClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.addClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED);}m.rowIndex++;},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnSelectEvent",{column:k});}else{}}},unselectColumn:function(k){k=this.getColumn(k);if(k&&k.selected){if(k.getKeyIndex()!==null){k.selected=false;var l=k.getThEl();c.removeClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.removeClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED);}m.rowIndex++;},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnselectEvent",{column:k});}else{}}},getSelectedColumns:function(n){var k=[];var l=this._oColumnSet.keys;for(var m=0,j=l.length;m<j;m++){if(l[m].selected){k[k.length]=l[m];}}return k;},highlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.addClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.addClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED);}n.rowIndex++;},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnHighlightEvent",{column:l});}else{}},unhighlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.removeClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.removeClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED);}n.rowIndex++;},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";
137 this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnhighlightEvent",{column:l});}else{}},addRow:function(o,k){if(h.isNumber(k)&&(k<0||k>this._oRecordSet.getLength())){return;}if(o&&h.isObject(o)){var m=this._oRecordSet.addRecord(o,k);if(m){var i;var j=this.get("paginator");if(j){var n=j.get("totalRecords");if(n!==e.Paginator.VALUE_UNLIMITED){j.set("totalRecords",n+1);}i=this.getRecordIndex(m);var l=(j.getPageRecords())[1];if(i<=l){this.render();}this.fireEvent("rowAddEvent",{record:m});return;}else{i=this.getRecordIndex(m);if(h.isNumber(i)){this._oChainRender.add({method:function(r){if((this instanceof d)&&this._sId){var s=r.record;var p=r.recIndex;var t=this._addTrEl(s);if(t){var q=(this._elTbody.rows[p])?this._elTbody.rows[p]:null;this._elTbody.insertBefore(t,q);if(p===0){this._setFirstRow();}if(q===null){this._setLastRow();}this._setRowStripes();this.hideTableMessage();this.fireEvent("rowAddEvent",{record:s});}}},argument:{record:m,recIndex:i},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}},addRows:function(k,n){if(h.isNumber(n)&&(n<0||n>this._oRecordSet.getLength())){return;}if(h.isArray(k)){var o=this._oRecordSet.addRecords(k,n);if(o){var s=this.getRecordIndex(o[0]);var r=this.get("paginator");if(r){var p=r.get("totalRecords");if(p!==e.Paginator.VALUE_UNLIMITED){r.set("totalRecords",p+o.length);}var q=(r.getPageRecords())[1];if(s<=q){this.render();}this.fireEvent("rowsAddEvent",{records:o});return;}else{var m=this.get("renderLoopSize");var j=s+k.length;var i=(j-s);var l=(s>=this._elTbody.rows.length);this._oChainRender.add({method:function(x){if((this instanceof d)&&this._sId){var y=x.aRecords,w=x.nCurrentRow,v=x.nCurrentRecord,t=m>0?Math.min(w+m,j):j,z=document.createDocumentFragment(),u=(this._elTbody.rows[w])?this._elTbody.rows[w]:null;for(;w<t;w++,v++){z.appendChild(this._addTrEl(y[v]));}this._elTbody.insertBefore(z,u);x.nCurrentRow=w;x.nCurrentRecord=v;}},iterations:(m>0)?Math.ceil(j/m):1,argument:{nCurrentRow:s,nCurrentRecord:0,aRecords:o},scope:this,timeout:(m>0)?0:-1});this._oChainRender.add({method:function(u){var t=u.recIndex;if(t===0){this._setFirstRow();}if(u.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{records:o});},argument:{recIndex:s,isLast:l},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}},updateRow:function(u,k){var r=u;if(!h.isNumber(r)){r=this.getRecordIndex(u);}if(h.isNumber(r)&&(r>=0)){var s=this._oRecordSet,q=s.getRecord(r);if(q){var o=this._oRecordSet.setRecord(k,r),j=this.getTrEl(q),p=q?q.getData():null;if(o){var t=this._aSelections||[],n=0,l=q.getId(),m=o.getId();for(;n<t.length;n++){if((t[n]===l)){t[n]=m;}else{if(t[n].recordId===l){t[n].recordId=m;}}}if(this._oAnchorRecord&&this._oAnchorRecord.getId()===l){this._oAnchorRecord=o;}if(this._oAnchorCell&&this._oAnchorCell.record.getId()===l){this._oAnchorCell.record=o;}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var v=this.get("paginator");if(v){var i=(v.getPageRecords())[0],w=(v.getPageRecords())[1];if((r>=i)||(r<=w)){this.render();}}else{if(j){this._updateTrEl(j,o);}else{this.getTbodyEl().appendChild(this._addTrEl(o));}}this.fireEvent("rowUpdateEvent",{record:o,oldData:p});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}return;},updateRows:function(A,m){if(h.isArray(m)){var s=A,l=this._oRecordSet,o=l.getLength();if(!h.isNumber(A)){s=this.getRecordIndex(A);}if(h.isNumber(s)&&(s>=0)&&(s<l.getLength())){var E=s+m.length,B=l.getRecords(s,m.length),G=l.setRecords(m,s);if(G){var t=this._aSelections||[],D=0,C,u,x,z,y=this._oAnchorRecord?this._oAnchorRecord.getId():null,n=this._oAnchorCell?this._oAnchorCell.record.getId():null;for(;D<B.length;D++){z=B[D].getId();u=G[D];x=u.getId();for(C=0;C<t.length;C++){if((t[C]===z)){t[C]=x;}else{if(t[C].recordId===z){t[C].recordId=x;}}}if(y&&y===z){this._oAnchorRecord=u;}if(n&&n===z){this._oAnchorCell.record=u;}}var F=this.get("paginator");if(F){var r=(F.getPageRecords())[0],p=(F.getPageRecords())[1];if((s>=r)||(E<=p)){this.render();}this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B});return;}else{var k=this.get("renderLoopSize"),v=m.length,w=(E>=o),q=(E>o);this._oChainRender.add({method:function(K){if((this instanceof d)&&this._sId){var L=K.aRecords,J=K.nCurrentRow,I=K.nDataPointer,H=k>0?Math.min(J+k,s+L.length):s+L.length;for(;J<H;J++,I++){if(q&&(J>=o)){this._elTbody.appendChild(this._addTrEl(L[I]));}else{this._updateTrEl(this._elTbody.rows[J],L[I]);}}K.nCurrentRow=J;K.nDataPointer=I;}},iterations:(k>0)?Math.ceil(v/k):1,argument:{nCurrentRow:s,aRecords:G,nDataPointer:0,isAdding:q},scope:this,timeout:(k>0)?0:-1});this._oChainRender.add({method:function(j){var i=j.recIndex;if(i===0){this._setFirstRow();}if(j.isLast){this._setLastRow();}this._setRowStripes();this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B});},argument:{recIndex:s,isLast:w},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return;}}}}},deleteRow:function(s){var k=(h.isNumber(s))?s:this.getRecordIndex(s);if(h.isNumber(k)){var t=this.getRecord(k);if(t){var m=this.getTrIndex(k);var p=t.getId();var r=this._aSelections||[];for(var n=r.length-1;n>-1;n--){if((h.isString(r[n])&&(r[n]===p))||(h.isObject(r[n])&&(r[n].recordId===p))){r.splice(n,1);}}var l=this._oRecordSet.deleteRecord(k);if(l){var q=this.get("paginator");if(q){var o=q.get("totalRecords"),i=q.getPageRecords();if(o!==e.Paginator.VALUE_UNLIMITED){q.set("totalRecords",o-1);}if(!i||k<=i[1]){this.render();}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();}else{if(h.isNumber(m)){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var j=(k===this._oRecordSet.getLength());this._deleteTrEl(m);if(this._elTbody.rows.length>0){if(m===0){this._setFirstRow();
138 }if(j){this._setLastRow();}if(m!=this._elTbody.rows.length){this._setRowStripes(m);}}this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return;}}}}}return null;},deleteRows:function(y,s){var l=(h.isNumber(y))?y:this.getRecordIndex(y);if(h.isNumber(l)){var z=this.getRecord(l);if(z){var m=this.getTrIndex(l);var u=z.getId();var x=this._aSelections||[];for(var q=x.length-1;q>-1;q--){if((h.isString(x[q])&&(x[q]===u))||(h.isObject(x[q])&&(x[q].recordId===u))){x.splice(q,1);}}var n=l;var w=l;if(s&&h.isNumber(s)){n=(s>0)?l+s-1:l;w=(s>0)?l:l+s+1;s=(s>0)?s:s*-1;if(w<0){w=0;s=n-w+1;}}else{s=1;}var p=this._oRecordSet.deleteRecords(w,s);if(p){var v=this.get("paginator"),r=this.get("renderLoopSize");if(v){var t=v.get("totalRecords"),k=v.getPageRecords();if(t!==e.Paginator.VALUE_UNLIMITED){v.set("totalRecords",t-p.length);}if(!k||w<=k[1]){this.render();}this._oChainRender.add({method:function(j){if((this instanceof d)&&this._sId){this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s});}},scope:this,timeout:(r>0)?0:-1});this._runRenderChain();return;}else{if(h.isNumber(m)){var o=w;var i=s;this._oChainRender.add({method:function(B){if((this instanceof d)&&this._sId){var A=B.nCurrentRow,j=(r>0)?(Math.max(A-r,o)-1):o-1;for(;A>j;--A){this._deleteTrEl(A);}B.nCurrentRow=A;}},iterations:(r>0)?Math.ceil(s/r):1,argument:{nCurrentRow:n},scope:this,timeout:(r>0)?0:-1});this._oChainRender.add({method:function(){if(this._elTbody.rows.length>0){this._setFirstRow();this._setLastRow();this._setRowStripes();}this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s});},scope:this,timeout:-1});this._runRenderChain();return;}}}}}return null;},formatCell:function(j,l,m){if(!l){l=this.getRecord(j);}if(!m){m=this.getColumn(this.getCellIndex(j.parentNode));}if(l&&m){var i=m.field;var n=l.getData(i);var k=typeof m.formatter==="function"?m.formatter:d.Formatter[m.formatter+""]||d.Formatter.defaultFormatter;if(k){k.call(this,j,l,m,n);}else{j.innerHTML=n;}this.fireEvent("cellFormatEvent",{record:l,column:m,key:m.key,el:j});}else{}},updateCell:function(k,m,o,j){m=(m instanceof YAHOO.widget.Column)?m:this.getColumn(m);if(m&&m.getField()&&(k instanceof YAHOO.widget.Record)){var l=m.getField(),n=k.getData(l);this._oRecordSet.updateRecordValue(k,l,o);var i=this.getTdEl({record:k,column:m});if(i){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.formatCell(i.firstChild,k,m);this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n});}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});if(!j){this._runRenderChain();}}else{this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n});}}},_updatePaginator:function(j){var i=this.get("paginator");if(i&&j!==i){i.unsubscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}if(j){j.subscribe("changeRequest",this.onPaginatorChangeRequest,this,true);}},_handlePaginatorChange:function(l){if(l.prevValue===l.newValue){return;}var n=l.newValue,m=l.prevValue,k=this._defaultPaginatorContainers();if(m){if(m.getContainerNodes()[0]==k[0]){m.set("containers",[]);}m.destroy();if(k[0]){if(n&&!n.getContainerNodes().length){n.set("containers",k);}else{for(var j=k.length-1;j>=0;--j){if(k[j]){k[j].parentNode.removeChild(k[j]);}}}}}if(!this._bInit){this.render();}if(n){this.renderPaginator();}},_defaultPaginatorContainers:function(l){var j=this._sId+"-paginator0",k=this._sId+"-paginator1",i=c.get(j),m=c.get(k);if(l&&(!i||!m)){if(!i){i=document.createElement("div");i.id=j;c.addClass(i,d.CLASS_PAGINATOR);this._elContainer.insertBefore(i,this._elContainer.firstChild);}if(!m){m=document.createElement("div");m.id=k;c.addClass(m,d.CLASS_PAGINATOR);this._elContainer.appendChild(m);}}return[i,m];},_destroyPaginator:function(){var i=this.get("paginator");if(i){i.destroy();}},renderPaginator:function(){var i=this.get("paginator");if(!i){return;}if(!i.getContainerNodes().length){i.set("containers",this._defaultPaginatorContainers(true));}i.render();},doBeforePaginatorChange:function(i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true;},onPaginatorChangeRequest:function(l){var j=this.doBeforePaginatorChange(l);if(j){if(this.get("dynamicData")){var i=this.getState();i.pagination=l;var k=this.get("generateRequest")(i,this);this.unselectAllRows();this.unselectAllCells();var m={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:i,scope:this};this._oDataSource.sendRequest(k,m);}else{l.paginator.setStartIndex(l.recordOffset,true);l.paginator.setRowsPerPage(l.rowsPerPage,true);this.render();}}else{}},_elLastHighlightedTd:null,_aSelections:null,_oAnchorRecord:null,_oAnchorCell:null,_unselectAllTrEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody);c.removeClass(i,d.CLASS_SELECTED);},_getSelectionTrigger:function(){var l=this.get("selectionMode");var k={};var o,i,j,n,m;if((l=="cellblock")||(l=="cellrange")||(l=="singlecell")){o=this.getLastSelectedCell();if(!o){return null;}else{i=this.getRecord(o.recordId);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null;}else{k.record=i;k.recordIndex=j;k.el=this.getTdEl(o);k.trIndex=m;k.column=this.getColumn(o.columnKey);k.colKeyIndex=k.column.getKeyIndex();k.cell=o;return k;}}}else{i=this.getLastSelectedRecord();if(!i){return null;}else{i=this.getRecord(i);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null;}else{k.record=i;k.recordIndex=j;k.el=n;k.trIndex=m;return k;}}}},_getSelectionAnchor:function(k){var j=this.get("selectionMode");var l={};var m,o,i;if((j=="cellblock")||(j=="cellrange")||(j=="singlecell")){var n=this._oAnchorCell;if(!n){if(k){n=this._oAnchorCell=k.cell;}else{return null;}}m=this._oAnchorCell.record;o=this._oRecordSet.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0;}else{i=this.getRecordIndex(this.getLastTrEl());
139 }}l.record=m;l.recordIndex=o;l.trIndex=i;l.column=this._oAnchorCell.column;l.colKeyIndex=l.column.getKeyIndex();l.cell=n;return l;}else{m=this._oAnchorRecord;if(!m){if(k){m=this._oAnchorRecord=k.record;}else{return null;}}o=this.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0;}else{i=this.getRecordIndex(this.getLastTrEl());}}l.record=m;l.recordIndex=o;l.trIndex=i;return l;}},_handleStandardSelectionByMouse:function(k){var j=k.target;var m=this.getTrEl(j);if(m){var p=k.event;var s=p.shiftKey;var o=p.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&p.metaKey);var r=this.getRecord(m);var l=this._oRecordSet.getRecordIndex(r);var q=this._getSelectionAnchor();var n;if(s&&o){if(q){if(this.isSelected(q.record)){if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l;n++){if(!this.isSelected(n)){this.selectRow(n);}}}else{for(n=q.recordIndex-1;n>=l;n--){if(!this.isSelected(n)){this.selectRow(n);}}}}else{if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l-1;n++){if(this.isSelected(n)){this.unselectRow(n);}}}else{for(n=l+1;n<=q.recordIndex-1;n++){if(this.isSelected(n)){this.unselectRow(n);}}}this.selectRow(r);}}else{this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r);}else{this.selectRow(r);}}}else{if(s){this.unselectAllRows();if(q){if(q.recordIndex<l){for(n=q.recordIndex;n<=l;n++){this.selectRow(n);}}else{for(n=q.recordIndex;n>=l;n--){this.selectRow(n);}}}else{this._oAnchorRecord=r;this.selectRow(r);}}else{if(o){this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r);}else{this.selectRow(r);}}else{this._handleSingleSelectionByMouse(k);return;}}}}},_handleStandardSelectionByKey:function(m){var i=g.getCharCode(m);if((i==38)||(i==40)){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null;}g.stopEvent(m);var l=this._getSelectionAnchor(j);if(k){if((i==40)&&(l.recordIndex<=j.trIndex)){this.selectRow(this.getNextTrEl(j.el));}else{if((i==38)&&(l.recordIndex>=j.trIndex)){this.selectRow(this.getPreviousTrEl(j.el));}else{this.unselectRow(j.el);}}}else{this._handleSingleSelectionByKey(m);}}},_handleSingleSelectionByMouse:function(k){var l=k.target;var j=this.getTrEl(l);if(j){var i=this.getRecord(j);this._oAnchorRecord=i;this.unselectAllRows();this.selectRow(i);}},_handleSingleSelectionByKey:function(l){var i=g.getCharCode(l);if((i==38)||(i==40)){var j=this._getSelectionTrigger();if(!j){return null;}g.stopEvent(l);var k;if(i==38){k=this.getPreviousTrEl(j.el);if(k===null){k=this.getFirstTrEl();}}else{if(i==40){k=this.getNextTrEl(j.el);if(k===null){k=this.getLastTrEl();}}}this.unselectAllRows();this.selectRow(k);this._oAnchorRecord=this.getRecord(k);}},_handleCellBlockSelectionByMouse:function(A){var B=A.target;var l=this.getTdEl(B);if(l){var z=A.event;var q=z.shiftKey;var m=z.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&z.metaKey);var s=this.getTrEl(l);var r=this.getTrIndex(s);var v=this.getColumn(l);var w=v.getKeyIndex();var u=this.getRecord(s);var D=this._oRecordSet.getRecordIndex(u);var p={record:u,column:v};var t=this._getSelectionAnchor();var o=this.getTbodyEl().rows;var n,k,C,y,x;if(q&&m){if(t){if(this.isSelected(t.cell)){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<=w;y++){this.selectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w;y<t.colKeyIndex;y++){this.selectCell(s.cells[y]);}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}else{n=Math.min(t.trIndex,w);k=Math.max(t.trIndex,w);for(y=t.trIndex;y>=r;y--){for(x=k;x>=n;x--){this.selectCell(o[y].cells[x]);}}}}}else{if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<w;y++){this.unselectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w+1;y<t.colKeyIndex;y++){this.unselectCell(s.cells[y]);}}}}if(t.recordIndex<D){for(y=t.trIndex;y<=r;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex===t.trIndex){if(x>t.colKeyIndex){this.unselectCell(C.cells[x]);}}else{if(C.sectionRowIndex===r){if(x<w){this.unselectCell(C.cells[x]);}}else{this.unselectCell(C.cells[x]);}}}}}else{for(y=r;y<=t.trIndex;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex==r){if(x>w){this.unselectCell(C.cells[x]);}}else{if(C.sectionRowIndex==t.trIndex){if(x<t.colKeyIndex){this.unselectCell(C.cells[x]);}}else{this.unselectCell(C.cells[x]);}}}}}this.selectCell(l);}}else{this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p);}else{this.selectCell(p);}}}else{if(q){this.unselectAllCells();if(t){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex;y<=w;y++){this.selectCell(s.cells[y]);}}else{if(w<t.colKeyIndex){for(y=w;y<=t.colKeyIndex;y++){this.selectCell(s.cells[y]);}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}else{n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=r;y<=t.trIndex;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x]);}}}}}else{this._oAnchorCell=p;this.selectCell(p);}}else{if(m){this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p);}else{this.selectCell(p);}}else{this._handleSingleCellSelectionByMouse(A);}}}}},_handleCellBlockSelectionByKey:function(o){var j=g.getCharCode(o);var t=o.shiftKey;if((j==9)||!t){this._handleSingleCellSelectionByKey(o);return;}if((j>36)&&(j<41)){var u=this._getSelectionTrigger();if(!u){return null;}g.stopEvent(o);var r=this._getSelectionAnchor(u);var k,s,l,q,m;var p=this.getTbodyEl().rows;var n=u.el.parentNode;if(j==40){if(r.recordIndex<=u.recordIndex){m=this.getNextTrEl(u.el);if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q);}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k]);}}}else{if(j==38){if(r.recordIndex>=u.recordIndex){m=this.getPreviousTrEl(u.el);
140 if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q);}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k]);}}}else{if(j==39){if(r.colKeyIndex<=u.colKeyIndex){if(u.colKeyIndex<n.cells.length-1){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q);}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex]);}}}else{if(j==37){if(r.colKeyIndex>=u.colKeyIndex){if(u.colKeyIndex>0){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q);}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q);}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex]);}}}}}}}},_handleCellRangeSelectionByMouse:function(y){var z=y.target;var k=this.getTdEl(z);if(k){var x=y.event;var o=x.shiftKey;var l=x.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&x.metaKey);var q=this.getTrEl(k);var p=this.getTrIndex(q);var t=this.getColumn(k);var u=t.getKeyIndex();var s=this.getRecord(q);var B=this._oRecordSet.getRecordIndex(s);var n={record:s,column:t};var r=this._getSelectionAnchor();var m=this.getTbodyEl().rows;var A,w,v;if(o&&l){if(r){if(this.isSelected(r.cell)){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<=u;w++){this.selectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u;w<r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex<B){for(w=r.colKeyIndex+1;w<q.cells.length;w++){this.selectCell(q.cells[w]);}for(w=r.trIndex+1;w<p;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v]);}}for(w=0;w<=u;w++){this.selectCell(q.cells[w]);}}else{for(w=u;w<q.cells.length;w++){this.selectCell(q.cells[w]);}for(w=p+1;w<r.trIndex;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v]);}}for(w=0;w<r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<u;w++){this.unselectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u+1;w<r.colKeyIndex;w++){this.unselectCell(q.cells[w]);}}}}if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex===r.trIndex){if(v>r.colKeyIndex){this.unselectCell(A.cells[v]);}}else{if(A.sectionRowIndex===p){if(v<u){this.unselectCell(A.cells[v]);}}else{this.unselectCell(A.cells[v]);}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>u){this.unselectCell(A.cells[v]);}}else{if(A.sectionRowIndex==r.trIndex){if(v<r.colKeyIndex){this.unselectCell(A.cells[v]);}}else{this.unselectCell(A.cells[v]);}}}}}this.selectCell(k);}}else{this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n);}else{this.selectCell(n);}}}else{if(o){this.unselectAllCells();if(r){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex;w<=u;w++){this.selectCell(q.cells[w]);}}else{if(u<r.colKeyIndex){for(w=u;w<=r.colKeyIndex;w++){this.selectCell(q.cells[w]);}}}}else{if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==r.trIndex){if(v>=r.colKeyIndex){this.selectCell(A.cells[v]);}}else{if(A.sectionRowIndex==p){if(v<=u){this.selectCell(A.cells[v]);}}else{this.selectCell(A.cells[v]);}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>=u){this.selectCell(A.cells[v]);}}else{if(A.sectionRowIndex==r.trIndex){if(v<=r.colKeyIndex){this.selectCell(A.cells[v]);}}else{this.selectCell(A.cells[v]);}}}}}}}else{this._oAnchorCell=n;this.selectCell(n);}}else{if(l){this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n);}else{this.selectCell(n);}}else{this._handleSingleCellSelectionByMouse(y);}}}}},_handleCellRangeSelectionByKey:function(n){var j=g.getCharCode(n);var r=n.shiftKey;if((j==9)||!r){this._handleSingleCellSelectionByKey(n);return;}if((j>36)&&(j<41)){var s=this._getSelectionTrigger();if(!s){return null;}g.stopEvent(n);var q=this._getSelectionAnchor(s);var k,l,p;var o=this.getTbodyEl().rows;var m=s.el.parentNode;if(j==40){l=this.getNextTrEl(s.el);if(q.recordIndex<=s.recordIndex){for(k=s.colKeyIndex+1;k<m.cells.length;k++){p=m.cells[k];this.selectCell(p);}if(l){for(k=0;k<=s.colKeyIndex;k++){p=l.cells[k];this.selectCell(p);}}}else{for(k=s.colKeyIndex;k<m.cells.length;k++){this.unselectCell(m.cells[k]);}if(l){for(k=0;k<s.colKeyIndex;k++){this.unselectCell(l.cells[k]);}}}}else{if(j==38){l=this.getPreviousTrEl(s.el);if(q.recordIndex>=s.recordIndex){for(k=s.colKeyIndex-1;k>-1;k--){p=m.cells[k];this.selectCell(p);}if(l){for(k=m.cells.length-1;k>=s.colKeyIndex;k--){p=l.cells[k];this.selectCell(p);}}}else{for(k=s.colKeyIndex;k>-1;k--){this.unselectCell(m.cells[k]);}if(l){for(k=m.cells.length-1;k>s.colKeyIndex;k--){this.unselectCell(l.cells[k]);}}}}else{if(j==39){l=this.getNextTrEl(s.el);if(q.recordIndex<s.recordIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p);}else{if(l){p=l.cells[0];this.selectCell(p);}}}else{if(q.recordIndex>s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex<m.cells.length-1){}else{}}else{if(q.colKeyIndex<=s.colKeyIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p);}else{if(s.trIndex<o.length-1){p=l.cells[0];this.selectCell(p);}}}else{this.unselectCell(m.cells[s.colKeyIndex]);}}}}else{if(j==37){l=this.getPreviousTrEl(s.el);if(q.recordIndex<s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}else{if(q.recordIndex>s.recordIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p);}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p);
141 }}}else{if(q.colKeyIndex>=s.colKeyIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p);}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p);}}}else{this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}}}}}}}}},_handleSingleCellSelectionByMouse:function(n){var o=n.target;var k=this.getTdEl(o);if(k){var j=this.getTrEl(k);var i=this.getRecord(j);var m=this.getColumn(k);var l={record:i,column:m};this._oAnchorCell=l;this.unselectAllCells();this.selectCell(l);}},_handleSingleCellSelectionByKey:function(m){var i=g.getCharCode(m);if((i==9)||((i>36)&&(i<41))){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null;}var l;if(i==40){l=this.getBelowTdEl(j.el);if(l===null){l=j.el;}}else{if(i==38){l=this.getAboveTdEl(j.el);if(l===null){l=j.el;}}else{if((i==39)||(!k&&(i==9))){l=this.getNextTdEl(j.el);if(l===null){return;}}else{if((i==37)||(k&&(i==9))){l=this.getPreviousTdEl(j.el);if(l===null){return;}}}}}g.stopEvent(m);this.unselectAllCells();this.selectCell(l);this._oAnchorCell={record:this.getRecord(l),column:this.getColumn(l)};}},getSelectedTrEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody);},selectRow:function(p){var o,i;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p);i=this.getTrEl(o);}else{if(h.isNumber(p)){o=this.getRecord(p);i=this.getTrEl(o);}else{i=this.getTrEl(p);o=this.getRecord(i);}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m);}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break;}}}if(l>-1){n.splice(l,1);}n.push(m);this._aSelections=n;if(!this._oAnchorRecord){this._oAnchorRecord=o;}if(i){c.addClass(i,d.CLASS_SELECTED);}this.fireEvent("rowSelectEvent",{record:o,el:i});}else{}},unselectRow:function(p){var i=this.getTrEl(p);var o;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p);}else{if(h.isNumber(p)){o=this.getRecord(p);}else{o=this.getRecord(i);}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m);}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break;}}}if(l>-1){n.splice(l,1);this._aSelections=n;c.removeClass(i,d.CLASS_SELECTED);this.fireEvent("rowUnselectEvent",{record:o,el:i});return;}}},unselectAllRows:function(){var k=this._aSelections||[],m,l=[];for(var i=k.length-1;i>-1;i--){if(h.isString(k[i])){m=k.splice(i,1);l[l.length]=this.getRecord(h.isArray(m)?m[0]:m);}}this._aSelections=k;this._unselectAllTrEls();this.fireEvent("unselectAllRowsEvent",{records:l});},_unselectAllTdEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody);c.removeClass(i,d.CLASS_SELECTED);},getSelectedTdEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody);},selectCell:function(i){var p=this.getTdEl(i);if(p){var o=this.getRecord(p);var q=this.getColumn(this.getCellIndex(p));var m=q.getKey();if(o&&m){var n=this._aSelections||[];var l=o.getId();for(var k=n.length-1;k>-1;k--){if((n[k].recordId===l)&&(n[k].columnKey===m)){n.splice(k,1);break;}}n.push({recordId:l,columnKey:m});this._aSelections=n;if(!this._oAnchorCell){this._oAnchorCell={record:o,column:q};}c.addClass(p,d.CLASS_SELECTED);this.fireEvent("cellSelectEvent",{record:o,column:q,key:m,el:p});return;}}},unselectCell:function(i){var o=this.getTdEl(i);if(o){var n=this.getRecord(o);var p=this.getColumn(this.getCellIndex(o));var l=p.getKey();if(n&&l){var m=this._aSelections||[];var q=n.getId();for(var k=m.length-1;k>-1;k--){if((m[k].recordId===q)&&(m[k].columnKey===l)){m.splice(k,1);this._aSelections=m;c.removeClass(o,d.CLASS_SELECTED);this.fireEvent("cellUnselectEvent",{record:n,column:p,key:l,el:o});return;}}}}},unselectAllCells:function(){var k=this._aSelections||[];for(var i=k.length-1;i>-1;i--){if(h.isObject(k[i])){k.splice(i,1);}}this._aSelections=k;this._unselectAllTdEls();this.fireEvent("unselectAllCellsEvent");},isSelected:function(p){if(p&&(p.ownerDocument==document)){return(c.hasClass(this.getTdEl(p),d.CLASS_SELECTED)||c.hasClass(this.getTrEl(p),d.CLASS_SELECTED));}else{var n,k,i;var m=this._aSelections;if(m&&m.length>0){if(p instanceof YAHOO.widget.Record){n=p;}else{if(h.isNumber(p)){n=this.getRecord(p);}}if(n){k=n.getId();if(m.indexOf){if(m.indexOf(k)>-1){return true;}}else{for(i=m.length-1;i>-1;i--){if(m[i]===k){return true;}}}}else{if(p.record&&p.column){k=p.record.getId();var l=p.column.getKey();for(i=m.length-1;i>-1;i--){if((m[i].recordId===k)&&(m[i].columnKey===l)){return true;}}}}}}return false;},getSelectedRows:function(){var i=[];var l=this._aSelections||[];for(var k=0;k<l.length;k++){if(h.isString(l[k])){i.push(l[k]);}}return i;},getSelectedCells:function(){var k=[];var l=this._aSelections||[];for(var i=0;i<l.length;i++){if(l[i]&&h.isObject(l[i])){k.push(l[i]);}}return k;},getLastSelectedRecord:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(h.isString(k[j])){return k[j];}}}},getLastSelectedCell:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(k[j].recordId&&k[j].columnKey){return k[j];}}}},highlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.addClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowHighlightEvent",{record:j,el:i});return;}},unhighlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.removeClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowUnhighlightEvent",{record:j,el:i});return;}},highlightCell:function(i){var l=this.getTdEl(i);if(l){if(this._elLastHighlightedTd){this.unhighlightCell(this._elLastHighlightedTd);}var k=this.getRecord(l);var m=this.getColumn(this.getCellIndex(l));var j=m.getKey();c.addClass(l,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=l;this.fireEvent("cellHighlightEvent",{record:k,column:m,key:j,el:l});return;}},unhighlightCell:function(i){var k=this.getTdEl(i);if(k){var j=this.getRecord(k);c.removeClass(k,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=null;this.fireEvent("cellUnhighlightEvent",{record:j,column:this.getColumn(this.getCellIndex(k)),key:this.getColumn(this.getCellIndex(k)).getKey(),el:k});
142 return;}},addCellEditor:function(j,i){j.editor=i;j.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);j.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);j.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);j.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);j.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);j.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);j.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);j.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true);},getCellEditor:function(){return this._oCellEditor;},showCellEditor:function(p,q,l){p=this.getTdEl(p);if(p){l=this.getColumn(p);if(l&&l.editor){var j=this._oCellEditor;if(j){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(j.isActive){this.cancelCellEditor();}}}if(l.editor instanceof YAHOO.widget.BaseCellEditor){j=l.editor;var n=j.attach(this,p);if(n){j.render();j.move();n=this.doBeforeShowCellEditor(j);if(n){j.show();this._oCellEditor=j;}}}else{if(!q||!(q instanceof YAHOO.widget.Record)){q=this.getRecord(p);}if(!l||!(l instanceof YAHOO.widget.Column)){l=this.getColumn(p);}if(q&&l){if(!this._oCellEditor||this._oCellEditor.container){this._initCellEditorEl();}j=this._oCellEditor;j.cell=p;j.record=q;j.column=l;j.validator=(l.editorOptions&&h.isFunction(l.editorOptions.validator))?l.editorOptions.validator:null;j.value=q.getData(l.key);j.defaultValue=null;var k=j.container;var o=c.getX(p);var m=c.getY(p);if(isNaN(o)||isNaN(m)){o=p.offsetLeft+c.getX(this._elTbody.parentNode)-this._elTbody.scrollLeft;m=p.offsetTop+c.getY(this._elTbody.parentNode)-this._elTbody.scrollTop+this._elThead.offsetHeight;}k.style.left=o+"px";k.style.top=m+"px";this.doBeforeShowCellEditor(this._oCellEditor);k.style.display="";g.addListener(k,"keydown",function(s,r){if((s.keyCode==27)){r.cancelCellEditor();r.focusTbodyEl();}else{r.fireEvent("editorKeydownEvent",{editor:r._oCellEditor,event:s});}},this);var i;if(h.isString(l.editor)){switch(l.editor){case"checkbox":i=d.editCheckbox;break;case"date":i=d.editDate;break;case"dropdown":i=d.editDropdown;break;case"radio":i=d.editRadio;break;case"textarea":i=d.editTextarea;break;case"textbox":i=d.editTextbox;break;default:i=null;}}else{if(h.isFunction(l.editor)){i=l.editor;}}if(i){i(this._oCellEditor,this);if(!l.editorOptions||!l.editorOptions.disableBtns){this.showCellEditorBtns(k);}j.isActive=true;this.fireEvent("editorShowEvent",{editor:j});return;}}}}}},_initCellEditorEl:function(){var i=document.createElement("div");i.id=this._sId+"-celleditor";i.style.display="none";i.tabIndex=0;c.addClass(i,d.CLASS_EDITOR);var k=c.getFirstChild(document.body);if(k){i=c.insertBefore(i,k);}else{i=document.body.appendChild(i);}var j={};j.container=i;j.value=null;j.isActive=false;this._oCellEditor=j;},doBeforeShowCellEditor:function(i){return true;},saveCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{if(this._oCellEditor.isActive){var i=this._oCellEditor.value;var j=this._oCellEditor.record.getData(this._oCellEditor.column.key);if(this._oCellEditor.validator){i=this._oCellEditor.value=this._oCellEditor.validator.call(this,i,j,this._oCellEditor);if(i===null){this.resetCellEditor();this.fireEvent("editorRevertEvent",{editor:this._oCellEditor,oldData:j,newData:i});return;}}this._oRecordSet.updateRecordValue(this._oCellEditor.record,this._oCellEditor.column.key,this._oCellEditor.value);this.formatCell(this._oCellEditor.cell.firstChild,this._oCellEditor.record,this._oCellEditor.column);this._oChainRender.add({method:function(){this.validateColumnWidths();},scope:this});this._oChainRender.run();this.resetCellEditor();this.fireEvent("editorSaveEvent",{editor:this._oCellEditor,oldData:j,newData:i});}}}},cancelCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{if(this._oCellEditor.isActive){this.resetCellEditor();this.fireEvent("editorCancelEvent",{editor:this._oCellEditor});}}}},destroyCellEditor:function(){if(this._oCellEditor){this._oCellEditor.destroy();this._oCellEditor=null;}},_onEditorShowEvent:function(i){this.fireEvent("editorShowEvent",i);},_onEditorKeydownEvent:function(i){this.fireEvent("editorKeydownEvent",i);},_onEditorRevertEvent:function(i){this.fireEvent("editorRevertEvent",i);},_onEditorSaveEvent:function(i){this.fireEvent("editorSaveEvent",i);},_onEditorCancelEvent:function(i){this.fireEvent("editorCancelEvent",i);},_onEditorBlurEvent:function(i){this.fireEvent("editorBlurEvent",i);},_onEditorBlockEvent:function(i){this.fireEvent("editorBlockEvent",i);},_onEditorUnblockEvent:function(i){this.fireEvent("editorUnblockEvent",i);},onEditorBlurEvent:function(i){if(i.editor.disableBtns){if(i.editor.save){i.editor.save();}}else{if(i.editor.cancel){i.editor.cancel();}}},onEditorBlockEvent:function(i){this.disable();},onEditorUnblockEvent:function(i){this.undisable();},doBeforeLoadData:function(i,j,k){return true;},onEventSortColumn:function(k){var i=k.event;var m=k.target;var j=this.getThEl(m)||this.getTdEl(m);if(j){var l=this.getColumn(j);if(l.sortable){g.stopEvent(i);this.sortColumn(l);}}else{}},onEventSelectColumn:function(i){this.selectColumn(i.target);},onEventHighlightColumn:function(i){this.highlightColumn(i.target);},onEventUnhighlightColumn:function(i){this.unhighlightColumn(i.target);},onEventSelectRow:function(j){var i=this.get("selectionMode");if(i=="single"){this._handleSingleSelectionByMouse(j);}else{this._handleStandardSelectionByMouse(j);}},onEventSelectCell:function(j){var i=this.get("selectionMode");if(i=="cellblock"){this._handleCellBlockSelectionByMouse(j);}else{if(i=="cellrange"){this._handleCellRangeSelectionByMouse(j);}else{this._handleSingleCellSelectionByMouse(j);}}},onEventHighlightRow:function(i){this.highlightRow(i.target);},onEventUnhighlightRow:function(i){this.unhighlightRow(i.target);},onEventHighlightCell:function(i){this.highlightCell(i.target);
143 },onEventUnhighlightCell:function(i){this.unhighlightCell(i.target);},onEventFormatCell:function(i){var l=i.target;var j=this.getTdEl(l);if(j){var k=this.getColumn(this.getCellIndex(j));this.formatCell(j.firstChild,this.getRecord(j),k);}else{}},onEventShowCellEditor:function(i){if(!this.isDisabled()){this.showCellEditor(i.target);}},onEventSaveCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save();}else{this.saveCellEditor();}}},onEventCancelCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel();}else{this.cancelCellEditor();}}},onDataReturnInitializeTable:function(i,j,k){if((this instanceof d)&&this._sId){this.initializeTable();this.onDataReturnSetRows(i,j,k);}},onDataReturnReplaceRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){this._oRecordSet.reset();if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset;}else{if(k){i=k.getStartIndex();}}}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render();}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnAppendRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results);this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnInsertRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results,(l?l.insertIndex:0));this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnUpdateRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.updateRows((l?l.updateIndex:0),k.results);this._handleDataReturnPayload(j,k,l);}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}},onDataReturnSetRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset;}else{if(k){i=k.getStartIndex();}}this._oRecordSet.reset();}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render();}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR);}}}else{}},handleDataReturnPayload:function(j,i,k){return k||{};},_handleDataReturnPayload:function(k,j,l){l=this.handleDataReturnPayload(k,j,l);if(l){var i=this.get("paginator");if(i){if(this.get("dynamicData")){if(e.Paginator.isNumeric(l.totalRecords)){i.set("totalRecords",l.totalRecords);}}else{i.set("totalRecords",this._oRecordSet.getLength());}if(h.isObject(l.pagination)){i.set("rowsPerPage",l.pagination.rowsPerPage);i.set("recordOffset",l.pagination.recordOffset);}}if(l.sortedBy){this.set("sortedBy",l.sortedBy);}else{if(l.sorting){this.set("sortedBy",l.sorting);}}}},showCellEditorBtns:function(k){var l=k.appendChild(document.createElement("div"));c.addClass(l,d.CLASS_BUTTON);var j=l.appendChild(document.createElement("button"));c.addClass(j,d.CLASS_DEFAULT);j.innerHTML="OK";g.addListener(j,"click",function(n,m){m.onEventSaveCellEditor(n,m);m.focusTbodyEl();},this,true);var i=l.appendChild(document.createElement("button"));i.innerHTML="Cancel";g.addListener(i,"click",function(n,m){m.onEventCancelCellEditor(n,m);m.focusTbodyEl();},this,true);},resetCellEditor:function(){var i=this._oCellEditor.container;i.style.display="none";g.purgeElement(i,true);i.innerHTML="";this._oCellEditor.value=null;this._oCellEditor.isActive=false;},getBody:function(){return this.getTbodyEl();},getCell:function(i){return this.getTdEl(i);},getRow:function(i){return this.getTrEl(i);},refreshView:function(){this.render();},select:function(k){if(!h.isArray(k)){k=[k];}for(var j=0;j<k.length;j++){this.selectRow(k[j]);}},onEventEditCell:function(i){this.onEventShowCellEditor(i);},_syncColWidths:function(){this.validateColumnWidths();}});d.prototype.onDataReturnSetRecords=d.prototype.onDataReturnSetRows;d.prototype.onPaginatorChange=d.prototype.onPaginatorChangeRequest;d.editCheckbox=function(){};d.editDate=function(){};d.editDropdown=function(){};d.editRadio=function(){};d.editTextarea=function(){};d.editTextbox=function(){};})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,a=YAHOO.env.ua,d=f.Dom,j=f.Event,i=f.DataSourceBase,g=e.DataTable,b=e.Paginator;e.ScrollingDataTable=function(n,m,k,l){l=l||{};if(l.scrollable){l.scrollable=false;}this._init();e.ScrollingDataTable.superclass.constructor.call(this,n,m,k,l);this.subscribe("columnShowEvent",this._onColumnChange);};var h=e.ScrollingDataTable;c.augmentObject(h,{CLASS_HEADER:"yui-dt-hd",CLASS_BODY:"yui-dt-bd"});c.extend(h,g,{_elHdContainer:null,_elHdTable:null,_elBdContainer:null,_elBdThead:null,_elTmpContainer:null,_elTmpTable:null,_bScrollbarX:null,initAttributes:function(k){k=k||{};h.superclass.initAttributes.call(this,k);this.setAttributeConfig("width",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elHdContainer.style.width=l;this._elBdContainer.style.width=l;this._syncScrollX();this._syncScrollOverhang();}}});this.setAttributeConfig("height",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elBdContainer.style.height=l;
144 this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();}}});this.setAttributeConfig("COLOR_COLUMNFILLER",{value:"#F2F2F2",validator:c.isString,method:function(l){if(this._elHdContainer){this._elHdContainer.style.backgroundColor=l;}}});},_init:function(){this._elHdContainer=null;this._elHdTable=null;this._elBdContainer=null;this._elBdThead=null;this._elTmpContainer=null;this._elTmpTable=null;},_initDomElements:function(k){this._initContainerEl(k);if(this._elContainer&&this._elHdContainer&&this._elBdContainer){this._initTableEl();if(this._elHdTable&&this._elTable){this._initColgroupEl(this._elHdTable);this._initTheadEl(this._elHdTable,this._elTable);this._initTbodyEl(this._elTable);this._initMsgTbodyEl(this._elTable);}}if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody||!this._elHdTable||!this._elBdThead){return false;}else{return true;}},_destroyContainerEl:function(k){d.removeClass(k,g.CLASS_SCROLLABLE);h.superclass._destroyContainerEl.call(this,k);this._elHdContainer=null;this._elBdContainer=null;},_initContainerEl:function(l){h.superclass._initContainerEl.call(this,l);if(this._elContainer){l=this._elContainer;d.addClass(l,g.CLASS_SCROLLABLE);var k=document.createElement("div");k.style.width=this.get("width")||"";k.style.backgroundColor=this.get("COLOR_COLUMNFILLER");d.addClass(k,h.CLASS_HEADER);this._elHdContainer=k;l.appendChild(k);var m=document.createElement("div");m.style.width=this.get("width")||"";m.style.height=this.get("height")||"";d.addClass(m,h.CLASS_BODY);j.addListener(m,"scroll",this._onScroll,this);this._elBdContainer=m;l.appendChild(m);}},_initCaptionEl:function(k){},_destroyHdTableEl:function(){var k=this._elHdTable;if(k){j.purgeElement(k,true);k.parentNode.removeChild(k);this._elBdThead=null;}},_initTableEl:function(){if(this._elHdContainer){this._destroyHdTableEl();this._elHdTable=this._elHdContainer.appendChild(document.createElement("table"));j.delegate(this._elHdTable,"mouseenter",this._onTableMouseover,"thead ."+g.CLASS_LABEL,this);j.delegate(this._elHdTable,"mouseleave",this._onTableMouseout,"thead ."+g.CLASS_LABEL,this);}h.superclass._initTableEl.call(this,this._elBdContainer);},_initTheadEl:function(l,k){l=l||this._elHdTable;k=k||this._elTable;this._initBdTheadEl(k);h.superclass._initTheadEl.call(this,l);},_initThEl:function(l,k){h.superclass._initThEl.call(this,l,k);l.id=this.getId()+"-fixedth-"+k.getSanitizedKey();},_destroyBdTheadEl:function(){var k=this._elBdThead;if(k){var l=k.parentNode;j.purgeElement(k,true);l.removeChild(k);this._elBdThead=null;this._destroyColumnHelpers();}},_initBdTheadEl:function(t){if(t){this._destroyBdTheadEl();var p=t.insertBefore(document.createElement("thead"),t.firstChild);var v=this._oColumnSet,u=v.tree,o,l,s,q,n,m,r;for(q=0,m=u.length;q<m;q++){l=p.appendChild(document.createElement("tr"));for(n=0,r=u[q].length;n<r;n++){s=u[q][n];o=l.appendChild(document.createElement("th"));this._initBdThEl(o,s,q,n);}}this._elBdThead=p;}},_initBdThEl:function(n,m){n.id=this.getId()+"-th-"+m.getSanitizedKey();n.rowSpan=m.getRowspan();n.colSpan=m.getColspan();if(m.abbr){n.abbr=m.abbr;}var l=m.getKey();var k=c.isValue(m.label)?m.label:l;n.innerHTML=k;},_initTbodyEl:function(k){h.superclass._initTbodyEl.call(this,k);k.style.marginTop=(this._elTbody.offsetTop>0)?"-"+this._elTbody.offsetTop+"px":0;},_focusEl:function(l){l=l||this._elTbody;var k=this;this._storeScrollPositions();setTimeout(function(){setTimeout(function(){try{l.focus();k._restoreScrollPositions();}catch(m){}},0);},0);},_runRenderChain:function(){this._storeScrollPositions();this._oChainRender.run();},_storeScrollPositions:function(){this._nScrollTop=this._elBdContainer.scrollTop;this._nScrollLeft=this._elBdContainer.scrollLeft;},clearScrollPositions:function(){this._nScrollTop=0;this._nScrollLeft=0;},_restoreScrollPositions:function(){if(this._nScrollTop){this._elBdContainer.scrollTop=this._nScrollTop;this._nScrollTop=null;}if(this._nScrollLeft){this._elBdContainer.scrollLeft=this._nScrollLeft;this._elHdContainer.scrollLeft=this._nScrollLeft;this._nScrollLeft=null;}},_validateColumnWidth:function(n,k){if(!n.width&&!n.hidden){var p=n.getThEl();if(n._calculatedWidth){this._setColumnWidth(n,"auto","visible");}if(p.offsetWidth!==k.offsetWidth){var m=(p.offsetWidth>k.offsetWidth)?n.getThLinerEl():k.firstChild;var l=Math.max(0,(m.offsetWidth-(parseInt(d.getStyle(m,"paddingLeft"),10)|0)-(parseInt(d.getStyle(m,"paddingRight"),10)|0)),n.minWidth);var o="visible";if((n.maxAutoWidth>0)&&(l>n.maxAutoWidth)){l=n.maxAutoWidth;o="hidden";}this._elTbody.style.display="none";this._setColumnWidth(n,l+"px",o);n._calculatedWidth=l;this._elTbody.style.display="";}}},validateColumnWidths:function(s){var u=this._oColumnSet.keys,w=u.length,l=this.getFirstTrEl();if(a.ie){this._setOverhangValue(1);}if(u&&l&&(l.childNodes.length===w)){var m=this.get("width");if(m){this._elHdContainer.style.width="";this._elBdContainer.style.width="";}this._elContainer.style.width="";if(s&&c.isNumber(s.getKeyIndex())){this._validateColumnWidth(s,l.childNodes[s.getKeyIndex()]);}else{var t,k=[],o,q,r;for(q=0;q<w;q++){s=u[q];if(!s.width&&!s.hidden&&s._calculatedWidth){k[k.length]=s;}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){this._setColumnWidth(k[q],"auto","visible");}this._elTbody.style.display="";k=[];for(q=0;q<w;q++){s=u[q];t=l.childNodes[q];if(!s.width&&!s.hidden){var n=s.getThEl();if(n.offsetWidth!==t.offsetWidth){var v=(n.offsetWidth>t.offsetWidth)?s.getThLinerEl():t.firstChild;var p=Math.max(0,(v.offsetWidth-(parseInt(d.getStyle(v,"paddingLeft"),10)|0)-(parseInt(d.getStyle(v,"paddingRight"),10)|0)),s.minWidth);var x="visible";if((s.maxAutoWidth>0)&&(p>s.maxAutoWidth)){p=s.maxAutoWidth;x="hidden";}k[k.length]=[s,p,x];}}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){o=k[q];this._setColumnWidth(o[0],o[1]+"px",o[2]);o[0]._calculatedWidth=o[1];}this._elTbody.style.display="";}if(m){this._elHdContainer.style.width=m;this._elBdContainer.style.width=m;
145 }}this._syncScroll();this._restoreScrollPositions();},_syncScroll:function(){this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();if(a.opera){this._elHdContainer.scrollLeft=this._elBdContainer.scrollLeft;if(!this.get("width")){document.body.style+="";}}},_syncScrollY:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("width")){this._elContainer.style.width=(l.scrollHeight>l.clientHeight)?(k.parentNode.clientWidth+19)+"px":(k.parentNode.clientWidth+2)+"px";}},_syncScrollX:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("height")&&(a.ie)){l.style.height=(l.scrollWidth>l.offsetWidth)?(k.parentNode.offsetHeight+18)+"px":k.parentNode.offsetHeight+"px";}if(this._elTbody.rows.length===0){this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";}else{this._elMsgTbody.parentNode.style.width="";}},_syncScrollOverhang:function(){var l=this._elBdContainer,k=1;if((l.scrollHeight>l.clientHeight)&&(l.scrollWidth>l.clientWidth)){k=18;}this._setOverhangValue(k);},_setOverhangValue:function(n){var p=this._oColumnSet.headers[this._oColumnSet.headers.length-1]||[],l=p.length,k=this._sId+"-fixedth-",o=n+"px solid "+this.get("COLOR_COLUMNFILLER");this._elThead.style.display="none";for(var m=0;m<l;m++){d.get(k+p[m]).style.borderRight=o;}this._elThead.style.display="";},getHdContainerEl:function(){return this._elHdContainer;},getBdContainerEl:function(){return this._elBdContainer;},getHdTableEl:function(){return this._elHdTable;},getBdTableEl:function(){return this._elTable;},disable:function(){var k=this._elMask;k.style.width=this._elBdContainer.offsetWidth+"px";k.style.height=this._elHdContainer.offsetHeight+this._elBdContainer.offsetHeight+"px";k.style.display="";this.fireEvent("disableEvent");},removeColumn:function(m){var k=this._elHdContainer.scrollLeft;var l=this._elBdContainer.scrollLeft;m=h.superclass.removeColumn.call(this,m);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=l;return m;},insertColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.insertColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o;},reorderColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.reorderColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o;},setColumnWidth:function(l,k){l=this.getColumn(l);if(l){this._storeScrollPositions();if(c.isNumber(k)){k=(k>l.minWidth)?k:l.minWidth;l.width=k;this._setColumnWidth(l,k+"px");this._syncScroll();this.fireEvent("columnSetWidthEvent",{column:l,width:k});}else{if(k===null){l.width=k;this._setColumnWidth(l,"auto");this.validateColumnWidths(l);this.fireEvent("columnUnsetWidthEvent",{column:l});}}this._clearTrTemplateEl();}else{}},scrollTo:function(m){var l=this.getTdEl(m);if(l){this.clearScrollPositions();this.getBdContainerEl().scrollLeft=l.offsetLeft;this.getBdContainerEl().scrollTop=l.parentNode.offsetTop;}else{var k=this.getTrEl(m);if(k){this.clearScrollPositions();this.getBdContainerEl().scrollTop=k.offsetTop;}}},showTableMessage:function(o,k){var p=this._elMsgTd;if(c.isString(o)){p.firstChild.innerHTML=o;}if(c.isString(k)){d.addClass(p.firstChild,k);}var n=this.getTheadEl();var l=n.parentNode;var m=l.offsetWidth;this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:o,className:k});},_onColumnChange:function(k){var l=(k.column)?k.column:(k.editor)?k.editor.column:null;this._storeScrollPositions();this.validateColumnWidths(l);},_onScroll:function(m,l){l._elHdContainer.scrollLeft=l._elBdContainer.scrollLeft;if(l._oCellEditor&&l._oCellEditor.isActive){l.fireEvent("editorBlurEvent",{editor:l._oCellEditor});l.cancelCellEditor();}var n=j.getTarget(m);var k=n.nodeName.toLowerCase();l.fireEvent("tableScrollEvent",{event:m,target:n});},_onTheadKeydown:function(n,l){if(j.getCharCode(n)===9){setTimeout(function(){if((l instanceof h)&&l._sId){l._elBdContainer.scrollLeft=l._elHdContainer.scrollLeft;}},0);}var o=j.getTarget(n);var k=o.nodeName.toLowerCase();var m=true;while(o&&(k!="table")){switch(k){case"body":return;case"input":case"textarea":break;case"thead":m=l.fireEvent("theadKeyEvent",{target:o,event:n});break;default:break;}if(m===false){return;}else{o=o.parentNode;if(o){k=o.nodeName.toLowerCase();}}}l.fireEvent("tableKeyEvent",{target:(o||l._elContainer),event:n});}});})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,d=f.Dom,i=f.Event,h=e.DataTable;e.BaseCellEditor=function(k,j){this._sId=this._sId||d.generateId(null,"yui-ceditor");YAHOO.widget.BaseCellEditor._nCount++;this._sType=k;this._initConfigs(j);this._initEvents();this._needsRender=true;};var a=e.BaseCellEditor;c.augmentObject(a,{_nCount:0,CLASS_CELLEDITOR:"yui-ceditor"});a.prototype={_sId:null,_sType:null,_oDataTable:null,_oColumn:null,_oRecord:null,_elTd:null,_elContainer:null,_elCancelBtn:null,_elSaveBtn:null,_initConfigs:function(k){if(k&&YAHOO.lang.isObject(k)){for(var j in k){if(j){this[j]=k[j];}}}},_initEvents:function(){this.createEvent("showEvent");this.createEvent("keydownEvent");this.createEvent("invalidDataEvent");this.createEvent("revertEvent");this.createEvent("saveEvent");this.createEvent("cancelEvent");this.createEvent("blurEvent");this.createEvent("blockEvent");this.createEvent("unblockEvent");},_initContainerEl:function(){if(this._elContainer){YAHOO.util.Event.purgeElement(this._elContainer,true);this._elContainer.innerHTML="";}var j=document.createElement("div");j.id=this.getId()+"-container";j.style.display="none";j.tabIndex=0;this.className=c.isArray(this.className)?this.className:this.className?[this.className]:[];this.className[this.className.length]=h.CLASS_EDITOR;j.className=this.className.join(" ");document.body.insertBefore(j,document.body.firstChild);this._elContainer=j;},_initShimEl:function(){if(this.useIFrame){if(!this._elIFrame){var j=document.createElement("iframe");
146 j.src="javascript:false";j.frameBorder=0;j.scrolling="no";j.style.display="none";j.className=h.CLASS_EDITOR_SHIM;j.tabIndex=-1;j.role="presentation";j.title="Presentational iframe shim";document.body.insertBefore(j,document.body.firstChild);this._elIFrame=j;}}},_hide:function(){this.getContainerEl().style.display="none";if(this._elIFrame){this._elIFrame.style.display="none";}this.isActive=false;this.getDataTable()._oCellEditor=null;},asyncSubmitter:null,value:null,defaultValue:null,validator:null,resetInvalidData:true,isActive:false,LABEL_SAVE:"Save",LABEL_CANCEL:"Cancel",disableBtns:false,useIFrame:false,className:null,toString:function(){return"CellEditor instance "+this._sId;},getId:function(){return this._sId;},getDataTable:function(){return this._oDataTable;},getColumn:function(){return this._oColumn;},getRecord:function(){return this._oRecord;},getTdEl:function(){return this._elTd;},getContainerEl:function(){return this._elContainer;},destroy:function(){this.unsubscribeAll();var k=this.getColumn();if(k){k.editor=null;}var j=this.getContainerEl();if(j){i.purgeElement(j,true);j.parentNode.removeChild(j);}},render:function(){if(!this._needsRender){return;}this._initContainerEl();this._initShimEl();i.addListener(this.getContainerEl(),"keydown",function(l,j){if((l.keyCode==27)){var k=i.getTarget(l);if(k.nodeName&&k.nodeName.toLowerCase()==="select"){k.blur();}j.cancel();}j.fireEvent("keydownEvent",{editor:j,event:l});},this);this.renderForm();if(!this.disableBtns){this.renderBtns();}this.doAfterRender();this._needsRender=false;},renderBtns:function(){var l=this.getContainerEl().appendChild(document.createElement("div"));l.className=h.CLASS_BUTTON;var k=l.appendChild(document.createElement("button"));k.className=h.CLASS_DEFAULT;k.innerHTML=this.LABEL_SAVE;i.addListener(k,"click",function(m){this.save();},this,true);this._elSaveBtn=k;var j=l.appendChild(document.createElement("button"));j.innerHTML=this.LABEL_CANCEL;i.addListener(j,"click",function(m){this.cancel();},this,true);this._elCancelBtn=j;},attach:function(n,l){if(n instanceof YAHOO.widget.DataTable){this._oDataTable=n;l=n.getTdEl(l);if(l){this._elTd=l;var m=n.getColumn(l);if(m){this._oColumn=m;var j=n.getRecord(l);if(j){this._oRecord=j;var k=j.getData(this.getColumn().getField());this.value=(k!==undefined)?k:this.defaultValue;return true;}}}}return false;},move:function(){var m=this.getContainerEl(),l=this.getTdEl(),j=d.getX(l),n=d.getY(l);if(isNaN(j)||isNaN(n)){var k=this.getDataTable().getTbodyEl();j=l.offsetLeft+d.getX(k.parentNode)-k.scrollLeft;n=l.offsetTop+d.getY(k.parentNode)-k.scrollTop+this.getDataTable().getTheadEl().offsetHeight;}m.style.left=j+"px";m.style.top=n+"px";if(this._elIFrame){this._elIFrame.style.left=j+"px";this._elIFrame.style.top=n+"px";}},show:function(){var k=this.getContainerEl(),j=this._elIFrame;this.resetForm();this.isActive=true;k.style.display="";if(j){j.style.width=k.offsetWidth+"px";j.style.height=k.offsetHeight+"px";j.style.display="";}this.focus();this.fireEvent("showEvent",{editor:this});},block:function(){this.fireEvent("blockEvent",{editor:this});},unblock:function(){this.fireEvent("unblockEvent",{editor:this});},save:function(){var k=this.getInputValue();var l=k;if(this.validator){l=this.validator.call(this.getDataTable(),k,this.value,this);if(l===undefined){if(this.resetInvalidData){this.resetForm();}this.fireEvent("invalidDataEvent",{editor:this,oldData:this.value,newData:k});return;}}var m=this;var j=function(o,n){var p=m.value;if(o){m.value=n;m.getDataTable().updateCell(m.getRecord(),m.getColumn(),n);m._hide();m.fireEvent("saveEvent",{editor:m,oldData:p,newData:m.value});}else{m.resetForm();m.fireEvent("revertEvent",{editor:m,oldData:p,newData:n});}m.unblock();};this.block();if(c.isFunction(this.asyncSubmitter)){this.asyncSubmitter.call(this,j,l);}else{j(true,l);}},cancel:function(){if(this.isActive){this._hide();this.fireEvent("cancelEvent",{editor:this});}else{}},renderForm:function(){},doAfterRender:function(){},handleDisabledBtns:function(){},resetForm:function(){},focus:function(){},getInputValue:function(){}};c.augmentProto(a,f.EventProvider);e.CheckboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-checkboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.CheckboxCellEditor.superclass.constructor.call(this,j.type||"checkbox",j);};c.extend(e.CheckboxCellEditor,a,{checkboxOptions:null,checkboxes:null,value:null,renderForm:function(){if(c.isArray(this.checkboxOptions)){var n,o,q,l,m,k;for(m=0,k=this.checkboxOptions.length;m<k;m++){n=this.checkboxOptions[m];o=c.isValue(n.value)?n.value:n;q=this.getId()+"-chk"+m;this.getContainerEl().innerHTML+='<input type="checkbox"'+' id="'+q+'"'+' value="'+o+'" />';l=this.getContainerEl().appendChild(document.createElement("label"));l.htmlFor=q;l.innerHTML=c.isValue(n.label)?n.label:n;}var p=[];for(m=0;m<k;m++){p[p.length]=this.getContainerEl().childNodes[m*2];}this.checkboxes=p;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){var p=c.isArray(this.value)?this.value:[this.value];for(var o=0,n=this.checkboxes.length;o<n;o++){this.checkboxes[o].checked=false;for(var m=0,l=p.length;m<l;m++){if(this.checkboxes[o].value==p[m]){this.checkboxes[o].checked=true;}}}},focus:function(){this.checkboxes[0].focus();},getInputValue:function(){var k=[];for(var m=0,l=this.checkboxes.length;m<l;m++){if(this.checkboxes[m].checked){k[k.length]=this.checkboxes[m].value;}}return k;}});c.augmentObject(e.CheckboxCellEditor,a);e.DateCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dateceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DateCellEditor.superclass.constructor.call(this,j.type||"date",j);};c.extend(e.DateCellEditor,a,{calendar:null,calendarOptions:null,defaultValue:new Date(),renderForm:function(){if(YAHOO.widget.Calendar){var k=this.getContainerEl().appendChild(document.createElement("div"));
147 k.id=this.getId()+"-dateContainer";var l=new YAHOO.widget.Calendar(this.getId()+"-date",k.id,this.calendarOptions);l.render();k.style.cssFloat="none";l.hideEvent.subscribe(function(){this.cancel();},this,true);if(b.ie){var j=this.getContainerEl().appendChild(document.createElement("div"));j.style.clear="both";}this.calendar=l;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){this.calendar.selectEvent.subscribe(function(j){this.save();},this,true);},resetForm:function(){var j=this.value||(new Date());this.calendar.select(j);this.calendar.cfg.setProperty("pagedate",j,false);this.calendar.render();this.calendar.show();},focus:function(){},getInputValue:function(){return this.calendar.getSelectedDates()[0];}});c.augmentObject(e.DateCellEditor,a);e.DropdownCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dropdownceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DropdownCellEditor.superclass.constructor.call(this,j.type||"dropdown",j);};c.extend(e.DropdownCellEditor,a,{dropdownOptions:null,dropdown:null,multiple:false,size:null,renderForm:function(){var n=this.getContainerEl().appendChild(document.createElement("select"));n.style.zoom=1;if(this.multiple){n.multiple="multiple";}if(c.isNumber(this.size)){n.size=this.size;}this.dropdown=n;if(c.isArray(this.dropdownOptions)){var o,m;for(var l=0,k=this.dropdownOptions.length;l<k;l++){o=this.dropdownOptions[l];m=document.createElement("option");m.value=(c.isValue(o.value))?o.value:o;m.innerHTML=(c.isValue(o.label))?o.label:o;m=n.appendChild(m);}if(this.disableBtns){this.handleDisabledBtns();}}},handleDisabledBtns:function(){if(this.multiple){i.addListener(this.dropdown,"blur",function(j){this.save();},this,true);}else{if(!b.ie){i.addListener(this.dropdown,"change",function(j){this.save();},this,true);}else{i.addListener(this.dropdown,"blur",function(j){this.save();},this,true);i.addListener(this.dropdown,"click",function(j){this.save();},this,true);}}},resetForm:function(){var s=this.dropdown.options,p=0,o=s.length;if(c.isArray(this.value)){var l=this.value,k=0,r=l.length,q={};for(;p<o;p++){s[p].selected=false;q[s[p].value]=s[p];}for(;k<r;k++){if(q[l[k]]){q[l[k]].selected=true;}}}else{for(;p<o;p++){if(this.value==s[p].value){s[p].selected=true;}}}},focus:function(){this.getDataTable()._focusEl(this.dropdown);},getInputValue:function(){var n=this.dropdown.options;if(this.multiple){var k=[],m=0,l=n.length;for(;m<l;m++){if(n[m].selected){k.push(n[m].value);}}return k;}else{return n[n.selectedIndex].value;}}});c.augmentObject(e.DropdownCellEditor,a);e.RadioCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-radioceditor");YAHOO.widget.BaseCellEditor._nCount++;e.RadioCellEditor.superclass.constructor.call(this,j.type||"radio",j);};c.extend(e.RadioCellEditor,a,{radios:null,radioOptions:null,renderForm:function(){if(c.isArray(this.radioOptions)){var k,l,r,o;for(var n=0,p=this.radioOptions.length;n<p;n++){k=this.radioOptions[n];l=c.isValue(k.value)?k.value:k;r=this.getId()+"-radio"+n;this.getContainerEl().innerHTML+='<input type="radio"'+' name="'+this.getId()+'"'+' value="'+l+'"'+' id="'+r+'" />';o=this.getContainerEl().appendChild(document.createElement("label"));o.htmlFor=r;o.innerHTML=(c.isValue(k.label))?k.label:k;}var q=[],s;for(var m=0;m<p;m++){s=this.getContainerEl().childNodes[m*2];q[q.length]=s;}this.radios=q;if(this.disableBtns){this.handleDisabledBtns();}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save();}},this,true);},resetForm:function(){for(var m=0,l=this.radios.length;m<l;m++){var k=this.radios[m];if(this.value==k.value){k.checked=true;return;}}},focus:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){this.radios[l].focus();return;}}},getInputValue:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){return this.radios[l].value;}}}});c.augmentObject(e.RadioCellEditor,a);e.TextareaCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textareaceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextareaCellEditor.superclass.constructor.call(this,j.type||"textarea",j);};c.extend(e.TextareaCellEditor,a,{textarea:null,renderForm:function(){var j=this.getContainerEl().appendChild(document.createElement("textarea"));this.textarea=j;if(this.disableBtns){this.handleDisabledBtns();}},handleDisabledBtns:function(){i.addListener(this.textarea,"blur",function(j){this.save();},this,true);},move:function(){this.textarea.style.width=this.getTdEl().offsetWidth+"px";this.textarea.style.height="3em";YAHOO.widget.TextareaCellEditor.superclass.move.call(this);},resetForm:function(){this.textarea.value=this.value;},focus:function(){this.getDataTable()._focusEl(this.textarea);this.textarea.select();},getInputValue:function(){return this.textarea.value;}});c.augmentObject(e.TextareaCellEditor,a);e.TextboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextboxCellEditor.superclass.constructor.call(this,j.type||"textbox",j);};c.extend(e.TextboxCellEditor,a,{textbox:null,renderForm:function(){var j;if(b.webkit>420){j=this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));}else{j=this.getContainerEl().appendChild(document.createElement("input"));}j.type="text";this.textbox=j;i.addListener(j,"keypress",function(k){if((k.keyCode===13)){YAHOO.util.Event.preventDefault(k);this.save();}},this,true);if(this.disableBtns){this.handleDisabledBtns();}},move:function(){this.textbox.style.width=this.getTdEl().offsetWidth+"px";e.TextboxCellEditor.superclass.move.call(this);},resetForm:function(){this.textbox.value=c.isValue(this.value)?this.value.toString():"";},focus:function(){this.getDataTable()._focusEl(this.textbox);this.textbox.select();},getInputValue:function(){return this.textbox.value;
148 }});c.augmentObject(e.TextboxCellEditor,a);h.Editors={checkbox:e.CheckboxCellEditor,"date":e.DateCellEditor,dropdown:e.DropdownCellEditor,radio:e.RadioCellEditor,textarea:e.TextareaCellEditor,textbox:e.TextboxCellEditor};e.CellEditor=function(k,j){if(k&&h.Editors[k]){c.augmentObject(a,h.Editors[k]);return new h.Editors[k](j);}else{return new a(null,j);}};var g=e.CellEditor;c.augmentObject(g,a);})();YAHOO.register("datatable",YAHOO.widget.DataTable,{version:"2.9.0",build:"2800"});
149
150
@@ -24,5 +24,5 b''
24 24 ${c.log_data}
25 25 </div>
26 26 </div>
27 </div>
28 </%def> No newline at end of file
27 </div>
28 </%def>
@@ -23,7 +23,7 b''
23 23 ${l.repository_name}
24 24 %endif
25 25 </td>
26
26
27 27 <td>${l.action_date}</td>
28 28 <td>${l.user_ip}</td>
29 29 </tr>
@@ -36,7 +36,7 b''
36 36 ypjax(e.target.href,"user_log",function(){show_more_event();tooltip_activate();});
37 37 YUE.preventDefault(e);
38 38 },'.pager_link');
39
39
40 40 YUE.delegate("user_log","click",function(e,matchedEl,container){
41 41 var el = e.target;
42 42 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
@@ -48,6 +48,6 b''
48 48 <div class="pagination-wh pagination-left">
49 49 ${c.users_log.pager('$link_previous ~2~ $link_next')}
50 50 </div>
51 %else:
52 ${_('No actions yet')}
53 %endif No newline at end of file
51 %else:
52 ${_('No actions yet')}
53 %endif
@@ -6,9 +6,9 b''
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 ${_('Ldap')}
11 ${_('Ldap')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
@@ -19,7 +19,7 b''
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 23 </div>
24 24 ${h.form(url('ldap_settings'))}
25 25 <div class="form">
@@ -84,20 +84,12 b''
84 84 <div class="label"><label for="ldap_attr_email">${_('E-mail Attribute')}</label></div>
85 85 <div class="input">${h.text('ldap_attr_email',class_='small')}</div>
86 86 </div>
87
87
88 88 <div class="buttons">
89 89 ${h.submit('save',_('Save'),class_="ui-button")}
90 </div>
90 </div>
91 91 </div>
92 </div>
93 ${h.end_form()}
92 </div>
93 ${h.end_form()}
94 94 </div>
95 </%def>
96
97
98
99
100
101
102
103
95 </%def>
@@ -6,9 +6,9 b''
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 ${_('Permissions')}
11 ${_('Permissions')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
@@ -19,7 +19,7 b''
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 23 </div>
24 24 <h3>${_('Default permissions')}</h3>
25 25 ${h.form(url('permission', id='default'),method='put')}
@@ -35,21 +35,21 b''
35 35 ${h.checkbox('anonymous',True)}
36 36 </div>
37 37 </div>
38 </div>
38 </div>
39 39 <div class="field">
40 40 <div class="label label-select">
41 41 <label for="default_perm">${_('Repository permission')}:</label>
42 42 </div>
43 43 <div class="select">
44 44 ${h.select('default_perm','',c.perms_choices)}
45
45
46 46 ${h.checkbox('overwrite_default','true')}
47 47 <label for="overwrite_default">
48 <span class="tooltip"
48 <span class="tooltip"
49 49 title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
50 50 ${_('overwrite existing settings')}</span> </label>
51 </div>
52 </div>
51 </div>
52 </div>
53 53 <div class="field">
54 54 <div class="label">
55 55 <label for="default_register">${_('Registration')}:</label>
@@ -57,7 +57,7 b''
57 57 <div class="select">
58 58 ${h.select('default_register','',c.register_choices)}
59 59 </div>
60 </div>
60 </div>
61 61 <div class="field">
62 62 <div class="label">
63 63 <label for="default_create">${_('Repository creation')}:</label>
@@ -65,20 +65,13 b''
65 65 <div class="select">
66 66 ${h.select('default_create','',c.create_choices)}
67 67 </div>
68 </div>
69
68 </div>
69
70 70 <div class="buttons">
71 71 ${h.submit('set',_('set'),class_="ui-button")}
72 </div>
72 </div>
73 73 </div>
74 </div>
74 </div>
75 75 ${h.end_form()}
76 76 </div>
77 </%def>
78
79
80
81
82
83
84
77 </%def>
@@ -6,9 +6,9 b''
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
11 ${h.link_to(_('Repositories'),h.url('repos'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('add new')}
14 14 </%def>
@@ -21,8 +21,8 b''
21 21 <div class="box">
22 22 <!-- box / title -->
23 23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 25 </div>
26 26 <%include file="repo_add_base.html"/>
27 </div>
28 </%def> No newline at end of file
27 </div>
28 </%def>
@@ -22,7 +22,7 b''
22 22 <div class="input">
23 23 ${h.text('clone_uri',class_="small")}
24 24 </div>
25 </div>
25 </div>
26 26 <div class="field">
27 27 <div class="label">
28 28 <label for="repo_group">${_('Repository group')}:</label>
@@ -30,7 +30,7 b''
30 30 <div class="input">
31 31 ${h.select('repo_group','',c.repo_groups,class_="medium")}
32 32 </div>
33 </div>
33 </div>
34 34 <div class="field">
35 35 <div class="label">
36 36 <label for="repo_type">${_('Type')}:</label>
@@ -38,7 +38,7 b''
38 38 <div class="input">
39 39 ${h.select('repo_type','hg',c.backends,class_="small")}
40 40 </div>
41 </div>
41 </div>
42 42 <div class="field">
43 43 <div class="label label-textarea">
44 44 <label for="description">${_('Description')}:</label>
@@ -57,7 +57,7 b''
57 57 </div>
58 58 <div class="buttons">
59 59 ${h.submit('add',_('add'),class_="ui-button")}
60 </div>
60 </div>
61 61 </div>
62 </div>
63 ${h.end_form()}
62 </div>
63 ${h.end_form()}
@@ -17,8 +17,8 b''
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 21 </div>
22 22 <%include file="repo_add_base.html"/>
23 </div>
24 </%def> No newline at end of file
23 </div>
24 </%def>
@@ -6,9 +6,9 b''
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
11 ${h.link_to(_('Repositories'),h.url('repos'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
14 14 </%def>
@@ -21,7 +21,7 b''
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
@@ -42,7 +42,7 b''
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 44 </div>
45 </div>
45 </div>
46 46 <div class="field">
47 47 <div class="label">
48 48 <label for="repo_group">${_('Repository group')}:</label>
@@ -50,7 +50,7 b''
50 50 <div class="input">
51 51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 52 </div>
53 </div>
53 </div>
54 54 <div class="field">
55 55 <div class="label">
56 56 <label for="repo_type">${_('Type')}:</label>
@@ -58,7 +58,7 b''
58 58 <div class="input">
59 59 ${h.select('repo_type','hg',c.backends,class_="medium")}
60 60 </div>
61 </div>
61 </div>
62 62 <div class="field">
63 63 <div class="label label-textarea">
64 64 <label for="description">${_('Description')}:</label>
@@ -67,7 +67,7 b''
67 67 ${h.textarea('description',cols=23,rows=5)}
68 68 </div>
69 69 </div>
70
70
71 71 <div class="field">
72 72 <div class="label label-checkbox">
73 73 <label for="private">${_('Private')}:</label>
@@ -83,7 +83,7 b''
83 83 <div class="checkboxes">
84 84 ${h.checkbox('enable_statistics',value="True")}
85 85 </div>
86 </div>
86 </div>
87 87 <div class="field">
88 88 <div class="label label-checkbox">
89 89 <label for="enable_downloads">${_('Enable downloads')}:</label>
@@ -91,7 +91,7 b''
91 91 <div class="checkboxes">
92 92 ${h.checkbox('enable_downloads',value="True")}
93 93 </div>
94 </div>
94 </div>
95 95 <div class="field">
96 96 <div class="label">
97 97 <label for="user">${_('Owner')}:</label>
@@ -102,8 +102,8 b''
102 102 <div id="owner_container"></div>
103 103 </div>
104 104 </div>
105 </div>
106
105 </div>
106
107 107 <div class="field">
108 108 <div class="label">
109 109 <label for="input">${_('Permissions')}:</label>
@@ -111,87 +111,114 b''
111 111 <div class="input">
112 112 <%include file="repo_edit_perms.html"/>
113 113 </div>
114
115 <div class="buttons">
116 ${h.submit('save','Save',class_="ui-button")}
117 ${h.reset('reset','Reset',class_="ui-button")}
118 </div>
119 </div>
114
115 <div class="buttons">
116 ${h.submit('save','Save',class_="ui-button")}
117 ${h.reset('reset','Reset',class_="ui-button")}
118 </div>
119 </div>
120 120 </div>
121 </div>
121 </div>
122 122 ${h.end_form()}
123 123 </div>
124 124
125 125 <div class="box box-right">
126 126 <div class="title">
127 <h5>${_('Administration')}</h5>
127 <h5>${_('Administration')}</h5>
128 128 </div>
129
129
130 130 <h3>${_('Statistics')}</h3>
131 131 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
132 132 <div class="form">
133 133 <div class="fields">
134 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="refresh_icon action_button",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
135 <div class="field" style="border:none">
134 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
135 <div class="field" style="border:none;color:#888">
136 136 <ul>
137 137 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
138 <li>${_('Percentage of stats gathered')}: ${c.stats_percentage} %</li>
138 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
139 139 </ul>
140 140 </div>
141
142 141 </div>
143 </div>
142 </div>
144 143 ${h.end_form()}
145
144
146 145 %if c.repo_info.clone_uri:
147 146 <h3>${_('Remote')}</h3>
148 147 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
149 148 <div class="form">
150 149 <div class="fields">
151 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="pull_icon action_button",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
150 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
152 151 <div class="field" style="border:none">
153 152 <ul>
154 153 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
155 </ul>
156 </div>
154 </ul>
155 </div>
157 156 </div>
158 </div>
157 </div>
159 158 ${h.end_form()}
160 159 %endif
161
160
162 161 <h3>${_('Cache')}</h3>
163 162 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
164 163 <div class="form">
165 164 <div class="fields">
166 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="refresh_icon action_button",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
165 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
167 166 </div>
168 </div>
167 </div>
169 168 ${h.end_form()}
170
169
171 170 <h3>${_('Public journal')}</h3>
172 171 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
173 172 <div class="form">
174 <div class="fields">
175 173 ${h.hidden('auth_token',str(h.get_token()))}
174 <div class="field">
176 175 %if c.in_public_journal:
177 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="stop_following_icon action_button")}
176 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
178 177 %else:
179 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="start_following_icon action_button")}
178 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
180 179 %endif
181 </div>
180 </div>
181 <div class="field" style="border:none;color:#888">
182 <ul>
183 <li>${_('''All actions made on this repository will be accessible to everyone in public journal''')}
184 </li>
185 </ul>
186 </div>
182 187 </div>
183 188 ${h.end_form()}
184
189
185 190 <h3>${_('Delete')}</h3>
186 191 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
187 192 <div class="form">
188 193 <div class="fields">
189 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
194 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
195 </div>
196 <div class="field" style="border:none;color:#888">
197 <ul>
198 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
199 If you need fully delete it from filesystem please do it manually''')}
200 </li>
201 </ul>
190 202 </div>
191 </div>
203 </div>
192 204 ${h.end_form()}
193
205
206 <h3>${_('Set as fork')}</h3>
207 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
208 <div class="form">
209 <div class="fields">
210 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
211 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
212 </div>
213 <div class="field" style="border:none;color:#888">
214 <ul>
215 <li>${_('''Manually set this repository as a fork of another''')}</li>
216 </ul>
217 </div>
218 </div>
219 ${h.end_form()}
220
194 221 </div>
195 222
196 223
197 </%def>
224 </%def>
@@ -1,4 +1,4 b''
1 <table id="permissions_manage">
1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
@@ -7,7 +7,7 b''
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 ## USERS
10 ## USERS
11 11 %for r2p in c.repo_info.repo_to_perm:
12 12 %if r2p.user.username =='default' and c.repo_info.private:
13 13 <tr>
@@ -16,7 +16,7 b''
16 16 ${_('private repository')}
17 17 </span>
18 18 </td>
19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url("/images/icons/user.png")}"/>${r2p.user.username}</td>
19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}</td>
20 20 </tr>
21 21 %else:
22 22 <tr id="id${id(r2p.user.username)}">
@@ -32,13 +32,13 b''
32 32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
33 33 ${_('revoke')}
34 34 </span>
35 %endif
35 %endif
36 36 </td>
37 37 </tr>
38 38 %endif
39 39 %endfor
40
41 ## USERS GROUPS
40
41 ## USERS GROUPS
42 42 %for g2p in c.repo_info.users_group_to_perm:
43 43 <tr id="id${id(g2p.users_group.users_group_name)}">
44 44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
@@ -92,10 +92,10 b' function ajaxActionUser(user_id, field_i'
92 92 var postData = '_method=delete&user_id=' + user_id;
93 93 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
94 94 };
95
95
96 96 function ajaxActionUsersGroup(users_group_id,field_id){
97 97 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
98 var callback = {
98 var callback = {
99 99 success:function(o){
100 100 var tr = YUD.get(String(field_id));
101 101 tr.parentNode.removeChild(tr);
@@ -104,7 +104,7 b' function ajaxActionUsersGroup(users_grou'
104 104 alert("${_('Failed to remove users group')}");
105 105 },
106 106 };
107 var postData = '_method=delete&users_group_id='+users_group_id;
107 var postData = '_method=delete&users_group_id='+users_group_id;
108 108 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
109 109 };
110 110
@@ -267,7 +267,7 b' YAHOO.example.FnMultipleFields = functio'
267 267
268 268 membersAC.itemSelectEvent.subscribe(myHandler);
269 269 if(ownerAC.itemSelectEvent){
270 ownerAC.itemSelectEvent.subscribe(myHandler);
270 ownerAC.itemSelectEvent.subscribe(myHandler);
271 271 }
272 272
273 273 return {
@@ -277,5 +277,5 b' YAHOO.example.FnMultipleFields = functio'
277 277 ownerAC: ownerAC,
278 278 };
279 279 }();
280
281 </script> No newline at end of file
280
281 </script>
@@ -14,75 +14,111 b''
14 14 </%def>
15 15 <%def name="main()">
16 16 <div class="box">
17 <!-- box / title -->
17
18 18 <div class="title">
19 19 ${self.breadcrumbs()}
20 20 <ul class="links">
21 21 <li>
22 <span>${h.link_to(_(u'ADD NEW REPOSITORY'),h.url('new_repo'))}</span>
23 </li>
24 </ul>
22 <span>${h.link_to(_(u'ADD REPOSITORY'),h.url('new_repo'))}</span>
23 </li>
24 </ul>
25 25 </div>
26 <!-- end box / title -->
26
27 27 <div class="table">
28 <table class="table_disp">
29 <tr class="header">
30 <th class="left">${_('Name')}</th>
31 <th class="left">${_('Description')}</th>
32 <th class="left">${_('Last change')}</th>
33 <th class="left">${_('Tip')}</th>
34 <th class="left">${_('Contact')}</th>
35 <th class="left">${_('action')}</th>
36 </tr>
37 %for cnt,repo in enumerate(c.repos_list):
38 <tr class="parity${cnt%2}">
39 <td>
40 ## TYPE OF REPO
41 %if repo['dbrepo']['repo_type'] =='hg':
42 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
43 %elif repo['dbrepo']['repo_type'] =='git':
44 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
45 %else:
46
47 %endif
48
49 ## PRIVATE/PUBLIC REPO
50 %if repo['dbrepo']['private']:
51 <img alt="${_('private')}" src="${h.url('/images/icons/lock.png')}"/>
52 %else:
53 <img alt="${_('public')}" src="${h.url('/images/icons/lock_open.png')}"/>
54 %endif
55 ${h.link_to(repo['name'],h.url('edit_repo',repo_name=repo['name']))}
56
57 %if repo['dbrepo_fork']:
58 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
59 <img class="icon" alt="${_('public')}"
60 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
61 src="${h.url("/images/icons/arrow_divide.png")}"/></a>
62 %endif
63 </td>
64 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
65 <td>${h.age(repo['last_change'])}</td>
66 <td>
67 %if repo['rev']>=0:
68 ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])),
69 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
70 class_="tooltip",
71 title=h.tooltip(repo['last_msg']))}
72 %else:
73 ${_('No changesets yet')}
74 %endif
75 </td>
76 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
77 <td>
78 ${h.form(url('repo', repo_name=repo['name']),method='delete')}
79 ${h.submit('remove_%s' % repo['name'],_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
80 ${h.end_form()}
81 </td>
82 </tr>
83 %endfor
28 <div id='repos_list_wrap' class="yui-skin-sam">
29 <%cnt=0%>
30 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
31
32 <table id="repos_list">
33 <thead>
34 <tr>
35 <th class="left"></th>
36 <th class="left">${_('Name')}</th>
37 <th class="left">${_('Description')}</th>
38 <th class="left">${_('Last change')}</th>
39 <th class="left">${_('Tip')}</th>
40 <th class="left">${_('Contact')}</th>
41 <th class="left">${_('Action')}</th>
42 </tr>
43 </thead>
44
45 %for cnt,repo in enumerate(c.repos_list,1):
46 <tr class="parity${cnt%2}">
47 <td class="quick_repo_menu">
48 ${dt.quick_menu(repo['name'])}
49 </td>
50 <td class="reponame">
51 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
52 </td>
53 ##DESCRIPTION
54 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
55 ${h.truncate(repo['description'],60)}</span>
56 </td>
57 ##LAST CHANGE
58 <td>
59 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
60 </td>
61 ##LAST REVISION
62 <td>
63 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
64 </td>
65 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
66 <td>
67 ${h.form(url('repo', repo_name=repo['name']),method='delete')}
68 ${h.submit('remove_%s' % repo['name'],_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
69 ${h.end_form()}
70 </td>
71 </tr>
72 %endfor
84 73 </table>
74 </div>
85 75 </div>
86 </div>
87
88 </%def>
76 </div>
77 <script>
78
79 // main table sorting
80 var myColumnDefs = [
81 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
82 {key:"name",label:"${_('Name')}",sortable:true,
83 sortOptions: { sortFunction: nameSort }},
84 {key:"desc",label:"${_('Description')}",sortable:true},
85 {key:"last_change",label:"${_('Last Change')}",sortable:true,
86 sortOptions: { sortFunction: ageSort }},
87 {key:"tip",label:"${_('Tip')}",sortable:true,
88 sortOptions: { sortFunction: revisionSort }},
89 {key:"owner",label:"${_('Owner')}",sortable:true},
90 {key:"action",label:"${_('Action')}",sortable:false},
91 ];
92
93 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
94
95 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
96
97 myDataSource.responseSchema = {
98 fields: [
99 {key:"menu"},
100 {key:"name"},
101 {key:"desc"},
102 {key:"last_change"},
103 {key:"tip"},
104 {key:"owner"},
105 {key:"action"},
106 ]
107 };
108
109 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
110 {
111 sortedBy:{key:"name",dir:"asc"},
112 MSG_SORTASC:"${_('Click to sort ascending')}",
113 MSG_SORTDESC:"${_('Click to sort descending')}",
114 MSG_EMPTY:"${_('No records found.')}",
115 MSG_ERROR:"${_('Data error.')}",
116 MSG_LOADING:"${_('Loading...')}",
117 }
118 );
119 myDataTable.subscribe('postRenderEvent',function(oArgs) {
120 tooltip_activate();
121 quick_repo_menu();
122 });
123 </script>
124 </%def>
@@ -5,14 +5,12 b''
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs()">
8 <span class="groups_breadcrumbs">
9 ${_('Groups')}
8 <span class="groups_breadcrumbs"> ${_('Groups')}
10 9 %if c.group.parent_group:
11 &raquo; ${h.link_to(c.group.parent_group.name,
12 h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
10 &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
13 11 %endif
14 12 &raquo; "${c.group.name}" ${_('with')}
15 </span>
13 </span>
16 14 </%def>
17 15
18 16 <%def name="page_nav()">
@@ -20,4 +18,4 b''
20 18 </%def>
21 19 <%def name="main()">
22 20 <%include file="/index_base.html" args="parent=self"/>
23 </%def>
21 </%def>
@@ -5,9 +5,9 b''
5 5 ${_('Add repos group')} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 11 &raquo;
12 12 ${_('add new repos group')}
13 13 </%def>
@@ -20,7 +20,7 b''
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('repos_groups'))}
@@ -35,7 +35,7 b''
35 35 ${h.text('group_name',class_='medium')}
36 36 </div>
37 37 </div>
38
38
39 39 <div class="field">
40 40 <div class="label label-textarea">
41 41 <label for="group_description">${_('Description')}:</label>
@@ -44,7 +44,7 b''
44 44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 45 </div>
46 46 </div>
47
47
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="group_parent_id">${_('Group parent')}:</label>
@@ -52,13 +52,13 b''
52 52 <div class="input">
53 53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 54 </div>
55 </div>
56
55 </div>
56
57 57 <div class="buttons">
58 58 ${h.submit('save',_('save'),class_="ui-button")}
59 </div>
59 </div>
60 60 </div>
61 61 </div>
62 62 ${h.end_form()}
63 </div>
64 </%def>
63 </div>
64 </%def>
@@ -5,9 +5,9 b''
5 5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 11 &raquo;
12 12 ${_('edit repos group')} "${c.repos_group.name}"
13 13 </%def>
@@ -20,7 +20,7 b''
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
@@ -35,7 +35,7 b''
35 35 ${h.text('group_name',class_='medium')}
36 36 </div>
37 37 </div>
38
38
39 39 <div class="field">
40 40 <div class="label label-textarea">
41 41 <label for="group_description">${_('Description')}:</label>
@@ -44,7 +44,7 b''
44 44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 45 </div>
46 46 </div>
47
47
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="group_parent_id">${_('Group parent')}:</label>
@@ -52,13 +52,22 b''
52 52 <div class="input">
53 53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 54 </div>
55 </div>
56
55 </div>
56 <div class="field">
57 <div class="label">
58 <label for="input">${_('Permissions')}:</label>
59 </div>
60 <div class="input">
61 <%include file="repos_group_edit_perms.html"/>
62 </div>
63
64 </div>
57 65 <div class="buttons">
58 ${h.submit('save',_('save'),class_="ui-button")}
59 </div>
66 ${h.submit('save',_('Save'),class_="ui-button")}
67 ${h.reset('reset',_('Reset'),class_="ui-button")}
68 </div>
60 69 </div>
61 70 </div>
62 71 ${h.end_form()}
63 </div>
64 </%def>
72 </div>
73 </%def>
@@ -20,25 +20,25 b''
20 20 <ul class="links">
21 21 <li>
22 22 <span>${h.link_to(_(u'ADD NEW GROUP'),h.url('new_repos_group'))}</span>
23 </li>
24 </ul>
23 </li>
24 </ul>
25 25 </div>
26 26 <!-- end box / title -->
27 27 <div class="table">
28 28 % if c.groups:
29 29 <table class="table_disp">
30
30
31 31 <thead>
32 32 <tr>
33 33 <th class="left"><a href="#">${_('Group name')}</a></th>
34 34 <th class="left"><a href="#">${_('Description')}</a></th>
35 <th class="left"><a href="#">${_('Number of repositories')}</a></th>
35 <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
36 36 <th class="left">${_('action')}</th>
37 37 </tr>
38 38 </thead>
39
39
40 40 ## REPO GROUPS
41
41
42 42 % for gr in c.groups:
43 43 <tr>
44 44 <td>
@@ -51,18 +51,18 b''
51 51 <td><b>${gr.repositories.count()}</b></td>
52 52 <td>
53 53 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
54 ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
54 ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group: %s') % gr.name+"');")}
55 55 ${h.end_form()}
56 </td>
56 </td>
57 57 </tr>
58 58 % endfor
59
59
60 60 </table>
61 61 % else:
62 62 ${_('There are no repositories groups yet')}
63 63 % endif
64
64
65 65 </div>
66 </div>
67
68 </%def>
66 </div>
67
68 </%def>
@@ -17,10 +17,10 b''
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23
23
24 24 <h3>${_('Built in hooks - read only')}</h3>
25 25 <div class="form">
26 26 <div class="fields">
@@ -36,29 +36,29 b''
36 36 % endfor
37 37 </div>
38 38 </div>
39
39
40 40 <h3>${_('Custom hooks')}</h3>
41 41 ${h.form(url('admin_setting', setting_id='hooks'),method='put')}
42 42 <div class="form">
43 43 <div class="fields">
44
44
45 45 % for hook in c.custom_hooks:
46 46 <div class="field" id="${'id%s' % hook.ui_id }">
47 47 <div class="label label">
48 48 <label for="${hook.ui_key}">${hook.ui_key}</label>
49 49 </div>
50 <div class="input" style="margin-left:280px">
50 <div class="input" style="margin-left:280px">
51 51 ${h.hidden('hook_ui_key',hook.ui_key)}
52 52 ${h.hidden('hook_ui_value',hook.ui_value)}
53 53 ${h.text('hook_ui_value_new',hook.ui_value,size=60)}
54 <span class="delete_icon action_button"
54 <span class="delete_icon action_button"
55 55 onclick="ajaxActionHook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
56 56 ${_('remove')}
57 57 </span>
58 58 </div>
59 59 </div>
60 % endfor
61
60 % endfor
61
62 62 <div class="field">
63 63 <div class="input" style="margin-left:-180px;position: absolute;">
64 64 <div class="input">
@@ -71,7 +71,7 b''
71 71 </div>
72 72 <div class="buttons" style="margin-left:280px">
73 73 ${h.submit('save',_('Save'),class_="ui-button")}
74 </div>
74 </div>
75 75 </div>
76 76 </div>
77 77 ${h.end_form()}
@@ -92,5 +92,5 b' function ajaxActionHook(hook_id,field_id'
92 92 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
93 93 };
94 94 </script>
95
96 </%def>
95
96 </%def>
@@ -17,15 +17,15 b''
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23
23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28
28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
@@ -40,19 +40,19 b''
40 40 </div>
41 41 </div>
42 42 </div>
43
43
44 44 <div class="buttons">
45 45 ${h.submit('rescan',_('Rescan repositories'),class_="ui-button")}
46 </div>
46 </div>
47 47 </div>
48 </div>
48 </div>
49 49 ${h.end_form()}
50
50
51 51 <h3>${_('Whoosh indexing')}</h3>
52 52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 53 <div class="form">
54 54 <!-- fields -->
55
55
56 56 <div class="fields">
57 57 <div class="field">
58 58 <div class="label label-checkbox">
@@ -65,21 +65,21 b''
65 65 </div>
66 66 </div>
67 67 </div>
68
68
69 69 <div class="buttons">
70 70 ${h.submit('reindex',_('Reindex'),class_="ui-button")}
71 </div>
71 </div>
72 72 </div>
73 </div>
73 </div>
74 74 ${h.end_form()}
75
76 <h3>${_('Global application settings')}</h3>
75
76 <h3>${_('Global application settings')}</h3>
77 77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 78 <div class="form">
79 79 <!-- fields -->
80
80
81 81 <div class="fields">
82
82
83 83 <div class="field">
84 84 <div class="label">
85 85 <label for="rhodecode_title">${_('Application name')}:</label>
@@ -88,7 +88,7 b''
88 88 ${h.text('rhodecode_title',size=30)}
89 89 </div>
90 90 </div>
91
91
92 92 <div class="field">
93 93 <div class="label">
94 94 <label for="rhodecode_realm">${_('Realm text')}:</label>
@@ -97,7 +97,7 b''
97 97 ${h.text('rhodecode_realm',size=30)}
98 98 </div>
99 99 </div>
100
100
101 101 <div class="field">
102 102 <div class="label">
103 103 <label for="rhodecode_ga_code">${_('GA code')}:</label>
@@ -106,22 +106,22 b''
106 106 ${h.text('rhodecode_ga_code',size=30)}
107 107 </div>
108 108 </div>
109
109
110 110 <div class="buttons">
111 111 ${h.submit('save',_('Save settings'),class_="ui-button")}
112 112 ${h.reset('reset',_('Reset'),class_="ui-button")}
113 </div>
113 </div>
114 114 </div>
115 </div>
115 </div>
116 116 ${h.end_form()}
117 117
118 <h3>${_('Mercurial settings')}</h3>
118 <h3>${_('Mercurial settings')}</h3>
119 119 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
120 120 <div class="form">
121 121 <!-- fields -->
122
122
123 123 <div class="fields">
124
124
125 125 <div class="field">
126 126 <div class="label label-checkbox">
127 127 <label>${_('Web')}:</label>
@@ -132,15 +132,12 b''
132 132 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
133 133 </div>
134 134 </div>
135 </div>
135 </div>
136 136
137 137 <div class="field">
138 138 <div class="label label-checkbox">
139 139 <label>${_('Hooks')}:</label>
140 140 </div>
141 <div class="input">
142 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'))}
143 </div>
144 141 <div class="checkboxes">
145 142 <div class="checkbox">
146 143 ${h.checkbox('hooks_changegroup_update','True')}
@@ -157,30 +154,32 b''
157 154 <div class="checkbox">
158 155 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
159 156 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
160 </div>
157 </div>
161 158 </div>
162 </div>
163
159 <div class="input" style="margin-top:10px">
160 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
161 </div>
162 </div>
164 163 <div class="field">
165 164 <div class="label">
166 165 <label for="paths_root_path">${_('Repositories location')}:</label>
167 166 </div>
168 167 <div class="input">
169 ${h.text('paths_root_path',size=30,readonly="readonly")}
170 <span id="path_unlock" class="tooltip"
168 ${h.text('paths_root_path',size=30,readonly="readonly")}
169 <span id="path_unlock" class="tooltip"
171 170 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
172 171 ${_('unlock')}</span>
173 172 </div>
174 173 </div>
175
174
176 175 <div class="buttons">
177 176 ${h.submit('save',_('Save settings'),class_="ui-button")}
178 177 ${h.reset('reset',_('Reset'),class_="ui-button")}
179 </div>
178 </div>
180 179 </div>
181 </div>
180 </div>
182 181 ${h.end_form()}
183
182
184 183 <script type="text/javascript">
185 184 YAHOO.util.Event.onDOMReady(function(){
186 185 YAHOO.util.Event.addListener('path_unlock','click',function(){
@@ -188,5 +187,28 b''
188 187 });
189 188 });
190 189 </script>
190
191 <h3>${_('Test Email')}</h3>
192 ${h.form(url('admin_setting', setting_id='email'),method='put')}
193 <div class="form">
194 <!-- fields -->
195
196 <div class="fields">
197 <div class="field">
198 <div class="label">
199 <label for="test_email">${_('Email to')}:</label>
200 </div>
201 <div class="input">
202 ${h.text('test_email',size=30)}
203 </div>
204 </div>
205
206 <div class="buttons">
207 ${h.submit('send',_('Send'),class_="ui-button")}
208 </div>
209 </div>
210 </div>
211 ${h.end_form()}
212
191 213 </div>
192 </%def>
214 </%def>
@@ -5,9 +5,9 b''
5 5 ${_('Add user')} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Users'),h.url('users'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Users'),h.url('users'))}
11 11 &raquo;
12 12 ${_('add new user')}
13 13 </%def>
@@ -20,7 +20,7 b''
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('users'))}
@@ -35,7 +35,7 b''
35 35 ${h.text('username',class_='small')}
36 36 </div>
37 37 </div>
38
38
39 39 <div class="field">
40 40 <div class="label">
41 41 <label for="password">${_('Password')}:</label>
@@ -44,7 +44,7 b''
44 44 ${h.password('password',class_='small')}
45 45 </div>
46 46 </div>
47
47
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="password_confirmation">${_('Password confirmation')}:</label>
@@ -52,8 +52,8 b''
52 52 <div class="input">
53 53 ${h.password('password_confirmation',class_="small",autocomplete="off")}
54 54 </div>
55 </div>
56
55 </div>
56
57 57 <div class="field">
58 58 <div class="label">
59 59 <label for="name">${_('First Name')}:</label>
@@ -62,7 +62,7 b''
62 62 ${h.text('name',class_='small')}
63 63 </div>
64 64 </div>
65
65
66 66 <div class="field">
67 67 <div class="label">
68 68 <label for="lastname">${_('Last Name')}:</label>
@@ -71,7 +71,7 b''
71 71 ${h.text('lastname',class_='small')}
72 72 </div>
73 73 </div>
74
74
75 75 <div class="field">
76 76 <div class="label">
77 77 <label for="email">${_('Email')}:</label>
@@ -80,21 +80,21 b''
80 80 ${h.text('email',class_='small')}
81 81 </div>
82 82 </div>
83
83
84 84 <div class="field">
85 85 <div class="label label-checkbox">
86 86 <label for="active">${_('Active')}:</label>
87 87 </div>
88 88 <div class="checkboxes">
89 ${h.checkbox('active',value=True)}
89 ${h.checkbox('active',value=True,checked='checked')}
90 90 </div>
91 91 </div>
92
92
93 93 <div class="buttons">
94 94 ${h.submit('save',_('save'),class_="ui-button")}
95 </div>
95 </div>
96 96 </div>
97 97 </div>
98 98 ${h.end_form()}
99 </div>
100 </%def>
99 </div>
100 </%def>
@@ -18,13 +18,13 b''
18 18 <div class="box box-left">
19 19 <!-- box / title -->
20 20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 22 </div>
23 23 <!-- end box / title -->
24 24 <div>
25 25 ${h.form(url('admin_settings_my_account_update'),method='put')}
26 26 <div class="form">
27
27
28 28 <div class="field">
29 29 <div class="gravatar_box">
30 30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
@@ -34,15 +34,15 b''
34 34 <br/>${_('Using')} ${c.user.email}
35 35 %else:
36 36 <br/>${c.user.email}
37 %endif
37 %endif
38 38 </p>
39 39 </div>
40 </div>
40 </div>
41 41 <div class="field">
42 42 <div class="label">
43 43 <label>${_('API key')}</label> ${c.user.api_key}
44 44 </div>
45 </div>
45 </div>
46 46 <div class="fields">
47 47 <div class="field">
48 48 <div class="label">
@@ -52,7 +52,7 b''
52 52 ${h.text('username',class_="medium")}
53 53 </div>
54 54 </div>
55
55
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="new_password">${_('New password')}:</label>
@@ -61,7 +61,7 b''
61 61 ${h.password('new_password',class_="medium",autocomplete="off")}
62 62 </div>
63 63 </div>
64
64
65 65 <div class="field">
66 66 <div class="label">
67 67 <label for="password_confirmation">${_('New password confirmation')}:</label>
@@ -70,7 +70,7 b''
70 70 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
71 71 </div>
72 72 </div>
73
73
74 74 <div class="field">
75 75 <div class="label">
76 76 <label for="name">${_('First Name')}:</label>
@@ -79,7 +79,7 b''
79 79 ${h.text('name',class_="medium")}
80 80 </div>
81 81 </div>
82
82
83 83 <div class="field">
84 84 <div class="label">
85 85 <label for="lastname">${_('Last Name')}:</label>
@@ -88,7 +88,7 b''
88 88 ${h.text('lastname',class_="medium")}
89 89 </div>
90 90 </div>
91
91
92 92 <div class="field">
93 93 <div class="label">
94 94 <label for="email">${_('Email')}:</label>
@@ -97,30 +97,31 b''
97 97 ${h.text('email',class_="medium")}
98 98 </div>
99 99 </div>
100
100
101 101 <div class="buttons">
102 102 ${h.submit('save',_('Save'),class_="ui-button")}
103 103 ${h.reset('reset',_('Reset'),class_="ui-button")}
104 </div>
104 </div>
105 105 </div>
106 </div>
106 </div>
107 107 ${h.end_form()}
108 108 </div>
109 </div>
109 </div>
110 110
111 111 <div class="box box-right">
112 112 <!-- box / title -->
113 113 <div class="title">
114 <h5>${_('My repositories')}
115 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
114 <h5>
115 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
116 ${_('My repositories')}
116 117 </h5>
117 118 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
118 119 <ul class="links">
119 120 <li>
120 121 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
121 </li>
122 </ul>
123 %endif
122 </li>
123 </ul>
124 %endif
124 125 </div>
125 126 <!-- end box / title -->
126 127 <div class="table">
@@ -129,98 +130,61 b''
129 130 <tr>
130 131 <th class="left">${_('Name')}</th>
131 132 <th class="left">${_('revision')}</th>
132 <th colspan="2" class="left">${_('action')}</th>
133 <th colspan="2" class="left">${_('action')}</th>
133 134 </thead>
134 135 <tbody>
135 136 %if c.user_repos:
136 137 %for repo in c.user_repos:
137 138 <tr>
138 139 <td>
139 %if repo['dbrepo']['repo_type'] =='hg':
140 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
141 %elif repo['dbrepo']['repo_type'] =='git':
142 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
140 %if h.is_hg(repo['dbrepo']['repo_type']):
141 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
142 %elif h.is_git(repo['dbrepo']['repo_type']):
143 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
143 144 %else:
144
145 %endif
145
146 %endif
146 147 %if repo['dbrepo']['private']:
147 <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
148 <img class="icon" alt="${_('private')}" src="${h.url('/images/icons/lock.png')}"/>
148 149 %else:
149 <img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
150 <img class="icon" alt="${_('public')}" src="${h.url('/images/icons/lock_open.png')}"/>
150 151 %endif
151
152
152 153 ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
153 154 %if repo['dbrepo_fork']:
154 155 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
155 156 <img class="icon" alt="${_('public')}"
156 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
157 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
157 158 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
158 %endif
159 </td>
159 %endif
160 </td>
160 161 <td><span class="tooltip" title="${repo['last_change']}">${("r%s:%s") % (repo['rev'],h.short_id(repo['tip']))}</span></td>
161 162 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
162 163 <td>
163 164 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
164 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
165 ${h.end_form()}
165 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
166 ${h.end_form()}
166 167 </td>
167 168 </tr>
168 169 %endfor
169 170 %else:
170 171 <div style="padding:5px 0px 10px 0px;">
171 ${_('No repositories yet')}
172 ${_('No repositories yet')}
172 173 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
173 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-button-small")}
174 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
174 175 %endif
175 176 </div>
176 177 %endif
177 178 </tbody>
178 179 </table>
179 180 </div>
180
181 181 </div>
182 <script type="text/javascript">
183 var D = YAHOO.util.Dom;
184 var E = YAHOO.util.Event;
185 var S = YAHOO.util.Selector;
186
187 var q_filter = D.get('q_filter');
188 var F = YAHOO.namespace('q_filter');
189
190 E.on(q_filter,'click',function(){
191 q_filter.value = '';
192 });
193
194 F.filterTimeout = null;
195
196 F.updateFilter = function() {
197 // Reset timeout
198 F.filterTimeout = null;
199
200 var obsolete = [];
201 var nodes = S.query('div.table tr td a.repo_name');
202 var req = q_filter.value.toLowerCase();
203 for (n in nodes){
204 D.setStyle(nodes[n].parentNode.parentNode,'display','')
205 }
206 if (req){
207 for (n in nodes){
208 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
209 obsolete.push(nodes[n]);
210 }
211 }
212 if(obsolete){
213 for (n in obsolete){
214 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
215 }
216 }
217 }
218 }
219
220 E.on(q_filter,'keyup',function(e){
221 clearTimeout(F.filterTimeout);
222 F.filterTimeout = setTimeout(F.updateFilter,600);
223 });
224
225 </script>
226 </%def>
182 <script type="text/javascript">
183 var nodes = YUQ('div.table tr td a.repo_name');
184 var target = 'q_filter';
185 var func = function(node){
186 return node.parentNode.parentNode;
187 }
188 q_filter(target,nodes,func);
189 </script>
190 </%def>
@@ -22,8 +22,8 b''
22 22 <li>
23 23 <span>${h.link_to(_(u'ADD NEW USER'),h.url('new_user'))}</span>
24 24 </li>
25
26 </ul>
25
26 </ul>
27 27 </div>
28 28 <!-- end box / title -->
29 29 <div class="table">
@@ -53,7 +53,7 b''
53 53 <td>
54 54 ${h.form(url('delete_user', id=user.user_id),method='delete')}
55 55 ${h.submit('remove_',_('delete'),id="remove_user_%s" % user.user_id,
56 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user')+"');")}
56 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user: %s') % user.username+"');")}
57 57 ${h.end_form()}
58 58 </td>
59 59 </tr>
@@ -5,9 +5,9 b''
5 5 ${_('Add users group')} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Users groups'),h.url('users_groups'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Users groups'),h.url('users_groups'))}
11 11 &raquo;
12 12 ${_('add new users group')}
13 13 </%def>
@@ -20,7 +20,7 b''
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('users_groups'))}
@@ -35,21 +35,21 b''
35 35 ${h.text('users_group_name',class_='small')}
36 36 </div>
37 37 </div>
38
38
39 39 <div class="field">
40 40 <div class="label label-checkbox">
41 41 <label for="users_group_active">${_('Active')}:</label>
42 42 </div>
43 43 <div class="checkboxes">
44 ${h.checkbox('users_group_active',value=True)}
44 ${h.checkbox('users_group_active',value=True, checked='checked')}
45 45 </div>
46 46 </div>
47
47
48 48 <div class="buttons">
49 49 ${h.submit('save',_('save'),class_="ui-button")}
50 </div>
50 </div>
51 51 </div>
52 52 </div>
53 53 ${h.end_form()}
54 </div>
55 </%def>
54 </div>
55 </%def>
@@ -6,9 +6,9 b''
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
11 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
11 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.users_group.users_group_name}"
14 14 </%def>
@@ -21,7 +21,7 b''
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
@@ -36,7 +36,7 b''
36 36 ${h.text('users_group_name',class_='small')}
37 37 </div>
38 38 </div>
39
39
40 40 <div class="field">
41 41 <div class="label label-checkbox">
42 42 <label for="users_group_active">${_('Active')}:</label>
@@ -50,7 +50,7 b''
50 50 <label for="users_group_active">${_('Members')}:</label>
51 51 </div>
52 52 <div class="select">
53 <table>
53 <table>
54 54 <tr>
55 55 <td>
56 56 <div>
@@ -59,193 +59,45 b''
59 59 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
60 60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
61 61 ${_('Remove all elements')}
62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_right.png")}"/>
62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
63 63 </div>
64 64 </div>
65 65 <div style="float:left;width:20px;padding-top:50px">
66 <img alt="add" id="add_element"
67 style="padding:2px;cursor:pointer"
68 src="${h.url("/images/icons/arrow_left.png")}"/>
66 <img alt="add" id="add_element"
67 style="padding:2px;cursor:pointer"
68 src="${h.url('/images/icons/arrow_left.png')}"/>
69 69 <br />
70 <img alt="remove" id="remove_element"
71 style="padding:2px;cursor:pointer"
72 src="${h.url("/images/icons/arrow_right.png")}"/>
70 <img alt="remove" id="remove_element"
71 style="padding:2px;cursor:pointer"
72 src="${h.url('/images/icons/arrow_right.png')}"/>
73 73 </div>
74 74 <div style="float:left">
75 75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
76 76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
77 77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
78 <img alt="add" style="vertical-align:text-bottom" src="${h.url("/images/icons/arrow_left.png")}"/>
78 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
79 79 ${_('Add all elements')}
80 </div>
80 </div>
81 81 </div>
82 82 </div>
83 </td>
84 </tr>
85 </table>
86 </div>
83 </td>
84 </tr>
85 </table>
86 </div>
87 87
88 </div>
88 </div>
89 89 <div class="buttons">
90 90 ${h.submit('save',_('save'),class_="ui-button")}
91 </div>
92 </div>
93 </div>
94 ${h.end_form()}
91 </div>
92 </div>
93 </div>
94 ${h.end_form()}
95 95 </div>
96
97 <script type="text/javascript">
98 YAHOO.util.Event.onDOMReady(function(){
99 var D = YAHOO.util.Dom;
100 var E = YAHOO.util.Event;
101
102 //definition of containers ID's
103 var available_container = 'available_members';
104 var selected_container = 'users_group_members';
105
106 //form containing containers id
107 var form_id = 'edit_users_group';
108
109 //temp container for selected storage.
110 var cache = new Array();
111 var av_cache = new Array();
112 var c = D.get(selected_container);
113 var ac = D.get(available_container);
114
115 //get only selected options for further fullfilment
116 for(var i = 0;node =c.options[i];i++){
117 if(node.selected){
118 //push selected to my temp storage left overs :)
119 cache.push(node);
120 }
121 }
122
123 //clear 'selected' select
124 //c.options.length = 0;
125 96
126 //fill it with remembered options
127 //for(var i = 0;node = cache[i];i++){
128 // c.options[i]=new Option(node.text, node.value, false, false);
129 //}
130
131
132 //get all available options to cache
133 for(var i = 0;node =ac.options[i];i++){
134 //push selected to my temp storage left overs :)
135 av_cache.push(node);
136 }
137
138 //fill available only with those not in choosen
139 ac.options.length=0;
140 tmp_cache = new Array();
141
142 for(var i = 0;node = av_cache[i];i++){
143 var add = true;
144 for(var i2 = 0;node_2 = cache[i2];i2++){
145 if(node.value == node_2.value){
146 add=false;
147 break;
148 }
149 }
150 if(add){
151 tmp_cache.push(new Option(node.text, node.value, false, false));
152 }
153 }
154
155 for(var i = 0;node = tmp_cache[i];i++){
156 ac.options[i] = node;
157 }
158
159 function prompts_action_callback(e){
160
161 var choosen = D.get(selected_container);
162 var available = D.get(available_container);
163
164 //get checked and unchecked options from field
165 function get_checked(from_field){
166 //temp container for storage.
167 var sel_cache = new Array();
168 var oth_cache = new Array();
169
170 for(var i = 0;node = from_field.options[i];i++){
171 if(node.selected){
172 //push selected fields :)
173 sel_cache.push(node);
174 }
175 else{
176 oth_cache.push(node)
177 }
178 }
179
180 return [sel_cache,oth_cache]
181 }
182
183 //fill the field with given options
184 function fill_with(field,options){
185 //clear firtst
186 field.options.length=0;
187 for(var i = 0;node = options[i];i++){
188 field.options[i]=new Option(node.text, node.value,
189 false, false);
190 }
191
192 }
193 //adds to current field
194 function add_to(field,options){
195 for(var i = 0;node = options[i];i++){
196 field.appendChild(new Option(node.text, node.value,
197 false, false));
198 }
199 }
200
201 // add action
202 if (this.id=='add_element'){
203 var c = get_checked(available);
204 add_to(choosen,c[0]);
205 fill_with(available,c[1]);
206 }
207 // remove action
208 if (this.id=='remove_element'){
209 var c = get_checked(choosen);
210 add_to(available,c[0]);
211 fill_with(choosen,c[1]);
212 }
213 // add all elements
214 if(this.id=='add_all_elements'){
215 for(var i=0; node = available.options[i];i++){
216 choosen.appendChild(new Option(node.text,
217 node.value, false, false));
218 }
219 available.options.length = 0;
220 }
221 //remove all elements
222 if(this.id=='remove_all_elements'){
223 for(var i=0; node = choosen.options[i];i++){
224 available.appendChild(new Option(node.text,
225 node.value, false, false));
226 }
227 choosen.options.length = 0;
228 }
229
230 }
231
232
233 E.addListener(['add_element','remove_element',
234 'add_all_elements','remove_all_elements'],'click',
235 prompts_action_callback)
236
237 E.addListener(form_id,'submit',function(){
238 var choosen = D.get(selected_container);
239 for (var i = 0; i < choosen.options.length; i++) {
240 choosen.options[i].selected = 'selected';
241 }
242 })
243 });
244 </script>
245 97 <div class="box box-right">
246 98 <!-- box / title -->
247 99 <div class="title">
248 <h5>${_('Permissions')}</h5>
100 <h5>${_('Permissions')}</h5>
249 101 </div>
250 102 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
251 103 <div class="form">
@@ -262,9 +114,167 b''
262 114 <div class="buttons">
263 115 ${h.submit('save',_('Save'),class_="ui-button")}
264 116 ${h.reset('reset',_('Reset'),class_="ui-button")}
265 </div>
266 </div>
117 </div>
118 </div>
119 </div>
120 ${h.end_form()}
121 </div>
122
123 <div class="box box-right">
124 <!-- box / title -->
125 <div class="title">
126 <h5>${_('Group members')}</h5>
127 </div>
128 <div class="group_members_wrap">
129 <ul class="group_members">
130 %for user in c.group_members_obj:
131 <li>
132 <div class="group_member">
133 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
134 <div>${user.username}</div>
135 <div>${user.full_name}</div>
136 </div>
137 </li>
138 %endfor
139 </ul>
267 140 </div>
268 ${h.end_form()}
269 141 </div>
270 </%def>
142 <script type="text/javascript">
143 YAHOO.util.Event.onDOMReady(function(){
144 var D = YAHOO.util.Dom;
145 var E = YAHOO.util.Event;
146
147 //definition of containers ID's
148 var available_container = 'available_members';
149 var selected_container = 'users_group_members';
150
151 //form containing containers id
152 var form_id = 'edit_users_group';
153
154 //temp container for selected storage.
155 var cache = new Array();
156 var av_cache = new Array();
157 var c = D.get(selected_container);
158 var ac = D.get(available_container);
159
160 //get only selected options for further fullfilment
161 for(var i = 0;node =c.options[i];i++){
162 if(node.selected){
163 //push selected to my temp storage left overs :)
164 cache.push(node);
165 }
166 }
167
168 //get all available options to cache
169 for(var i = 0;node =ac.options[i];i++){
170 //push selected to my temp storage left overs :)
171 av_cache.push(node);
172 }
173
174 //fill available only with those not in choosen
175 ac.options.length=0;
176 tmp_cache = new Array();
177
178 for(var i = 0;node = av_cache[i];i++){
179 var add = true;
180 for(var i2 = 0;node_2 = cache[i2];i2++){
181 if(node.value == node_2.value){
182 add=false;
183 break;
184 }
185 }
186 if(add){
187 tmp_cache.push(new Option(node.text, node.value, false, false));
188 }
189 }
190
191 for(var i = 0;node = tmp_cache[i];i++){
192 ac.options[i] = node;
193 }
194
195 function prompts_action_callback(e){
196
197 var choosen = D.get(selected_container);
198 var available = D.get(available_container);
199
200 //get checked and unchecked options from field
201 function get_checked(from_field){
202 //temp container for storage.
203 var sel_cache = new Array();
204 var oth_cache = new Array();
205
206 for(var i = 0;node = from_field.options[i];i++){
207 if(node.selected){
208 //push selected fields :)
209 sel_cache.push(node);
210 }
211 else{
212 oth_cache.push(node)
213 }
214 }
215
216 return [sel_cache,oth_cache]
217 }
218
219 //fill the field with given options
220 function fill_with(field,options){
221 //clear firtst
222 field.options.length=0;
223 for(var i = 0;node = options[i];i++){
224 field.options[i]=new Option(node.text, node.value,
225 false, false);
226 }
227
228 }
229 //adds to current field
230 function add_to(field,options){
231 for(var i = 0;node = options[i];i++){
232 field.appendChild(new Option(node.text, node.value,
233 false, false));
234 }
235 }
236
237 // add action
238 if (this.id=='add_element'){
239 var c = get_checked(available);
240 add_to(choosen,c[0]);
241 fill_with(available,c[1]);
242 }
243 // remove action
244 if (this.id=='remove_element'){
245 var c = get_checked(choosen);
246 add_to(available,c[0]);
247 fill_with(choosen,c[1]);
248 }
249 // add all elements
250 if(this.id=='add_all_elements'){
251 for(var i=0; node = available.options[i];i++){
252 choosen.appendChild(new Option(node.text,
253 node.value, false, false));
254 }
255 available.options.length = 0;
256 }
257 //remove all elements
258 if(this.id=='remove_all_elements'){
259 for(var i=0; node = choosen.options[i];i++){
260 available.appendChild(new Option(node.text,
261 node.value, false, false));
262 }
263 choosen.options.length = 0;
264 }
265
266 }
267
268 E.addListener(['add_element','remove_element',
269 'add_all_elements','remove_all_elements'],'click',
270 prompts_action_callback)
271
272 E.addListener(form_id,'submit',function(){
273 var choosen = D.get(selected_container);
274 for (var i = 0; i < choosen.options.length; i++) {
275 choosen.options[i].selected = 'selected';
276 }
277 });
278 });
279 </script>
280 </%def>
@@ -22,8 +22,8 b''
22 22 <li>
23 23 <span>${h.link_to(_(u'ADD NEW USER GROUP'),h.url('new_users_group'))}</span>
24 24 </li>
25
26 </ul>
25
26 </ul>
27 27 </div>
28 28 <!-- end box / title -->
29 29 <div class="table">
@@ -42,7 +42,7 b''
42 42 <td>
43 43 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
44 44 ${h.submit('remove_','delete',id="remove_group_%s" % u_group.users_group_id,
45 class_="delete_icon action_button",onclick="return confirm('Confirm to delete this users group');")}
45 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this users group: %s') % u_group.users_group_name+"');")}
46 46 ${h.end_form()}
47 47 </td>
48 48 </tr>
@@ -3,73 +3,7 b''
3 3
4 4 <!-- HEADER -->
5 5 <div id="header">
6 <!-- user -->
7 <ul id="logged-user">
8 <li class="first">
9 <div id="quick_login" style="display:none">
10 ${h.form(h.url('login_home',came_from=h.url.current()))}
11 <div class="form">
12 <div class="fields">
13 <div class="field">
14 <div class="label">
15 <label for="username">${_('Username')}:</label>
16 </div>
17 <div class="input">
18 ${h.text('username',class_='focus',size=40)}
19 </div>
20
21 </div>
22 <div class="field">
23 <div class="label">
24 <label for="password">${_('Password')}:</label>
25 </div>
26 <div class="input">
27 ${h.password('password',class_='focus',size=40)}
28 </div>
29
30 </div>
31 <div class="buttons">
32 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
33 <div class="register">
34 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
35 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
36 %endif
37 </div>
38 ${h.submit('sign_in',_('Sign In'),class_="ui-button")}
39 </div>
40 </div>
41 </div>
42 ${h.end_form()}
43 </div>
44
45 <div class="gravatar">
46 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
47 </div>
48 <div class="account">
49 %if c.rhodecode_user.username == 'default':
50 <a href="${h.url('public_journal')}">${_('Public journal')}</a>
51 %else:
52 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
53 %endif
54 </div>
55 </li>
56 <li>
57 <a href="${h.url('home')}">${_('Home')}</a>
58 </li>
59 %if c.rhodecode_user.username != 'default':
60 <li>
61 <a href="${h.url('journal')}">${_('Journal')}</a>
62 ##(${c.unread_journal}
63 </li>
64 %endif
65 %if c.rhodecode_user.username == 'default':
66 <li class="last highlight">${h.link_to(_(u'Login'),h.url('login_home'),id='quick_login_link')}</li>
67 %else:
68 <li class="last highlight">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
69 %endif
70 </ul>
71 <!-- end user -->
72 <div id="header-inner" class="title">
6 <div id="header-inner" class="title hover">
73 7 <div id="logo">
74 8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
75 9 </div>
@@ -78,11 +12,11 b''
78 12 <!-- END MENU -->
79 13 ${self.body()}
80 14 </div>
81 </div>
15 </div>
82 16 <!-- END HEADER -->
83
17
84 18 <!-- CONTENT -->
85 <div id="content">
19 <div id="content">
86 20 <div class="flash_msg">
87 21 <% messages = h.flash.pop_messages() %>
88 22 % if messages:
@@ -92,11 +26,11 b''
92 26 % endfor
93 27 </ul>
94 28 % endif
95 </div>
96 <div id="main">
29 </div>
30 <div id="main">
97 31 ${next.main()}
98 32 </div>
99 </div>
33 </div>
100 34 <!-- END CONTENT -->
101 35
102 36 <!-- FOOTER -->
@@ -107,7 +41,7 b''
107 41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
108 42 </p>
109 43 <p class="footer-link-right">
110 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
44 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
111 45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
112 46 </p>
113 47 </div>
@@ -126,159 +60,143 b''
126 60 </div>
127 61 </%def>
128 62
63 <%def name="usermenu()">
64 <div class="user-menu">
65 <div class="container">
66 <div class="gravatar" id="quick_login_link">
67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
68 </div>
69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
70 <div class="notifications">
71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
72 </div>
73 %endif
74 </div>
75 <div id="quick_login" style="display:none">
76 %if c.rhodecode_user.username == 'default':
77 <h4>${_('Login to your account')}</h4>
78 ${h.form(h.url('login_home',came_from=h.url.current()))}
79 <div class="form">
80 <div class="fields">
81 <div class="field">
82 <div class="label">
83 <label for="username">${_('Username')}:</label>
84 </div>
85 <div class="input">
86 ${h.text('username',class_='focus',size=40)}
87 </div>
88
89 </div>
90 <div class="field">
91 <div class="label">
92 <label for="password">${_('Password')}:</label>
93 </div>
94 <div class="input">
95 ${h.password('password',class_='focus',size=40)}
96 </div>
97
98 </div>
99 <div class="buttons">
100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
101 <div class="register">
102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
104 %endif
105 </div>
106 <div class="submit">
107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
108 </div>
109 </div>
110 </div>
111 </div>
112 ${h.end_form()}
113 %else:
114 <div class="links_left">
115 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
116 <div class="email">${c.rhodecode_user.email}</div>
117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
118 <div class="inbox"><a href="${h.url('notifications')}">${_('Inbox')}: ${c.unread_notifications}</a></div>
119 </div>
120 <div class="links_right">
121 <ol class="links">
122 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
123 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
124 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
125 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
126 </ol>
127 </div>
128 %endif
129 </div>
130 </div>
131 </%def>
129 132
130 133 <%def name="menu(current=None)">
131 <%
134 <%
132 135 def is_current(selected):
133 136 if selected == current:
134 137 return h.literal('class="current"')
135 138 %>
136 %if current not in ['home','admin']:
137 ##REGULAR MENU
139 %if current not in ['home','admin']:
140 ##REGULAR MENU
138 141 <ul id="quick">
139 142 <!-- repo switcher -->
140 143 <li>
141 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
144 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
142 145 <span class="icon">
143 146 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
144 147 </span>
145 <span>&darr;</span>
148 <span>&darr;</span>
146 149 </a>
147 150 <ul id="repo_switcher_list" class="repo_switcher">
148 151 <li>
149 152 <a href="#">${_('loading...')}</a>
150 153 </li>
151 154 </ul>
152 <script type="text/javascript">
153 YUE.on('repo_switcher','mouseover',function(){
154 function qfilter(){
155 var S = YAHOO.util.Selector;
156
157 var q_filter = YUD.get('q_filter_rs');
158 var F = YAHOO.namespace('q_filter_rs');
159
160 YUE.on(q_filter,'click',function(){
161 q_filter.value = '';
162 });
163
164 F.filterTimeout = null;
165
166 F.updateFilter = function() {
167 // Reset timeout
168 F.filterTimeout = null;
169
170 var obsolete = [];
171 var nodes = S.query('ul#repo_switcher_list li a.repo_name');
172 var req = YUD.get('q_filter_rs').value;
173 for (n in nodes){
174 YUD.setStyle(nodes[n].parentNode,'display','')
175 }
176 if (req){
177 for (n in nodes){
178 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
179 obsolete.push(nodes[n]);
180 }
181 }
182 if(obsolete){
183 for (n in obsolete){
184 YUD.setStyle(obsolete[n].parentNode,'display','none');
185 }
186 }
187 }
188 }
189
190 YUE.on(q_filter,'keyup',function(e){
191 clearTimeout(F.filterTimeout);
192 F.filterTimeout = setTimeout(F.updateFilter,600);
193 });
194 }
195 var loaded = YUD.hasClass('repo_switcher','loaded');
196 if(!loaded){
197 YUD.addClass('repo_switcher','loaded');
198 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
199 function(o){qfilter();},
200 function(o){YUD.removeClass('repo_switcher','loaded');}
201 ,null);
202 }
203 return false;
204 });
205 </script>
206 155 </li>
207
156
208 157 <li ${is_current('summary')}>
209 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
158 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
210 159 <span class="icon">
211 160 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
212 161 </span>
213 <span>${_('Summary')}</span>
214 </a>
162 <span>${_('Summary')}</span>
163 </a>
215 164 </li>
216 ##<li ${is_current('shortlog')}>
217 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
218 ## <span class="icon">
219 ## <img src="${h.url('/images/icons/application_view_list.png')}" alt="${_('Shortlog')}" />
220 ## </span>
221 ## <span>${_('Shortlog')}</span>
222 ## </a>
223 ##</li>
224 165 <li ${is_current('changelog')}>
225 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
166 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
226 167 <span class="icon">
227 168 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
228 169 </span>
229 <span>${_('Changelog')}</span>
230 </a>
231 </li>
232
170 <span>${_('Changelog')}</span>
171 </a>
172 </li>
173
233 174 <li ${is_current('switch_to')}>
234 <a title="${_('Switch to')}" href="#">
175 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
235 176 <span class="icon">
236 177 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
237 178 </span>
238 <span>${_('Switch to')}</span>
239 </a>
240 <ul>
241 <li>
242 ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
243 <ul>
244 %if c.rhodecode_repo.branches.values():
245 %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
246 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
247 %endfor
248 %else:
249 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
250 %endif
251 </ul>
252 </li>
253 <li>
254 ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
255 <ul>
256 %if c.rhodecode_repo.tags.values():
257 %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
258 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
259 %endfor
260 %else:
261 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
262 %endif
263 </ul>
264 </li>
179 <span>${_('Switch to')}</span>
180 </a>
181 <ul id="switch_to_list" class="switch_to">
182 <li><a href="#">${_('loading...')}</a></li>
265 183 </ul>
266 184 </li>
267 185 <li ${is_current('files')}>
268 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
186 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
269 187 <span class="icon">
270 188 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
271 189 </span>
272 <span>${_('Files')}</span>
273 </a>
274 </li>
275
190 <span>${_('Files')}</span>
191 </a>
192 </li>
193
276 194 <li ${is_current('options')}>
277 <a title="${_('Options')}" href="#">
195 <a class="menu_link" title="${_('Options')}" href="#">
278 196 <span class="icon">
279 197 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
280 198 </span>
281 <span>${_('Options')}</span>
199 <span>${_('Options')}</span>
282 200 </a>
283 201 <ul>
284 202 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
@@ -290,10 +208,10 b''
290 208 %endif
291 209 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
292 210 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
293
211
294 212 % if h.HasPermissionAll('hg.admin')('access admin main page'):
295 213 <li>
296 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
214 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
297 215 <%def name="admin_menu()">
298 216 <ul>
299 217 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
@@ -303,18 +221,18 b''
303 221 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
304 222 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
305 223 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
306 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
224 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
307 225 </ul>
308 226 </%def>
309
227
310 228 ${admin_menu()}
311 229 </li>
312 230 % endif
313 </ul>
231 </ul>
314 232 </li>
315
233
316 234 <li>
317 <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
235 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
318 236 <span class="icon_short">
319 237 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
320 238 </span>
@@ -322,56 +240,99 b''
322 240 </a>
323 241 </li>
324 242 <li>
325 <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
243 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
326 244 <span class="icon_short">
327 245 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
328 246 </span>
329 247 <span class="short">${c.repository_forks}</span>
330 248 </a>
331 </li>
332
249 </li>
250 ${usermenu()}
333 251 </ul>
252 <script type="text/javascript">
253 YUE.on('repo_switcher','mouseover',function(){
254 function qfilter(){
255 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
256 var target = 'q_filter_rs';
257 var func = function(node){
258 return node.parentNode;
259 }
260 q_filter(target,nodes,func);
261 }
262 var loaded = YUD.hasClass('repo_switcher','loaded');
263 if(!loaded){
264 YUD.addClass('repo_switcher','loaded');
265 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
266 function(o){qfilter();},
267 function(o){YUD.removeClass('repo_switcher','loaded');}
268 ,null);
269 }
270 return false;
271 });
272
273 YUE.on('branch_tag_switcher','mouseover',function(){
274 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
275 if(!loaded){
276 YUD.addClass('branch_tag_switcher','loaded');
277 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
278 function(o){},
279 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
280 ,null);
281 }
282 return false;
283 });
284 </script>
334 285 %else:
335 286 ##ROOT MENU
336 287 <ul id="quick">
337 288 <li>
338 <a title="${_('Home')}" href="${h.url('home')}">
289 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
339 290 <span class="icon">
340 291 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
341 292 </span>
342 <span>${_('Home')}</span>
343 </a>
293 <span>${_('Home')}</span>
294 </a>
344 295 </li>
345 % if c.rhodecode_user.username != 'default':
296 %if c.rhodecode_user.username != 'default':
346 297 <li>
347 <a title="${_('Journal')}" href="${h.url('journal')}">
298 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
348 299 <span class="icon">
349 300 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
350 301 </span>
351 <span>${_('Journal')}</span>
352 </a>
302 <span>${_('Journal')}</span>
303 </a>
353 304 </li>
354 % endif
305 %else:
306 <li>
307 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
308 <span class="icon">
309 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
310 </span>
311 <span>${_('Public journal')}</span>
312 </a>
313 </li>
314 %endif
355 315 <li>
356 <a title="${_('Search')}" href="${h.url('search')}">
316 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
357 317 <span class="icon">
358 318 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
359 319 </span>
360 <span>${_('Search')}</span>
361 </a>
320 <span>${_('Search')}</span>
321 </a>
362 322 </li>
363
323
364 324 %if h.HasPermissionAll('hg.admin')('access admin main page'):
365 325 <li ${is_current('admin')}>
366 <a title="${_('Admin')}" href="${h.url('admin_home')}">
326 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
367 327 <span class="icon">
368 328 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
369 329 </span>
370 <span>${_('Admin')}</span>
330 <span>${_('Admin')}</span>
371 331 </a>
372 332 ${admin_menu()}
373 333 </li>
374 334 %endif
335 ${usermenu()}
375 336 </ul>
376 %endif
337 %endif
377 338 </%def>
@@ -11,22 +11,21 b''
11 11 <%def name="css()">
12 12 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
13 13 <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}"/>
14 <link rel="stylesheet" type="text/css" href="${h.url('/css/diff.css')}"/>
15 14 ## EXTRA FOR CSS
16 15 ${self.css_extra()}
17 16 </%def>
18 17 <%def name="css_extra()">
19 18 </%def>
20
19
21 20 ${self.css()}
22
21
23 22 %if c.ga_code:
24 23 <!-- Analytics -->
25 24 <script type="text/javascript">
26 25 var _gaq = _gaq || [];
27 26 _gaq.push(['_setAccount', '${c.ga_code}']);
28 27 _gaq.push(['_trackPageview']);
29
28
30 29 (function() {
31 30 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
32 31 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
@@ -34,7 +33,7 b''
34 33 })();
35 34 </script>
36 35 %endif
37
36
38 37 ## JAVASCRIPT ##
39 38 <%def name="js()">
40 39 <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
@@ -45,17 +44,21 b''
45 44 <script type="text/javascript" src="${h.url('/js/rhodecode.js')}"></script>
46 45 ## EXTRA FOR JS
47 46 ${self.js_extra()}
48
47
49 48 <script type="text/javascript">
50 var base_url = "${h.url('toggle_following')}";
51 function onSuccess(target){
49 var follow_base_url = "${h.url('toggle_following')}";
50 var stop_follow_text = "${_('Stop following this repository')}";
51 var start_follow_text = "${_('Start following this repository')}";
52
53
54 var onSuccessFollow = function(target){
52 55 var f = YUD.get(target.id);
53 56 var f_cnt = YUD.get('current_followers_count');
54
57
55 58 if(f.getAttribute('class')=='follow'){
56 59 f.setAttribute('class','following');
57 f.setAttribute('title',"${_('Stop following this repository')}");
58
60 f.setAttribute('title',stop_follow_text);
61
59 62 if(f_cnt){
60 63 var cnt = Number(f_cnt.innerHTML)+1;
61 64 f_cnt.innerHTML = cnt;
@@ -63,46 +66,50 b''
63 66 }
64 67 else{
65 68 f.setAttribute('class','follow');
66 f.setAttribute('title',"${_('Start following this repository')}");
69 f.setAttribute('title',start_follow_text);
67 70 if(f_cnt){
68 71 var cnt = Number(f_cnt.innerHTML)+1;
69 72 f_cnt.innerHTML = cnt;
70 }
73 }
71 74 }
72 75 }
73
74 function toggleFollowingUser(target,fallows_user_id,token,user_id){
76
77 var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
75 78 args = 'follows_user_id='+fallows_user_id;
76 79 args+= '&amp;auth_token='+token;
77 80 if(user_id != undefined){
78 81 args+="&amp;user_id="+user_id;
79 82 }
80 YUC.asyncRequest('POST',base_url,{
83 YUC.asyncRequest('POST',follow_base_url,{
81 84 success:function(o){
82 onSuccess(target);
85 onSuccessFollow(target);
83 86 }
84 87 },args);
85 88 return false;
86 89 }
87
88 function toggleFollowingRepo(target,fallows_repo_id,token,user_id){
89
90
91 var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
92
90 93 args = 'follows_repo_id='+fallows_repo_id;
91 94 args+= '&amp;auth_token='+token;
92 95 if(user_id != undefined){
93 96 args+="&amp;user_id="+user_id;
94 }
95 YUC.asyncRequest('POST',base_url,{
97 }
98 YUC.asyncRequest('POST',follow_base_url,{
96 99 success:function(o){
97 onSuccess(target);
100 onSuccessFollow(target);
98 101 }
99 },args);
102 },args);
100 103 return false;
101 104 }
102 105 YUE.onDOMReady(function(){
103
106 tooltip_activate();
107 show_more_event();
108
104 109 YUE.on('quick_login_link','click',function(e){
105
110 // make sure we don't redirect
111 YUE.preventDefault(e);
112
106 113 if(YUD.hasClass('quick_login_link','enabled')){
107 114 YUD.setStyle('quick_login','display','none');
108 115 YUD.removeClass('quick_login_link','enabled');
@@ -110,35 +117,19 b''
110 117 else{
111 118 YUD.setStyle('quick_login','display','');
112 119 YUD.addClass('quick_login_link','enabled');
113 YUD.get('username').focus();
120 var usr = YUD.get('username');
121 if(usr){
122 usr.focus();
123 }
114 124 }
115 //make sure we don't redirect
116 YUE.preventDefault(e);
117 125 });
118
119 tooltip_activate();
120 show_more_event();
121
122 YUE.on(YUQ('.quick_repo_menu'),'click',function(e){
123 var menu = e.currentTarget.firstElementChild;
124 if(YUD.hasClass(menu,'hidden')){
125 YUD.addClass(e.currentTarget,'active');
126 YUD.removeClass(menu,'hidden');
127 }else{
128 YUD.removeClass(e.currentTarget,'active');
129 YUD.addClass(menu,'hidden');
130 }
131 })
132
133 })
126 })
134 127 </script>
135
136 128 </%def>
137 <%def name="js_extra()">
138 </%def>
129 <%def name="js_extra()"></%def>
139 130 ${self.js()}
140 131 </head>
141 132 <body id="body">
142 133 ${next.body()}
143 134 </body>
144 </html> No newline at end of file
135 </html>
@@ -6,15 +6,16 b''
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 <input class="q_filter_box" id="q_filter_branches" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
9 10 ${h.link_to(u'Home',h.url('/'))}
10 &raquo;
11 &raquo;
11 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 13 &raquo;
13 14 ${_('branches')}
14 15 </%def>
15 16
16 17 <%def name="page_nav()">
17 ${self.menu('branches')}
18 ${self.menu('branches')}
18 19 </%def>
19 20
20 21 <%def name="main()">
@@ -27,5 +28,50 b''
27 28 <div class="table">
28 29 <%include file='branches_data.html'/>
29 30 </div>
30 </div>
31 </%def> No newline at end of file
31 </div>
32 <script type="text/javascript">
33
34 // main table sorting
35 var myColumnDefs = [
36 {key:"name",label:"${_('Name')}",sortable:true},
37 {key:"date",label:"${_('Date')}",sortable:true,
38 sortOptions: { sortFunction: dateSort }},
39 {key:"author",label:"${_('Author')}",sortable:true},
40 {key:"revision",label:"${_('Revision')}",sortable:true,
41 sortOptions: { sortFunction: revisionSort }},
42 ];
43
44 var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data"));
45
46 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
47
48 myDataSource.responseSchema = {
49 fields: [
50 {key:"name"},
51 {key:"date"},
52 {key:"author"},
53 {key:"revision"},
54 ]
55 };
56
57 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
58 {
59 sortedBy:{key:"name",dir:"asc"},
60 MSG_SORTASC:"${_('Click to sort ascending')}",
61 MSG_SORTDESC:"${_('Click to sort descending')}",
62 MSG_EMPTY:"${_('No records found.')}",
63 MSG_ERROR:"${_('Data error.')}",
64 MSG_LOADING:"${_('Loading...')}",
65 }
66 );
67 myDataTable.subscribe('postRenderEvent',function(oArgs) {
68 tooltip_activate();
69 var func = function(node){
70 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
71 }
72 q_filter('q_filter_branches',YUQ('div.table tr td .logtags .branchtag a'),func);
73 });
74
75 </script>
76
77 </%def>
@@ -1,53 +1,52 b''
1 % if c.repo_branches:
2 <table class="table_disp">
1 %if c.repo_branches:
2 <div id="table_wrap" class="yui-skin-sam">
3 <table id="branches_data">
4 <thead>
3 5 <tr>
6 <th class="left">${_('name')}</th>
4 7 <th class="left">${_('date')}</th>
5 <th class="left">${_('name')}</th>
6 8 <th class="left">${_('author')}</th>
7 9 <th class="left">${_('revision')}</th>
8 <th class="left">${_('links')}</th>
9 10 </tr>
11 </thead>
10 12 %for cnt,branch in enumerate(c.repo_branches.items()):
11 13 <tr class="parity${cnt%2}">
12 <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span>
13 </td>
14 14 <td>
15 15 <span class="logtags">
16 16 <span class="branchtag">${h.link_to(branch[0],
17 h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
18 </span>
19 </td>
17 h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
18 </span>
19 </td>
20 <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span></td>
20 21 <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
21 <td>r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</td>
22 <td class="nowrap">
23 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
24 |
25 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
26 </td>
27 </tr>
22 <td>
23 <div>
24 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
25 </div>
26 </td>
27 </tr>
28 28 %endfor
29 29 % if hasattr(c,'repo_closed_branches') and c.repo_closed_branches:
30 30 %for cnt,branch in enumerate(c.repo_closed_branches.items()):
31 31 <tr class="parity${cnt%2}">
32 <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span>
33 </td>
34 32 <td>
35 33 <span class="logtags">
36 34 <span class="branchtag">${h.link_to(branch[0]+' [closed]',
37 35 h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
38 </span>
39 </td>
36 </span>
37 </td>
38 <td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span></td>
40 39 <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
41 <td>r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</td>
42 <td class="nowrap">
43 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
44 |
45 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
40 <td>
41 <div>
42 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id)}">r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</a></pre>
43 </div>
46 44 </td>
47 </tr>
45 </tr>
48 46 %endfor
49 %endif
47 %endif
50 48 </table>
49 </div>
51 50 %else:
52 51 ${_('There are no branches yet')}
53 %endif No newline at end of file
52 %endif
@@ -11,11 +11,11 b''
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
@@ -33,52 +33,65 b''
33 33 <div id="graph_content">
34 34 <div class="container_header">
35 35 ${h.form(h.url.current(),method='get')}
36 <div class="info_box">
37 ${h.submit('set',_('Show'),class_="ui-button-small")}
36 <div class="info_box" style="float:left">
37 ${h.submit('set',_('Show'),class_="ui-btn")}
38 38 ${h.text('size',size=1,value=c.size)}
39 <span class="rev">${_('revisions')}</span>
39 ${_('revisions')}
40 40 </div>
41 41 ${h.end_form()}
42 42 <div id="rev_range_container" style="display:none"></div>
43 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
43 44 </div>
44
45
45 46 %for cnt,cs in enumerate(c.pagination):
46 <div id="chg_${cnt+1}" class="container">
47 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
47 48 <div class="left">
48 <div class="date">
49 <div>
49 50 ${h.checkbox(cs.short_id,class_="changeset_range")}
50 <span>${_('commit')} ${cs.revision}: ${h.short_id(cs.raw_id)}@${cs.date}</span>
51 <span class="tooltip" title="${h.age(cs.date)}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
51 52 </div>
52 53 <div class="author">
53 54 <div class="gravatar">
54 55 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
55 56 </div>
56 <div title="${h.email_or_none(cs.author)}" class="user">${h.person(cs.author)}</div>
57 ##<span><a href="mailto:${h.email_or_none(cs.author)}">${h.email_or_none(cs.author)}</a></span><br/>
57 <div title="${cs.author}" class="user">${h.person(cs.author)}</div>
58 58 </div>
59 <div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
60 </div>
59 <div class="date">${cs.date}</div>
60 </div>
61 <div class="mid">
62 <div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
63 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
64 </div>
61 65 <div class="right">
62 66 <div id="${cs.raw_id}_changes_info" class="changes">
63 <span id="${cs.raw_id}" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</span>
64 </div>
65 %if len(cs.parents)>1:
66 <div class="merge">
67 ${_('merge')}<img alt="merge" src="${h.url('/images/icons/arrow_join.png')}"/>
68 </div>
69 %endif
70 %if cs.parents:
67 <div id="${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</div>
68 <div class="comments-container">
69 %if len(c.comments.get(cs.raw_id,[])) > 0:
70 <div class="comments-cnt" title="${('comments')}">
71 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
72 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
73 <img src="${h.url('/images/icons/comments.png')}">
74 </a>
75 </div>
76 %endif
77 </div>
78 </div>
79 %if cs.parents:
71 80 %for p_cs in reversed(cs.parents):
72 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
73 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
81 <div class="parent">${_('Parent')}
82 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
83 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
74 84 </div>
75 85 %endfor
76 %else:
77 <div class="parent">${_('No parents')}</div>
78 %endif
79
86 %else:
87 <div class="parent">${_('No parents')}</div>
88 %endif
89
80 90 <span class="logtags">
81 %if cs.branch:
91 %if len(cs.parents)>1:
92 <span class="merge">${_('merge')}</span>
93 %endif
94 %if h.is_hg(c.rhodecode_repo) and cs.branch:
82 95 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
83 96 ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
84 97 %endif
@@ -86,26 +99,26 b''
86 99 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
87 100 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
88 101 %endfor
89 </span>
90 </div>
102 </span>
103 </div>
91 104 </div>
92
105
93 106 %endfor
94 107 <div class="pagination-wh pagination-left">
95 108 ${c.pagination.pager('$link_previous ~2~ $link_next')}
96 </div>
109 </div>
97 110 </div>
98 111 </div>
99
112
100 113 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
101 114 <script type="text/javascript">
102 115 YAHOO.util.Event.onDOMReady(function(){
103
116
104 117 //Monitor range checkboxes and build a link to changesets
105 //ranges
118 //ranges
106 119 var checkboxes = YUD.getElementsByClassName('changeset_range');
107 120 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
108 YUE.on(checkboxes,'click',function(e){
121 YUE.on(checkboxes,'click',function(e){
109 122 var checked_checkboxes = [];
110 123 for (pos in checkboxes){
111 124 if(checkboxes[pos].checked){
@@ -115,10 +128,10 b''
115 128 if(checked_checkboxes.length>1){
116 129 var rev_end = checked_checkboxes[0].name;
117 130 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
118
131
119 132 var url = url_tmpl.replace('__REVRANGE__',
120 133 rev_start+'...'+rev_end);
121
134
122 135 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
123 136 link = link.replace('__S',rev_start);
124 137 link = link.replace('__E',rev_end);
@@ -127,19 +140,60 b''
127 140 }
128 141 else{
129 142 YUD.setStyle('rev_range_container','display','none');
130
143
131 144 }
132 });
133
134 //Fetch changeset details
145 });
146
147 var msgs = YUQ('.message');
148 // get first element height
149 var el = YUQ('#graph_content .container')[0];
150 var row_h = el.clientHeight;
151 for(var i=0;i<msgs.length;i++){
152 var m = msgs[i];
153
154 var h = m.clientHeight;
155 var pad = YUD.getStyle(m,'padding');
156 if(h > row_h){
157 var offset = row_h - (h+12);
158 YUD.setStyle(m.nextElementSibling,'display','block');
159 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
160 };
161 }
162 YUE.on(YUQ('.expand'),'click',function(e){
163 var elem = e.currentTarget.parentNode.parentNode;
164 YUD.setStyle(e.currentTarget,'display','none');
165 YUD.setStyle(elem,'height','auto');
166
167 //redraw the graph, max_w and jsdata are global vars
168 set_canvas(max_w);
169
170 var r = new BranchRenderer();
171 r.render(jsdata,max_w);
172
173 })
174
175 // Fetch changeset details
135 176 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
136 177 var id = e.currentTarget.id
137 178 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
138 179 var url = url.replace('__CS__',id);
139 180 ypjax(url,id+'_changes_info',function(){tooltip_activate()});
140 181 });
141
142
182
183 // change branch filter
184 YUE.on(YUD.get('branch_filter'),'change',function(e){
185 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
186 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
187 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
188 var url = url.replace('__BRANCH__',selected_branch);
189 if(selected_branch != ''){
190 window.location = url;
191 }else{
192 window.location = url_main;
193 }
194
195 });
196
143 197 function set_canvas(heads) {
144 198 var c = document.getElementById('graph_nodes');
145 199 var t = document.getElementById('graph_content');
@@ -153,7 +207,7 b''
153 207 var heads = 1;
154 208 var max_heads = 0;
155 209 var jsdata = ${c.jsdata|n};
156
210
157 211 for( var i=0;i<jsdata.length;i++){
158 212 var m = Math.max.apply(Math, jsdata[i][1]);
159 213 if (m>max_heads){
@@ -162,15 +216,15 b''
162 216 }
163 217 var max_w = Math.max(100,max_heads*25);
164 218 set_canvas(max_w);
165
219
166 220 var r = new BranchRenderer();
167 221 r.render(jsdata,max_w);
168
222
169 223 });
170 224 </script>
171 225 %else:
172 226 ${_('There are no changes yet')}
173 %endif
227 %endif
174 228 </div>
175 </div>
176 </%def> No newline at end of file
229 </div>
230 </%def>
@@ -1,9 +1,9 b''
1 % if len(c.cs.affected_files) <= c.affected_files_cut_off:
1 % if len(c.cs.affected_files) <= c.affected_files_cut_off:
2 2 <span class="removed tooltip" title="<b>${_('removed')}</b>${h.changed_tooltip(c.cs.removed)}">${len(c.cs.removed)}</span>
3 3 <span class="changed tooltip" title="<b>${_('changed')}</b>${h.changed_tooltip(c.cs.changed)}">${len(c.cs.changed)}</span>
4 4 <span class="added tooltip" title="<b>${_('added')}</b>${h.changed_tooltip(c.cs.added)}">${len(c.cs.added)}</span>
5 5 % else:
6 6 <span class="removed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
7 7 <span class="changed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
8 <span class="added tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
9 % endif No newline at end of file
8 <span class="added tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
9 % endif
@@ -11,11 +11,11 b''
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
@@ -27,19 +27,24 b''
27 27 <div class="table">
28 28 <div class="diffblock">
29 29 <div class="code-header">
30 <div>
31 ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
32 &raquo; <span>${h.link_to(_('raw diff'),
33 h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</span>
34 &raquo; <span>${h.link_to(_('download diff'),
35 h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</span>
36 </div>
30 <div class="hash">
31 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
32 </div>
33 <div class="date">
34 ${c.changeset.date}
35 </div>
36 <div class="diff-actions">
37 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
38 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
39 ${c.ignorews_url()}
40 ${c.context_url()}
41 </div>
42 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
37 43 </div>
38 44 </div>
39 45 <div id="changeset_content">
40 46 <div class="container">
41 47 <div class="left">
42 <div class="date">${_('commit')} ${c.changeset.revision}: ${h.short_id(c.changeset.raw_id)}@${c.changeset.date}</div>
43 48 <div class="author">
44 49 <div class="gravatar">
45 50 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
@@ -47,93 +52,141 b''
47 52 <span>${h.person(c.changeset.author)}</span><br/>
48 53 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
49 54 </div>
50 <div class="message">${h.link_to(h.wrap_paragraphs(c.changeset.message),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</div>
55 <div class="message">${h.urlify_commit(h.wrap_paragraphs(c.changeset.message),c.repo_name)}</div>
51 56 </div>
52 57 <div class="right">
53 58 <div class="changes">
54 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
59 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
55 60 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
56 61 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
57 62 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
58 63 % else:
59 64 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
60 65 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
61 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
62 % endif
63 </div>
64 %if len(c.changeset.parents)>1:
65 <div class="merge">
66 ${_('merge')}<img alt="merge" src="${h.url("/images/icons/arrow_join.png")}"/>
67 </div>
68 %endif
69
66 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
67 % endif
68 </div>
69
70 70 %if c.changeset.parents:
71 71 %for p_cs in reversed(c.changeset.parents):
72 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
73 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
72 <div class="parent">${_('Parent')}
73 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
74 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
74 75 </div>
75 76 %endfor
76 %else:
77 <div class="parent">${_('No parents')}</div>
78 %endif
77 %else:
78 <div class="parent">${_('No parents')}</div>
79 %endif
79 80 <span class="logtags">
81 %if len(c.changeset.parents)>1:
82 <span class="merge">${_('merge')}</span>
83 %endif
80 84 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
81 85 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
82 86 %for tag in c.changeset.tags:
83 87 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
84 88 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
85 89 %endfor
86 </span>
87 </div>
90 </span>
91 </div>
88 92 </div>
89 <span style="font-size:1.1em;font-weight: bold">
90 ${_('%s files affected with %s additions and %s deletions.') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
93 <span>
94 ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
91 95 </span>
92 96 <div class="cs_files">
93 97 %for change,filenode,diff,cs1,cs2,stat in c.changes:
94 98 <div class="cs_${change}">
95 <div class="node">${h.link_to(h.safe_unicode(filenode.path),
96 h.url.current(anchor=h.repo_name_slug('C%s' % h.safe_unicode(filenode.path))))}</div>
99 <div class="node">
100 %if change != 'removed':
101 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path)+"_target")}
102 %else:
103 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
104 %endif
105 </div>
97 106 <div class="changes">${h.fancy_file_stats(stat)}</div>
98 107 </div>
99 108 %endfor
100 109 % if c.cut_off:
101 110 ${_('Changeset was too big and was cut off...')}
102 111 % endif
103 </div>
112 </div>
104 113 </div>
105
114
106 115 </div>
107
108 %for change,filenode,diff,cs1,cs2,stat in c.changes:
109 %if change !='removed':
110 <div style="clear:both;height:10px"></div>
111 <div class="diffblock">
112 <div id="${h.repo_name_slug('C%s' % h.safe_unicode(filenode.path))}" class="code-header">
113 <div class="changeset_header">
114 <span class="changeset_file">
115 ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
116 revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
117 </span>
118 %if 1:
119 &raquo; <span>${h.link_to(_('diff'),
120 h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff'))}</span>
121 &raquo; <span>${h.link_to(_('raw diff'),
122 h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw'))}</span>
123 &raquo; <span>${h.link_to(_('download diff'),
124 h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download'))}</span>
125 %endif
126 </div>
127 </div>
128 <div class="code-body">
129 %if diff:
130 ${diff|n}
131 %else:
132 ${_('No changes in this file')}
133 %endif
134 </div>
135 </div>
136 %endif
137 %endfor
138 </div>
116
117 ## diff block
118 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
119 ${diff_block.diff_block(c.changes)}
120
121 ## template for inline comment form
122 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
123 ${comment.comment_inline_form(c.changeset)}
124
125 ${comment.comments(c.changeset)}
126
127 <script type="text/javascript">
128 var deleteComment = function(comment_id){
129
130 var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
131 var postData = '_method=delete';
132 var success = function(o){
133 var n = YUD.get('comment-'+comment_id);
134 n.parentNode.removeChild(n);
135 }
136 ajaxPOST(url,postData,success);
137 }
138
139 YUE.onDOMReady(function(){
140
141 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
142 var show = 'none';
143 var target = e.currentTarget;
144 if(target.checked){
145 var show = ''
146 }
147 var boxid = YUD.getAttribute(target,'id_for');
148 var comments = YUQ('#{0} .inline-comments'.format(boxid));
149 for(c in comments){
150 YUD.setStyle(comments[c],'display',show);
151 }
152 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
153 for(c in btns){
154 YUD.setStyle(btns[c],'display',show);
155 }
156 })
157
158 YUE.on(YUQ('.line'),'click',function(e){
159 var tr = e.currentTarget;
160 injectInlineForm(tr);
161 });
162
163 // inject comments into they proper positions
164 var file_comments = YUQ('.inline-comment-placeholder');
165
166 for (f in file_comments){
167 var box = file_comments[f];
168 var inlines = box.children;
169 for(var i=0; i<inlines.length; i++){
170 try{
171
172 var inline = inlines[i];
173 var lineno = YUD.getAttribute(inlines[i],'line');
174 var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
175 var target_line = YUD.get(lineid);
176
177 var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
178 YUD.insertAfter(add,target_line.parentNode);
179
180 var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
181 YUD.insertAfter(comment,target_line.parentNode);
182 }catch(e){
183 console.log(e);
184 }
185 }
186 }
187 })
188
189 </script>
190
191 </div>
139 192 </%def>
@@ -1,3 +1,4 b''
1 ## -*- coding: utf-8 -*-
1 2 <%inherit file="/base/base.html"/>
2 3
3 4 <%def name="title()">
@@ -13,7 +14,7 b''
13 14 </%def>
14 15
15 16 <%def name="page_nav()">
16 ${self.menu('changelog')}
17 ${self.menu('changelog')}
17 18 </%def>
18 19
19 20 <%def name="main()">
@@ -24,27 +25,23 b''
24 25 </div>
25 26 <div class="table">
26 27 <div id="body" class="diffblock">
27 <div class="code-header">
28 <div>
28 <div class="code-header cv">
29 <h3 class="code-header-title">${_('Compare View')}</h3>
30 <div>
29 31 ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
30 <h3>${_('Compare View')}</h3>
31 ##&raquo; <span>${h.link_to(_('raw diff'),
32 ##h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</span>
33 ##&raquo; <span>${h.link_to(_('download diff'),
34 ##h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</span>
35 32 </div>
36 33 </div>
37 34 </div>
38 35 <div id="changeset_compare_view_content">
39 36 <div class="container">
40 <table class="compare_view_commits">
37 <table class="compare_view_commits noborder">
41 38 %for cs in c.cs_ranges:
42 39 <tr>
43 40 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
44 41 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
45 42 <td><div class="author">${h.person(cs.author)}</div></td>
46 43 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
47 <td><div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div></td>
44 <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
48 45 </tr>
49 46 %endfor
50 47 </table>
@@ -54,44 +51,39 b''
54 51 %for cs in c.cs_ranges:
55 52 <div class="cur_cs">r${cs}</div>
56 53 %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
57 <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.repo_name_slug('C%s-%s' % (cs.short_id,h.safe_unicode(filenode.path)))))}</div>
54 <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID(cs.raw_id,filenode.path)))}</div>
58 55 %endfor
59 %endfor
60 </div>
56 %endfor
57 </div>
61 58 </div>
62
59
63 60 </div>
64 %for cs in c.cs_ranges:
65 %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
66 %if change !='removed':
67 <div style="clear:both;height:10px"></div>
68 <div class="diffblock">
69 <div id="${h.repo_name_slug('C%s-%s' % (cs.short_id,h.safe_unicode(filenode.path)))}" class="code-header">
70 <div class="changeset_header">
71 <span class="changeset_file">
72 ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
73 revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
74 </span>
75 %if 1:
76 &raquo; <span>${h.link_to(_('diff'),
77 h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff'))}</span>
78 &raquo; <span>${h.link_to(_('raw diff'),
79 h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw'))}</span>
80 &raquo; <span>${h.link_to(_('download diff'),
81 h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download'))}</span>
82 %endif
83 </div>
84 </div>
85 <div class="code-body">
86 %if diff:
87 ${diff|n}
88 %else:
89 ${_('No changes in this file')}
90 %endif
91 </div>
92 </div>
93 %endif
94 %endfor
95 %endfor
61 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
62 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
63 %for cs in c.cs_ranges:
64 ##${comment.comment_inline_form(cs)}
65 ## diff block
66 <h3 style="border:none;padding-top:8px;">${'r%s:%s' % (cs.revision,h.short_id(cs.raw_id))}</h3>
67 ${diff_block.diff_block(c.changes[cs.raw_id])}
68 ##${comment.comments(cs)}
69
70 %endfor
71 <script type="text/javascript">
72
73 YUE.onDOMReady(function(){
74
75 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
76 var act = e.currentTarget.nextElementSibling;
77
78 if(YUD.hasClass(act,'active')){
79 YUD.removeClass(act,'active');
80 YUD.setStyle(act,'display','none');
81 }else{
82 YUD.addClass(act,'active');
83 YUD.setStyle(act,'display','');
84 }
85 });
86 })
87 </script>
96 88 </div>
97 </%def> No newline at end of file
89 </%def>
@@ -5,4 +5,4 b''
5 5 ${c.parent_tmpl}
6 6 ${c.changeset.message}
7 7
8 ${c.diffs|n} No newline at end of file
8 ${c.diffs|n}
@@ -6,10 +6,10 b''
6 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 7 %if c.redirect_time:
8 8 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
9 %endif
9 %endif
10 10 <link rel="icon" href="${h.url("/images/hgicon.png")}" type="image/png" />
11 11 <meta name="robots" content="index, nofollow"/>
12
12
13 13 <!-- stylesheets -->
14 14 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
15 15 <style type="text/css">
@@ -28,27 +28,26 b''
28 28 margin:10px;
29 29 }
30 30 </style>
31
31
32 32 </head>
33 33 <body>
34 34
35 35 <div id="login">
36 <div class="table">
36 <div class="table">
37 37 <div id="main_div">
38 38 <div style="font-size:2.0em;margin: 10px">${c.rhodecode_name}</div>
39 39 <h1 class="error_message">${c.error_message}</h1>
40
40
41 41 <p>${c.error_explanation}</p>
42
42
43 43 %if c.redirect_time:
44 44 <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
45 %endif
46
45 %endif
46
47 47 </div>
48 48 </div>
49 49 <!-- end login -->
50 50 </div>
51 51 </body>
52
52
53 53 </html>
54
@@ -13,7 +13,7 b''
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 ${self.menu('files')}
16 ${self.menu('files')}
17 17 </%def>
18 18 <%def name="main()">
19 19 <div class="box">
@@ -21,33 +21,27 b''
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 <div class="table">
25 <div id="body" class="diffblock">
26 <div class="code-header">
27 <div class="changeset_header">
28 <span class="changeset_file">${h.link_to(c.f_path,h.url('files_home',repo_name=c.repo_name,
29 revision=c.changeset_2.raw_id,f_path=c.f_path))}</span>
30 &raquo; <span>${h.link_to(_('diff'),
31 h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='diff'))}</span>
32 &raquo; <span>${h.link_to(_('raw diff'),
33 h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='raw'))}</span>
34 &raquo; <span>${h.link_to(_('download diff'),
35 h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='download'))}</span>
36 </div>
37 </div>
38 <div class="code-body">
39 %if c.no_changes:
40 ${_('No changes')}
41 %elif c.big_diff:
42 ${_('Diff is to big to display')} ${h.link_to(_('raw diff'),
43 h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='raw'))}
44 %else:
45 ${c.cur_diff|n}
46 %endif
47 </div>
48 </div>
24 <div>
25 ## diff block
26 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
27 ${diff_block.diff_block(c.changes)}
49 28 </div>
50 </div>
51 </%def>
29 </div>
30 <script>
31 YUE.onDOMReady(function(){
32
33 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
34 var act = e.currentTarget.nextElementSibling;
52 35
53 No newline at end of file
36 if(YUD.hasClass(act,'active')){
37 YUD.removeClass(act,'active');
38 YUD.setStyle(act,'display','none');
39 }else{
40 YUD.addClass(act,'active');
41 YUD.setStyle(act,'display','');
42 }
43 });
44
45 })
46 </script>
47 </%def>
@@ -10,13 +10,13 b''
10 10 ${h.link_to(c.repo_name,h.url('files_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('files')}
13 %if c.files_list:
13 %if c.file:
14 14 @ r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
15 %endif
15 %endif
16 16 </%def>
17 17
18 18 <%def name="page_nav()">
19 ${self.menu('files')}
19 ${self.menu('files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
@@ -27,29 +27,22 b''
27 27 <ul class="links">
28 28 <li>
29 29 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.changeset.branch}</a></span>
30 </li>
31 </ul>
30 </li>
31 </ul>
32 32 </div>
33 33 <div class="table">
34 34 <div id="files_data">
35 %if c.files_list:
36 <h3 class="files_location">
37 ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)}
38 </h3>
39 %if c.files_list.is_dir():
40 <%include file='files_browser.html'/>
41 %else:
42 <%include file='files_source.html'/>
43 %endif
44 %else:
45 <h2>
46 <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
47 ${_('No files at given path')}: "${c.f_path or "/"}"
48 </h2>
49 %endif
50
51 </div>
35 <%include file='files_ypjax.html'/>
36 </div>
52 37 </div>
53 </div>
54
55 </%def> No newline at end of file
38 </div>
39 <script type="text/javascript">
40 var YPJAX_TITLE = "${c.repo_name} ${_('Files')} - ${c.rhodecode_name}";
41 var current_url = "${h.url.current()}";
42 var node_list_url = '${h.url("files_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path='__FPATH__')}';
43 var url_base = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.path)}';
44 var truncated_lbl = "${_('search truncated')}";
45 var nomatch_lbl = "${_('no matching files')}";
46 fileBrowserListeners(current_url, node_list_url, url_base, truncated_lbl, nomatch_lbl);
47 </script>
48 </%def>
@@ -20,7 +20,7 b''
20 20 </%def>
21 21
22 22 <%def name="page_nav()">
23 ${self.menu('files')}
23 ${self.menu('files')}
24 24 </%def>
25 25 <%def name="main()">
26 26 <div class="box">
@@ -31,8 +31,8 b''
31 31 <li>
32 32 <span style="text-transform: uppercase;">
33 33 <a href="#">${_('branch')}: ${c.cs.branch}</a></span>
34 </li>
35 </ul>
34 </li>
35 </ul>
36 36 </div>
37 37 <div class="table">
38 38 <div id="files_data">
@@ -46,16 +46,16 b''
46 46 </div>
47 47 <div class="input">
48 48 <input type="text" value="" size="30" name="filename" id="filename">
49 <input type="button" class="ui-button-small" value="upload file" id="upload_file_enable">
49 ${_('or')} <span class="ui-btn" id="upload_file_enable">${_('Upload file')}</span>
50 50 </div>
51 </div>
51 </div>
52 52 <div id="upload_file_container" class="field" style="display:none">
53 53 <div class="label">
54 54 <label for="location">${_('Upload file')}</label>
55 55 </div>
56 56 <div class="file">
57 57 <input type="file" size="30" name="upload_file" id="upload_file">
58 <input type="button" class="ui-button-small" value="create file" id="file_enable">
58 ${_('or')} <span class="ui-btn" id="file_enable">${_('Create new file')}</span>
59 59 </div>
60 60 </div>
61 61 <div class="field">
@@ -66,45 +66,27 b''
66 66 <input type="text" value="${c.f_path}" size="30" name="location" id="location">
67 67 ${_('use / to separate directories')}
68 68 </div>
69 </div>
69 </div>
70 70 </div>
71 </div>
71 </div>
72 72 <div id="body" class="codeblock">
73 <div id="editor_container">
73 <div id="editor_container">
74 74 <pre id="editor_pre"></pre>
75 75 <textarea id="editor" name="content" style="display:none"></textarea>
76 76 </div>
77 77 <div style="padding: 10px;color:#666666">${_('commit message')}</div>
78 78 <textarea id="commit" name="message" style="height: 100px;width: 99%;margin-left:4px"></textarea>
79 79 </div>
80 <div style="text-align: right;padding-top: 5px">
81 <input id="reset" type="button" value="${_('Reset')}" class="ui-button-small" />
82 ${h.submit('commit',_('Commit changes'),class_="ui-button-small-blue")}
80 <div style="text-align: l;padding-top: 5px">
81 ${h.submit('commit',_('Commit changes'),class_="ui-btn")}
82 ${h.reset('reset',_('Reset'),class_="ui-btn")}
83 83 </div>
84 84 ${h.end_form()}
85 85 <script type="text/javascript">
86 var myCodeMirror = CodeMirror.fromTextArea(YUD.get('editor'),{
87 mode: "null",
88 lineNumbers:true
89 });
90 YUE.on('reset','click',function(e){
91 window.location="${h.url('files_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path)}";
92 });
93
94 YUE.on('file_enable','click',function(){
95 YUD.setStyle('editor_container','display','');
96 YUD.setStyle('upload_file_container','display','none');
97 YUD.setStyle('filename_container','display','');
98 });
99
100 YUE.on('upload_file_enable','click',function(){
101 YUD.setStyle('editor_container','display','none');
102 YUD.setStyle('upload_file_container','display','');
103 YUD.setStyle('filename_container','display','none');
104 });
105
86 var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path)}";
87 initCodeMirror('editor',reset_url);
106 88 </script>
107 </div>
89 </div>
108 90 </div>
109 </div>
110 </%def> No newline at end of file
91 </div>
92 </%def>
@@ -13,7 +13,7 b''
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 ${self.menu('files')}
16 ${self.menu('files')}
17 17 </%def>
18 18 <%def name="main()">
19 19 <div class="box">
@@ -23,55 +23,55 b''
23 23 <ul class="links">
24 24 <li>
25 25 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.cs.branch}</a></span>
26 </li>
27 </ul>
26 </li>
27 </ul>
28 28 </div>
29 29 <div class="table">
30 30 <div id="files_data">
31 31 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
32 <dl class="overview">
33 <dt>${_('Revision')}</dt>
34 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),
35 h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))} </dd>
36 <dt>${_('Size')}</dt>
37 <dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
38 <dt>${_('Mimetype')}</dt>
39 <dd>${c.file.mimetype}</dd>
40 <dt>${_('Options')}</dt>
41 <dd>${h.link_to(_('show source'),
42 h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
43 / ${h.link_to(_('show as raw'),
44 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
45 / ${h.link_to(_('download as raw'),
46 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
47 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
48 % if not c.file.is_binary:
49 / ${h.link_to(_('edit'),
50 h.url('files_edit_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
51 % endif
52 % endif
53 </dd>
54 <dt>${_('History')}</dt>
32 <dl>
33 <dt style="padding-top:10px;font-size:16px">${_('History')}</dt>
55 34 <dd>
56 35 <div>
57 36 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
58 37 ${h.hidden('diff2',c.file.last_changeset.raw_id)}
59 38 ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
60 ${h.submit('diff','diff to revision',class_="ui-button-small")}
61 ${h.submit('show_rev','show at revision',class_="ui-button-small")}
39 ${h.submit('diff','diff to revision',class_="ui-btn")}
40 ${h.submit('show_rev','show at revision',class_="ui-btn")}
62 41 ${h.end_form()}
63 42 </div>
64 </dd>
43 </dd>
65 44 </dl>
66 45 <div id="body" class="codeblock">
67 <div class="code-header">
68 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${h.short_id(c.file.last_changeset.raw_id)}</div>
69 <div class="commit">"${c.file.message}"</div>
70 </div>
46 <div class="code-header">
47 <div class="stats">
48 <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
49 <div class="left item">${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</div>
50 <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
51 <div class="left item last">${c.file.mimetype}</div>
52 <div class="buttons">
53 ${h.link_to(_('show source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
54 ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
55 ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
56 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
57 % if not c.file.is_binary:
58 ${h.link_to(_('edit'),h.url('files_edit_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
59 % endif
60 % endif
61 </div>
62 </div>
63 <div class="author">
64 <div class="gravatar">
65 <img alt="gravatar" src="${h.gravatar_url(h.email(c.cs.author),16)}"/>
66 </div>
67 <div title="${c.cs.author}" class="user">${h.person(c.cs.author)}</div>
68 </div>
69 <div class="commit">${c.file.last_changeset.message}</div>
70 </div>
71 71 <div class="code-body">
72 72 %if c.file.is_binary:
73 73 ${_('Binary file (%s)') % c.file.mimetype}
74 %else:
74 %else:
75 75 % if c.file.size < c.cut_off_limit:
76 76 ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
77 77 %else:
@@ -81,13 +81,13 b''
81 81 <script type="text/javascript">
82 82 function highlight_lines(lines){
83 83 for(pos in lines){
84 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
84 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
85 85 }
86 }
86 }
87 87 page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
88 88 if (page_highlights.length == 2){
89 89 highlight_ranges = page_highlights[1].split(",");
90
90
91 91 var h_lines = [];
92 92 for (pos in highlight_ranges){
93 93 var _range = highlight_ranges[pos].split('-');
@@ -98,26 +98,26 b''
98 98 for(var i=start;i<=end;i++){
99 99 h_lines.push(i);
100 100 }
101 }
101 }
102 102 }
103 103 else{
104 104 h_lines.push(parseInt(highlight_ranges[pos]));
105 105 }
106 106 }
107 107 highlight_lines(h_lines);
108
109 //remember original location
108
109 //remember original location
110 110 var old_hash = location.href.substring(location.href.indexOf('#'));
111
112 // this makes a jump to anchor moved by 3 posstions for padding
111
112 // this makes a jump to anchor moved by 3 posstions for padding
113 113 window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
114
115 //sets old anchor
114
115 //sets old anchor
116 116 window.location.hash = old_hash;
117
117
118 118 }
119 </script>
120 %endif
119 </script>
120 %endif
121 121 </div>
122 122 </div>
123 123 <script type="text/javascript">
@@ -129,8 +129,8 b''
129 129 window.location = url;
130 130 });
131 131 });
132 </script>
133 </div>
132 </script>
133 </div>
134 134 </div>
135 </div>
136 </%def>
135 </div>
136 </%def>
@@ -10,12 +10,12 b''
10 10 <div class="browser-nav">
11 11 ${h.form(h.url.current())}
12 12 <div class="info_box">
13 <span class="rev">${_('view')}@rev</span>
14 <a class="ui-button-small" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
13 <span class="rev">${_('view')}@rev</span>
14 <a class="ui-btn" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
15 15 ${h.text('at_rev',value=c.changeset.revision,size=5)}
16 <a class="ui-button-small" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
17 ## ${h.submit('view',_('view'),class_="ui-button-small")}
18 </div>
16 <a class="ui-btn" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
17 ## ${h.submit('view',_('view'),class_="ui-btn")}
18 </div>
19 19 ${h.end_form()}
20 20 </div>
21 21 <div class="browser-branch">
@@ -24,136 +24,22 b''
24 24 </div>
25 25 <div class="browser-search">
26 26 <div id="search_activate_id" class="search_activate">
27 <a class="ui-button-small" id="filter_activate" href="#">${_('search file list')}</a>
27 <a class="ui-btn" id="filter_activate" href="#">${_('search file list')}</a>
28 28 </div>
29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
30 30 <div id="add_node_id" class="add_node">
31 <a class="ui-button-small" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
31 <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
32 32 </div>
33 % endif
33 % endif
34 34 <div>
35 35 <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
36 36 <div id="node_filter_box" style="display:none">
37 ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)}/<input type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
38
39 <script type="text/javascript">
40
41 YUE.on('stay_at_branch','click',function(e){
42 if(e.target.checked){
43 var uri = "${h.url.current(branch='__BRANCH__')}"
44 uri = uri.replace('__BRANCH__',e.target.value);
45 window.location = uri;
46 }
47 else{
48 window.location = "${h.url.current()}";
49 }
50
51 })
52
53 var n_filter = YUD.get('node_filter');
54 var F = YAHOO.namespace('node_filter');
55
56 var url = '${h.url("files_nodelist_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
57 var node_url = '${h.url("files_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
58
59 url = url.replace('__REPO__','${c.repo_name}');
60 url = url.replace('__REVISION__','${c.changeset.raw_id}');
61 url = url.replace('__FPATH__','${c.files_list.path}');
37 ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
38 </div>
39 </div>
40 </div>
41 </div>
62 42
63 node_url = node_url.replace('__REPO__','${c.repo_name}');
64 node_url = node_url.replace('__REVISION__','${c.changeset.raw_id}');
65
66
67 F.filterTimeout = null;
68 var nodes = null;
69
70
71 F.initFilter = function(){
72 YUD.setStyle('node_filter_box_loading','display','');
73 YUD.setStyle('search_activate_id','display','none');
74 YUD.setStyle('add_node_id','display','none');
75 YUC.initHeader('X-PARTIAL-XHR',true);
76 YUC.asyncRequest('GET',url,{
77 success:function(o){
78 nodes = JSON.parse(o.responseText);
79 YUD.setStyle('node_filter_box_loading','display','none');
80 YUD.setStyle('node_filter_box','display','');
81 },
82 failure:function(o){
83 console.log('failed to load');
84 }
85 },null);
86 }
87
88 F.updateFilter = function(e) {
89
90 return function(){
91 // Reset timeout
92 F.filterTimeout = null;
93 var query = e.target.value;
94 var match = [];
95 var matches = 0;
96 var matches_max = 20;
97 if (query != ""){
98 for(var i=0;i<nodes.length;i++){
99 var pos = nodes[i].toLowerCase().indexOf(query)
100 if(query && pos != -1){
101
102 matches++
103 //show only certain amount to not kill browser
104 if (matches > matches_max){
105 break;
106 }
107
108 var n = nodes[i];
109 var n_hl = n.substring(0,pos)
110 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
111 +n.substring(pos+query.length)
112 match.push('<tr><td><a class="browser-file" href="{0}">{1}</a></td><td colspan="5"></td></tr>'.format(node_url.replace('__FPATH__',n),n_hl));
113 }
114 if(match.length >= matches_max){
115 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('search truncated')}"));
116 }
117
118 }
119 }
120
121 if(query != ""){
122 YUD.setStyle('tbody','display','none');
123 YUD.setStyle('tbody_filtered','display','');
124
125 if (match.length==0){
126 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('no matching files')}"));
127 }
128
129 YUD.get('tbody_filtered').innerHTML = match.join("");
130 }
131 else{
132 YUD.setStyle('tbody','display','');
133 YUD.setStyle('tbody_filtered','display','none');
134 }
135
136 }
137 }
138
139
140 YUE.on(YUD.get('filter_activate'),'click',function(){
141 F.initFilter();
142 })
143 YUE.on(n_filter,'click',function(){
144 n_filter.value = '';
145 });
146 YUE.on(n_filter,'keyup',function(e){
147 clearTimeout(F.filterTimeout);
148 F.filterTimeout = setTimeout(F.updateFilter(e),600);
149 });
150 </script>
151
152 </div>
153 </div>
154 </div>
155 </div>
156
157 43 <div class="browser-body">
158 44 <table class="code-browser">
159 45 <thead>
@@ -166,12 +52,12 b''
166 52 <th>${_('Last commiter')}</th>
167 53 </tr>
168 54 </thead>
169
55
170 56 <tbody id="tbody">
171 %if c.files_list.parent:
57 %if c.file.parent:
172 58 <tr class="parity0">
173 <td>
174 ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.files_list.parent.path),class_="browser-dir")}
59 <td>
60 ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.parent.path),class_="browser-dir ypjax-link")}
175 61 </td>
176 62 <td></td>
177 63 <td></td>
@@ -180,16 +66,16 b''
180 66 <td></td>
181 67 </tr>
182 68 %endif
183
184 %for cnt,node in enumerate(c.files_list):
69
70 %for cnt,node in enumerate(c.file):
185 71 <tr class="parity${cnt%2}">
186 72 <td>
187 ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node))}
73 ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node)+" ypjax-link")}
188 74 </td>
189 75 <td>
190 76 %if node.is_file():
191 77 ${h.format_byte_size(node.size,binary=True)}
192 %endif
78 %endif
193 79 </td>
194 80 <td>
195 81 %if node.is_file():
@@ -198,8 +84,9 b''
198 84 </td>
199 85 <td>
200 86 %if node.is_file():
201 <span class="tooltip" title="${node.last_changeset.raw_id}">
202 ${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</span>
87 <div class="tooltip" title="${node.last_changeset.message}">
88 <pre>${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</pre>
89 </div>
203 90 %endif
204 91 </td>
205 92 <td>
@@ -210,14 +97,16 b''
210 97 </td>
211 98 <td>
212 99 %if node.is_file():
213 ${node.last_changeset.author}
214 %endif
100 <span title="${node.last_changeset.author}">
101 ${h.person(node.last_changeset.author)}
102 </span>
103 %endif
215 104 </td>
216 105 </tr>
217 106 %endfor
218 107 </tbody>
219 108 <tbody id="tbody_filtered" style="display:none">
220 </tbody>
109 </tbody>
221 110 </table>
222 111 </div>
223 </div> No newline at end of file
112 </div>
@@ -20,7 +20,7 b''
20 20 </%def>
21 21
22 22 <%def name="page_nav()">
23 ${self.menu('files')}
23 ${self.menu('files')}
24 24 </%def>
25 25 <%def name="main()">
26 26 <div class="box">
@@ -31,34 +31,48 b''
31 31 <li>
32 32 <span style="text-transform: uppercase;">
33 33 <a href="#">${_('branch')}: ${c.cs.branch}</a></span>
34 </li>
35 </ul>
34 </li>
35 </ul>
36 36 </div>
37 37 <div class="table">
38 38 <div id="files_data">
39 39 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
40 40 ${h.form(h.url.current(),method='post',id='eform')}
41 41 <div id="body" class="codeblock">
42 <div class="code-header">
43 <div class="stats">
44 <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
45 <div class="left item">${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</div>
46 <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
47 <div class="left item last">${c.file.mimetype}</div>
48 <div class="buttons">
49 ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
50 ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
51 ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
52 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
53 % if not c.file.is_binary:
54 ${h.link_to(_('source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
55 % endif
56 % endif
57 </div>
58 </div>
59 <div class="commit">${_('Editing file')}: ${c.file.path}</div>
60 </div>
42 61 <pre id="editor_pre"></pre>
43 <textarea id="editor" name="content" style="display:none">${c.file.content|n}</textarea>
62 <textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
44 63 <div style="padding: 10px;color:#666666">${_('commit message')}</div>
45 64 <textarea id="commit" name="message" style="height: 60px;width: 99%;margin-left:4px"></textarea>
46 65 </div>
47 <div style="text-align: right;padding-top: 5px">
48 <input id="reset" type="button" value="${_('Reset')}" class="ui-button-small" />
49 ${h.submit('commit',_('Commit changes'),class_="ui-button-small-blue")}
66 <div style="text-align: left;padding-top: 5px">
67 ${h.submit('commit',_('Commit changes'),class_="ui-btn")}
68 ${h.reset('reset',_('Reset'),class_="ui-btn")}
50 69 </div>
51 70 ${h.end_form()}
52 71 <script type="text/javascript">
53 var myCodeMirror = CodeMirror.fromTextArea(YUD.get('editor'),{
54 mode: "null",
55 lineNumbers:true
56 });
57 YUE.on('reset','click',function(){
58 window.location="${h.url('files_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.file.path)}";
59 })
72 var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path)}";
73 initCodeMirror('editor',reset_url);
60 74 </script>
61 </div>
75 </div>
62 76 </div>
63 </div>
64 </%def> No newline at end of file
77 </div>
78 </%def>
@@ -1,62 +1,59 b''
1 1 <dl>
2 <dt>${_('Revision')}</dt>
3 <dd>
4 ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,h.short_id(c.files_list.last_changeset.raw_id)),
5 h.url('changeset_home',repo_name=c.repo_name,revision=c.files_list.last_changeset.raw_id))}
6 </dd>
7 <dt>${_('Size')}</dt>
8 <dd>${h.format_byte_size(c.files_list.size,binary=True)}</dd>
9 <dt>${_('Mimetype')}</dt>
10 <dd>${c.files_list.mimetype}</dd>
11 <dt>${_('Options')}</dt>
12 <dd>${h.link_to(_('show annotation'),
13 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
14 / ${h.link_to(_('show as raw'),
15 h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
16 / ${h.link_to(_('download as raw'),
17 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
18 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
19 % if not c.files_list.is_binary:
20 / ${h.link_to(_('edit'),
21 h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
22 % endif
23 % endif
24 </dd>
25 <dt>${_('History')}</dt>
2 <dt style="padding-top:10px;font-size:16px">${_('History')}</dt>
26 3 <dd>
27 4 <div>
28 5 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
29 ${h.hidden('diff2',c.files_list.last_changeset.raw_id)}
30 ${h.select('diff1',c.files_list.last_changeset.raw_id,c.file_history)}
31 ${h.submit('diff','diff to revision',class_="ui-button-small")}
32 ${h.submit('show_rev','show at revision',class_="ui-button-small")}
6 ${h.hidden('diff2',c.file.last_changeset.raw_id)}
7 ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
8 ${h.submit('diff','diff to revision',class_="ui-btn")}
9 ${h.submit('show_rev','show at revision',class_="ui-btn")}
33 10 ${h.end_form()}
34 11 </div>
35 12 </dd>
36 </dl>
13 </dl>
37 14
38
39 15 <div id="body" class="codeblock">
40 16 <div class="code-header">
41 <div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${h.short_id(c.files_list.last_changeset.raw_id)}</div>
42 <div class="commit">"${c.files_list.last_changeset.message}"</div>
17 <div class="stats">
18 <div class="left img"><img src="${h.url('/images/icons/file.png')}"/></div>
19 <div class="left item"><pre>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</pre></div>
20 <div class="left item"><pre>${h.format_byte_size(c.file.size,binary=True)}</pre></div>
21 <div class="left item last"><pre>${c.file.mimetype}</pre></div>
22 <div class="buttons">
23 ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
24 ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
25 ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
26 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
27 % if not c.file.is_binary:
28 ${h.link_to(_('edit'),h.url('files_edit_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
29 % endif
30 % endif
31 </div>
32 </div>
33 <div class="author">
34 <div class="gravatar">
35 <img alt="gravatar" src="${h.gravatar_url(h.email(c.file.last_changeset.author),16)}"/>
36 </div>
37 <div title="${c.file.last_changeset.author}" class="user">${h.person(c.file.last_changeset.author)}</div>
38 </div>
39 <div class="commit">${h.urlify_commit(c.file.last_changeset.message,c.repo_name)}</div>
43 40 </div>
44 41 <div class="code-body">
45 %if c.files_list.is_binary:
46 ${_('Binary file (%s)') % c.files_list.mimetype}
42 %if c.file.is_binary:
43 ${_('Binary file (%s)') % c.file.mimetype}
47 44 %else:
48 % if c.files_list.size < c.cut_off_limit:
49 ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
45 % if c.file.size < c.cut_off_limit:
46 ${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
50 47 %else:
51 48 ${_('File is too big to display')} ${h.link_to(_('show as raw'),
52 h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
49 h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id,f_path=c.f_path))}
53 50 %endif
54 51 <script type="text/javascript">
55 52 function highlight_lines(lines){
56 53 for(pos in lines){
57 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
54 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
58 55 }
59 }
56 }
60 57 page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
61 58 if (page_highlights.length == 2){
62 59 highlight_ranges = page_highlights[1].split(",");
@@ -71,26 +68,26 b''
71 68 for(var i=start;i<=end;i++){
72 69 h_lines.push(i);
73 70 }
74 }
71 }
75 72 }
76 73 else{
77 74 h_lines.push(parseInt(highlight_ranges[pos]));
78 75 }
79 76 }
80 77 highlight_lines(h_lines);
81
82 //remember original location
78
79 //remember original location
83 80 var old_hash = location.href.substring(location.href.indexOf('#'));
84
85 // this makes a jump to anchor moved by 3 posstions for padding
81
82 // this makes a jump to anchor moved by 3 posstions for padding
86 83 window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
87
88 //sets old anchor
84
85 //sets old anchor
89 86 window.location.hash = old_hash;
90
87
91 88 }
92 89 </script>
93 %endif
90 %endif
94 91 </div>
95 92 </div>
96 93
@@ -98,77 +95,10 b''
98 95 YUE.onDOMReady(function(){
99 96 YUE.on('show_rev','click',function(e){
100 97 YUE.preventDefault(e);
101 var cs = YAHOO.util.Dom.get('diff1').value;
98 var cs = YUD.get('diff1').value;
102 99 var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
103 100 window.location = url;
104 101 });
105
106 function getIdentNode(n){
107 //iterate thru nodes untill matched interesting node !
108
109 if (typeof n == 'undefined'){
110 return -1
111 }
112
113 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
114 return n
115 }
116 else{
117 return getIdentNode(n.parentNode);
118 }
119 }
120
121 function getSelectionLink() {
122 //get selection from start/to nodes
123 if (typeof window.getSelection != "undefined") {
124 s = window.getSelection();
125
126 from = getIdentNode(s.anchorNode);
127 till = getIdentNode(s.focusNode);
128
129 f_int = parseInt(from.id.replace('L',''));
130 t_int = parseInt(till.id.replace('L',''));
131
132 if (f_int > t_int){
133 //highlight from bottom
134 offset = -35;
135 ranges = [t_int,f_int];
136
137 }
138 else{
139 //highligth from top
140 offset = 35;
141 ranges = [f_int,t_int];
142 }
143
144 if (ranges[0] != ranges[1]){
145 if(YUD.get('linktt') == null){
146 hl_div = document.createElement('div');
147 hl_div.id = 'linktt';
148 }
149 anchor = '#L'+ranges[0]+'-'+ranges[1];
150 hl_div.innerHTML = '';
151 l = document.createElement('a');
152 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
153 l.innerHTML = "${_('Selection link')}"
154 hl_div.appendChild(l);
155
156 YUD.get('body').appendChild(hl_div);
157
158 xy = YUD.getXY(till.id);
159
160 YUD.addClass('linktt','yui-tt');
161 YUD.setStyle('linktt','top',xy[1]+offset+'px');
162 YUD.setStyle('linktt','left',xy[0]+'px');
163 YUD.setStyle('linktt','visibility','visible');
164 }
165 else{
166 YUD.setStyle('linktt','visibility','hidden');
167 }
168 }
169 }
170
171 YUE.on('hlcode','mouseup',getSelectionLink)
172
102 YUE.on('hlcode','mouseup',getSelectionLink("${_('Selection link')}"))
173 103 });
174 104 </script>
@@ -7,7 +7,7 b''
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(u'Home',h.url('/'))}
10 &raquo;
10 &raquo;
11 11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 12 &raquo;
13 13 ${_('followers')}
@@ -26,7 +26,7 b''
26 26 <div class="table">
27 27 <div id="followers">
28 28 ${c.followers_data}
29 </div>
29 </div>
30 30 </div>
31 </div>
32 </%def> No newline at end of file
31 </div>
32 </%def>
@@ -9,11 +9,11 b''
9 9 <span style="font-size: 20px"> <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname})</span>
10 10 </div>
11 11 <div style="clear:both;padding-top: 10px"></div>
12 <div class="follower_date">${_('Started following')} -
12 <div class="follower_date">${_('Started following')} -
13 13 <span class="tooltip" title="${f.follows_from}"> ${h.age(f.follows_from)}</span></div>
14 14 <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
15 </div>
16 % endfor
15 </div>
16 % endfor
17 17
18 18 <div class="pagination-wh pagination-left">
19 19 <script type="text/javascript">
@@ -25,4 +25,4 b' YUE.onDOMReady(function(){'
25 25 });
26 26 </script>
27 27 ${c.followers_pager.pager('$link_previous ~2~ $link_next')}
28 </div> No newline at end of file
28 </div>
@@ -8,7 +8,7 b''
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(u'Home',h.url('/'))}
10 10 &raquo;
11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
12 12 &raquo;
13 13 ${_('fork')}
14 14 </%def>
@@ -20,21 +20,30 b''
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 24 </div>
25 25 ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
26 26 <div class="form">
27 27 <!-- fields -->
28 28 <div class="fields">
29 29 <div class="field">
30 <div class="label">
31 <label for="repo_name">${_('Fork name')}:</label>
32 </div>
33 <div class="input">
34 ${h.text('fork_name',class_="small")}
35 ${h.hidden('repo_type',c.repo_info.repo_type)}
36 </div>
37 </div>
30 <div class="label">
31 <label for="repo_name">${_('Fork name')}:</label>
32 </div>
33 <div class="input">
34 ${h.text('repo_name',class_="small")}
35 ${h.hidden('repo_type',c.repo_info.repo_type)}
36 ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
37 </div>
38 </div>
39 <div class="field">
40 <div class="label">
41 <label for="repo_group">${_('Repository group')}:</label>
42 </div>
43 <div class="input">
44 ${h.select('repo_group','',c.repo_groups,class_="medium")}
45 </div>
46 </div>
38 47 <div class="field">
39 48 <div class="label label-textarea">
40 49 <label for="description">${_('Description')}:</label>
@@ -50,12 +59,28 b''
50 59 <div class="checkboxes">
51 60 ${h.checkbox('private',value="True")}
52 61 </div>
62 </div>
63 <div class="field">
64 <div class="label label-checkbox">
65 <label for="private">${_('Copy permissions')}:</label>
66 </div>
67 <div class="checkboxes">
68 ${h.checkbox('copy_permissions',value="True")}
69 </div>
70 </div>
71 <div class="field">
72 <div class="label label-checkbox">
73 <label for="private">${_('Update after clone')}:</label>
74 </div>
75 <div class="checkboxes">
76 ${h.checkbox('update_after_clone',value="True")}
77 </div>
53 78 </div>
54 79 <div class="buttons">
55 80 ${h.submit('',_('fork this repository'),class_="ui-button")}
56 </div>
81 </div>
57 82 </div>
58 </div>
59 ${h.end_form()}
83 </div>
84 ${h.end_form()}
60 85 </div>
61 </%def>
86 </%def>
@@ -7,7 +7,7 b''
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(u'Home',h.url('/'))}
10 &raquo;
10 &raquo;
11 11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 12 &raquo;
13 13 ${_('forks')}
@@ -26,7 +26,7 b''
26 26 <div class="table">
27 27 <div id="forks">
28 28 ${c.forks_data}
29 </div>
29 </div>
30 30 </div>
31 </div>
32 </%def> No newline at end of file
31 </div>
32 </%def>
@@ -8,15 +8,15 b''
8 8 <img alt="gravatar" src="${h.gravatar_url(f.user.email,24)}"/>
9 9 </div>
10 10 <span style="font-size: 20px">
11 <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname}) /
11 <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname}) /
12 12 ${h.link_to(f.repo_name,h.url('summary_home',repo_name=f.repo_name))}
13 13 </span>
14 14 <div style="padding:5px 3px 3px 42px;">${f.description}</div>
15 15 </div>
16 16 <div style="clear:both;padding-top: 10px"></div>
17 <div class="follower_date">${_('forked')} -
17 <div class="follower_date">${_('forked')} -
18 18 <span class="tooltip" title="${f.created_on}"> ${h.age(f.created_on)}</span></div>
19 <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
19 <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
20 20 </div>
21 21 % endfor
22 22 <div class="pagination-wh pagination-left">
@@ -29,7 +29,7 b''
29 29 });
30 30 </script>
31 31 ${c.forks_pager.pager('$link_previous ~2~ $link_next')}
32 </div>
32 </div>
33 33 % else:
34 ${_('There are no forks yet')}
35 % endif No newline at end of file
34 ${_('There are no forks yet')}
35 % endif
@@ -1,14 +1,8 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/base.html"/>
3 <%def name="title()">
4 ${_('Dashboard')} - ${c.rhodecode_name}
5 </%def>
6 <%def name="breadcrumbs()">
7 ${c.rhodecode_name}
8 </%def>
9 <%def name="page_nav()">
10 ${self.menu('home')}
11 </%def>
3 <%def name="title()">${_('Dashboard')} - ${c.rhodecode_name}</%def>
4 <%def name="breadcrumbs()"></%def>
5 <%def name="page_nav()">${self.menu('home')}</%def>
12 6 <%def name="main()">
13 7 <%include file="index_base.html" args="parent=self"/>
14 </%def>
8 </%def>
@@ -1,54 +1,60 b''
1 <%page args="parent" />
1 <%page args="parent" />
2 2 <div class="box">
3 3 <!-- box / title -->
4 4 <div class="title">
5 5 <h5>
6 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
7 ${parent.breadcrumbs()} <span id="repo_count"></span> ${_('repositories')}
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
8 7 </h5>
9 8 %if c.rhodecode_user.username != 'default':
10 9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
11 10 <ul class="links">
12 11 <li>
13 <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
14 </li>
15 </ul>
12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
13 </li>
14 </ul>
16 15 %endif
17 16 %endif
18 17 </div>
19 18 <!-- end box / title -->
20 19 <div class="table">
21 20 % if c.groups:
22 <table>
23 <thead>
21 <div id='groups_list_wrap' class="yui-skin-sam">
22 <table id="groups_list">
23 <thead>
24 <tr>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
28 </tr>
29 </thead>
30
31 ## REPO GROUPS
32 % for gr in c.groups:
24 33 <tr>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
34 <td>
35 <div style="white-space: nowrap">
36 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
37 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
38 </div>
39 </td>
40 <td>${gr.group_description}</td>
41 ## this is commented out since for multi nested repos can be HEAVY!
42 ## in number of executed queries during traversing uncomment at will
43 ##<td><b>${gr.repositories_recursive_count}</b></td>
28 44 </tr>
29 </thead>
30
31 ## REPO GROUPS
32
33 % for gr in c.groups:
34 <tr>
35 <td>
36 <div style="white-space: nowrap">
37 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
38 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
39 </div>
40 </td>
41 <td>${gr.group_description}</td>
42 ##<td><b>${gr.repositories.count()}</b></td>
43 </tr>
44 % endfor
45
46 </table>
45 % endfor
46
47 </table>
48 </div>
47 49 <div style="height: 20px"></div>
48 50 % endif
49 51 <div id="welcome" style="display:none;text-align:center">
50 52 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
51 53 </div>
54 <div id='repos_list_wrap' class="yui-skin-sam">
55 <%cnt=0%>
56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
57
52 58 <table id="repos_list">
53 59 <thead>
54 60 <tr>
@@ -63,87 +69,37 b''
63 69 </tr>
64 70 </thead>
65 71 <tbody>
66 %for cnt,repo in enumerate(c.repos_list):
72 %for cnt,repo in enumerate(c.repos_list,1):
67 73 <tr class="parity${cnt%2}">
74 ##QUICK MENU
68 75 <td class="quick_repo_menu">
69 <ul class="menu_items hidden">
70 <li>
71 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo['name'])}">
72 <span class="icon">
73 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
74 </span>
75 <span>${_('Summary')}</span>
76 </a>
77 </li>
78 <li>
79 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo['name'])}">
80 <span class="icon">
81 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
82 </span>
83 <span>${_('Changelog')}</span>
84 </a>
85 </li>
86 <li>
87 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo['name'])}">
88 <span class="icon">
89 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
90 </span>
91 <span>${_('Files')}</span>
92 </a>
93 </li>
94 </ul>
76 ${dt.quick_menu(repo['name'])}
95 77 </td>
96 <td>
97 ## TYPE OF REPO
98 <div style="white-space: nowrap">
99 %if repo['dbrepo']['repo_type'] =='hg':
100 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
101 %elif repo['dbrepo']['repo_type'] =='git':
102 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
103 %endif
104
105 ##PRIVATE/PUBLIC
106 %if repo['dbrepo']['private']:
107 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
108 %else:
109 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
110 %endif
111
112 ##NAME
113 ${h.link_to(repo['name'],
114 h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
115 %if repo['dbrepo_fork']:
116 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
117 <img class="icon" alt="${_('fork')}"
118 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
119 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
120 %endif
121 </div>
78 ##REPO NAME AND ICONS
79 <td class="reponame">
80 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
122 81 </td>
123 82 ##DESCRIPTION
124 83 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
125 84 ${h.truncate(repo['description'],60)}</span>
126 85 </td>
127 ##LAST CHANGE
128 <td>
129 <span class="tooltip" title="${repo['last_change']}">
130 ${h.age(repo['last_change'])}</span>
131 </td>
86 ##LAST CHANGE DATE
132 87 <td>
133 %if repo['rev']>=0:
134 <a title="${h.tooltip('%s\n%s' % (repo['author'],repo['last_msg']))}" class="tooltip" href="${h.url('changeset_home',repo_name=repo['name'],revision=repo['tip'])}">${'r%s:%s' % (repo['rev'],h.short_id(repo['tip']))}</a>
135 %else:
136 ${_('No changesets yet')}
137 %endif
88 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
138 89 </td>
90 ##LAST REVISION
91 <td>
92 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
93 </td>
94 ##
139 95 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
140 96 <td>
141 97 %if c.rhodecode_user.username != 'default':
142 98 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
143 99 %else:
144 100 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
145 %endif:
146 </td>
101 %endif:
102 </td>
147 103 <td>
148 104 %if c.rhodecode_user.username != 'default':
149 105 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
@@ -156,71 +112,86 b''
156 112 </tbody>
157 113 </table>
158 114 </div>
115 </div>
159 116 </div>
160
161
162 <script type="text/javascript">
163 var D = YAHOO.util.Dom;
164 var E = YAHOO.util.Event;
165 var S = YAHOO.util.Selector;
166
167 var q_filter = D.get('q_filter');
168 var F = YAHOO.namespace('q_filter');
169
170 E.on(q_filter,'click',function(){
171 q_filter.value = '';
172 });
117 <script>
118 YUD.get('repo_count').innerHTML = ${cnt};
119 var func = function(node){
120 return node.parentNode.parentNode.parentNode.parentNode;
121 }
122
123
124 // groups table sorting
125 var myColumnDefs = [
126 {key:"name",label:"${_('Group Name')}",sortable:true,
127 sortOptions: { sortFunction: groupNameSort }},
128 {key:"desc",label:"${_('Description')}",sortable:true},
129 ];
130
131 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
132
133 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
134 myDataSource.responseSchema = {
135 fields: [
136 {key:"name"},
137 {key:"desc"},
138 ]
139 };
140
141 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
142 {
143 sortedBy:{key:"name",dir:"asc"},
144 MSG_SORTASC:"${_('Click to sort ascending')}",
145 MSG_SORTDESC:"${_('Click to sort descending')}"
146 }
147 );
173 148
174 F.filterTimeout = null;
175
176 function set_count(count){
177
178 if(count == 0){
179 YUD.setStyle('repos_list','display','none');
180 YUD.setStyle('welcome','display','');
181 }
182 else{
183 YUD.setStyle('repos_list','display','');
184 YUD.setStyle('welcome','display','none');
185 }
186 YUD.get('repo_count').innerHTML = count;
187
188 }
189
190
191 //set initial count for repos
192 var nodes = S.query('div.table tr td div a.repo_name');
193
194 set_count(nodes.length)
195 F.updateFilter = function() {
196 // Reset timeout
197 F.filterTimeout = null;
198
199 var obsolete = [];
200 nodes = S.query('div.table tr td div a.repo_name');
201 var req = q_filter.value.toLowerCase();
202 for (n in nodes){
203 D.setStyle(nodes[n].parentNode.parentNode.parentNode,'display','')
204 }
205 if (req){
206 for (n in nodes){
207 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
208 obsolete.push(nodes[n]);
209 }
210 }
211 if(obsolete){
212 for (n in obsolete){
213 D.setStyle(obsolete[n].parentNode.parentNode.parentNode,'display','none');
214 }
215 }
216 }
217 // set new count into dashboard
218 set_count(nodes.length - obsolete.length)
219 }
220
221 E.on(q_filter,'keyup',function(e){
222 clearTimeout(F.filterTimeout);
223 F.filterTimeout = setTimeout(F.updateFilter,600);
224 });
225
149 // main table sorting
150 var myColumnDefs = [
151 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
152 {key:"name",label:"${_('Name')}",sortable:true,
153 sortOptions: { sortFunction: nameSort }},
154 {key:"desc",label:"${_('Description')}",sortable:true},
155 {key:"last_change",label:"${_('Last Change')}",sortable:true,
156 sortOptions: { sortFunction: ageSort }},
157 {key:"tip",label:"${_('Tip')}",sortable:true,
158 sortOptions: { sortFunction: revisionSort }},
159 {key:"owner",label:"${_('Owner')}",sortable:true},
160 {key:"rss",label:"",sortable:false},
161 {key:"atom",label:"",sortable:false},
162 ];
163
164 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
165
166 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
167
168 myDataSource.responseSchema = {
169 fields: [
170 {key:"menu"},
171 {key:"name"},
172 {key:"desc"},
173 {key:"last_change"},
174 {key:"tip"},
175 {key:"owner"},
176 {key:"rss"},
177 {key:"atom"},
178 ]
179 };
180
181 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
182 {
183 sortedBy:{key:"name",dir:"asc"},
184 MSG_SORTASC:"${_('Click to sort ascending')}",
185 MSG_SORTDESC:"${_('Click to sort descending')}",
186 MSG_EMPTY:"${_('No records found.')}",
187 MSG_ERROR:"${_('Data error.')}",
188 MSG_LOADING:"${_('Loading...')}",
189 }
190 );
191 myDataTable.subscribe('postRenderEvent',function(oArgs) {
192 tooltip_activate();
193 quick_repo_menu();
194 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
195 });
196
226 197 </script>
@@ -10,60 +10,211 b''
10 10 ${self.menu('home')}
11 11 </%def>
12 12 <%def name="main()">
13
13
14 14 <div class="box box-left">
15 15 <!-- box / title -->
16 16 <div class="title">
17 17 <h5>${_('Journal')}</h5>
18 <ul class="links">
19 <li>
20 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
21 </a></span>
22 </li>
23 </ul>
18 24 </div>
19 <script type="text/javascript">
20 function show_more_event(){
21 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
22 var el = e.target;
23 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
24 YUD.setStyle(el.parentNode,'display','none');
25 });
26 }
27 </script>
28 25 <div id="journal">${c.journal_data}</div>
29 26 </div>
30
31 27 <div class="box box-right">
32 28 <!-- box / title -->
33 29 <div class="title">
34 <h5>${_('Following')}</h5>
30 <h5>
31 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
32 <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a>
33 </h5>
34 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
35 <ul class="links">
36 <li>
37 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
38 </li>
39 </ul>
40 %endif
41 </div>
42 <!-- end box / title -->
43 <div id="my" class="table">
44 %if c.user_repos:
45 <div id='repos_list_wrap' class="yui-skin-sam">
46 <table id="repos_list">
47 <thead>
48 <tr>
49 <th></th>
50 <th class="left">${_('Name')}</th>
51 <th class="left">${_('Tip')}</th>
52 <th class="left">${_('Action')}</th>
53 <th class="left">${_('Action')}</th>
54 </thead>
55 <tbody>
56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
57 %for repo in c.user_repos:
58 <tr>
59 ##QUICK MENU
60 <td class="quick_repo_menu">
61 ${dt.quick_menu(repo['name'])}
62 </td>
63 ##REPO NAME AND ICONS
64 <td class="reponame">
65 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
66 </td>
67 ##LAST REVISION
68 <td>
69 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
70 </td>
71 ##
72 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
73 <td>
74 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
75 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
76 ${h.end_form()}
77 </td>
78 </tr>
79 %endfor
80 </tbody>
81 </table>
82 </div>
83 %else:
84 <div style="padding:5px 0px 10px 0px;">
85 ${_('No repositories yet')}
86 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
87 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
88 %endif
89 </div>
90 %endif
35 91 </div>
36 <div>
37 %if c.following:
38 %for entry in c.following:
39 <div class="currently_following">
40 %if entry.follows_user_id:
41 <img title="${_('following user')}" alt="${_('user')}" src="${h.url("/images/icons/user.png")}"/>
42 ${entry.follows_user.full_contact}
43 %endif
44
45 %if entry.follows_repo_id:
46
47 <div style="float:left;padding-right:5px">
48 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
49 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
50 </span>
51 </div>
52 %if entry.follows_repository.private:
53 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
54 %else:
55 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
56 %endif
57
58 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',
59 repo_name=entry.follows_repository.repo_name))}
60
61 %endif
62 </div>
63 %endfor
64 %else:
65 ${_('You are not following any users or repositories')}
66 %endif
92
93 <div id="watched" class="table" style="display:none">
94 %if c.following:
95 <table>
96 <thead>
97 <tr>
98 <th class="left">${_('Name')}</th>
99 </thead>
100 <tbody>
101 %for entry in c.following:
102 <tr>
103 <td>
104 %if entry.follows_user_id:
105 <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
106 ${entry.follows_user.full_contact}
107 %endif
108
109 %if entry.follows_repo_id:
110 <div style="float:right;padding-right:5px">
111 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
112 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
113 </span>
114 </div>
115
116 %if h.is_hg(entry.follows_repository):
117 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
118 %elif h.is_git(entry.follows_repository):
119 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
120 %endif
121
122 %if entry.follows_repository.private:
123 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
124 %else:
125 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
126 %endif
127 <span class="watched_repo">
128 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
129 </span>
130 %endif
131 </td>
132 </tr>
133 %endfor
134 </tbody>
135 </table>
136 %else:
137 <div style="padding:5px 0px 10px 0px;">
138 ${_('You are not following any users or repositories')}
139 </div>
140 %endif
67 141 </div>
68 </div>
69 </%def>
142 </div>
143
144 <script type="text/javascript">
145
146 YUE.on('show_my','click',function(e){
147 YUD.setStyle('watched','display','none');
148 YUD.setStyle('my','display','');
149 var nodes = YUQ('#my tr td a.repo_name');
150 var target = 'q_filter';
151 var func = function(node){
152 return node.parentNode.parentNode.parentNode.parentNode;
153 }
154 q_filter(target,nodes,func);
155 YUE.preventDefault(e);
156 })
157 YUE.on('show_watched','click',function(e){
158 YUD.setStyle('my','display','none');
159 YUD.setStyle('watched','display','');
160 var nodes = YUQ('#watched .watched_repo a');
161 var target = 'q_filter';
162 var func = function(node){
163 return node.parentNode.parentNode;
164 }
165 q_filter(target,nodes,func);
166 YUE.preventDefault(e);
167 })
168 YUE.on('refresh','click',function(e){
169 ypjax(e.currentTarget.href,"journal",function(){show_more_event();tooltip_activate();});
170 YUE.preventDefault(e);
171 });
172
173
174 // main table sorting
175 var myColumnDefs = [
176 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
177 {key:"name",label:"${_('Name')}",sortable:true,
178 sortOptions: { sortFunction: nameSort }},
179 {key:"tip",label:"${_('Tip')}",sortable:true,
180 sortOptions: { sortFunction: revisionSort }},
181 {key:"action1",label:"",sortable:false},
182 {key:"action2",label:"",sortable:false},
183 ];
184
185 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
186
187 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
188
189 myDataSource.responseSchema = {
190 fields: [
191 {key:"menu"},
192 {key:"name"},
193 {key:"tip"},
194 {key:"action1"},
195 {key:"action2"}
196 ]
197 };
198
199 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
200 {
201 sortedBy:{key:"name",dir:"asc"},
202 MSG_SORTASC:"${_('Click to sort ascending')}",
203 MSG_SORTDESC:"${_('Click to sort descending')}",
204 MSG_EMPTY:"${_('No records found.')}",
205 MSG_ERROR:"${_('Data error.')}",
206 MSG_LOADING:"${_('Loading...')}",
207 }
208 );
209 myDataTable.subscribe('postRenderEvent',function(oArgs) {
210 tooltip_activate();
211 quick_repo_menu();
212 var func = function(node){
213 return node.parentNode.parentNode.parentNode.parentNode;
214 }
215 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
216 });
217
218
219 </script>
220 </%def>
@@ -20,7 +20,7 b''
20 20 h.url('summary_home',repo_name=entry.repository.repo_name))}
21 21 %else:
22 22 ${entry.repository_name}
23 %endif
23 %endif
24 24 </span>
25 25 </div>
26 26 <div class="journal_action_params">${h.literal(h.action_parser(entry)[1]())}</div>
@@ -30,18 +30,20 b''
30 30 </div>
31 31 %endfor
32 32 %endfor
33
34 <div class="pagination-wh pagination-left">
35 <script type="text/javascript">
36 YUE.onDOMReady(function(){
37 YUE.delegate("journal","click",function(e, matchedEl, container){
38 ypjax(e.target.href,"journal",function(){show_more_event();tooltip_activate();});
39 YUE.preventDefault(e);
40 },'.pager_link');
41 });
42 </script>
43 ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
44 </div>
33
34 <div class="pagination-wh pagination-left">
35 <script type="text/javascript">
36 YUE.onDOMReady(function(){
37 YUE.delegate("journal","click",function(e, matchedEl, container){
38 ypjax(e.target.href,"journal",function(){show_more_event();tooltip_activate();});
39 YUE.preventDefault(e);
40 },'.pager_link');
41 });
42 </script>
43 ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
44 </div>
45 45 %else:
46 ${_('No entries yet')}
47 %endif No newline at end of file
46 <div style="padding:5px 0px 10px 10px;">
47 ${_('No entries yet')}
48 </div>
49 %endif
@@ -10,7 +10,7 b''
10 10 ${self.menu('home')}
11 11 </%def>
12 12 <%def name="main()">
13
13
14 14 <div class="box">
15 15 <!-- box / title -->
16 16 <div class="title">
@@ -21,10 +21,10 b''
21 21 </li>
22 22 <li>
23 23 <span>${h.link_to(_('Atom'),h.url('public_journal_atom'),class_='atom_icon')}</span>
24 </li>
25
26 </ul>
27
24 </li>
25
26 </ul>
27
28 28 </div>
29 29 <script type="text/javascript">
30 30 function show_more_event(){
@@ -34,8 +34,8 b''
34 34 YUD.setStyle(el.parentNode,'display','none');
35 35 });
36 36 }
37 </script>
37 </script>
38 38 <div id="journal">${c.journal_data}</div>
39 39 </div>
40
41 </%def>
40
41 </%def>
@@ -15,12 +15,12 b''
15 15 % endfor
16 16 </ul>
17 17 % endif
18 </div>
18 </div>
19 19 <!-- login -->
20 20 <div class="title top-left-rounded-corner top-right-rounded-corner">
21 21 <h5>${_('Sign In to')} ${c.rhodecode_name}</h5>
22 22 </div>
23 <div class="inner">
23 <div class="inner">
24 24 ${h.form(h.url.current(came_from=c.came_from))}
25 25 <div class="form">
26 26 <!-- fields -->
@@ -33,8 +33,8 b''
33 33 <div class="input">
34 34 ${h.text('username',class_='focus',size=40)}
35 35 </div>
36
37 </div>
36
37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="password">${_('Password')}:</label>
@@ -42,14 +42,14 b''
42 42 <div class="input">
43 43 ${h.password('password',class_='focus',size=40)}
44 44 </div>
45
45
46 46 </div>
47 ##<div class="field">
48 ## <div class="checkbox">
49 ## <input type="checkbox" id="remember" name="remember" />
50 ## <label for="remember">Remember me</label>
51 ## </div>
52 ##</div>
47 <div class="field">
48 <div class="checkbox">
49 <input type="checkbox" id="remember" name="remember" />
50 <label for="remember">${_('Remember me')}</label>
51 </div>
52 </div>
53 53 <div class="buttons">
54 54 ${h.submit('sign_in',_('Sign In'),class_="ui-button")}
55 55 </div>
@@ -59,7 +59,7 b''
59 59 <div class="links">
60 60 ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
61 61 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
62 /
62 /
63 63 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
64 64 %endif
65 65 </div>
@@ -6,7 +6,7 b''
6 6 </%def>
7 7
8 8 <div id="register">
9
9
10 10 <div class="title top-left-rounded-corner top-right-rounded-corner">
11 11 <h5>${_('Reset your password to')} ${c.rhodecode_name}</h5>
12 12 </div>
@@ -15,7 +15,7 b''
15 15 <div class="form">
16 16 <!-- fields -->
17 17 <div class="fields">
18
18
19 19 <div class="field">
20 20 <div class="label">
21 21 <label for="email">${_('Email address')}:</label>
@@ -24,13 +24,13 b''
24 24 ${h.text('email')}
25 25 </div>
26 26 </div>
27
27
28 28 <div class="buttons">
29 29 <div class="nohighlight">
30 30 ${h.submit('send',_('Reset my password'),class_="ui-button")}
31 31 <div class="activation_msg">${_('Password reset link will be send to matching email address')}</div>
32 32 </div>
33 </div>
33 </div>
34 34 </div>
35 35 </div>
36 36 ${h.end_form()}
@@ -38,7 +38,6 b''
38 38 YUE.onDOMReady(function(){
39 39 YUD.get('email').focus();
40 40 })
41 </script>
42 </div>
41 </script>
42 </div>
43 43 </div>
44
@@ -4,9 +4,9 b''
4 4 <%def name="title()">
5 5 ${_('Sign Up')} - ${c.rhodecode_name}
6 6 </%def>
7
7
8 8 <div id="register">
9
9
10 10 <div class="title top-left-rounded-corner top-right-rounded-corner">
11 11 <h5>${_('Sign Up to')} ${c.rhodecode_name}</h5>
12 12 </div>
@@ -23,7 +23,7 b''
23 23 ${h.text('username',class_="medium")}
24 24 </div>
25 25 </div>
26
26
27 27 <div class="field">
28 28 <div class="label">
29 29 <label for="password">${_('Password')}:</label>
@@ -32,7 +32,7 b''
32 32 ${h.password('password',class_="medium")}
33 33 </div>
34 34 </div>
35
35
36 36 <div class="field">
37 37 <div class="label">
38 38 <label for="password">${_('Re-enter password')}:</label>
@@ -41,7 +41,7 b''
41 41 ${h.password('password_confirmation',class_="medium")}
42 42 </div>
43 43 </div>
44
44
45 45 <div class="field">
46 46 <div class="label">
47 47 <label for="name">${_('First Name')}:</label>
@@ -50,7 +50,7 b''
50 50 ${h.text('name',class_="medium")}
51 51 </div>
52 52 </div>
53
53
54 54 <div class="field">
55 55 <div class="label">
56 56 <label for="lastname">${_('Last Name')}:</label>
@@ -59,7 +59,7 b''
59 59 ${h.text('lastname',class_="medium")}
60 60 </div>
61 61 </div>
62
62
63 63 <div class="field">
64 64 <div class="label">
65 65 <label for="email">${_('Email')}:</label>
@@ -68,7 +68,7 b''
68 68 ${h.text('email',class_="medium")}
69 69 </div>
70 70 </div>
71
71
72 72 <div class="buttons">
73 73 <div class="nohighlight">
74 74 ${h.submit('sign_up',_('Sign Up'),class_="ui-button")}
@@ -78,7 +78,7 b''
78 78 <div class="activation_msg">${_('Your account must wait for activation by administrator')}</div>
79 79 %endif
80 80 </div>
81 </div>
81 </div>
82 82 </div>
83 83 </div>
84 84 ${h.end_form()}
@@ -86,7 +86,6 b''
86 86 YUE.onDOMReady(function(){
87 87 YUD.get('username').focus();
88 88 })
89 </script>
90 </div>
89 </script>
90 </div>
91 91 </div>
92
@@ -1,23 +1,20 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <li class="qfilter_rs">
4 <input type="text"
5 style="border:0"
6 value="quick filter..."
7 name="filter" size="15" id="q_filter_rs" />
4 <input type="text" style="border:0" value="quick filter..." name="filter" size="15" id="q_filter_rs" />
8 5 </li>
9
6
10 7 %for repo in c.repos_list:
11
8
12 9 %if repo['dbrepo']['private']:
13 10 <li>
14 <img src="${h.url("/images/icons/lock.png")}" alt="${_('Private repository')}" class="repo_switcher_type"/>
11 <img src="${h.url('/images/icons/lock.png')}" alt="${_('Private repository')}" class="repo_switcher_type"/>
15 12 ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
16 13 </li>
17 14 %else:
18 15 <li>
19 <img src="${h.url("/images/icons/lock_open.png")}" alt="${_('Public repository')}" class="repo_switcher_type" />
16 <img src="${h.url('/images/icons/lock_open.png')}" alt="${_('Public repository')}" class="repo_switcher_type" />
20 17 ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
21 18 </li>
22 %endif
23 %endfor No newline at end of file
19 %endif
20 %endfor
@@ -2,11 +2,11 b''
2 2 <%inherit file="/base/base.html"/>
3 3 <%def name="title()">
4 4 ${_('Search')}
5 ${'"%s"' % c.cur_query if c.cur_query else None}
5 ${'"%s"' % c.cur_query if c.cur_query else None}
6 6 %if c.repo_name:
7 7 ${_('in repository: ') + c.repo_name}
8 8 %else:
9 ${_('in all repositories')}
9 ${_('in all repositories')}
10 10 %endif
11 11 - ${c.rhodecode_name}
12 12 </%def>
@@ -26,12 +26,12 b''
26 26 ${_('in repository: ') + c.repo_name}
27 27 %else:
28 28 ${_('in all repositories')}
29 %endif
29 %endif
30 30 </h5>
31 31 </div>
32 32 <!-- end box / title -->
33 33 %if c.repo_name:
34 ${h.form(h.url('search_repo',search_repo=c.repo_name),method='get')}
34 ${h.form(h.url('search_repo',search_repo=c.repo_name),method='get')}
35 35 %else:
36 36 ${h.form(h.url('search'),method='get')}
37 37 %endif
@@ -40,13 +40,13 b''
40 40 <div class="field field-first field-noborder">
41 41 <div class="label">
42 42 <label for="q">${_('Search term')}</label>
43 </div>
43 </div>
44 44 <div class="input">${h.text('q',c.cur_query,class_="small")}
45 45 <div class="button highlight">
46 46 <input type="submit" value="${_('Search')}" class="ui-button"/>
47 47 </div>
48 48 </div>
49 <div style="font-weight: bold;clear:Both;margin-left:200px">${c.runtime}</div>
49 <div style="font-weight: bold;clear:Both;margin-left:200px">${c.runtime}</div>
50 50 </div>
51 51
52 52 <div class="field">
@@ -58,14 +58,14 b''
58 58 ##('commit',_('Commit messages')),
59 59 ('path',_('File names')),
60 60 ##('repository',_('Repository names')),
61 ])}
61 ])}
62 62 </div>
63 63 </div>
64
64
65 65 </div>
66 66 </div>
67 67 ${h.end_form()}
68
68
69 69 %if c.cur_search == 'content':
70 70 <%include file='search_content.html'/>
71 71 %elif c.cur_search == 'path':
@@ -74,7 +74,7 b''
74 74 <%include file='search_commit.html'/>
75 75 %elif c.cur_search == 'repository':
76 76 <%include file='search_repository.html'/>
77 %endif
77 %endif
78 78 </div>
79 79
80 </%def>
80 </%def>
@@ -5,10 +5,11 b''
5 5 <div class="table">
6 6 <div id="body${cnt}" class="codeblock">
7 7 <div class="code-header">
8 <div class="revision">${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
9 h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}</div>
8 <div class="search-path">${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
9 h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
10 </div>
10 11 </div>
11 <div class="code-body">
12 <div class="search-code-body">
12 13 <pre>${h.literal(sr['content_short_hl'])}</pre>
13 14 </div>
14 15 </div>
@@ -19,13 +20,13 b''
19 20 <div id="body${cnt}" class="codeblock">
20 21 <div class="error">${_('Permission denied')}</div>
21 22 </div>
22 </div>
23 </div>
23 24 %endif
24
25 %endif
25
26 %endif
26 27 %endfor
27 28 %if c.cur_query and c.formated_results:
28 29 <div class="pagination-wh pagination-left" style="padding-left:16px">
29 30 ${c.formated_results.pager('$link_previous ~2~ $link_next')}
30 </div>
31 %endif No newline at end of file
31 </div>
32 %endif
@@ -1,27 +1,26 b''
1 1 ##path search
2 <div class="search">
3 %for cnt,sr in enumerate(c.formated_results):
4 %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
5 <div class="search_path">
2
3 %for cnt,sr in enumerate(c.formated_results):
4 %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
5 <div class="search_path">
6 <div class="link">
7 ${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
8 h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
9 </div>
10 </div>
11 %else:
12 %if cnt == 0:
13 <div class="error">
6 14 <div class="link">
7 ${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
8 h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
15 ${_('Permission denied')}
9 16 </div>
10 17 </div>
11 %else:
12 %if cnt == 0:
13 <div class="error">
14 <div class="link">
15 ${_('Permission denied')}
16 </div>
17 </div>
18 %endif
19
20 %endif
21 %endfor
22 %if c.cur_query and c.formated_results:
23 <div class="pagination-wh pagination-left">
24 ${c.formated_results.pager('$link_previous ~2~ $link_next')}
25 </div>
26 %endif
27 </div> No newline at end of file
18 %endif
19
20 %endif
21 %endfor
22 %if c.cur_query and c.formated_results:
23 <div class="pagination-wh pagination-left">
24 ${c.formated_results.pager('$link_previous ~2~ $link_next')}
25 </div>
26 %endif
@@ -8,9 +8,9 b''
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(u'Home',h.url('/'))}
10 10 &raquo;
11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
12 &raquo;
13 ${_('Settings')}
11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
12 &raquo;
13 ${_('Settings')}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
@@ -20,7 +20,7 b''
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 24 </div>
25 25 ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
26 26 <div class="form">
@@ -41,7 +41,7 b''
41 41 <div class="input">
42 42 ${h.select('repo_group','',c.repo_groups,class_="medium")}
43 43 </div>
44 </div>
44 </div>
45 45 <div class="field">
46 46 <div class="label label-textarea">
47 47 <label for="description">${_('Description')}:</label>
@@ -50,7 +50,7 b''
50 50 ${h.textarea('description',cols=23,rows=5)}
51 51 </div>
52 52 </div>
53
53
54 54 <div class="field">
55 55 <div class="label label-checkbox">
56 56 <label for="private">${_('Private')}:</label>
@@ -59,7 +59,7 b''
59 59 ${h.checkbox('private',value="True")}
60 60 </div>
61 61 </div>
62
62
63 63 <div class="field">
64 64 <div class="label">
65 65 <label for="">${_('Permissions')}:</label>
@@ -67,16 +67,14 b''
67 67 <div class="input">
68 68 <%include file="../admin/repos/repo_edit_perms.html"/>
69 69 </div>
70
70
71 71 <div class="buttons">
72 72 ${h.submit('save','Save',class_="ui-button")}
73 73 ${h.reset('reset','Reset',class_="ui-button")}
74 </div>
74 </div>
75 75 </div>
76 </div>
76 </div>
77 77 ${h.end_form()}
78 78 </div>
79 79 </div>
80 </%def>
81
82
80 </%def>
@@ -8,7 +8,7 b''
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(u'Home',h.url('/'))}
11 &raquo;
11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('shortlog')}
@@ -29,5 +29,5 b''
29 29 ${c.shortlog_data}
30 30 </div>
31 31 </div>
32 </div>
33 </%def> No newline at end of file
32 </div>
33 </%def>
@@ -1,31 +1,35 b''
1 1 ## -*- coding: utf-8 -*-
2 % if c.repo_changesets:
3 <table>
2 %if c.repo_changesets:
3 <table class="table_disp">
4 4 <tr>
5 <th class="left">${_('commit message')}</th>
5 <th class="left">${_('revision')}</th>
6 <th class="left">${_('commit message')}</th>
6 7 <th class="left">${_('age')}</th>
7 8 <th class="left">${_('author')}</th>
8 <th class="left">${_('revision')}</th>
9 9 <th class="left">${_('branch')}</th>
10 10 <th class="left">${_('tags')}</th>
11 <th class="left">${_('links')}</th>
12
13 11 </tr>
14 12 %for cnt,cs in enumerate(c.repo_changesets):
15 13 <tr class="parity${cnt%2}">
16 14 <td>
15 <div><pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre></div>
16 </td>
17 <td>
17 18 ${h.link_to(h.truncate(cs.message,50),
18 19 h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
19 20 title=cs.message)}
20 21 </td>
21 22 <td><span class="tooltip" title="${cs.date}">
22 23 ${h.age(cs.date)}</span>
23 </td>
24 </td>
24 25 <td title="${cs.author}">${h.person(cs.author)}</td>
25 <td>r${cs.revision}:${h.short_id(cs.raw_id)}</td>
26 26 <td>
27 27 <span class="logtags">
28 <span class="branchtag">${cs.branch}</span>
28 <span class="branchtag">
29 %if h.is_hg(c.rhodecode_repo):
30 ${cs.branch}
31 %endif
32 </span>
29 33 </span>
30 34 </td>
31 35 <td>
@@ -35,11 +39,6 b''
35 39 %endfor
36 40 </span>
37 41 </td>
38 <td class="nowrap">
39 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
40 |
41 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
42 </td>
43 42 </tr>
44 43 %endfor
45 44
@@ -50,7 +49,7 b''
50 49 YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
51 50 ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
52 51 YUE.preventDefault(e);
53 },'.pager_link');
52 },'.pager_link');
54 53 });
55 54 </script>
56 55
@@ -58,5 +57,27 b''
58 57 ${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
59 58 </div>
60 59 %else:
61 ${_('There are no changes yet')}
60
61 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
62 <h4>${_('Add or upload files directly via RhodeCode')}</h4>
63 <div style="margin: 20px 30px;">
64 <div id="add_node_id" class="add_node">
65 <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('add new file')}</a>
66 </div>
67 </div>
62 68 %endif
69
70
71 <h4>${_('Push new repo')}</h4>
72 <pre>
73 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
74 ${c.rhodecode_repo.alias} add README # add first file
75 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
76 ${c.rhodecode_repo.alias} push # push changes back
77 </pre>
78
79 <h4>${_('Existing repository?')}</h4>
80 <pre>
81 ${c.rhodecode_repo.alias} push ${c.clone_repo_url}
82 </pre>
83 %endif
This diff has been collapsed as it changes many lines, (1148 lines changed) Show them Hide them
@@ -6,18 +6,25 b''
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 <div class="box box-left">
20 <%
21 summary = lambda n:{False:'summary-short'}.get(n)
22 %>
23 %if c.show_stats:
24 <div class="box box-left">
25 %else:
26 <div class="box">
27 %endif
21 28 <!-- box / title -->
22 29 <div class="title">
23 30 ${self.breadcrumbs()}
@@ -25,81 +32,81 b''
25 32 <!-- end box / title -->
26 33 <div class="form">
27 34 <div id="summary" class="fields">
28
35
29 36 <div class="field">
30 <div class="label">
37 <div class="label-summary">
31 38 <label>${_('Name')}:</label>
32 39 </div>
33 <div class="input-short">
40 <div class="input ${summary(c.show_stats)}">
41 <div style="float:right;padding:5px 0px 0px 5px">
42 %if c.rhodecode_user.username != 'default':
43 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
44 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
45 %else:
46 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
47 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
48 %endif
49 </div>
34 50 %if c.rhodecode_user.username != 'default':
35 51 %if c.following:
36 52 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
37 53 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
38 </span>
54 </span>
39 55 %else:
40 56 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
41 57 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
42 58 </span>
43 59 %endif
44 %endif:
45
60 %endif:
46 61 ##REPO TYPE
47 %if c.dbrepo.repo_type =='hg':
62 %if h.is_hg(c.dbrepo):
48 63 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
49 64 %endif
50 %if c.dbrepo.repo_type =='git':
65 %if h.is_git(c.dbrepo):
51 66 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
52 %endif
53
54 ##PUBLIC/PRIVATE
67 %endif
68
69 ##PUBLIC/PRIVATE
55 70 %if c.dbrepo.private:
56 71 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
57 72 %else:
58 73 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
59 74 %endif
60
75
61 76 ##REPO NAME
62 <span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
63
77 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
78
64 79 ##FORK
65 80 %if c.dbrepo.fork:
66 81 <div style="margin-top:5px;clear:both"">
67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
68 <img class="icon" alt="${_('public')}"
69 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
70 src="${h.url('/images/icons/arrow_divide.png')}"/>
71 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
82 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
83 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
72 84 </a>
73 85 </div>
74 86 %endif
75 87 ##REMOTE
76 88 %if c.dbrepo.clone_uri:
77 89 <div style="margin-top:5px;clear:both">
78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
79 <img class="icon" alt="${_('remote clone')}"
80 title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}"
81 src="${h.url('/images/icons/connect.png')}"/>
82 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
90 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
91 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
83 92 </a>
84 </div>
85 %endif
93 </div>
94 %endif
86 95 </div>
87 96 </div>
88
89
97
90 98 <div class="field">
91 <div class="label">
99 <div class="label-summary">
92 100 <label>${_('Description')}:</label>
93 101 </div>
94 <div class="input-short desc">${h.urlify_text(c.dbrepo.description)}</div>
102 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
95 103 </div>
96
97
104
98 105 <div class="field">
99 <div class="label">
106 <div class="label-summary">
100 107 <label>${_('Contact')}:</label>
101 108 </div>
102 <div class="input-short">
109 <div class="input ${summary(c.show_stats)}">
103 110 <div class="gravatar">
104 111 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
105 112 </div>
@@ -108,595 +115,584 b''
108 115 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
109 116 </div>
110 117 </div>
111
118
112 119 <div class="field">
113 <div class="label">
114 <label>${_('Last change')}:</label>
115 </div>
116 <div class="input-short">
117 <b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
118 h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> -
119 <span class="tooltip" title="${c.rhodecode_repo.last_change}">
120 ${h.age(c.rhodecode_repo.last_change)}</span><br/>
121 ${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author}
122
123 </div>
124 </div>
125
126 <div class="field">
127 <div class="label">
120 <div class="label-summary">
128 121 <label>${_('Clone url')}:</label>
129 122 </div>
130 <div class="input-short">
131 <input type="text" id="clone_url" readonly="readonly" value="${c.rhodecode_repo.alias} clone ${c.clone_repo_url}" size="70"/>
123 <div class="input ${summary(c.show_stats)}">
124 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
125 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
126 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
127 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
132 128 </div>
133 129 </div>
134
130
135 131 <div class="field">
136 <div class="label">
137 <label>${_('Trending source files')}:</label>
132 <div class="label-summary">
133 <label>${_('Trending files')}:</label>
138 134 </div>
139 <div class="input-short">
140 <div id="lang_stats"></div>
135 <div class="input ${summary(c.show_stats)}">
136 %if c.show_stats:
137 <div id="lang_stats"></div>
138 %else:
139 ${_('Statistics are disabled for this repository')}
140 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
141 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
142 %endif
143 %endif
141 144 </div>
142 145 </div>
143
146
144 147 <div class="field">
145 <div class="label">
148 <div class="label-summary">
146 149 <label>${_('Download')}:</label>
147 150 </div>
148 <div class="input-short">
151 <div class="input ${summary(c.show_stats)}">
149 152 %if len(c.rhodecode_repo.revisions) == 0:
150 153 ${_('There are no downloads yet')}
151 154 %elif c.enable_downloads is False:
152 155 ${_('Downloads are disabled for this repository')}
153 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
154 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
155 %endif
156 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
157 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
158 %endif
156 159 %else:
157 160 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
158 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
159 %if cnt >=1:
160 |
161 %endif
162 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
163 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
164 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
165 fname='tip'+archive['extension']),class_="archive_icon")}</span>
166 %endfor
161 <span id="${'zip_link'}">${h.link_to('Download as zip',h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
167 162 <span style="vertical-align: bottom">
168 <input id="archive_subrepos" type="checkbox" name="subrepos"/> <span class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</span>
163 <input id="archive_subrepos" type="checkbox" name="subrepos" />
164 <label for="archive_subrepos" class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</label>
169 165 </span>
170 166 %endif
171 167 </div>
172 168 </div>
173
174 <div class="field">
175 <div class="label">
176 <label>${_('Feeds')}:</label>
177 </div>
178 <div class="input-short">
179 %if c.rhodecode_user.username != 'default':
180 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
181 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
182 %else:
183 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
184 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
185 %endif
186 </div>
187 </div>
188 </div>
169 </div>
189 170 </div>
190 <script type="text/javascript">
191 YUE.onDOMReady(function(e){
192 id = 'clone_url';
193 YUE.on(id,'click',function(e){
194 if(YUD.hasClass(id,'selected')){
195 return
196 }
197 else{
198 YUD.addClass(id,'selected');
199 YUD.get(id).select();
200 }
201
202 })
203 })
204 var data = ${c.trending_languages|n};
205 var total = 0;
206 var no_data = true;
207 var tbl = document.createElement('table');
208 tbl.setAttribute('class','trending_language_tbl');
209 var cnt = 0;
210
211 for (var i=0;i<data.length;i++){
212 total += data[i][1].count;
213 cnt += 1;
214 no_data = false;
215
216 var hide = cnt>2;
217 var tr = document.createElement('tr');
218 if (hide){
219 tr.setAttribute('style','display:none');
220 tr.setAttribute('class','stats_hidden');
221 }
222 var k = data[i][0];
223 var obj = data[i][1];
224 var percentage = Math.round((obj.count/total*100),2);
225
226 var td1 = document.createElement('td');
227 td1.width = 150;
228 var trending_language_label = document.createElement('div');
229 trending_language_label.innerHTML = obj.desc+" ("+k+")";
230 td1.appendChild(trending_language_label);
231
232 var td2 = document.createElement('td');
233 td2.setAttribute('style','padding-right:14px !important');
234 var trending_language = document.createElement('div');
235 var nr_files = obj.count+" ${_('files')}";
236
237 trending_language.title = k+" "+nr_files;
171 </div>
238 172
239 if (percentage>22){
240 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
241 }
242 else{
243 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
244 }
245
246 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
247 trending_language.style.width=percentage+"%";
248 td2.appendChild(trending_language);
249
250 tr.appendChild(td1);
251 tr.appendChild(td2);
252 tbl.appendChild(tr);
253 if(cnt == 3){
254 var show_more = document.createElement('tr');
255 var td = document.createElement('td');
256 lnk = document.createElement('a');
257
258 lnk.href='#';
259 lnk.innerHTML = "${_('show more')}";
260 lnk.id='code_stats_show_more';
261 td.appendChild(lnk);
262
263 show_more.appendChild(td);
264 show_more.appendChild(document.createElement('td'));
265 tbl.appendChild(show_more);
266 }
267
268 }
269 if(no_data){
270 var tr = document.createElement('tr');
271 var td1 = document.createElement('td');
272 td1.innerHTML = "${c.no_data_msg}";
273 tr.appendChild(td1);
274 tbl.appendChild(tr);
275 }
276 YUD.get('lang_stats').appendChild(tbl);
277 YUE.on('code_stats_show_more','click',function(){
278 l = YUD.getElementsByClassName('stats_hidden')
279 for (e in l){
280 YUD.setStyle(l[e],'display','');
281 };
282 YUD.setStyle(YUD.get('code_stats_show_more'),
283 'display','none');
284 })
285
286 var tmpl_links = {}
287 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
288 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
289 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
290 fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_="archive_icon")}';
291 %endfor
292
293 YUE.on(['download_options','archive_subrepos'],'change',function(e){
294 var sm = YUD.get('download_options');
295 var new_cs = sm.options[sm.selectedIndex];
296
297 for(k in tmpl_links){
298 var s = YUD.get(k+'_link');
299 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
300 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text);
301 s.title = s.title.replace('__CS_EXT__',k);
302 var url = tmpl_links[k].replace('__CS__',new_cs.value);
303 var subrepos = YUD.get('archive_subrepos').checked
304 url = url.replace('__SUB__',subrepos);
305 s.innerHTML = url
306 }
307 });
308 </script>
309 </div>
310
173 %if c.show_stats:
311 174 <div class="box box-right" style="min-height:455px">
312 175 <!-- box / title -->
313 176 <div class="title">
314 177 <h5>${_('Commit activity by day / author')}</h5>
315 178 </div>
316
179
317 180 <div class="graph">
318 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
181 <div style="padding:0 10px 10px 17px;">
319 182 %if c.no_data:
320 183 ${c.no_data_msg}
321 184 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
322 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
323 %endif
324
185 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
186 %endif
325 187 %else:
326 ${_('Loaded in')} ${c.stats_percentage} %
188 ${_('Stats gathered: ')} ${c.stats_percentage}%
327 189 %endif
328 </div>
190 </div>
329 191 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
330 192 <div style="clear: both;height: 10px"></div>
331 193 <div id="overview" style="width:450px;height:100px;float:left"></div>
332
194
333 195 <div id="legend_data" style="clear:both;margin-top:10px;">
334 196 <div id="legend_container"></div>
335 197 <div id="legend_choices">
336 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
198 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
337 199 </div>
338 200 </div>
339 <script type="text/javascript">
340 /**
341 * Plots summary graph
342 *
343 * @class SummaryPlot
344 * @param {from} initial from for detailed graph
345 * @param {to} initial to for detailed graph
346 * @param {dataset}
347 * @param {overview_dataset}
348 */
349 function SummaryPlot(from,to,dataset,overview_dataset) {
350 var initial_ranges = {
351 "xaxis":{
352 "from":from,
353 "to":to,
354 },
355 };
356 var dataset = dataset;
357 var overview_dataset = [overview_dataset];
358 var choiceContainer = YUD.get("legend_choices");
359 var choiceContainerTable = YUD.get("legend_choices_tables");
360 var plotContainer = YUD.get('commit_history');
361 var overviewContainer = YUD.get('overview');
362
363 var plot_options = {
364 bars: {show:true,align:'center',lineWidth:4},
365 legend: {show:true, container:"legend_container"},
366 points: {show:true,radius:0,fill:false},
367 yaxis: {tickDecimals:0,},
368 xaxis: {
369 mode: "time",
370 timeformat: "%d/%m",
371 min:from,
372 max:to,
373 },
374 grid: {
375 hoverable: true,
376 clickable: true,
377 autoHighlight:true,
378 color: "#999"
379 },
380 //selection: {mode: "x"}
381 };
382 var overview_options = {
383 legend:{show:false},
384 bars: {show:true,barWidth: 2,},
385 shadowSize: 0,
386 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
387 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
388 grid: {color: "#999",},
389 selection: {mode: "x"}
390 };
391
392 /**
393 *get dummy data needed in few places
394 */
395 function getDummyData(label){
396 return {"label":label,
397 "data":[{"time":0,
398 "commits":0,
399 "added":0,
400 "changed":0,
401 "removed":0,
402 }],
403 "schema":["commits"],
404 "color":'#ffffff',
405 }
406 }
407
408 /**
409 * generate checkboxes accordindly to data
410 * @param keys
411 * @returns
412 */
413 function generateCheckboxes(data) {
414 //append checkboxes
415 var i = 0;
416 choiceContainerTable.innerHTML = '';
417 for(var pos in data) {
418
419 data[pos].color = i;
420 i++;
421 if(data[pos].label != ''){
422 choiceContainerTable.innerHTML += '<tr><td>'+
423 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
424 +data[pos].label+
425 '</td></tr>';
426 }
427 }
428 }
429
430 /**
431 * ToolTip show
432 */
433 function showTooltip(x, y, contents) {
434 var div=document.getElementById('tooltip');
435 if(!div) {
436 div = document.createElement('div');
437 div.id="tooltip";
438 div.style.position="absolute";
439 div.style.border='1px solid #fdd';
440 div.style.padding='2px';
441 div.style.backgroundColor='#fee';
442 document.body.appendChild(div);
443 }
444 YUD.setStyle(div, 'opacity', 0);
445 div.innerHTML = contents;
446 div.style.top=(y + 5) + "px";
447 div.style.left=(x + 5) + "px";
448
449 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
450 anim.animate();
451 }
452
453 /**
454 * This function will detect if selected period has some changesets
455 for this user if it does this data is then pushed for displaying
456 Additionally it will only display users that are selected by the checkbox
457 */
458 function getDataAccordingToRanges(ranges) {
459
460 var data = [];
461 var new_dataset = {};
462 var keys = [];
463 var max_commits = 0;
464 for(var key in dataset){
465
466 for(var ds in dataset[key].data){
467 commit_data = dataset[key].data[ds];
468 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
469
470 if(new_dataset[key] === undefined){
471 new_dataset[key] = {data:[],schema:["commits"],label:key};
472 }
473 new_dataset[key].data.push(commit_data);
474 }
475 }
476 if (new_dataset[key] !== undefined){
477 data.push(new_dataset[key]);
478 }
479 }
480
481 if (data.length > 0){
482 return data;
483 }
484 else{
485 //just return dummy data for graph to plot itself
486 return [getDummyData('')];
487 }
488 }
489
490 /**
491 * redraw using new checkbox data
492 */
493 function plotchoiced(e,args){
494 var cur_data = args[0];
495 var cur_ranges = args[1];
496
497 var new_data = [];
498 var inputs = choiceContainer.getElementsByTagName("input");
201 </div>
202 </div>
203 %endif
499 204
500 //show only checked labels
501 for(var i=0; i<inputs.length; i++) {
502 var checkbox_key = inputs[i].name;
503
504 if(inputs[i].checked){
505 for(var d in cur_data){
506 if(cur_data[d].label == checkbox_key){
507 new_data.push(cur_data[d]);
508 }
509 }
510 }
511 else{
512 //push dummy data to not hide the label
513 new_data.push(getDummyData(checkbox_key));
514 }
515 }
516
517 var new_options = YAHOO.lang.merge(plot_options, {
518 xaxis: {
519 min: cur_ranges.xaxis.from,
520 max: cur_ranges.xaxis.to,
521 mode:"time",
522 timeformat: "%d/%m",
523 },
524 });
525 if (!new_data){
526 new_data = [[0,1]];
527 }
528 // do the zooming
529 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
530
531 plot.subscribe("plotselected", plotselected);
532
533 //resubscribe plothover
534 plot.subscribe("plothover", plothover);
535
536 // don't fire event on the overview to prevent eternal loop
537 overview.setSelection(cur_ranges, true);
538
539 }
540
541 /**
542 * plot only selected items from overview
543 * @param ranges
544 * @returns
545 */
546 function plotselected(ranges,cur_data) {
547 //updates the data for new plot
548 var data = getDataAccordingToRanges(ranges);
549 generateCheckboxes(data);
550
551 var new_options = YAHOO.lang.merge(plot_options, {
552 xaxis: {
553 min: ranges.xaxis.from,
554 max: ranges.xaxis.to,
555 mode:"time",
556 timeformat: "%d/%m",
557 },
558 });
559 // do the zooming
560 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
561
562 plot.subscribe("plotselected", plotselected);
563
564 //resubscribe plothover
565 plot.subscribe("plothover", plothover);
566
567 // don't fire event on the overview to prevent eternal loop
568 overview.setSelection(ranges, true);
569
570 //resubscribe choiced
571 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
572 }
573
574 var previousPoint = null;
575
576 function plothover(o) {
577 var pos = o.pos;
578 var item = o.item;
579
580 //YUD.get("x").innerHTML = pos.x.toFixed(2);
581 //YUD.get("y").innerHTML = pos.y.toFixed(2);
582 if (item) {
583 if (previousPoint != item.datapoint) {
584 previousPoint = item.datapoint;
585
586 var tooltip = YUD.get("tooltip");
587 if(tooltip) {
588 tooltip.parentNode.removeChild(tooltip);
589 }
590 var x = item.datapoint.x.toFixed(2);
591 var y = item.datapoint.y.toFixed(2);
592
593 if (!item.series.label){
594 item.series.label = 'commits';
595 }
596 var d = new Date(x*1000);
597 var fd = d.toDateString()
598 var nr_commits = parseInt(y);
599
600 var cur_data = dataset[item.series.label].data[item.dataIndex];
601 var added = cur_data.added;
602 var changed = cur_data.changed;
603 var removed = cur_data.removed;
604
605 var nr_commits_suffix = " ${_('commits')} ";
606 var added_suffix = " ${_('files added')} ";
607 var changed_suffix = " ${_('files changed')} ";
608 var removed_suffix = " ${_('files removed')} ";
609
610
611 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
612 if(added==1){added_suffix=" ${_('file added')} ";}
613 if(changed==1){changed_suffix=" ${_('file changed')} ";}
614 if(removed==1){removed_suffix=" ${_('file removed')} ";}
615
616 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
617 +'<br/>'+
618 nr_commits + nr_commits_suffix+'<br/>'+
619 added + added_suffix +'<br/>'+
620 changed + changed_suffix + '<br/>'+
621 removed + removed_suffix + '<br/>');
622 }
623 }
624 else {
625 var tooltip = YUD.get("tooltip");
626
627 if(tooltip) {
628 tooltip.parentNode.removeChild(tooltip);
629 }
630 previousPoint = null;
631 }
632 }
633
634 /**
635 * MAIN EXECUTION
636 */
637
638 var data = getDataAccordingToRanges(initial_ranges);
639 generateCheckboxes(data);
640
641 //main plot
642 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
643
644 //overview
645 var overview = YAHOO.widget.Flot(overviewContainer,
646 overview_dataset, overview_options);
647
648 //show initial selection on overview
649 overview.setSelection(initial_ranges);
650
651 plot.subscribe("plotselected", plotselected);
652 plot.subscribe("plothover", plothover)
653
654 overview.subscribe("plotselected", function (ranges) {
655 plot.setSelection(ranges);
656 });
657
658 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
659 }
660 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
661 </script>
662
205 <div class="box">
206 <div class="title">
207 <div class="breadcrumbs">
208 %if c.repo_changesets:
209 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
210 %else:
211 ${_('Quick start')}
212 %endif
213 </div>
663 214 </div>
664 </div>
665
666 <div class="box">
667 <div class="title">
668 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
669 </div>
670 215 <div class="table">
671 216 <div id="shortlog_data">
672 217 <%include file='../shortlog/shortlog_data.html'/>
673 218 </div>
674 ##%if c.repo_changesets:
675 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
676 ##%endif
219 </div>
220 </div>
221
222 %if c.readme_data:
223 <div class="box" style="background-color: #FAFAFA">
224 <div id="readme" class="title">
225 <div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
226 </div>
227 <div class="readme">
228 <div class="readme_box">
229 ${c.readme_data|n}
230 </div>
677 231 </div>
678 232 </div>
679 <div class="box">
680 <div class="title">
681 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
682 </div>
683 <div class="table">
684 <%include file='../tags/tags_data.html'/>
685 %if c.repo_changesets:
686 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
687 %endif
688 </div>
689 </div>
690 <div class="box">
691 <div class="title">
692 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
693 </div>
694 <div class="table">
695 <%include file='../branches/branches_data.html'/>
696 %if c.repo_changesets:
697 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
698 %endif
699 </div>
700 </div>
233 %endif
234
235 <script type="text/javascript">
236 var clone_url = 'clone_url';
237 YUE.on(clone_url,'click',function(e){
238 if(YUD.hasClass(clone_url,'selected')){
239 return
240 }
241 else{
242 YUD.addClass(clone_url,'selected');
243 YUD.get(clone_url).select();
244 }
245 })
246
247 YUE.on('clone_by_name','click',function(e){
248 // show url by name and hide name button
249 YUD.setStyle('clone_url','display','');
250 YUD.setStyle('clone_by_name','display','none');
251
252 // hide url by id and show name button
253 YUD.setStyle('clone_by_id','display','');
254 YUD.setStyle('clone_url_id','display','none');
255
256 })
257 YUE.on('clone_by_id','click',function(e){
258
259 // show url by id and hide id button
260 YUD.setStyle('clone_by_id','display','none');
261 YUD.setStyle('clone_url_id','display','');
262
263 // hide url by name and show id button
264 YUD.setStyle('clone_by_name','display','');
265 YUD.setStyle('clone_url','display','none');
266 })
267
268
269 var tmpl_links = {};
270 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
271 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
272 %endfor
273
274 YUE.on(['download_options','archive_subrepos'],'change',function(e){
275 var sm = YUD.get('download_options');
276 var new_cs = sm.options[sm.selectedIndex];
277
278 for(k in tmpl_links){
279 var s = YUD.get(k+'_link');
280 if(s){
281 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
282 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
283 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
284
285 var url = tmpl_links[k].replace('__CS__',new_cs.value);
286 var subrepos = YUD.get('archive_subrepos').checked;
287 url = url.replace('__SUB__',subrepos);
288 url = url.replace('__NAME__',title_tmpl);
289 s.innerHTML = url
290 }
291 }
292 });
293 </script>
294 %if c.show_stats:
295 <script type="text/javascript">
296 var data = ${c.trending_languages|n};
297 var total = 0;
298 var no_data = true;
299 var tbl = document.createElement('table');
300 tbl.setAttribute('class','trending_language_tbl');
301 var cnt = 0;
302 for (var i=0;i<data.length;i++){
303 total+= data[i][1].count;
304 }
305 for (var i=0;i<data.length;i++){
306 cnt += 1;
307 no_data = false;
308
309 var hide = cnt>2;
310 var tr = document.createElement('tr');
311 if (hide){
312 tr.setAttribute('style','display:none');
313 tr.setAttribute('class','stats_hidden');
314 }
315 var k = data[i][0];
316 var obj = data[i][1];
317 var percentage = Math.round((obj.count/total*100),2);
318
319 var td1 = document.createElement('td');
320 td1.width = 150;
321 var trending_language_label = document.createElement('div');
322 trending_language_label.innerHTML = obj.desc+" ("+k+")";
323 td1.appendChild(trending_language_label);
324
325 var td2 = document.createElement('td');
326 td2.setAttribute('style','padding-right:14px !important');
327 var trending_language = document.createElement('div');
328 var nr_files = obj.count+" ${_('files')}";
329
330 trending_language.title = k+" "+nr_files;
331
332 if (percentage>22){
333 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
334 }
335 else{
336 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
337 }
338
339 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
340 trending_language.style.width=percentage+"%";
341 td2.appendChild(trending_language);
342
343 tr.appendChild(td1);
344 tr.appendChild(td2);
345 tbl.appendChild(tr);
346 if(cnt == 3){
347 var show_more = document.createElement('tr');
348 var td = document.createElement('td');
349 lnk = document.createElement('a');
350
351 lnk.href='#';
352 lnk.innerHTML = "${_('show more')}";
353 lnk.id='code_stats_show_more';
354 td.appendChild(lnk);
355
356 show_more.appendChild(td);
357 show_more.appendChild(document.createElement('td'));
358 tbl.appendChild(show_more);
359 }
360
361 }
362
363 YUD.get('lang_stats').appendChild(tbl);
364 YUE.on('code_stats_show_more','click',function(){
365 l = YUD.getElementsByClassName('stats_hidden')
366 for (e in l){
367 YUD.setStyle(l[e],'display','');
368 };
369 YUD.setStyle(YUD.get('code_stats_show_more'),
370 'display','none');
371 });
372 </script>
373 <script type="text/javascript">
374 /**
375 * Plots summary graph
376 *
377 * @class SummaryPlot
378 * @param {from} initial from for detailed graph
379 * @param {to} initial to for detailed graph
380 * @param {dataset}
381 * @param {overview_dataset}
382 */
383 function SummaryPlot(from,to,dataset,overview_dataset) {
384 var initial_ranges = {
385 "xaxis":{
386 "from":from,
387 "to":to,
388 },
389 };
390 var dataset = dataset;
391 var overview_dataset = [overview_dataset];
392 var choiceContainer = YUD.get("legend_choices");
393 var choiceContainerTable = YUD.get("legend_choices_tables");
394 var plotContainer = YUD.get('commit_history');
395 var overviewContainer = YUD.get('overview');
396
397 var plot_options = {
398 bars: {show:true,align:'center',lineWidth:4},
399 legend: {show:true, container:"legend_container"},
400 points: {show:true,radius:0,fill:false},
401 yaxis: {tickDecimals:0,},
402 xaxis: {
403 mode: "time",
404 timeformat: "%d/%m",
405 min:from,
406 max:to,
407 },
408 grid: {
409 hoverable: true,
410 clickable: true,
411 autoHighlight:true,
412 color: "#999"
413 },
414 //selection: {mode: "x"}
415 };
416 var overview_options = {
417 legend:{show:false},
418 bars: {show:true,barWidth: 2,},
419 shadowSize: 0,
420 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
421 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
422 grid: {color: "#999",},
423 selection: {mode: "x"}
424 };
425
426 /**
427 *get dummy data needed in few places
428 */
429 function getDummyData(label){
430 return {"label":label,
431 "data":[{"time":0,
432 "commits":0,
433 "added":0,
434 "changed":0,
435 "removed":0,
436 }],
437 "schema":["commits"],
438 "color":'#ffffff',
439 }
440 }
441
442 /**
443 * generate checkboxes accordindly to data
444 * @param keys
445 * @returns
446 */
447 function generateCheckboxes(data) {
448 //append checkboxes
449 var i = 0;
450 choiceContainerTable.innerHTML = '';
451 for(var pos in data) {
452
453 data[pos].color = i;
454 i++;
455 if(data[pos].label != ''){
456 choiceContainerTable.innerHTML +=
457 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
458 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
459 }
460 }
461 }
701 462
702 </%def>
463 /**
464 * ToolTip show
465 */
466 function showTooltip(x, y, contents) {
467 var div=document.getElementById('tooltip');
468 if(!div) {
469 div = document.createElement('div');
470 div.id="tooltip";
471 div.style.position="absolute";
472 div.style.border='1px solid #fdd';
473 div.style.padding='2px';
474 div.style.backgroundColor='#fee';
475 document.body.appendChild(div);
476 }
477 YUD.setStyle(div, 'opacity', 0);
478 div.innerHTML = contents;
479 div.style.top=(y + 5) + "px";
480 div.style.left=(x + 5) + "px";
481
482 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
483 anim.animate();
484 }
485
486 /**
487 * This function will detect if selected period has some changesets
488 for this user if it does this data is then pushed for displaying
489 Additionally it will only display users that are selected by the checkbox
490 */
491 function getDataAccordingToRanges(ranges) {
492
493 var data = [];
494 var new_dataset = {};
495 var keys = [];
496 var max_commits = 0;
497 for(var key in dataset){
498
499 for(var ds in dataset[key].data){
500 commit_data = dataset[key].data[ds];
501 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
502
503 if(new_dataset[key] === undefined){
504 new_dataset[key] = {data:[],schema:["commits"],label:key};
505 }
506 new_dataset[key].data.push(commit_data);
507 }
508 }
509 if (new_dataset[key] !== undefined){
510 data.push(new_dataset[key]);
511 }
512 }
513
514 if (data.length > 0){
515 return data;
516 }
517 else{
518 //just return dummy data for graph to plot itself
519 return [getDummyData('')];
520 }
521 }
522
523 /**
524 * redraw using new checkbox data
525 */
526 function plotchoiced(e,args){
527 var cur_data = args[0];
528 var cur_ranges = args[1];
529
530 var new_data = [];
531 var inputs = choiceContainer.getElementsByTagName("input");
532
533 //show only checked labels
534 for(var i=0; i<inputs.length; i++) {
535 var checkbox_key = inputs[i].name;
536
537 if(inputs[i].checked){
538 for(var d in cur_data){
539 if(cur_data[d].label == checkbox_key){
540 new_data.push(cur_data[d]);
541 }
542 }
543 }
544 else{
545 //push dummy data to not hide the label
546 new_data.push(getDummyData(checkbox_key));
547 }
548 }
549
550 var new_options = YAHOO.lang.merge(plot_options, {
551 xaxis: {
552 min: cur_ranges.xaxis.from,
553 max: cur_ranges.xaxis.to,
554 mode:"time",
555 timeformat: "%d/%m",
556 },
557 });
558 if (!new_data){
559 new_data = [[0,1]];
560 }
561 // do the zooming
562 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
563
564 plot.subscribe("plotselected", plotselected);
565
566 //resubscribe plothover
567 plot.subscribe("plothover", plothover);
568
569 // don't fire event on the overview to prevent eternal loop
570 overview.setSelection(cur_ranges, true);
571
572 }
573
574 /**
575 * plot only selected items from overview
576 * @param ranges
577 * @returns
578 */
579 function plotselected(ranges,cur_data) {
580 //updates the data for new plot
581 var data = getDataAccordingToRanges(ranges);
582 generateCheckboxes(data);
583
584 var new_options = YAHOO.lang.merge(plot_options, {
585 xaxis: {
586 min: ranges.xaxis.from,
587 max: ranges.xaxis.to,
588 mode:"time",
589 timeformat: "%d/%m",
590 },
591 });
592 // do the zooming
593 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
594
595 plot.subscribe("plotselected", plotselected);
596
597 //resubscribe plothover
598 plot.subscribe("plothover", plothover);
599
600 // don't fire event on the overview to prevent eternal loop
601 overview.setSelection(ranges, true);
602
603 //resubscribe choiced
604 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
605 }
606
607 var previousPoint = null;
608
609 function plothover(o) {
610 var pos = o.pos;
611 var item = o.item;
612
613 //YUD.get("x").innerHTML = pos.x.toFixed(2);
614 //YUD.get("y").innerHTML = pos.y.toFixed(2);
615 if (item) {
616 if (previousPoint != item.datapoint) {
617 previousPoint = item.datapoint;
618
619 var tooltip = YUD.get("tooltip");
620 if(tooltip) {
621 tooltip.parentNode.removeChild(tooltip);
622 }
623 var x = item.datapoint.x.toFixed(2);
624 var y = item.datapoint.y.toFixed(2);
625
626 if (!item.series.label){
627 item.series.label = 'commits';
628 }
629 var d = new Date(x*1000);
630 var fd = d.toDateString()
631 var nr_commits = parseInt(y);
632
633 var cur_data = dataset[item.series.label].data[item.dataIndex];
634 var added = cur_data.added;
635 var changed = cur_data.changed;
636 var removed = cur_data.removed;
637
638 var nr_commits_suffix = " ${_('commits')} ";
639 var added_suffix = " ${_('files added')} ";
640 var changed_suffix = " ${_('files changed')} ";
641 var removed_suffix = " ${_('files removed')} ";
642
643
644 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
645 if(added==1){added_suffix=" ${_('file added')} ";}
646 if(changed==1){changed_suffix=" ${_('file changed')} ";}
647 if(removed==1){removed_suffix=" ${_('file removed')} ";}
648
649 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
650 +'<br/>'+
651 nr_commits + nr_commits_suffix+'<br/>'+
652 added + added_suffix +'<br/>'+
653 changed + changed_suffix + '<br/>'+
654 removed + removed_suffix + '<br/>');
655 }
656 }
657 else {
658 var tooltip = YUD.get("tooltip");
659
660 if(tooltip) {
661 tooltip.parentNode.removeChild(tooltip);
662 }
663 previousPoint = null;
664 }
665 }
666
667 /**
668 * MAIN EXECUTION
669 */
670
671 var data = getDataAccordingToRanges(initial_ranges);
672 generateCheckboxes(data);
673
674 //main plot
675 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
676
677 //overview
678 var overview = YAHOO.widget.Flot(overviewContainer,
679 overview_dataset, overview_options);
680
681 //show initial selection on overview
682 overview.setSelection(initial_ranges);
683
684 plot.subscribe("plotselected", plotselected);
685 plot.subscribe("plothover", plothover)
686
687 overview.subscribe("plotselected", function (ranges) {
688 plot.setSelection(ranges);
689 });
690
691 // user choices on overview
692 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
693 }
694 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
695 </script>
696 %endif
697
698 </%def>
@@ -7,8 +7,9 b''
7 7
8 8
9 9 <%def name="breadcrumbs_links()">
10 <input class="q_filter_box" id="q_filter_tags" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
10 11 ${h.link_to(u'Home',h.url('/'))}
11 &raquo;
12 &raquo;
12 13 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 14 &raquo;
14 15 ${_('tags')}
@@ -27,5 +28,49 b''
27 28 <div class="table">
28 29 <%include file='tags_data.html'/>
29 30 </div>
30 </div>
31 </%def> No newline at end of file
31 </div>
32 <script type="text/javascript">
33
34 // main table sorting
35 var myColumnDefs = [
36 {key:"name",label:"${_('Name')}",sortable:true},
37 {key:"date",label:"${_('Date')}",sortable:true,
38 sortOptions: { sortFunction: dateSort }},
39 {key:"author",label:"${_('Author')}",sortable:true},
40 {key:"revision",label:"${_('Revision')}",sortable:true,
41 sortOptions: { sortFunction: revisionSort }},
42 ];
43
44 var myDataSource = new YAHOO.util.DataSource(YUD.get("tags_data"));
45
46 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
47
48 myDataSource.responseSchema = {
49 fields: [
50 {key:"name"},
51 {key:"date"},
52 {key:"author"},
53 {key:"revision"},
54 ]
55 };
56
57 var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
58 {
59 sortedBy:{key:"name",dir:"asc"},
60 MSG_SORTASC:"${_('Click to sort ascending')}",
61 MSG_SORTDESC:"${_('Click to sort descending')}",
62 MSG_EMPTY:"${_('No records found.')}",
63 MSG_ERROR:"${_('Data error.')}",
64 MSG_LOADING:"${_('Loading...')}",
65 }
66 );
67 myDataTable.subscribe('postRenderEvent',function(oArgs) {
68 tooltip_activate();
69 var func = function(node){
70 return node.parentNode.parentNode.parentNode.parentNode.parentNode;
71 }
72 q_filter('q_filter_tags',YUQ('div.table tr td .logtags .tagtag a'),func);
73 });
74
75 </script>
76 </%def>
@@ -1,33 +1,34 b''
1 %if c.repo_tags:
2 <table>
1 %if c.repo_tags:
2 <div id="table_wrap" class="yui-skin-sam">
3 <table id="tags_data">
4 <thead>
3 5 <tr>
4 <th class="left">${_('date')}</th>
5 <th class="left">${_('name')}</th>
6 <th class="left">${_('author')}</th>
7 <th class="left">${_('revision')}</th>
8 <th class="left">${_('links')}</th>
6 <th class="left">${_('Name')}</th>
7 <th class="left">${_('Date')}</th>
8 <th class="left">${_('Author')}</th>
9 <th class="left">${_('Revision')}</th>
9 10 </tr>
11 </thead>
10 12 %for cnt,tag in enumerate(c.repo_tags.items()):
11 <tr class="parity${cnt%2}">
12 <td><span class="tooltip" title="${h.age(tag[1].date)}">
13 ${tag[1].date}</span>
14 </td>
13 <tr class="parity${cnt%2}">
15 14 <td>
16 15 <span class="logtags">
17 16 <span class="tagtag">${h.link_to(tag[0],
18 h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}</span>
17 h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
18 </span>
19 19 </span>
20 </td>
20 </td>
21 <td><span class="tooltip" title="${h.age(tag[1].date)}">${tag[1].date}</span></td>
21 22 <td title="${tag[1].author}">${h.person(tag[1].author)}</td>
22 <td>r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</td>
23 <td class="nowrap">
24 ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
25 |
26 ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
27 </td>
28 </tr>
23 <td>
24 <div>
25 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id)}">r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</a></pre>
26 </div>
27 </td>
28 </tr>
29 29 %endfor
30 30 </table>
31 </div>
31 32 %else:
32 33 ${_('There are no tags yet')}
33 %endif No newline at end of file
34 %endif
@@ -8,9 +8,12 b' This module initializes the application '
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 import os
11 import time
12 import logging
11 13 from os.path import join as jn
12 14
13 15 from unittest import TestCase
16 from tempfile import _RandomNameSequence
14 17
15 18 from paste.deploy import loadapp
16 19 from paste.script.appinstall import SetupCommand
@@ -18,31 +21,47 b' from pylons import config, url'
18 21 from routes.util import URLGenerator
19 22 from webtest import TestApp
20 23
21 from rhodecode.model import meta
22 import logging
23
24
25 log = logging.getLogger(__name__)
24 from rhodecode.model.meta import Session
25 from rhodecode.model.db import User
26 26
27 27 import pylons.test
28 28
29 __all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
30 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
31 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS' ]
29 os.environ['TZ'] = 'UTC'
30 time.tzset()
31
32 log = logging.getLogger(__name__)
33
34 __all__ = [
35 'environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
36 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
37 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
38 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
39 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL'
40 ]
32 41
33 42 # Invoke websetup with the current config file
34 #SetupCommand('setup-app').run([config_file])
43 # SetupCommand('setup-app').run([config_file])
35 44
36 45 ##RUNNING DESIRED TESTS
37 46 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
38 # nosetests --pdb --pdb-failures
47 # nosetests --pdb --pdb-failures
39 48 environ = {}
40 49
41 50 #SOME GLOBALS FOR TESTS
42 from tempfile import _RandomNameSequence
51
43 52 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
44 53 TEST_USER_ADMIN_LOGIN = 'test_admin'
45 54 TEST_USER_ADMIN_PASS = 'test12'
55 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
56
57 TEST_USER_REGULAR_LOGIN = 'test_regular'
58 TEST_USER_REGULAR_PASS = 'test12'
59 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
60
61 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
62 TEST_USER_REGULAR2_PASS = 'test12'
63 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
64
46 65 HG_REPO = 'vcs_test_hg'
47 66 GIT_REPO = 'vcs_test_git'
48 67
@@ -60,12 +79,13 b' class TestController(TestCase):'
60 79
61 80 self.app = TestApp(wsgiapp)
62 81 url._push_object(URLGenerator(config['routes.map'], environ))
63 self.sa = meta.Session
82 self.Session = Session
64 83 self.index_location = config['app_conf']['index_dir']
65 84 TestCase.__init__(self, *args, **kwargs)
66 85
67 86 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
68 87 password=TEST_USER_ADMIN_PASS):
88 self._logged_username = username
69 89 response = self.app.post(url(controller='login', action='index'),
70 90 {'username':username,
71 91 'password':password})
@@ -74,10 +94,16 b' class TestController(TestCase):'
74 94 self.fail('could not login using %s %s' % (username, password))
75 95
76 96 self.assertEqual(response.status, '302 Found')
77 self.assertEqual(response.session['rhodecode_user'].username, username)
78 return response.follow()
97 ses = response.session['rhodecode_user']
98 self.assertEqual(ses.get('username'), username)
99 response = response.follow()
100 self.assertEqual(ses.get('is_authenticated'), True)
101
102 return response.session['rhodecode_user']
103
104 def _get_logged_user(self):
105 return User.get_by_username(self._logged_username)
79 106
80 107 def checkSessionFlash(self, response, msg):
81 108 self.assertTrue('flash' in response.session)
82 109 self.assertTrue(msg in response.session['flash'][0][1])
83
@@ -6,7 +6,8 b''
6 6 Test suite for making push/pull operations
7 7
8 8 :created_on: Dec 30, 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 11 :license: GPLv3, see COPYING for more details.
11 12 """
12 13 # This program is free software: you can redistribute it and/or modify
@@ -53,7 +54,8 b' add_cache(conf)'
53 54
54 55 USER = 'test_admin'
55 56 PASS = 'test12'
56 HOST = '127.0.0.1:5000'
57 HOST = 'hg.local'
58 METHOD = 'pull'
57 59 DEBUG = True
58 60 log = logging.getLogger(__name__)
59 61
@@ -80,7 +82,7 b' class Command(object):'
80 82 def get_session():
81 83 engine = engine_from_config(conf, 'sqlalchemy.db1.')
82 84 init_model(engine)
83 sa = meta.Session()
85 sa = meta.Session
84 86 return sa
85 87
86 88
@@ -153,9 +155,12 b' def get_anonymous_access():'
153 155 #==============================================================================
154 156 # TESTS
155 157 #==============================================================================
156 def test_clone_with_credentials(no_errors=False, repo=HG_REPO):
158 def test_clone_with_credentials(no_errors=False, repo=HG_REPO, method=METHOD,
159 seq=None):
157 160 cwd = path = jn(TESTS_TMP_PATH, repo)
158 161
162 if seq == None:
163 seq = _RandomNameSequence().next()
159 164
160 165 try:
161 166 shutil.rmtree(path, ignore_errors=True)
@@ -164,26 +169,43 b' def test_clone_with_credentials(no_error'
164 169 except OSError:
165 170 raise
166 171
167
168 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
172 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
169 173 {'user':USER,
170 174 'pass':PASS,
171 175 'host':HOST,
172 'cloned_repo':repo,
173 'dest':path + _RandomNameSequence().next()}
176 'cloned_repo':repo, }
174 177
175 stdout, stderr = Command(cwd).execute('hg clone', clone_url)
178 dest = path + seq
179 if method == 'pull':
180 stdout, stderr = Command(cwd).execute('hg', method, '--cwd', dest, clone_url)
181 else:
182 stdout, stderr = Command(cwd).execute('hg', method, clone_url, dest)
176 183
177 if no_errors is False:
178 assert """adding file changes""" in stdout, 'no messages about cloning'
179 assert """abort""" not in stderr , 'got error from clone'
184 if no_errors is False:
185 assert """adding file changes""" in stdout, 'no messages about cloning'
186 assert """abort""" not in stderr , 'got error from clone'
180 187
181 188 if __name__ == '__main__':
182 189 try:
183 190 create_test_user(force=False)
191 seq = None
192 import time
184 193
185 for i in range(int(sys.argv[2])):
186 test_clone_with_credentials(repo=sys.argv[1])
194 try:
195 METHOD = sys.argv[3]
196 except:
197 pass
187 198
199 if METHOD == 'pull':
200 seq = _RandomNameSequence().next()
201 test_clone_with_credentials(repo=sys.argv[1], method='clone',
202 seq=seq)
203 s = time.time()
204 for i in range(1, int(sys.argv[2]) + 1):
205 print 'take', i
206 test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
207 seq=seq)
208 print 'time taken %.3f' % (time.time() - s)
188 209 except Exception, e:
210 raise
189 211 sys.exit('stop on %s' % e)
@@ -1,5 +1,5 b''
1 1 from rhodecode.tests import *
2 from rhodecode.model.db import RhodeCodeSettings
2 from rhodecode.model.db import RhodeCodeSetting
3 3 from nose.plugins.skip import SkipTest
4 4
5 5 skip_ldap_test = False
@@ -22,7 +22,7 b' class TestLdapSettingsController(TestCon'
22 22 self.log_user()
23 23 if skip_ldap_test:
24 24 raise SkipTest('skipping due to missing ldap lib')
25
25
26 26 test_url = url(controller='admin/ldap_settings',
27 27 action='ldap_settings')
28 28
@@ -41,7 +41,8 b' class TestLdapSettingsController(TestCon'
41 41 'ldap_attr_lastname':'tester',
42 42 'ldap_attr_email':'test@example.com' })
43 43
44 new_settings = RhodeCodeSettings.get_ldap_settings()
44 new_settings = RhodeCodeSetting.get_ldap_settings()
45 print new_settings
45 46 self.assertEqual(new_settings['ldap_host'], u'dc.example.com',
46 47 'fail db write compare')
47 48
@@ -52,7 +53,7 b' class TestLdapSettingsController(TestCon'
52 53 self.log_user()
53 54 if skip_ldap_test:
54 55 raise SkipTest('skipping due to missing ldap lib')
55
56
56 57 test_url = url(controller='admin/ldap_settings',
57 58 action='ldap_settings')
58 59
@@ -70,13 +71,13 b' class TestLdapSettingsController(TestCon'
70 71 'ldap_attr_firstname':'',
71 72 'ldap_attr_lastname':'',
72 73 'ldap_attr_email':'' })
73
74
74 75 self.assertTrue("""<span class="error-message">The LDAP Login"""
75 76 """ attribute of the CN must be specified""" in
76 77 response.body)
77
78
79
78
79
80
80 81 self.assertTrue("""<span class="error-message">Please """
81 82 """enter a number</span>""" in response.body)
82 83
@@ -1,18 +1,16 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 import os
4 import vcs
4 from rhodecode.lib import vcs
5 5
6 6 from rhodecode.model.db import Repository
7 7 from rhodecode.tests import *
8 8
9 9 class TestAdminReposController(TestController):
10 10
11
12 11 def __make_repo(self):
13 12 pass
14 13
15
16 14 def test_index(self):
17 15 self.log_user()
18 16 response = self.app.get(url('repos'))
@@ -32,11 +30,10 b' class TestAdminReposController(TestContr'
32 30 'repo_group':'',
33 31 'description':description,
34 32 'private':private})
35
36 33 self.checkSessionFlash(response, 'created repository %s' % (repo_name))
37 34
38 35 #test if the repo was created in the database
39 new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
36 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
40 37 repo_name).one()
41 38
42 39 self.assertEqual(new_repo.repo_name, repo_name)
@@ -73,7 +70,7 b' class TestAdminReposController(TestContr'
73 70 'created repository %s' % (repo_name_unicode))
74 71
75 72 #test if the repo was created in the database
76 new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
73 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
77 74 repo_name_unicode).one()
78 75
79 76 self.assertEqual(new_repo.repo_name, repo_name_unicode)
@@ -113,7 +110,7 b' class TestAdminReposController(TestContr'
113 110 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
114 111
115 112 #test if the fork was created in the database
116 new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
113 new_repo = self.Session.query(Repository).filter(Repository.repo_name == repo_name).one()
117 114
118 115 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
119 116 assert new_repo.description == description, 'wrong description'
@@ -162,7 +159,7 b' class TestAdminReposController(TestContr'
162 159 response.session['flash'][0])
163 160
164 161 #test if the repo was created in the database
165 new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
162 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
166 163 repo_name).one()
167 164
168 165 self.assertEqual(new_repo.repo_name, repo_name)
@@ -182,7 +179,7 b' class TestAdminReposController(TestContr'
182 179 response.follow()
183 180
184 181 #check if repo was deleted from db
185 deleted_repo = self.sa.query(Repository).filter(Repository.repo_name
182 deleted_repo = self.Session.query(Repository).filter(Repository.repo_name
186 183 == repo_name).scalar()
187 184
188 185 self.assertEqual(deleted_repo, None)
@@ -1,7 +1,7 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 from rhodecode.lib.auth import get_crypt_password, check_password
4 from rhodecode.model.db import User, RhodeCodeSettings
4 from rhodecode.model.db import User, RhodeCodeSetting
5 5 from rhodecode.tests import *
6 6
7 7 class TestAdminSettingsController(TestController):
@@ -63,7 +63,7 b' class TestAdminSettingsController(TestCo'
63 63
64 64 self.checkSessionFlash(response, 'Updated application settings')
65 65
66 self.assertEqual(RhodeCodeSettings
66 self.assertEqual(RhodeCodeSetting
67 67 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
68 68
69 69 response = response.follow()
@@ -85,7 +85,7 b' class TestAdminSettingsController(TestCo'
85 85
86 86 self.assertTrue('Updated application settings' in
87 87 response.session['flash'][0][1])
88 self.assertEqual(RhodeCodeSettings
88 self.assertEqual(RhodeCodeSetting
89 89 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
90 90
91 91 response = response.follow()
@@ -109,7 +109,7 b' class TestAdminSettingsController(TestCo'
109 109 ))
110 110
111 111 self.checkSessionFlash(response, 'Updated application settings')
112 self.assertEqual(RhodeCodeSettings
112 self.assertEqual(RhodeCodeSetting
113 113 .get_app_settings()['rhodecode_title'],
114 114 new_title.decode('utf-8'))
115 115
@@ -145,7 +145,7 b' class TestAdminSettingsController(TestCo'
145 145 response.follow()
146 146
147 147 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
148 user = self.sa.query(User).filter(User.username == 'test_admin').one()
148 user = self.Session.query(User).filter(User.username == 'test_admin').one()
149 149 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
150 150 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
151 151 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
@@ -171,7 +171,7 b' class TestAdminSettingsController(TestCo'
171 171 self.checkSessionFlash(response,
172 172 'Your account was updated successfully')
173 173
174 user = self.sa.query(User).filter(User.username == 'test_admin').one()
174 user = self.Session.query(User).filter(User.username == 'test_admin').one()
175 175 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
176 176
177 177 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
@@ -1,11 +1,13 b''
1 1 from rhodecode.tests import *
2 from rhodecode.model.db import User
2 from rhodecode.model.db import User, Permission
3 3 from rhodecode.lib.auth import check_password
4 4 from sqlalchemy.orm.exc import NoResultFound
5 from rhodecode.model.user import UserModel
5 6
6 7 class TestAdminUsersController(TestController):
7 8
8 9 def test_index(self):
10 self.log_user()
9 11 response = self.app.get(url('users'))
10 12 # Test response...
11 13
@@ -21,30 +23,31 b' class TestAdminUsersController(TestContr'
21 23 lastname = 'lastname'
22 24 email = 'mail@mail.com'
23 25
24 response = self.app.post(url('users'), {'username':username,
25 'password':password,
26 'password_confirmation':password_confirmation,
27 'name':name,
28 'active':True,
29 'lastname':lastname,
30 'email':email})
26 response = self.app.post(url('users'),
27 {'username':username,
28 'password':password,
29 'password_confirmation':password_confirmation,
30 'name':name,
31 'active':True,
32 'lastname':lastname,
33 'email':email})
31 34
32 35
33 assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
36 self.assertTrue('''created user %s''' % (username) in
37 response.session['flash'][0])
34 38
35 new_user = self.sa.query(User).filter(User.username == username).one()
36
39 new_user = self.Session.query(User).\
40 filter(User.username == username).one()
37 41
38 assert new_user.username == username, 'wrong info about username'
39 assert check_password(password, new_user.password) == True , 'wrong info about password'
40 assert new_user.name == name, 'wrong info about name'
41 assert new_user.lastname == lastname, 'wrong info about lastname'
42 assert new_user.email == email, 'wrong info about email'
43
42 self.assertEqual(new_user.username,username)
43 self.assertEqual(check_password(password, new_user.password),True)
44 self.assertEqual(new_user.name,name)
45 self.assertEqual(new_user.lastname,lastname)
46 self.assertEqual(new_user.email,email)
44 47
45 48 response.follow()
46 49 response = response.follow()
47 assert """edit">newtestuser</a>""" in response.body
50 self.assertTrue("""edit">newtestuser</a>""" in response.body)
48 51
49 52 def test_create_err(self):
50 53 self.log_user()
@@ -61,16 +64,17 b' class TestAdminUsersController(TestContr'
61 64 'lastname':lastname,
62 65 'email':email})
63 66
64 assert """<span class="error-message">Invalid username</span>""" in response.body
65 assert """<span class="error-message">Please enter a value</span>""" in response.body
66 assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
67 self.assertTrue("""<span class="error-message">Invalid username</span>""" in response.body)
68 self.assertTrue("""<span class="error-message">Please enter a value</span>""" in response.body)
69 self.assertTrue("""<span class="error-message">An email address must contain a single @</span>""" in response.body)
67 70
68 71 def get_user():
69 self.sa.query(User).filter(User.username == username).one()
72 self.Session.query(User).filter(User.username == username).one()
70 73
71 74 self.assertRaises(NoResultFound, get_user), 'found user in database'
72 75
73 76 def test_new(self):
77 self.log_user()
74 78 response = self.app.get(url('new_user'))
75 79
76 80 def test_new_as_xml(self):
@@ -100,14 +104,17 b' class TestAdminUsersController(TestContr'
100 104
101 105 response = response.follow()
102 106
103 new_user = self.sa.query(User).filter(User.username == username).one()
107 new_user = self.Session.query(User)\
108 .filter(User.username == username).one()
104 109 response = self.app.delete(url('user', id=new_user.user_id))
105 110
106 assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
111 self.assertTrue("""successfully deleted user""" in
112 response.session['flash'][0])
107 113
108 114
109 115 def test_delete_browser_fakeout(self):
110 response = self.app.post(url('user', id=1), params=dict(_method='delete'))
116 response = self.app.post(url('user', id=1),
117 params=dict(_method='delete'))
111 118
112 119 def test_show(self):
113 120 response = self.app.get(url('user', id=1))
@@ -116,7 +123,57 b' class TestAdminUsersController(TestContr'
116 123 response = self.app.get(url('formatted_user', id=1, format='xml'))
117 124
118 125 def test_edit(self):
119 response = self.app.get(url('edit_user', id=1))
126 self.log_user()
127 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
128 response = self.app.get(url('edit_user', id=user.user_id))
129
130
131 def test_add_perm_create_repo(self):
132 self.log_user()
133 perm_none = Permission.get_by_key('hg.create.none')
134 perm_create = Permission.get_by_key('hg.create.repository')
135
136 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
137
138
139 #User should have None permission on creation repository
140 self.assertEqual(UserModel().has_perm(user, perm_none), False)
141 self.assertEqual(UserModel().has_perm(user, perm_create), False)
142
143 response = self.app.post(url('user_perm', id=user.user_id),
144 params=dict(_method='put',
145 create_repo_perm=True))
146
147 perm_none = Permission.get_by_key('hg.create.none')
148 perm_create = Permission.get_by_key('hg.create.repository')
149
150 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
151 #User should have None permission on creation repository
152 self.assertEqual(UserModel().has_perm(user, perm_none), False)
153 self.assertEqual(UserModel().has_perm(user, perm_create), True)
154
155 def test_revoke_perm_create_repo(self):
156 self.log_user()
157 perm_none = Permission.get_by_key('hg.create.none')
158 perm_create = Permission.get_by_key('hg.create.repository')
159
160 user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
161
162
163 #User should have None permission on creation repository
164 self.assertEqual(UserModel().has_perm(user, perm_none), False)
165 self.assertEqual(UserModel().has_perm(user, perm_create), False)
166
167 response = self.app.post(url('user_perm', id=user.user_id),
168 params=dict(_method='put'))
169
170 perm_none = Permission.get_by_key('hg.create.none')
171 perm_create = Permission.get_by_key('hg.create.repository')
172
173 user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
174 #User should have None permission on creation repository
175 self.assertEqual(UserModel().has_perm(user, perm_none), True)
176 self.assertEqual(UserModel().has_perm(user, perm_create), False)
120 177
121 178 def test_edit_as_xml(self):
122 179 response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
@@ -23,10 +23,6 b' class TestAdminUsersGroupsController(Tes'
23 23 self.checkSessionFlash(response,
24 24 'created users group %s' % TEST_USERS_GROUP)
25 25
26
27
28
29
30 26 def test_new(self):
31 27 response = self.app.get(url('new_users_group'))
32 28
@@ -52,13 +48,13 b' class TestAdminUsersGroupsController(Tes'
52 48 'created users group %s' % users_group_name)
53 49
54 50
55 gr = self.sa.query(UsersGroup)\
51 gr = self.Session.query(UsersGroup)\
56 52 .filter(UsersGroup.users_group_name ==
57 53 users_group_name).one()
58 54
59 55 response = self.app.delete(url('users_group', id=gr.users_group_id))
60 56
61 gr = self.sa.query(UsersGroup)\
57 gr = self.Session.query(UsersGroup)\
62 58 .filter(UsersGroup.users_group_name ==
63 59 users_group_name).scalar()
64 60
@@ -89,7 +85,3 b' class TestAdminUsersGroupsController(Tes'
89 85
90 86 def test_revoke_members(self):
91 87 pass
92
93
94
95
@@ -4,15 +4,8 b' class TestBranchesController(TestControl'
4 4
5 5 def test_index(self):
6 6 self.log_user()
7 response = self.app.get(url(controller='branches', action='index', repo_name=HG_REPO))
8
9 assert """<a href="/%s/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</a>""" % HG_REPO in response.body, 'wrong info about default branch'
10 assert """<a href="/%s/changeset/97e8b885c04894463c51898e14387d80c30ed1ee">git</a>""" % HG_REPO in response.body, 'wrong info about default git'
11 assert """<a href="/%s/changeset/2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</a>""" % HG_REPO in response.body, 'wrong info about default web'
12
13
14
15
16
17
18 # Test response...
7 response = self.app.get(url(controller='branches',
8 action='index', repo_name=HG_REPO))
9 response.mustcontain("""<a href="/%s/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/">default</a>""" % HG_REPO)
10 response.mustcontain("""<a href="/%s/files/97e8b885c04894463c51898e14387d80c30ed1ee/">git</a>""" % HG_REPO)
11 response.mustcontain("""<a href="/%s/files/2e6a2bf9356ca56df08807f4ad86d480da72a8f4/">web</a>""" % HG_REPO)
@@ -1,5 +1,6 b''
1 1 from rhodecode.tests import *
2 2
3
3 4 class TestChangelogController(TestController):
4 5
5 6 def test_index_hg(self):
@@ -7,23 +8,26 b' class TestChangelogController(TestContro'
7 8 response = self.app.get(url(controller='changelog', action='index',
8 9 repo_name=HG_REPO))
9 10
10 self.assertTrue("""<div id="chg_20" class="container">"""
11 in response.body)
12 self.assertTrue("""<input class="changeset_range" id="5e204e7583b9" """
13 """name="5e204e7583b9" type="checkbox" value="1" />"""
14 in response.body)
15 self.assertTrue("""<span>commit 154: 5e204e7583b9@2010-08-10 """
16 """02:18:46</span>""" in response.body)
17 self.assertTrue("""Small update at simplevcs app""" in response.body)
11 response.mustcontain("""<div id="chg_20" class="container tablerow1">""")
12 response.mustcontain(
13 """<input class="changeset_range" id="5e204e7583b9" """
14 """name="5e204e7583b9" type="checkbox" value="1" />"""
15 )
16 response.mustcontain(
17 """<span class="changeset_id">154:"""
18 """<span class="changeset_hash">5e204e7583b9</span></span>"""
19 )
18 20
21 response.mustcontain("""Small update at simplevcs app""")
19 22
20 self.assertTrue("""<span id="5e204e7583b9c8e7b93a020bd036564b1e"""
21 """731dae" class="changed_total tooltip" """
22 """title="Affected number of files, click to """
23 """show more details">3</span>""" in response.body)
23 response.mustcontain(
24 """<div id="5e204e7583b9c8e7b93a020bd036564b1e731dae" """
25 """style="float:right;" class="changed_total tooltip" """
26 """title="Affected number of files, click to show """
27 """more details">3</div>"""
28 )
24 29
25 30 #pagination
26
27 31 response = self.app.get(url(controller='changelog', action='index',
28 32 repo_name=HG_REPO), {'page':1})
29 33 response = self.app.get(url(controller='changelog', action='index',
@@ -37,25 +41,26 b' class TestChangelogController(TestContro'
37 41 response = self.app.get(url(controller='changelog', action='index',
38 42 repo_name=HG_REPO), {'page':6})
39 43
40
41 44 # Test response after pagination...
42 self.assertTrue("""<input class="changeset_range" id="46ad32a4f974" """
43 """name="46ad32a4f974" type="checkbox" value="1" />"""
44 in response.body)
45 self.assertTrue("""<span>commit 64: 46ad32a4f974@2010-04-20"""
46 """ 01:33:21</span>"""in response.body)
45 response.mustcontain(
46 """<input class="changeset_range" id="46ad32a4f974" """
47 """name="46ad32a4f974" type="checkbox" value="1" />"""
48 )
49 response.mustcontain(
50 """<span class="changeset_id">64:"""
51 """<span class="changeset_hash">46ad32a4f974</span></span>"""
52 )
47 53
48 self.assertTrue("""<span id="46ad32a4f974e45472a898c6b0acb600320"""
49 """579b1" class="changed_total tooltip" """
50 """title="Affected number of files, click to """
51 """show more details">21</span>"""in response.body)
52 self.assertTrue("""<div class="message"><a href="/%s/changeset/"""
53 """46ad32a4f974e45472a898c6b0acb600320579b1">"""
54 """Merge with 2e6a2bf9356ca56df08807f4ad86d48"""
55 """0da72a8f4</a></div>""" % HG_REPO in response.body)
54 response.mustcontain(
55 """<div id="46ad32a4f974e45472a898c6b0acb600320579b1" """
56 """style="float:right;" class="changed_total tooltip" """
57 """title="Affected number of files, click to show """
58 """more details">21</div>"""
59 )
56 60
57
58
59 #def test_index_git(self):
60 # self.log_user()
61 # response = self.app.get(url(controller='changelog', action='index', repo_name=GIT_REPO))
61 response.mustcontain(
62 """<a href="/%s/changeset/"""
63 """46ad32a4f974e45472a898c6b0acb600320579b1" """
64 """title="Merge with 2e6a2bf9356ca56df08807f4ad86d480da72a8f4">"""
65 """46ad32a4f974</a>""" % HG_REPO
66 )
@@ -6,6 +6,7 b' ARCHIVE_SPECS = {'
6 6 '.zip': ('application/zip', 'zip', ''),
7 7 }
8 8
9
9 10 class TestFilesController(TestController):
10 11
11 12 def test_index(self):
@@ -15,32 +16,29 b' class TestFilesController(TestController'
15 16 revision='tip',
16 17 f_path='/'))
17 18 # Test response...
18 assert '<a class="browser-dir" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>' in response.body, 'missing dir'
19 assert '<a class="browser-dir" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>' in response.body, 'missing dir'
20 assert '<a class="browser-dir" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>' in response.body, 'missing dir'
21 assert '<a class="browser-file" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>' in response.body, 'missing file'
22 assert '<a class="browser-file" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>' in response.body, 'missing file'
23
19 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>')
20 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>')
21 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>')
22 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>')
23 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>')
24 24
25 25 def test_index_revision(self):
26 26 self.log_user()
27 27
28 response = self.app.get(url(controller='files', action='index',
29 repo_name=HG_REPO,
30 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
31 f_path='/'))
32
33
28 response = self.app.get(
29 url(controller='files', action='index',
30 repo_name=HG_REPO,
31 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
32 f_path='/')
33 )
34 34
35 35 #Test response...
36 36
37 assert '<a class="browser-dir" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>' in response.body, 'missing dir'
38 assert '<a class="browser-dir" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>' in response.body, 'missing dir'
39 assert '<a class="browser-file" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>' in response.body, 'missing file'
40 assert '1.1 KiB' in response.body, 'missing size of setup.py'
41 assert 'text/x-python' in response.body, 'missing mimetype of setup.py'
42
43
37 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>')
38 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>')
39 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>')
40 response.mustcontain('1.1 KiB')
41 response.mustcontain('text/x-python')
44 42
45 43 def test_index_different_branch(self):
46 44 self.log_user()
@@ -50,11 +48,7 b' class TestFilesController(TestController'
50 48 revision='97e8b885c04894463c51898e14387d80c30ed1ee',
51 49 f_path='/'))
52 50
53
54
55 assert """<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""" in response.body, 'missing or wrong branch info'
56
57
51 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""")
58 52
59 53 def test_index_paging(self):
60 54 self.log_user()
@@ -70,7 +64,7 b' class TestFilesController(TestController'
70 64 revision=r[1],
71 65 f_path='/'))
72 66
73 assert """@ r%s:%s""" % (r[0], r[1][:12]) in response.body, 'missing info about current revision'
67 response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
74 68
75 69 def test_file_source(self):
76 70 self.log_user()
@@ -80,40 +74,40 b' class TestFilesController(TestController'
80 74 f_path='vcs/nodes.py'))
81 75
82 76 #test or history
83 assert """<optgroup label="Changesets">
84 <option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
85 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35</option>
86 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c</option>
87 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329</option>
88 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf</option>
89 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81</option>
90 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc</option>
91 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21</option>
92 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2</option>
93 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85</option>
94 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2</option>
95 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93</option>
96 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b</option>
97 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455</option>
98 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32</option>
99 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0</option>
100 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081</option>
101 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad</option>
102 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7</option>
103 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059</option>
104 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb</option>
105 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1</option>
106 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71</option>
107 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771</option>
108 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526</option>
109 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4</option>
110 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b</option>
111 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c</option>
112 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1</option>
113 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5</option>
114 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283</option>
115 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6</option>
116 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3</option>
77 response.mustcontain("""<optgroup label="Changesets">
78 <option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
79 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
80 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
81 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
82 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
83 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
84 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
85 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
86 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
87 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
88 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
89 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
90 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
91 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
92 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
93 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
94 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
95 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
96 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
97 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
98 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
99 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
100 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
101 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
102 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
103 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
104 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
105 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
106 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
107 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
108 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
109 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
110 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
117 111 </optgroup>
118 112 <optgroup label="Branches">
119 113 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
@@ -126,16 +120,15 b' class TestFilesController(TestController'
126 120 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
127 121 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
128 122 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
129 </optgroup>""" in response.body
123 </optgroup>""")
130 124
131
132 assert """<div class="commit">"Partially implemented #16. filecontent/commit message/author/node name are safe_unicode now.
125 response.mustcontain("""<div class="commit">Partially implemented #16. filecontent/commit message/author/node name are safe_unicode now.
133 126 In addition some other __str__ are unicode as well
134 127 Added test for unicode
135 128 Improved test to clone into uniq repository.
136 removed extra unicode conversion in diff."</div>""" in response.body
129 removed extra unicode conversion in diff.</div>""")
137 130
138 assert """<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""" in response.body, 'missing or wrong branch info'
131 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
139 132
140 133 def test_file_annotation(self):
141 134 self.log_user()
@@ -144,41 +137,41 b' removed extra unicode conversion in diff'
144 137 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
145 138 f_path='vcs/nodes.py'))
146 139
147 print response.body
148 assert """<optgroup label="Changesets">
149 <option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
150 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35</option>
151 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c</option>
152 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329</option>
153 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf</option>
154 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81</option>
155 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc</option>
156 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21</option>
157 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2</option>
158 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85</option>
159 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2</option>
160 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93</option>
161 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b</option>
162 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455</option>
163 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32</option>
164 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0</option>
165 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081</option>
166 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad</option>
167 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7</option>
168 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059</option>
169 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb</option>
170 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1</option>
171 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71</option>
172 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771</option>
173 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526</option>
174 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4</option>
175 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b</option>
176 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c</option>
177 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1</option>
178 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5</option>
179 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283</option>
180 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6</option>
181 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3</option>
140
141 response.mustcontain("""<optgroup label="Changesets">
142 <option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
143 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
144 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
145 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
146 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
147 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
148 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
149 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
150 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
151 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
152 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
153 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
154 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
155 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
156 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
157 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
158 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
159 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
160 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
161 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
162 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
163 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
164 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
165 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
166 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
167 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
168 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
169 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
170 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
171 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
172 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
173 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
174 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
182 175 </optgroup>
183 176 <optgroup label="Branches">
184 177 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
@@ -191,11 +184,10 b' removed extra unicode conversion in diff'
191 184 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
192 185 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
193 186 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
194 </optgroup>""" in response.body, 'missing or wrong history in annotation'
187 </optgroup>
188 """)
195 189
196 assert """<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""" in response.body, 'missing or wrong branch info'
197
198
190 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
199 191
200 192 def test_archival(self):
201 193 self.log_user()
@@ -260,10 +252,11 b' removed extra unicode conversion in diff'
260 252 revision=rev,
261 253 f_path=f_path))
262 254
263 assert """Revision %r does not exist for this repository""" % (rev) in response.session['flash'][0][1], 'No flash message'
264 assert """%s""" % (HG_REPO) in response.session['flash'][0][1], 'No flash message'
255 msg = """Revision %r does not exist for this repository""" % (rev)
256 self.checkSessionFlash(response, msg)
265 257
266
258 msg = """%s""" % (HG_REPO)
259 self.checkSessionFlash(response, msg)
267 260
268 261 def test_raw_file_wrong_f_path(self):
269 262 self.log_user()
@@ -273,7 +266,9 b' removed extra unicode conversion in diff'
273 266 repo_name=HG_REPO,
274 267 revision=rev,
275 268 f_path=f_path))
276 assert "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12]) in response.session['flash'][0][1], 'No flash message'
269
270 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
271 self.checkSessionFlash(response, msg)
277 272
278 273 #==========================================================================
279 274 # RAW RESPONSE - PLAIN
@@ -296,10 +291,11 b' removed extra unicode conversion in diff'
296 291 repo_name=HG_REPO,
297 292 revision=rev,
298 293 f_path=f_path))
294 msg = """Revision %r does not exist for this repository""" % (rev)
295 self.checkSessionFlash(response, msg)
299 296
300 assert """Revision %r does not exist for this repository""" % (rev) in response.session['flash'][0][1], 'No flash message'
301 assert """%s""" % (HG_REPO) in response.session['flash'][0][1], 'No flash message'
302
297 msg = """%s""" % (HG_REPO)
298 self.checkSessionFlash(response, msg)
303 299
304 300 def test_raw_wrong_f_path(self):
305 301 self.log_user()
@@ -309,5 +305,14 b' removed extra unicode conversion in diff'
309 305 repo_name=HG_REPO,
310 306 revision=rev,
311 307 f_path=f_path))
308 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
309 self.checkSessionFlash(response, msg)
312 310
313 assert "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12]) in response.session['flash'][0][1], 'No flash message'
311 def test_ajaxed_files_list(self):
312 self.log_user()
313 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
314 response = self.app.get(
315 url('files_nodelist_home', repo_name=HG_REPO,f_path='/',revision=rev),
316 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
317 )
318 response.mustcontain("vcs/web/simplevcs/views/repository.py")
@@ -20,10 +20,13 b' class TestForksController(TestController'
20 20 fork_name = HG_FORK
21 21 description = 'fork of vcs test'
22 22 repo_name = HG_REPO
23 response = self.app.post(url(controller='settings',
23 org_repo = Repository.get_by_repo_name(repo_name)
24 response = self.app.post(url(controller='forks',
24 25 action='fork_create',
25 26 repo_name=repo_name),
26 {'fork_name':fork_name,
27 {'repo_name':fork_name,
28 'repo_group':'',
29 'fork_parent_id':org_repo.repo_id,
27 30 'repo_type':'hg',
28 31 'description':description,
29 32 'private':'False'})
@@ -39,3 +42,45 b' class TestForksController(TestController'
39 42 #remove this fork
40 43 response = self.app.delete(url('repo', repo_name=fork_name))
41 44
45
46
47
48 def test_z_fork_create(self):
49 self.log_user()
50 fork_name = HG_FORK
51 description = 'fork of vcs test'
52 repo_name = HG_REPO
53 org_repo = Repository.get_by_repo_name(repo_name)
54 response = self.app.post(url(controller='forks', action='fork_create',
55 repo_name=repo_name),
56 {'repo_name':fork_name,
57 'repo_group':'',
58 'fork_parent_id':org_repo.repo_id,
59 'repo_type':'hg',
60 'description':description,
61 'private':'False'})
62
63 #test if we have a message that fork is ok
64 self.assertTrue('forked %s repository as %s' \
65 % (repo_name, fork_name) in response.session['flash'][0])
66
67 #test if the fork was created in the database
68 fork_repo = self.Session.query(Repository)\
69 .filter(Repository.repo_name == fork_name).one()
70
71 self.assertEqual(fork_repo.repo_name, fork_name)
72 self.assertEqual(fork_repo.fork.repo_name, repo_name)
73
74
75 #test if fork is visible in the list ?
76 response = response.follow()
77
78
79 # check if fork is marked as fork
80 # wait for cache to expire
81 import time
82 time.sleep(10)
83 response = self.app.get(url(controller='summary', action='index',
84 repo_name=fork_name))
85
86 self.assertTrue('Fork of %s' % repo_name in response.body)
@@ -1,22 +1,22 b''
1 1 from rhodecode.tests import *
2 2
3
3 4 class TestHomeController(TestController):
4 5
5 6 def test_index(self):
6 7 self.log_user()
7 8 response = self.app.get(url(controller='home', action='index'))
8 9 #if global permission is set
9 self.assertTrue('ADD NEW REPOSITORY' in response.body)
10 self.assertTrue('href="/%s/summary"' % HG_REPO in response.body)
11 # Test response...
10 response.mustcontain('ADD REPOSITORY')
11 response.mustcontain('href="/%s/summary"' % HG_REPO)
12 12
13 self.assertTrue("""<img class="icon" title="Mercurial repository" """
13 response.mustcontain("""<img class="icon" title="Mercurial repository" """
14 14 """alt="Mercurial repository" src="/images/icons/hg"""
15 """icon.png"/>""" in response.body)
16 self.assertTrue("""<img class="icon" title="public repository" """
15 """icon.png"/>""")
16 response.mustcontain("""<img class="icon" title="public repository" """
17 17 """alt="public repository" src="/images/icons/lock_"""
18 """open.png"/>""" in response.body)
19
20 self.assertTrue("""<a title="Marcin Kuzminski &lt;marcin@python-works.com&gt;
21 merge" class="tooltip" href="/vcs_test_hg/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">r173:27cd5cce30c9</a>"""
22 in response.body)
18 """open.png"/>""")
19
20 response.mustcontain(
21 """<a title="Marcin Kuzminski &lt;marcin@python-works.com&gt;:\n
22 merge" class="tooltip" href="/vcs_test_hg/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">r173:27cd5cce30c9</a>""")
@@ -16,10 +16,10 b' class TestJournalController(TestControll'
16 16
17 17 def test_stop_following_repository(self):
18 18 session = self.log_user()
19 # usr = self.sa.query(User).filter(User.username == 'test_admin').one()
20 # repo = self.sa.query(Repository).filter(Repository.repo_name == HG_REPO).one()
19 # usr = self.Session.query(User).filter(User.username == 'test_admin').one()
20 # repo = self.Session.query(Repository).filter(Repository.repo_name == HG_REPO).one()
21 21 #
22 # followings = self.sa.query(UserFollowing)\
22 # followings = self.Session.query(UserFollowing)\
23 23 # .filter(UserFollowing.user == usr)\
24 24 # .filter(UserFollowing.follows_repository == repo).all()
25 25 #
@@ -1,12 +1,19 b''
1 1 # -*- coding: utf-8 -*-
2 2 from rhodecode.tests import *
3 from rhodecode.model.db import User
3 from rhodecode.model.db import User, Notification
4 4 from rhodecode.lib import generate_api_key
5 5 from rhodecode.lib.auth import check_password
6
6 from rhodecode.model.meta import Session
7 7
8 8 class TestLoginController(TestController):
9 9
10 def tearDown(self):
11 for n in Notification.query().all():
12 Session.delete(n)
13
14 Session.commit()
15 self.assertEqual(Notification.query().all(), [])
16
10 17 def test_index(self):
11 18 response = self.app.get(url(controller='login', action='index'))
12 19 self.assertEqual(response.status, '200 OK')
@@ -17,7 +24,7 b' class TestLoginController(TestController'
17 24 {'username':'test_admin',
18 25 'password':'test12'})
19 26 self.assertEqual(response.status, '302 Found')
20 self.assertEqual(response.session['rhodecode_user'].username ,
27 self.assertEqual(response.session['rhodecode_user'].get('username') ,
21 28 'test_admin')
22 29 response = response.follow()
23 30 self.assertTrue('%s repository' % HG_REPO in response.body)
@@ -28,7 +35,7 b' class TestLoginController(TestController'
28 35 'password':'test12'})
29 36
30 37 self.assertEqual(response.status, '302 Found')
31 self.assertEqual(response.session['rhodecode_user'].username ,
38 self.assertEqual(response.session['rhodecode_user'].get('username') ,
32 39 'test_regular')
33 40 response = response.follow()
34 41 self.assertTrue('%s repository' % HG_REPO in response.body)
@@ -192,7 +199,7 b' class TestLoginController(TestController'
192 199 self.assertEqual(response.status , '302 Found')
193 200 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
194 201
195 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
202 ret = self.Session.query(User).filter(User.username == 'test_regular4').one()
196 203 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
197 204 assert check_password(password, ret.password) == True , 'password mismatch'
198 205 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
@@ -224,8 +231,8 b' class TestLoginController(TestController'
224 231 new.name = name
225 232 new.lastname = lastname
226 233 new.api_key = generate_api_key(username)
227 self.sa.add(new)
228 self.sa.commit()
234 self.Session.add(new)
235 self.Session.commit()
229 236
230 237 response = self.app.post(url(controller='login',
231 238 action='password_reset'),
@@ -247,7 +254,6 b' class TestLoginController(TestController'
247 254 # GOOD KEY
248 255
249 256 key = User.get_by_username(username).api_key
250
251 257 response = self.app.get(url(controller='login',
252 258 action='password_reset_confirmation',
253 259 key=key))
@@ -8,42 +8,3 b' class TestSettingsController(TestControl'
8 8 response = self.app.get(url(controller='settings', action='index',
9 9 repo_name=HG_REPO))
10 10 # Test response...
11
12 def test_fork(self):
13 self.log_user()
14 response = self.app.get(url(controller='settings', action='fork',
15 repo_name=HG_REPO))
16
17
18 def test_fork_create(self):
19 self.log_user()
20 fork_name = HG_FORK
21 description = 'fork of vcs test'
22 repo_name = HG_REPO
23 response = self.app.post(url(controller='settings', action='fork_create',
24 repo_name=repo_name),
25 {'fork_name':fork_name,
26 'repo_type':'hg',
27 'description':description,
28 'private':'False'})
29
30 #test if we have a message that fork is ok
31 assert 'forked %s repository as %s' \
32 % (repo_name, fork_name) in response.session['flash'][0], 'No flash message about fork'
33
34 #test if the fork was created in the database
35 fork_repo = self.sa.query(Repository).filter(Repository.repo_name == fork_name).one()
36
37 assert fork_repo.repo_name == fork_name, 'wrong name of repo name in new db fork repo'
38 assert fork_repo.fork.repo_name == repo_name, 'wrong fork parrent'
39
40
41 #test if fork is visible in the list ?
42 response = response.follow()
43
44
45 #check if fork is marked as fork
46 response = self.app.get(url(controller='summary', action='index',
47 repo_name=fork_name))
48
49 assert 'Fork of %s' % repo_name in response.body, 'no message about that this repo is a fork'
@@ -7,18 +7,22 b' class TestSummaryController(TestControll'
7 7
8 8 def test_index(self):
9 9 self.log_user()
10 ID = Repository.get_by_repo_name(HG_REPO).repo_id
10 11 response = self.app.get(url(controller='summary',
11 action='index', repo_name=HG_REPO))
12 action='index',
13 repo_name=HG_REPO))
12 14
13 15 #repo type
14 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
15 """title="Mercurial repository" alt="Mercurial """
16 """repository" src="/images/icons/hgicon.png"/>"""
17 in response.body)
18 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
19 """title="public repository" alt="public """
20 """repository" src="/images/icons/lock_open.png"/>"""
21 in response.body)
16 response.mustcontain(
17 """<img style="margin-bottom:2px" class="icon" """
18 """title="Mercurial repository" alt="Mercurial """
19 """repository" src="/images/icons/hgicon.png"/>"""
20 )
21 response.mustcontain(
22 """<img style="margin-bottom:2px" class="icon" """
23 """title="public repository" alt="public """
24 """repository" src="/images/icons/lock_open.png"/>"""
25 )
22 26
23 27 #codes stats
24 28 self._enable_stats()
@@ -26,7 +30,6 b' class TestSummaryController(TestControll'
26 30 invalidate_cache('get_repo_cached_%s' % HG_REPO)
27 31 response = self.app.get(url(controller='summary', action='index',
28 32 repo_name=HG_REPO))
29
30 33 response.mustcontain(
31 34 """var data = [["py", {"count": 42, "desc": ["Python"]}], """
32 35 """["rst", {"count": 11, "desc": ["Rst"]}], """
@@ -38,10 +41,26 b' class TestSummaryController(TestControll'
38 41 )
39 42
40 43 # clone url...
41 response.mustcontain("""<input type="text" id="clone_url" readonly="readonly" value="hg clone http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO)
44 response.mustcontain("""<input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/vcs_test_hg"/>""")
45 response.mustcontain("""<input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_1"/>""")
46
47 def test_index_by_id(self):
48 self.log_user()
49 ID = Repository.get_by_repo_name(HG_REPO).repo_id
50 response = self.app.get(url(controller='summary',
51 action='index',
52 repo_name='_%s' % ID))
53
54 #repo type
55 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
56 """title="Mercurial repository" alt="Mercurial """
57 """repository" src="/images/icons/hgicon.png"/>""")
58 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
59 """title="public repository" alt="public """
60 """repository" src="/images/icons/lock_open.png"/>""")
42 61
43 62 def _enable_stats(self):
44 63 r = Repository.get_by_repo_name(HG_REPO)
45 64 r.enable_statistics = True
46 self.sa.add(r)
47 self.sa.commit()
65 self.Session.add(r)
66 self.Session.commit()
@@ -5,9 +5,8 b' class TestTagsController(TestController)'
5 5 def test_index(self):
6 6 self.log_user()
7 7 response = self.app.get(url(controller='tags', action='index', repo_name=HG_REPO))
8 assert """<a href="/%s/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</a>""" % HG_REPO in response.body, 'wrong info about tip tag'
9 assert """<a href="/%s/changeset/fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</a>""" % HG_REPO in response.body, 'wrong info about 0.1.4 tag'
10 assert """<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</a>""" % HG_REPO in response.body, 'wrong info about 0.1.3 tag'
11 assert """<a href="/%s/changeset/a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</a>""" % HG_REPO in response.body, 'wrong info about 0.1.2 tag'
12 assert """<a href="/%s/changeset/eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</a>""" % HG_REPO in response.body, 'wrong info about 0.1.1 tag'
13 # Test response...
8 response.mustcontain("""<a href="/%s/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/">tip</a>""" % HG_REPO)
9 response.mustcontain("""<a href="/%s/files/fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200/">0.1.4</a>""" % HG_REPO)
10 response.mustcontain("""<a href="/%s/files/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf/">0.1.3</a>""" % HG_REPO)
11 response.mustcontain("""<a href="/%s/files/a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720/">0.1.2</a>""" % HG_REPO)
12 response.mustcontain("""<a href="/%s/files/eb3a60fc964309c1a318b8dfe26aa2d1586c85ae/">0.1.1</a>""" % HG_REPO)
@@ -6,12 +6,12 b''
6 6 Test for crawling a project for memory usage
7 7 This should be runned just as regular script together
8 8 with a watch script that will show memory usage.
9
9
10 10 watch -n1 ./rhodecode/tests/mem_watch
11 11
12 12 :created_on: Apr 21, 2010
13 13 :author: marcink
14 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
14 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
15 15 :license: GPLv3, see COPYING for more details.
16 16 """
17 17 # This program is free software: you can redistribute it and/or modify
@@ -31,11 +31,10 b''
31 31 import cookielib
32 32 import urllib
33 33 import urllib2
34 import vcs
35 34 import time
36 35
37 36 from os.path import join as jn
38
37 from rhodecode.lib import vcs
39 38
40 39 BASE_URI = 'http://127.0.0.1:5000/%s'
41 40 PROJECT = 'CPython'
@@ -52,7 +51,6 b' o.addheaders = ['
52 51 urllib2.install_opener(o)
53 52
54 53
55
56 54 def test_changelog_walk(pages=100):
57 55 total_time = 0
58 56 for i in range(1, pages):
@@ -67,7 +65,6 b' def test_changelog_walk(pages=100):'
67 65 total_time += e
68 66 print 'visited %s size:%s req:%s ms' % (full_uri, size, e)
69 67
70
71 68 print 'total_time', total_time
72 69 print 'average on req', total_time / float(pages)
73 70
@@ -103,6 +100,7 b' def test_files_walk(limit=100):'
103 100 repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
104 101
105 102 from rhodecode.lib.compat import OrderedSet
103 from rhodecode.lib.vcs.exceptions import RepositoryError
106 104
107 105 paths_ = OrderedSet([''])
108 106 try:
@@ -117,7 +115,7 b' def test_files_walk(limit=100):'
117 115 for f in files:
118 116 paths_.add(f.path)
119 117
120 except vcs.exception.RepositoryError, e:
118 except RepositoryError, e:
121 119 pass
122 120
123 121 cnt = 0
@@ -140,7 +138,6 b' def test_files_walk(limit=100):'
140 138 print 'average on req', total_time / float(cnt)
141 139
142 140
143
144 141 test_changelog_walk(40)
145 142 time.sleep(2)
146 143 test_changeset_walk(limit=100)
@@ -6,7 +6,8 b''
6 6 Test suite for making push/pull operations
7 7
8 8 :created_on: Dec 30, 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 11 :license: GPLv3, see COPYING for more details.
11 12 """
12 13 # This program is free software: you can redistribute it and/or modify
@@ -184,7 +185,6 b' def test_clone_with_credentials(no_error'
184 185 if anonymous_access:
185 186 print '\tenabled, disabling it '
186 187 set_anonymous_access(enable=False)
187 time.sleep(1)
188 188
189 189 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \
190 190 {'user':USER,
@@ -217,7 +217,6 b' def test_clone_anonymous():'
217 217 if not anonymous_access:
218 218 print '\tnot enabled, enabling it '
219 219 set_anonymous_access(enable=True)
220 time.sleep(1)
221 220
222 221 clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \
223 222 {'user':USER,
@@ -386,7 +385,7 b" if __name__ == '__main__':"
386 385
387 386 initial_logs = get_logs()
388 387 print 'initial activity logs: %s' % len(initial_logs)
389
388 s = time.time()
390 389 #test_push_modify_file()
391 390 test_clone_with_credentials()
392 391 test_clone_wrong_credentials()
@@ -399,3 +398,4 b" if __name__ == '__main__':"
399 398 test_push_wrong_credentials()
400 399
401 400 test_logs(initial_logs)
401 print 'finished ok in %.3f' % (time.time() - s)
@@ -5,9 +5,9 b''
5 5
6 6
7 7 Package for testing various lib/helper functions in rhodecode
8
8
9 9 :created_on: Jun 9, 2011
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -103,3 +103,12 b' class TestLibs(unittest.TestCase):'
103 103 for case in test_cases:
104 104 self.assertEqual(str2bool(case[0]), case[1])
105 105
106
107 def test_mention_extractor(self):
108 from rhodecode.lib import extract_mentioned_users
109 sample = ("@first hi there @marcink here's my email marcin@email.com "
110 "@lukaszb check it pls @ ttwelve @D[] @one@two@three "
111 "@MARCIN @maRCiN @2one_more22")
112 s = ['2one_more22', 'D', 'MARCIN', 'first', 'lukaszb',
113 'maRCiN', 'marcink', 'one']
114 self.assertEqual(s, extract_mentioned_users(sample))
@@ -4,15 +4,35 b' from rhodecode.tests import *'
4 4
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 from rhodecode.model.db import Group, User
7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
8 UsersGroup, UsersGroupMember, Permission
8 9 from sqlalchemy.exc import IntegrityError
10 from rhodecode.model.user import UserModel
11
12 from rhodecode.model.meta import Session
13 from rhodecode.model.notification import NotificationModel
14 from rhodecode.model.users_group import UsersGroupModel
15 from rhodecode.lib.auth import AuthUser
16
17
18 def _make_group(path, desc='desc', parent_id=None,
19 skip_if_exists=False):
20
21 gr = RepoGroup.get_by_group_name(path)
22 if gr and skip_if_exists:
23 return gr
24
25 gr = ReposGroupModel().create(path, desc, parent_id)
26 Session.commit()
27 return gr
28
9 29
10 30 class TestReposGroups(unittest.TestCase):
11 31
12 32 def setUp(self):
13 self.g1 = self.__make_group('test1', skip_if_exists=True)
14 self.g2 = self.__make_group('test2', skip_if_exists=True)
15 self.g3 = self.__make_group('test3', skip_if_exists=True)
33 self.g1 = _make_group('test1', skip_if_exists=True)
34 self.g2 = _make_group('test2', skip_if_exists=True)
35 self.g3 = _make_group('test3', skip_if_exists=True)
16 36
17 37 def tearDown(self):
18 38 print 'out'
@@ -25,101 +45,81 b' class TestReposGroups(unittest.TestCase)'
25 45 def _check_folders(self):
26 46 print os.listdir(TESTS_TMP_PATH)
27 47
28 def __make_group(self, path, desc='desc', parent_id=None,
29 skip_if_exists=False):
30
31 gr = Group.get_by_group_name(path)
32 if gr and skip_if_exists:
33 return gr
34
35 form_data = dict(group_name=path,
36 group_description=desc,
37 group_parent_id=parent_id)
38 gr = ReposGroupModel().create(form_data)
39 return gr
40
41 48 def __delete_group(self, id_):
42 49 ReposGroupModel().delete(id_)
43 50
44
45 51 def __update_group(self, id_, path, desc='desc', parent_id=None):
46 52 form_data = dict(group_name=path,
47 53 group_description=desc,
48 group_parent_id=parent_id)
54 group_parent_id=parent_id,
55 perms_updates=[],
56 perms_new=[])
49 57
50 58 gr = ReposGroupModel().update(id_, form_data)
51 59 return gr
52 60
53 61 def test_create_group(self):
54 g = self.__make_group('newGroup')
62 g = _make_group('newGroup')
55 63 self.assertEqual(g.full_path, 'newGroup')
56 64
57 65 self.assertTrue(self.__check_path('newGroup'))
58 66
59
60 67 def test_create_same_name_group(self):
61 self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup'))
62
68 self.assertRaises(IntegrityError, lambda:_make_group('newGroup'))
69 Session.rollback()
63 70
64 71 def test_same_subgroup(self):
65 sg1 = self.__make_group('sub1', parent_id=self.g1.group_id)
72 sg1 = _make_group('sub1', parent_id=self.g1.group_id)
66 73 self.assertEqual(sg1.parent_group, self.g1)
67 74 self.assertEqual(sg1.full_path, 'test1/sub1')
68 75 self.assertTrue(self.__check_path('test1', 'sub1'))
69 76
70 ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id)
77 ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
71 78 self.assertEqual(ssg1.parent_group, sg1)
72 79 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
73 80 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
74 81
75
76 82 def test_remove_group(self):
77 sg1 = self.__make_group('deleteme')
83 sg1 = _make_group('deleteme')
78 84 self.__delete_group(sg1.group_id)
79 85
80 self.assertEqual(Group.get(sg1.group_id), None)
86 self.assertEqual(RepoGroup.get(sg1.group_id), None)
81 87 self.assertFalse(self.__check_path('deteteme'))
82 88
83 sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id)
89 sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
84 90 self.__delete_group(sg1.group_id)
85 91
86 self.assertEqual(Group.get(sg1.group_id), None)
92 self.assertEqual(RepoGroup.get(sg1.group_id), None)
87 93 self.assertFalse(self.__check_path('test1', 'deteteme'))
88 94
89
90 95 def test_rename_single_group(self):
91 sg1 = self.__make_group('initial')
96 sg1 = _make_group('initial')
92 97
93 98 new_sg1 = self.__update_group(sg1.group_id, 'after')
94 99 self.assertTrue(self.__check_path('after'))
95 self.assertEqual(Group.get_by_group_name('initial'), None)
96
100 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
97 101
98 102 def test_update_group_parent(self):
99 103
100 sg1 = self.__make_group('initial', parent_id=self.g1.group_id)
104 sg1 = _make_group('initial', parent_id=self.g1.group_id)
101 105
102 106 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
103 107 self.assertTrue(self.__check_path('test1', 'after'))
104 self.assertEqual(Group.get_by_group_name('test1/initial'), None)
105
108 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
106 109
107 110 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
108 111 self.assertTrue(self.__check_path('test3', 'after'))
109 self.assertEqual(Group.get_by_group_name('test3/initial'), None)
110
112 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
111 113
112 114 new_sg1 = self.__update_group(sg1.group_id, 'hello')
113 115 self.assertTrue(self.__check_path('hello'))
114 116
115 self.assertEqual(Group.get_by_group_name('hello'), new_sg1)
116
117
117 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
118 118
119 119 def test_subgrouping_with_repo(self):
120 120
121 g1 = self.__make_group('g1')
122 g2 = self.__make_group('g2')
121 g1 = _make_group('g1')
122 g2 = _make_group('g2')
123 123
124 124 # create new repo
125 125 form_data = dict(repo_name='john',
@@ -143,7 +143,6 b' class TestReposGroups(unittest.TestCase)'
143 143 RepoModel().update(r.repo_name, form_data)
144 144 self.assertEqual(r.repo_name, 'g1/john')
145 145
146
147 146 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
148 147 self.assertTrue(self.__check_path('g2', 'g1'))
149 148
@@ -151,3 +150,406 b' class TestReposGroups(unittest.TestCase)'
151 150 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
152 151
153 152
153 class TestUser(unittest.TestCase):
154 def __init__(self, methodName='runTest'):
155 Session.remove()
156 super(TestUser, self).__init__(methodName=methodName)
157
158 def test_create_and_remove(self):
159 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
160 email=u'u232@rhodecode.org',
161 name=u'u1', lastname=u'u1')
162 Session.commit()
163 self.assertEqual(User.get_by_username(u'test_user'), usr)
164
165 # make users group
166 users_group = UsersGroupModel().create('some_example_group')
167 Session.commit()
168
169 UsersGroupModel().add_user_to_group(users_group, usr)
170 Session.commit()
171
172 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
173 self.assertEqual(UsersGroupMember.query().count(), 1)
174 UserModel().delete(usr.user_id)
175 Session.commit()
176
177 self.assertEqual(UsersGroupMember.query().all(), [])
178
179
180 class TestNotifications(unittest.TestCase):
181
182 def __init__(self, methodName='runTest'):
183 Session.remove()
184 self.u1 = UserModel().create_or_update(username=u'u1',
185 password=u'qweqwe',
186 email=u'u1@rhodecode.org',
187 name=u'u1', lastname=u'u1')
188 Session.commit()
189 self.u1 = self.u1.user_id
190
191 self.u2 = UserModel().create_or_update(username=u'u2',
192 password=u'qweqwe',
193 email=u'u2@rhodecode.org',
194 name=u'u2', lastname=u'u3')
195 Session.commit()
196 self.u2 = self.u2.user_id
197
198 self.u3 = UserModel().create_or_update(username=u'u3',
199 password=u'qweqwe',
200 email=u'u3@rhodecode.org',
201 name=u'u3', lastname=u'u3')
202 Session.commit()
203 self.u3 = self.u3.user_id
204
205 super(TestNotifications, self).__init__(methodName=methodName)
206
207 def _clean_notifications(self):
208 for n in Notification.query().all():
209 Session.delete(n)
210
211 Session.commit()
212 self.assertEqual(Notification.query().all(), [])
213
214 def tearDown(self):
215 self._clean_notifications()
216
217 def test_create_notification(self):
218 self.assertEqual([], Notification.query().all())
219 self.assertEqual([], UserNotification.query().all())
220
221 usrs = [self.u1, self.u2]
222 notification = NotificationModel().create(created_by=self.u1,
223 subject=u'subj', body=u'hi there',
224 recipients=usrs)
225 Session.commit()
226 u1 = User.get(self.u1)
227 u2 = User.get(self.u2)
228 u3 = User.get(self.u3)
229 notifications = Notification.query().all()
230 self.assertEqual(len(notifications), 1)
231
232 unotification = UserNotification.query()\
233 .filter(UserNotification.notification == notification).all()
234
235 self.assertEqual(notifications[0].recipients, [u1, u2])
236 self.assertEqual(notification.notification_id,
237 notifications[0].notification_id)
238 self.assertEqual(len(unotification), len(usrs))
239 self.assertEqual([x.user.user_id for x in unotification], usrs)
240
241 def test_user_notifications(self):
242 self.assertEqual([], Notification.query().all())
243 self.assertEqual([], UserNotification.query().all())
244
245 notification1 = NotificationModel().create(created_by=self.u1,
246 subject=u'subj', body=u'hi there1',
247 recipients=[self.u3])
248 Session.commit()
249 notification2 = NotificationModel().create(created_by=self.u1,
250 subject=u'subj', body=u'hi there2',
251 recipients=[self.u3])
252 Session.commit()
253 u3 = Session.query(User).get(self.u3)
254
255 self.assertEqual(sorted([x.notification for x in u3.notifications]),
256 sorted([notification2, notification1]))
257
258 def test_delete_notifications(self):
259 self.assertEqual([], Notification.query().all())
260 self.assertEqual([], UserNotification.query().all())
261
262 notification = NotificationModel().create(created_by=self.u1,
263 subject=u'title', body=u'hi there3',
264 recipients=[self.u3, self.u1, self.u2])
265 Session.commit()
266 notifications = Notification.query().all()
267 self.assertTrue(notification in notifications)
268
269 Notification.delete(notification.notification_id)
270 Session.commit()
271
272 notifications = Notification.query().all()
273 self.assertFalse(notification in notifications)
274
275 un = UserNotification.query().filter(UserNotification.notification
276 == notification).all()
277 self.assertEqual(un, [])
278
279 def test_delete_association(self):
280
281 self.assertEqual([], Notification.query().all())
282 self.assertEqual([], UserNotification.query().all())
283
284 notification = NotificationModel().create(created_by=self.u1,
285 subject=u'title', body=u'hi there3',
286 recipients=[self.u3, self.u1, self.u2])
287 Session.commit()
288
289 unotification = UserNotification.query()\
290 .filter(UserNotification.notification ==
291 notification)\
292 .filter(UserNotification.user_id == self.u3)\
293 .scalar()
294
295 self.assertEqual(unotification.user_id, self.u3)
296
297 NotificationModel().delete(self.u3,
298 notification.notification_id)
299 Session.commit()
300
301 u3notification = UserNotification.query()\
302 .filter(UserNotification.notification ==
303 notification)\
304 .filter(UserNotification.user_id == self.u3)\
305 .scalar()
306
307 self.assertEqual(u3notification, None)
308
309 # notification object is still there
310 self.assertEqual(Notification.query().all(), [notification])
311
312 #u1 and u2 still have assignments
313 u1notification = UserNotification.query()\
314 .filter(UserNotification.notification ==
315 notification)\
316 .filter(UserNotification.user_id == self.u1)\
317 .scalar()
318 self.assertNotEqual(u1notification, None)
319 u2notification = UserNotification.query()\
320 .filter(UserNotification.notification ==
321 notification)\
322 .filter(UserNotification.user_id == self.u2)\
323 .scalar()
324 self.assertNotEqual(u2notification, None)
325
326 def test_notification_counter(self):
327 self._clean_notifications()
328 self.assertEqual([], Notification.query().all())
329 self.assertEqual([], UserNotification.query().all())
330
331 NotificationModel().create(created_by=self.u1,
332 subject=u'title', body=u'hi there_delete',
333 recipients=[self.u3, self.u1])
334 Session.commit()
335
336 self.assertEqual(NotificationModel()
337 .get_unread_cnt_for_user(self.u1), 1)
338 self.assertEqual(NotificationModel()
339 .get_unread_cnt_for_user(self.u2), 0)
340 self.assertEqual(NotificationModel()
341 .get_unread_cnt_for_user(self.u3), 1)
342
343 notification = NotificationModel().create(created_by=self.u1,
344 subject=u'title', body=u'hi there3',
345 recipients=[self.u3, self.u1, self.u2])
346 Session.commit()
347
348 self.assertEqual(NotificationModel()
349 .get_unread_cnt_for_user(self.u1), 2)
350 self.assertEqual(NotificationModel()
351 .get_unread_cnt_for_user(self.u2), 1)
352 self.assertEqual(NotificationModel()
353 .get_unread_cnt_for_user(self.u3), 2)
354
355
356 class TestUsers(unittest.TestCase):
357
358 def __init__(self, methodName='runTest'):
359 super(TestUsers, self).__init__(methodName=methodName)
360
361 def setUp(self):
362 self.u1 = UserModel().create_or_update(username=u'u1',
363 password=u'qweqwe',
364 email=u'u1@rhodecode.org',
365 name=u'u1', lastname=u'u1')
366
367 def tearDown(self):
368 perm = Permission.query().all()
369 for p in perm:
370 UserModel().revoke_perm(self.u1, p)
371
372 UserModel().delete(self.u1)
373 Session.commit()
374
375 def test_add_perm(self):
376 perm = Permission.query().all()[0]
377 UserModel().grant_perm(self.u1, perm)
378 Session.commit()
379 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
380
381 def test_has_perm(self):
382 perm = Permission.query().all()
383 for p in perm:
384 has_p = UserModel().has_perm(self.u1, p)
385 self.assertEqual(False, has_p)
386
387 def test_revoke_perm(self):
388 perm = Permission.query().all()[0]
389 UserModel().grant_perm(self.u1, perm)
390 Session.commit()
391 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
392
393 #revoke
394 UserModel().revoke_perm(self.u1, perm)
395 Session.commit()
396 self.assertEqual(UserModel().has_perm(self.u1, perm), False)
397
398
399 class TestPermissions(unittest.TestCase):
400 def __init__(self, methodName='runTest'):
401 super(TestPermissions, self).__init__(methodName=methodName)
402
403 def setUp(self):
404 self.u1 = UserModel().create_or_update(
405 username=u'u1', password=u'qweqwe',
406 email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
407 )
408 self.a1 = UserModel().create_or_update(
409 username=u'a1', password=u'qweqwe',
410 email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
411 )
412 Session.commit()
413
414 def tearDown(self):
415 UserModel().delete(self.u1)
416 UserModel().delete(self.a1)
417 if hasattr(self, 'g1'):
418 ReposGroupModel().delete(self.g1.group_id)
419 if hasattr(self, 'g2'):
420 ReposGroupModel().delete(self.g2.group_id)
421
422 if hasattr(self, 'ug1'):
423 UsersGroupModel().delete(self.ug1, force=True)
424
425 Session.commit()
426
427 def test_default_perms_set(self):
428 u1_auth = AuthUser(user_id=self.u1.user_id)
429 perms = {
430 'repositories_groups': {},
431 'global': set([u'hg.create.repository', u'repository.read',
432 u'hg.register.manual_activate']),
433 'repositories': {u'vcs_test_hg': u'repository.read'}
434 }
435 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
436 perms['repositories'][HG_REPO])
437 new_perm = 'repository.write'
438 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
439 Session.commit()
440
441 u1_auth = AuthUser(user_id=self.u1.user_id)
442 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], new_perm)
443
444 def test_default_admin_perms_set(self):
445 a1_auth = AuthUser(user_id=self.a1.user_id)
446 perms = {
447 'repositories_groups': {},
448 'global': set([u'hg.admin']),
449 'repositories': {u'vcs_test_hg': u'repository.admin'}
450 }
451 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
452 perms['repositories'][HG_REPO])
453 new_perm = 'repository.write'
454 RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, perm=new_perm)
455 Session.commit()
456 # cannot really downgrade admins permissions !? they still get's set as
457 # admin !
458 u1_auth = AuthUser(user_id=self.a1.user_id)
459 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
460 perms['repositories'][HG_REPO])
461
462 def test_default_group_perms(self):
463 self.g1 = _make_group('test1', skip_if_exists=True)
464 self.g2 = _make_group('test2', skip_if_exists=True)
465 u1_auth = AuthUser(user_id=self.u1.user_id)
466 perms = {
467 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
468 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
469 'repositories': {u'vcs_test_hg': u'repository.read'}
470 }
471 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
472 perms['repositories'][HG_REPO])
473 self.assertEqual(u1_auth.permissions['repositories_groups'],
474 perms['repositories_groups'])
475
476 def test_default_admin_group_perms(self):
477 self.g1 = _make_group('test1', skip_if_exists=True)
478 self.g2 = _make_group('test2', skip_if_exists=True)
479 a1_auth = AuthUser(user_id=self.a1.user_id)
480 perms = {
481 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
482 'global': set(['hg.admin']),
483 'repositories': {u'vcs_test_hg': 'repository.admin'}
484 }
485
486 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
487 perms['repositories'][HG_REPO])
488 self.assertEqual(a1_auth.permissions['repositories_groups'],
489 perms['repositories_groups'])
490
491 def test_propagated_permission_from_users_group(self):
492 # make group
493 self.ug1 = UsersGroupModel().create('G1')
494 # add user to group
495 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
496
497 # set permission to lower
498 new_perm = 'repository.none'
499 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
500 Session.commit()
501 u1_auth = AuthUser(user_id=self.u1.user_id)
502 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
503 new_perm)
504
505 # grant perm for group this should override permission from user
506 new_perm = 'repository.write'
507 RepoModel().grant_users_group_permission(repo=HG_REPO,
508 group_name=self.ug1,
509 perm=new_perm)
510 # check perms
511 u1_auth = AuthUser(user_id=self.u1.user_id)
512 perms = {
513 'repositories_groups': {},
514 'global': set([u'hg.create.repository', u'repository.read',
515 u'hg.register.manual_activate']),
516 'repositories': {u'vcs_test_hg': u'repository.read'}
517 }
518 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
519 new_perm)
520 self.assertEqual(u1_auth.permissions['repositories_groups'],
521 perms['repositories_groups'])
522
523 def test_propagated_permission_from_users_group_lower_weight(self):
524 # make group
525 self.ug1 = UsersGroupModel().create('G1')
526 # add user to group
527 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
528
529 # set permission to lower
530 new_perm_h = 'repository.write'
531 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
532 perm=new_perm_h)
533 Session.commit()
534 u1_auth = AuthUser(user_id=self.u1.user_id)
535 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
536 new_perm_h)
537
538 # grant perm for group this should NOT override permission from user
539 # since it's lower than granted
540 new_perm_l = 'repository.read'
541 RepoModel().grant_users_group_permission(repo=HG_REPO,
542 group_name=self.ug1,
543 perm=new_perm_l)
544 # check perms
545 u1_auth = AuthUser(user_id=self.u1.user_id)
546 perms = {
547 'repositories_groups': {},
548 'global': set([u'hg.create.repository', u'repository.read',
549 u'hg.register.manual_activate']),
550 'repositories': {u'vcs_test_hg': u'repository.write'}
551 }
552 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
553 new_perm_h)
554 self.assertEqual(u1_auth.permissions['repositories_groups'],
555 perms['repositories_groups'])
@@ -7,7 +7,7 b''
7 7
8 8 :created_on: Dec 11, 2010
9 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 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
@@ -23,11 +23,11 b''
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 import os
27 26 import logging
28 27
29 28 from rhodecode.config.environment import load_environment
30 29 from rhodecode.lib.db_manage import DbManage
30 from rhodecode.model.meta import Session
31 31
32 32
33 33 log = logging.getLogger(__name__)
@@ -45,5 +45,5 b' def setup_app(command, conf, vars):'
45 45 dbmanage.admin_prompt()
46 46 dbmanage.create_permissions()
47 47 dbmanage.populate_default_permissions()
48
48 Session.commit()
49 49 load_environment(conf.global_conf, conf.local_conf, initial=True)
@@ -11,6 +11,8 b' verbosity=2'
11 11 with-pylons=test.ini
12 12 detailed-errors=1
13 13 nologcapture=1
14 #pdb=1
15 #pdb-failures=1
14 16
15 17 # Babel configuration
16 18 [compile_catalog]
@@ -7,20 +7,21 b' from rhodecode import requirements'
7 7 if __py_version__ < (2, 5):
8 8 raise Exception('RhodeCode requires python 2.5 or later')
9 9
10
11 10 dependency_links = [
12 11 ]
13 12
14 classifiers = ['Development Status :: 5 - Production/Stable',
15 'Environment :: Web Environment',
16 'Framework :: Pylons',
17 'Intended Audience :: Developers',
18 'License :: OSI Approved :: GNU General Public License (GPL)',
19 'Operating System :: OS Independent',
20 'Programming Language :: Python',
21 'Programming Language :: Python :: 2.5',
22 'Programming Language :: Python :: 2.6',
23 'Programming Language :: Python :: 2.7', ]
13 classifiers = [
14 'Development Status :: 5 - Production/Stable',
15 'Environment :: Web Environment',
16 'Framework :: Pylons',
17 'Intended Audience :: Developers',
18 'License :: OSI Approved :: GNU General Public License (GPL)',
19 'Operating System :: OS Independent',
20 'Programming Language :: Python',
21 'Programming Language :: Python :: 2.5',
22 'Programming Language :: Python :: 2.6',
23 'Programming Language :: Python :: 2.7',
24 ]
24 25
25 26
26 27 # additional files from project that goes somewhere in the filesystem
@@ -51,6 +51,8 b' cut_off_limit = 256000'
51 51 force_https = false
52 52 commit_parse_limit = 25
53 53 use_gravatar = true
54 container_auth_enabled = false
55 proxypass_auth_enabled = false
54 56
55 57 ####################################
56 58 ### CELERY CONFIG ####
@@ -87,22 +89,28 b' beaker.cache.lock_dir=/tmp/data/cache/lo'
87 89 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
88 90
89 91 beaker.cache.super_short_term.type=memory
90 beaker.cache.super_short_term.expire=10
92 beaker.cache.super_short_term.expire=1
93 beaker.cache.super_short_term.key_length = 256
91 94
92 95 beaker.cache.short_term.type=memory
93 96 beaker.cache.short_term.expire=60
97 beaker.cache.short_term.key_length = 256
94 98
95 99 beaker.cache.long_term.type=memory
96 100 beaker.cache.long_term.expire=36000
101 beaker.cache.long_term.key_length = 256
97 102
98 103 beaker.cache.sql_cache_short.type=memory
99 beaker.cache.sql_cache_short.expire=10
104 beaker.cache.sql_cache_short.expire=1
105 beaker.cache.sql_cache_short.key_length = 256
100 106
101 107 beaker.cache.sql_cache_med.type=memory
102 108 beaker.cache.sql_cache_med.expire=360
109 beaker.cache.sql_cache_med.key_length = 256
103 110
104 111 beaker.cache.sql_cache_long.type=file
105 112 beaker.cache.sql_cache_long.expire=3600
113 beaker.cache.sql_cache_long.key_length = 256
106 114
107 115 ####################################
108 116 ### BEAKER SESSION ####
@@ -143,7 +151,7 b' logview.pylons.util = #eee'
143 151 #########################################################
144 152 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
145 153 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
146 #sqlalchemy.db1.echo = False
154 #sqlalchemy.db1.echo = false
147 155 #sqlalchemy.db1.pool_recycle = 3600
148 156 sqlalchemy.convert_unicode = true
149 157
@@ -1,10 +0,0 b''
1 .. _indexapi:
2
3 API Reference
4 =============
5
6 .. toctree::
7 :maxdepth: 3
8
9 models
10 api No newline at end of file
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
@@ -1,13 +0,0 b''
1 .. _screenshots:
2
3 .. figure:: images/screenshot1_main_page.png
4
5 Main page of RhodeCode
6
7 .. figure:: images/screenshot2_summary_page.png
8
9 Summary page
10
11 .. figure:: images/screenshot3_changelog_page.png
12
13 Changelog with DAG graph No newline at end of file
@@ -1,11 +0,0 b''
1 .. _api_key_access:
2
3 Access to RhodeCode via API KEY
4 ===============================
5
6 Starting from version 1.2 rss/atom feeds and journal feeds
7 can be accessed via **api_key**. This unique key is automatically generated for
8 each user in RhodeCode application. Using this key it is possible to access
9 feeds without having to log in. When user changes his password a new API KEY
10 is generated for him automatically. You can check your API KEY in account
11 settings page. No newline at end of file
@@ -1,119 +0,0 b''
1 div.diffblock {
2 overflow: auto;
3 padding: 0px;
4 border: 1px solid #ccc;
5 background: #f8f8f8;
6 font-size: 100%;
7 line-height: 100%;
8 /* new */
9 line-height: 125%;
10 }
11 div.diffblock .code-header{
12 border-bottom: 1px solid #CCCCCC;
13 background: #EEEEEE;
14 padding:10px 0 10px 0;
15 }
16 div.diffblock .code-header div{
17 margin-left:25px;
18 font-weight: bold;
19 }
20 div.diffblock .code-body{
21 background: #FFFFFF;
22 }
23 div.diffblock pre.raw{
24 background: #FFFFFF;
25 color:#000000;
26 }
27
28 table.code-difftable{
29 border-collapse: collapse;
30 width: 99%;
31 }
32 table.code-difftable td:target *{
33 background: repeat scroll 0 0 #FFFFBE !important;
34 text-decoration: underline;
35 }
36
37 table.code-difftable td {
38 padding: 0 !important;
39 background: none !important;
40 border:0 !important;
41 }
42
43
44 .code-difftable .context{
45 background:none repeat scroll 0 0 #DDE7EF;
46 }
47 .code-difftable .add{
48 background:none repeat scroll 0 0 #DDFFDD;
49 }
50 .code-difftable .add ins{
51 background:none repeat scroll 0 0 #AAFFAA;
52 text-decoration:none;
53 }
54
55 .code-difftable .del{
56 background:none repeat scroll 0 0 #FFDDDD;
57 }
58 .code-difftable .del del{
59 background:none repeat scroll 0 0 #FFAAAA;
60 text-decoration:none;
61 }
62
63 .code-difftable .lineno{
64 background:none repeat scroll 0 0 #EEEEEE !important;
65 padding-left:2px;
66 padding-right:2px;
67 text-align:right;
68 width:30px;
69 -moz-user-select:none;
70 -webkit-user-select: none;
71 }
72 .code-difftable .new {
73 border-right: 1px solid #CCC !important;
74 }
75 .code-difftable .old {
76 border-right: 1px solid #CCC !important;
77 }
78 .code-difftable .lineno pre{
79 color:#747474 !important;
80 font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
81 letter-spacing:-1px;
82 text-align:right;
83 width:20px;
84 }
85 .code-difftable .lineno a{
86 font-weight: 700;
87 cursor: pointer;
88 }
89 .code-difftable .code td{
90 margin:0;
91 padding: 0;
92 }
93 .code-difftable .code pre{
94 margin:0;
95 padding:0;
96 }
97
98 .code {
99 display: block;
100 width: 100%;
101 }
102 .code-diff {
103 padding: 0px;
104 margin-top: 5px;
105 margin-bottom: 5px;
106 border-left: 2px solid #ccc;
107 }
108 .code-diff pre, .line pre {
109 padding: 3px;
110 margin: 0;
111 }
112 .lineno a {
113 text-decoration: none;
114 }
115
116 .line{
117 padding:0;
118 margin:0;
119 } No newline at end of file
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
@@ -1,50 +0,0 b''
1 /*
2 CSS Browser Selector v0.3.5 (Feb 05, 2010)
3 Rafael Lima (http://rafael.adm.br)
4 http://rafael.adm.br/css_browser_selector
5 License: http://creativecommons.org/licenses/by/2.5/
6 Contributors: http://rafael.adm.br/css_browser_selector#contributors
7 2. Set CSS attributes with the code of each browser/os you want to hack
8
9 Examples:
10
11 * html.gecko div#header { margin: 1em; }
12 * .opera #header { margin: 1.2em; }
13 * .ie .mylink { font-weight: bold; }
14 * .mac.ie .mylink { font-weight: bold; }
15 * .[os].[browser] .mylink { font-weight: bold; } -> without space between .[os] and .[browser]
16
17 Available OS Codes [os]:
18
19 * win - Microsoft Windows
20 * linux - Linux (x11 and linux)
21 * mac - Mac OS
22 * freebsd - FreeBSD
23 * ipod - iPod Touch
24 * iphone - iPhone
25 * webtv - WebTV
26 * mobile - J2ME Devices (ex: Opera mini)
27
28 Available Browser Codes [browser]:
29
30 * ie - Internet Explorer (All versions)
31 * ie8 - Internet Explorer 8.x
32 * ie7 - Internet Explorer 7.x
33 * ie6 - Internet Explorer 6.x
34 * ie5 - Internet Explorer 5.x
35 * gecko - Mozilla, Firefox (all versions), Camino
36 * ff2 - Firefox 2
37 * ff3 - Firefox 3
38 * ff3_5 - Firefox 3.5 new
39 * opera - Opera (All versions)
40 * opera8 - Opera 8.x
41 * opera9 - Opera 9.x
42 * opera10 - Opera 10.x
43 * konqueror - Konqueror
44 * webkit or safari - Safari, NetNewsWire, OmniWeb, Shiira, Google Chrome
45 * safari3 - Safari 3.x
46 * chrome - Google Chrome
47 * iron - SRWare Iron new
48
49 */
50 function css_browser_selector(u){var ua = u.toLowerCase(),is=function(t){return ua.indexOf(t)>-1;},g='gecko',w='webkit',s='safari',o='opera',h=document.documentElement,b=[(!(/opera|webtv/i.test(ua))&&/msie\s(\d)/.test(ua))?('ie ie'+RegExp.$1):is('firefox/2')?g+' ff2':is('firefox/3.5')?g+' ff3 ff3_5':is('firefox/3')?g+' ff3':is('gecko/')?g:is('opera')?o+(/version\/(\d+)/.test(ua)?' '+o+RegExp.$1:(/opera(\s|\/)(\d+)/.test(ua)?' '+o+RegExp.$2:'')):is('konqueror')?'konqueror':is('chrome')?w+' chrome':is('iron')?w+' iron':is('applewebkit/')?w+' '+s+(/version\/(\d+)/.test(ua)?' '+s+RegExp.$1:''):is('mozilla/')?g:'',is('j2me')?'mobile':is('iphone')?'iphone':is('ipod')?'ipod':is('mac')?'mac':is('darwin')?'mac':is('webtv')?'webtv':is('win')?'win':is('freebsd')?'freebsd':(is('x11')||is('linux'))?'linux':'','js']; c = b.join(' '); h.className += ' '+c; return c;}; css_browser_selector(navigator.userAgent);
General Comments 0
You need to be logged in to leave comments. Login now