##// END OF EJS Templates
bumped version, some spelling fixes
marcink -
r569:000b675e default
parent child Browse files
Show More
@@ -1,35 +1,35 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 # Hg app, a web based mercurial repository managment based on pylons
3 # RhodeCode, a web based repository management based on pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 9, 2010
22 Hg app, a web based mercurial repository managment based on pylons
22 RhodeCode, a web based repository management based on pylons
23 23 versioning implementation: http://semver.org/
24 24 @author: marcink
25 25 """
26 26
27 VERSION = (0, 8, 5, 'beta')
27 VERSION = (1, 0, 0, 'rc1')
28 28
29 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 30
31 31 def get_version():
32 32 """
33 33 Returns shorter version (digit parts only) as string.
34 34 """
35 35 return '.'.join((str(each) for each in VERSION[:3]))
@@ -1,270 +1,270 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 # database managment for hg app
3 # database management for RhodeCode
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 10, 2010
23 database managment and creation for hg app
23 database management and creation for RhodeCode
24 24 @author: marcink
25 25 """
26 26
27 27 from os.path import dirname as dn, join as jn
28 28 import os
29 29 import sys
30 30 import uuid
31 31
32 32 from rhodecode.lib.auth import get_crypt_password
33 33 from rhodecode.lib.utils import ask_ok
34 34 from rhodecode.model import init_model
35 35 from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
36 36 UserToPerm
37 37 from rhodecode.model import meta
38 38 from sqlalchemy.engine import create_engine
39 39 import logging
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43 class DbManage(object):
44 44 def __init__(self, log_sql, dbname, root, tests=False):
45 45 self.dbname = dbname
46 46 self.tests = tests
47 47 self.root = root
48 48 dburi = 'sqlite:////%s' % jn(self.root, self.dbname)
49 49 engine = create_engine(dburi, echo=log_sql)
50 50 init_model(engine)
51 51 self.sa = meta.Session
52 52 self.db_exists = False
53 53
54 54 def check_for_db(self, override):
55 55 db_path = jn(self.root, self.dbname)
56 56 log.info('checking for existing db in %s', db_path)
57 57 if os.path.isfile(db_path):
58 58 self.db_exists = True
59 59 log.info('database exist')
60 60 if not override:
61 61 raise Exception('database already exists')
62 62
63 63 def create_tables(self, override=False):
64 64 """
65 65 Create a auth database
66 66 """
67 67 self.check_for_db(override)
68 68 if override:
69 69 log.info("database exist and it's going to be destroyed")
70 70 if self.tests:
71 71 destroy = True
72 72 else:
73 73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
74 74 if not destroy:
75 75 sys.exit()
76 76 if self.db_exists and destroy:
77 77 os.remove(jn(self.root, self.dbname))
78 78 checkfirst = not override
79 79 meta.Base.metadata.create_all(checkfirst=checkfirst)
80 80 log.info('Created tables for %s', self.dbname)
81 81
82 82 def admin_prompt(self):
83 83 if not self.tests:
84 84 import getpass
85 85 username = raw_input('Specify admin username:')
86 86 password = getpass.getpass('Specify admin password:')
87 87 confirm = getpass.getpass('Confirm password:')
88 88 if password != confirm:
89 89 log.error('passwords mismatch')
90 90 sys.exit()
91 91 email = raw_input('Specify admin email:')
92 92 self.create_user(username, password, email, True)
93 93 else:
94 94 log.info('creating admin and regular test users')
95 95 self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
96 96 self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
97 97 self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
98 98
99 99
100 100
101 101 def config_prompt(self, test_repo_path=''):
102 102 log.info('Setting up repositories config')
103 103
104 104 if not self.tests and not test_repo_path:
105 105 path = raw_input('Specify valid full path to your repositories'
106 106 ' you can change this later in application settings:')
107 107 else:
108 108 path = test_repo_path
109 109
110 110 if not os.path.isdir(path):
111 111 log.error('You entered wrong path: %s', path)
112 112 sys.exit()
113 113
114 114 hooks1 = RhodeCodeUi()
115 115 hooks1.ui_section = 'hooks'
116 116 hooks1.ui_key = 'changegroup.update'
117 117 hooks1.ui_value = 'hg update >&2'
118 118 hooks1.ui_active = False
119 119
120 120 hooks2 = RhodeCodeUi()
121 121 hooks2.ui_section = 'hooks'
122 122 hooks2.ui_key = 'changegroup.repo_size'
123 123 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
124 124
125 125 web1 = RhodeCodeUi()
126 126 web1.ui_section = 'web'
127 127 web1.ui_key = 'push_ssl'
128 128 web1.ui_value = 'false'
129 129
130 130 web2 = RhodeCodeUi()
131 131 web2.ui_section = 'web'
132 132 web2.ui_key = 'allow_archive'
133 133 web2.ui_value = 'gz zip bz2'
134 134
135 135 web3 = RhodeCodeUi()
136 136 web3.ui_section = 'web'
137 137 web3.ui_key = 'allow_push'
138 138 web3.ui_value = '*'
139 139
140 140 web4 = RhodeCodeUi()
141 141 web4.ui_section = 'web'
142 142 web4.ui_key = 'baseurl'
143 143 web4.ui_value = '/'
144 144
145 145 paths = RhodeCodeUi()
146 146 paths.ui_section = 'paths'
147 147 paths.ui_key = '/'
148 148 paths.ui_value = os.path.join(path, '*')
149 149
150 150
151 151 hgsettings1 = RhodeCodeSettings()
152 152
153 153 hgsettings1.app_settings_name = 'realm'
154 154 hgsettings1.app_settings_value = 'RhodeCode authentication'
155 155
156 156 hgsettings2 = RhodeCodeSettings()
157 157 hgsettings2.app_settings_name = 'title'
158 158 hgsettings2.app_settings_value = 'RhodeCode'
159 159
160 160 try:
161 161 self.sa.add(hooks1)
162 162 self.sa.add(hooks2)
163 163 self.sa.add(web1)
164 164 self.sa.add(web2)
165 165 self.sa.add(web3)
166 166 self.sa.add(web4)
167 167 self.sa.add(paths)
168 168 self.sa.add(hgsettings1)
169 169 self.sa.add(hgsettings2)
170 170 self.sa.commit()
171 171 except:
172 172 self.sa.rollback()
173 173 raise
174 174 log.info('created ui config')
175 175
176 176 def create_user(self, username, password, email='', admin=False):
177 177 log.info('creating administrator user %s', username)
178 178 new_user = User()
179 179 new_user.username = username
180 180 new_user.password = get_crypt_password(password)
181 181 new_user.name = 'RhodeCode'
182 182 new_user.lastname = 'Admin'
183 183 new_user.email = email
184 184 new_user.admin = admin
185 185 new_user.active = True
186 186
187 187 try:
188 188 self.sa.add(new_user)
189 189 self.sa.commit()
190 190 except:
191 191 self.sa.rollback()
192 192 raise
193 193
194 194 def create_default_user(self):
195 195 log.info('creating default user')
196 196 #create default user for handling default permissions.
197 197 def_user = User()
198 198 def_user.username = 'default'
199 199 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
200 200 def_user.name = 'default'
201 201 def_user.lastname = 'default'
202 202 def_user.email = 'default@default.com'
203 203 def_user.admin = False
204 204 def_user.active = False
205 205 try:
206 206 self.sa.add(def_user)
207 207 self.sa.commit()
208 208 except:
209 209 self.sa.rollback()
210 210 raise
211 211
212 212 def create_permissions(self):
213 213 #module.(access|create|change|delete)_[name]
214 214 #module.(read|write|owner)
215 215 perms = [('repository.none', 'Repository no access'),
216 216 ('repository.read', 'Repository read access'),
217 217 ('repository.write', 'Repository write access'),
218 218 ('repository.admin', 'Repository admin access'),
219 219 ('hg.admin', 'Hg Administrator'),
220 220 ('hg.create.repository', 'Repository create'),
221 221 ('hg.create.none', 'Repository creation disabled'),
222 222 ('hg.register.none', 'Register disabled'),
223 223 ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'),
224 224 ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'),
225 225 ]
226 226
227 227 for p in perms:
228 228 new_perm = Permission()
229 229 new_perm.permission_name = p[0]
230 230 new_perm.permission_longname = p[1]
231 231 try:
232 232 self.sa.add(new_perm)
233 233 self.sa.commit()
234 234 except:
235 235 self.sa.rollback()
236 236 raise
237 237
238 238 def populate_default_permissions(self):
239 239 log.info('creating default user permissions')
240 240
241 241 default_user = self.sa.query(User)\
242 242 .filter(User.username == 'default').scalar()
243 243
244 244 reg_perm = UserToPerm()
245 245 reg_perm.user = default_user
246 246 reg_perm.permission = self.sa.query(Permission)\
247 247 .filter(Permission.permission_name == 'hg.register.manual_activate')\
248 248 .scalar()
249 249
250 250 create_repo_perm = UserToPerm()
251 251 create_repo_perm.user = default_user
252 252 create_repo_perm.permission = self.sa.query(Permission)\
253 253 .filter(Permission.permission_name == 'hg.create.repository')\
254 254 .scalar()
255 255
256 256 default_repo_perm = UserToPerm()
257 257 default_repo_perm.user = default_user
258 258 default_repo_perm.permission = self.sa.query(Permission)\
259 259 .filter(Permission.permission_name == 'repository.read')\
260 260 .scalar()
261 261
262 262 try:
263 263 self.sa.add(reg_perm)
264 264 self.sa.add(create_repo_perm)
265 265 self.sa.add(default_repo_perm)
266 266 self.sa.commit()
267 267 except:
268 268 self.sa.rollback()
269 269 raise
270 270
@@ -1,490 +1,490 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 # Utilities for hg app
3 # Utilities for RhodeCode
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19
20 20 """
21 21 Created on April 18, 2010
22 Utilities for hg app
22 Utilities for RhodeCode
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from rhodecode.model import meta
29 29 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, UserLog
30 30 from vcs.backends.base import BaseChangeset
31 31 from vcs.utils.lazy import LazyProperty
32 32 import logging
33 33 import datetime
34 34 import os
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 def get_repo_slug(request):
40 40 return request.environ['pylons.routes_dict'].get('repo_name')
41 41
42 42 def is_mercurial(environ):
43 43 """
44 44 Returns True if request's target is mercurial server - header
45 45 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
46 46 """
47 47 http_accept = environ.get('HTTP_ACCEPT')
48 48 if http_accept and http_accept.startswith('application/mercurial'):
49 49 return True
50 50 return False
51 51
52 52 def action_logger(user, action, repo, ipaddr, sa=None):
53 53 """
54 54 Action logger for various action made by users
55 55 """
56 56
57 57 if not sa:
58 58 sa = meta.Session
59 59
60 60 try:
61 61 if hasattr(user, 'user_id'):
62 62 user_id = user.user_id
63 63 elif isinstance(user, basestring):
64 64 user_id = sa.query(User).filter(User.username == user).one()
65 65 else:
66 66 raise Exception('You have to provide user object or username')
67 67
68 68 repo_name = repo.lstrip('/')
69 69 user_log = UserLog()
70 70 user_log.user_id = user_id
71 71 user_log.action = action
72 72 user_log.repository_name = repo_name
73 73 user_log.repository = sa.query(Repository)\
74 74 .filter(Repository.repo_name == repo_name).one()
75 75 user_log.action_date = datetime.datetime.now()
76 76 user_log.user_ip = ipaddr
77 77 sa.add(user_log)
78 78 sa.commit()
79 79 log.info('Adding user %s, action %s on %s',
80 80 user.username, action, repo)
81 81 except Exception, e:
82 82 raise
83 83 sa.rollback()
84 84 log.error('could not log user action:%s', str(e))
85 85
86 86 def check_repo_dir(paths):
87 87 repos_path = paths[0][1].split('/')
88 88 if repos_path[-1] in ['*', '**']:
89 89 repos_path = repos_path[:-1]
90 90 if repos_path[0] != '/':
91 91 repos_path[0] = '/'
92 92 if not os.path.isdir(os.path.join(*repos_path)):
93 93 raise Exception('Not a valid repository in %s' % paths[0][1])
94 94
95 95 def check_repo_fast(repo_name, base_path):
96 96 if os.path.isdir(os.path.join(base_path, repo_name)):return False
97 97 return True
98 98
99 99 def check_repo(repo_name, base_path, verify=True):
100 100
101 101 repo_path = os.path.join(base_path, repo_name)
102 102
103 103 try:
104 104 if not check_repo_fast(repo_name, base_path):
105 105 return False
106 106 r = hg.repository(ui.ui(), repo_path)
107 107 if verify:
108 108 hg.verify(r)
109 109 #here we hnow that repo exists it was verified
110 110 log.info('%s repo is already created', repo_name)
111 111 return False
112 112 except RepoError:
113 113 #it means that there is no valid repo there...
114 114 log.info('%s repo is free for creation', repo_name)
115 115 return True
116 116
117 117 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
118 118 while True:
119 119 ok = raw_input(prompt)
120 120 if ok in ('y', 'ye', 'yes'): return True
121 121 if ok in ('n', 'no', 'nop', 'nope'): return False
122 122 retries = retries - 1
123 123 if retries < 0: raise IOError
124 124 print complaint
125 125
126 126 @cache_region('super_short_term', 'cached_hg_ui')
127 127 def get_hg_ui_cached():
128 128 try:
129 129 sa = meta.Session
130 130 ret = sa.query(RhodeCodeUi).all()
131 131 finally:
132 132 meta.Session.remove()
133 133 return ret
134 134
135 135
136 136 def get_hg_settings():
137 137 try:
138 138 sa = meta.Session
139 139 ret = sa.query(RhodeCodeSettings).all()
140 140 finally:
141 141 meta.Session.remove()
142 142
143 143 if not ret:
144 144 raise Exception('Could not get application settings !')
145 145 settings = {}
146 146 for each in ret:
147 147 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
148 148
149 149 return settings
150 150
151 151 def get_hg_ui_settings():
152 152 try:
153 153 sa = meta.Session
154 154 ret = sa.query(RhodeCodeUi).all()
155 155 finally:
156 156 meta.Session.remove()
157 157
158 158 if not ret:
159 159 raise Exception('Could not get application ui settings !')
160 160 settings = {}
161 161 for each in ret:
162 162 k = each.ui_key
163 163 v = each.ui_value
164 164 if k == '/':
165 165 k = 'root_path'
166 166
167 167 if k.find('.') != -1:
168 168 k = k.replace('.', '_')
169 169
170 170 if each.ui_section == 'hooks':
171 171 v = each.ui_active
172 172
173 173 settings[each.ui_section + '_' + k] = v
174 174
175 175 return settings
176 176
177 177 #propagated from mercurial documentation
178 178 ui_sections = ['alias', 'auth',
179 179 'decode/encode', 'defaults',
180 180 'diff', 'email',
181 181 'extensions', 'format',
182 182 'merge-patterns', 'merge-tools',
183 183 'hooks', 'http_proxy',
184 184 'smtp', 'patch',
185 185 'paths', 'profiling',
186 186 'server', 'trusted',
187 187 'ui', 'web', ]
188 188
189 189 def make_ui(read_from='file', path=None, checkpaths=True):
190 190 """
191 191 A function that will read python rc files or database
192 192 and make an mercurial ui object from read options
193 193
194 194 @param path: path to mercurial config file
195 195 @param checkpaths: check the path
196 196 @param read_from: read from 'file' or 'db'
197 197 """
198 198
199 199 baseui = ui.ui()
200 200
201 201 if read_from == 'file':
202 202 if not os.path.isfile(path):
203 203 log.warning('Unable to read config file %s' % path)
204 204 return False
205 205 log.debug('reading hgrc from %s', path)
206 206 cfg = config.config()
207 207 cfg.read(path)
208 208 for section in ui_sections:
209 209 for k, v in cfg.items(section):
210 210 baseui.setconfig(section, k, v)
211 211 log.debug('settings ui from file[%s]%s:%s', section, k, v)
212 212 if checkpaths:check_repo_dir(cfg.items('paths'))
213 213
214 214
215 215 elif read_from == 'db':
216 216 hg_ui = get_hg_ui_cached()
217 217 for ui_ in hg_ui:
218 218 if ui_.ui_active:
219 219 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
220 220 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
221 221
222 222
223 223 return baseui
224 224
225 225
226 226 def set_rhodecode_config(config):
227 227 hgsettings = get_hg_settings()
228 228
229 229 for k, v in hgsettings.items():
230 230 config[k] = v
231 231
232 232 def invalidate_cache(name, *args):
233 233 """Invalidates given name cache"""
234 234
235 235 from beaker.cache import region_invalidate
236 236 log.info('INVALIDATING CACHE FOR %s', name)
237 237
238 238 """propagate our arguments to make sure invalidation works. First
239 239 argument has to be the name of cached func name give to cache decorator
240 240 without that the invalidation would not work"""
241 241 tmp = [name]
242 242 tmp.extend(args)
243 243 args = tuple(tmp)
244 244
245 245 if name == 'cached_repo_list':
246 246 from rhodecode.model.hg_model import _get_repos_cached
247 247 region_invalidate(_get_repos_cached, None, *args)
248 248
249 249 if name == 'full_changelog':
250 250 from rhodecode.model.hg_model import _full_changelog_cached
251 251 region_invalidate(_full_changelog_cached, None, *args)
252 252
253 253 class EmptyChangeset(BaseChangeset):
254 254 """
255 255 An dummy empty changeset.
256 256 """
257 257
258 258 revision = -1
259 259 message = ''
260 260 author = ''
261 261 date = ''
262 262 @LazyProperty
263 263 def raw_id(self):
264 264 """
265 265 Returns raw string identifing this changeset, useful for web
266 266 representation.
267 267 """
268 268 return '0' * 40
269 269
270 270 @LazyProperty
271 271 def short_id(self):
272 272 return self.raw_id[:12]
273 273
274 274 def get_file_changeset(self, path):
275 275 return self
276 276
277 277 def get_file_content(self, path):
278 278 return u''
279 279
280 280 def get_file_size(self, path):
281 281 return 0
282 282
283 283 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
284 284 """
285 285 maps all found repositories into db
286 286 """
287 287 from rhodecode.model.repo_model import RepoModel
288 288
289 289 sa = meta.Session
290 290 user = sa.query(User).filter(User.admin == True).first()
291 291
292 292 rm = RepoModel()
293 293
294 294 for name, repo in initial_repo_list.items():
295 295 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
296 296 log.info('repository %s not found creating default', name)
297 297
298 298 form_data = {
299 299 'repo_name':name,
300 300 'description':repo.description if repo.description != 'unknown' else \
301 301 'auto description for %s' % name,
302 302 'private':False
303 303 }
304 304 rm.create(form_data, user, just_db=True)
305 305
306 306
307 307 if remove_obsolete:
308 308 #remove from database those repositories that are not in the filesystem
309 309 for repo in sa.query(Repository).all():
310 310 if repo.repo_name not in initial_repo_list.keys():
311 311 sa.delete(repo)
312 312 sa.commit()
313 313
314 314
315 315 meta.Session.remove()
316 316
317 317 from UserDict import DictMixin
318 318
319 319 class OrderedDict(dict, DictMixin):
320 320
321 321 def __init__(self, *args, **kwds):
322 322 if len(args) > 1:
323 323 raise TypeError('expected at most 1 arguments, got %d' % len(args))
324 324 try:
325 325 self.__end
326 326 except AttributeError:
327 327 self.clear()
328 328 self.update(*args, **kwds)
329 329
330 330 def clear(self):
331 331 self.__end = end = []
332 332 end += [None, end, end] # sentinel node for doubly linked list
333 333 self.__map = {} # key --> [key, prev, next]
334 334 dict.clear(self)
335 335
336 336 def __setitem__(self, key, value):
337 337 if key not in self:
338 338 end = self.__end
339 339 curr = end[1]
340 340 curr[2] = end[1] = self.__map[key] = [key, curr, end]
341 341 dict.__setitem__(self, key, value)
342 342
343 343 def __delitem__(self, key):
344 344 dict.__delitem__(self, key)
345 345 key, prev, next = self.__map.pop(key)
346 346 prev[2] = next
347 347 next[1] = prev
348 348
349 349 def __iter__(self):
350 350 end = self.__end
351 351 curr = end[2]
352 352 while curr is not end:
353 353 yield curr[0]
354 354 curr = curr[2]
355 355
356 356 def __reversed__(self):
357 357 end = self.__end
358 358 curr = end[1]
359 359 while curr is not end:
360 360 yield curr[0]
361 361 curr = curr[1]
362 362
363 363 def popitem(self, last=True):
364 364 if not self:
365 365 raise KeyError('dictionary is empty')
366 366 if last:
367 367 key = reversed(self).next()
368 368 else:
369 369 key = iter(self).next()
370 370 value = self.pop(key)
371 371 return key, value
372 372
373 373 def __reduce__(self):
374 374 items = [[k, self[k]] for k in self]
375 375 tmp = self.__map, self.__end
376 376 del self.__map, self.__end
377 377 inst_dict = vars(self).copy()
378 378 self.__map, self.__end = tmp
379 379 if inst_dict:
380 380 return (self.__class__, (items,), inst_dict)
381 381 return self.__class__, (items,)
382 382
383 383 def keys(self):
384 384 return list(self)
385 385
386 386 setdefault = DictMixin.setdefault
387 387 update = DictMixin.update
388 388 pop = DictMixin.pop
389 389 values = DictMixin.values
390 390 items = DictMixin.items
391 391 iterkeys = DictMixin.iterkeys
392 392 itervalues = DictMixin.itervalues
393 393 iteritems = DictMixin.iteritems
394 394
395 395 def __repr__(self):
396 396 if not self:
397 397 return '%s()' % (self.__class__.__name__,)
398 398 return '%s(%r)' % (self.__class__.__name__, self.items())
399 399
400 400 def copy(self):
401 401 return self.__class__(self)
402 402
403 403 @classmethod
404 404 def fromkeys(cls, iterable, value=None):
405 405 d = cls()
406 406 for key in iterable:
407 407 d[key] = value
408 408 return d
409 409
410 410 def __eq__(self, other):
411 411 if isinstance(other, OrderedDict):
412 412 return len(self) == len(other) and self.items() == other.items()
413 413 return dict.__eq__(self, other)
414 414
415 415 def __ne__(self, other):
416 416 return not self == other
417 417
418 418
419 419 #===============================================================================
420 420 # TEST FUNCTIONS
421 421 #===============================================================================
422 422 def create_test_index(repo_location, full_index):
423 423 """Makes default test index
424 424 @param repo_location:
425 425 @param full_index:
426 426 """
427 427 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
428 428 from rhodecode.lib.pidlock import DaemonLock, LockHeld
429 429 from rhodecode.lib.indexers import IDX_LOCATION
430 430 import shutil
431 431
432 432 if os.path.exists(IDX_LOCATION):
433 433 shutil.rmtree(IDX_LOCATION)
434 434
435 435 try:
436 436 l = DaemonLock()
437 437 WhooshIndexingDaemon(repo_location=repo_location)\
438 438 .run(full_index=full_index)
439 439 l.release()
440 440 except LockHeld:
441 441 pass
442 442
443 443 def create_test_env(repos_test_path, config):
444 444 """Makes a fresh database and
445 445 install test repository into tmp dir
446 446 """
447 447 from rhodecode.lib.db_manage import DbManage
448 448 import tarfile
449 449 import shutil
450 450 from os.path import dirname as dn, join as jn, abspath
451 451
452 452 log = logging.getLogger('TestEnvCreator')
453 453 # create logger
454 454 log.setLevel(logging.DEBUG)
455 455 log.propagate = True
456 456 # create console handler and set level to debug
457 457 ch = logging.StreamHandler()
458 458 ch.setLevel(logging.DEBUG)
459 459
460 460 # create formatter
461 461 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
462 462
463 463 # add formatter to ch
464 464 ch.setFormatter(formatter)
465 465
466 466 # add ch to logger
467 467 log.addHandler(ch)
468 468
469 469 #PART ONE create db
470 470 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
471 471 log.debug('making test db %s', dbname)
472 472
473 473 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
474 474 tests=True)
475 475 dbmanage.create_tables(override=True)
476 476 dbmanage.config_prompt(repos_test_path)
477 477 dbmanage.create_default_user()
478 478 dbmanage.admin_prompt()
479 479 dbmanage.create_permissions()
480 480 dbmanage.populate_default_permissions()
481 481
482 482 #PART TWO make test repo
483 483 log.debug('making test vcs repo')
484 484 if os.path.isdir('/tmp/vcs_test'):
485 485 shutil.rmtree('/tmp/vcs_test')
486 486
487 487 cur_dir = dn(dn(abspath(__file__)))
488 488 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
489 489 tar.extractall('/tmp')
490 490 tar.close()
@@ -1,186 +1,186 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 # Model for hg app
3 # Model for RhodeCode
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 9, 2010
22 Model for hg app
22 Model for RhodeCode
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui
27 27 from mercurial.hgweb.hgwebdir_mod import findrepos
28 28 from pylons.i18n.translation import _
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib.utils import invalidate_cache
31 31 from rhodecode.lib.auth import HasRepoPermissionAny
32 32 from rhodecode.model import meta
33 33 from rhodecode.model.db import Repository, User
34 34 from sqlalchemy.orm import joinedload
35 35 from vcs.exceptions import RepositoryError, VCSError
36 36 import logging
37 37 import os
38 38 import sys
39 39 log = logging.getLogger(__name__)
40 40
41 41 try:
42 42 from vcs.backends.hg import MercurialRepository
43 43 except ImportError:
44 44 sys.stderr.write('You have to import vcs module')
45 45 raise Exception('Unable to import vcs')
46 46
47 47 def _get_repos_cached_initial(app_globals, initial):
48 48 """return cached dict with repos
49 49 """
50 50 g = app_globals
51 51 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial)
52 52
53 53 @cache_region('long_term', 'cached_repo_list')
54 54 def _get_repos_cached():
55 55 """return cached dict with repos
56 56 """
57 57 log.info('getting all repositories list')
58 58 from pylons import app_globals as g
59 59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
60 60
61 61 @cache_region('super_short_term', 'cached_repos_switcher_list')
62 62 def _get_repos_switcher_cached(cached_repo_list):
63 63 repos_lst = []
64 64 for repo in [x for x in cached_repo_list.values()]:
65 65 if HasRepoPermissionAny('repository.write', 'repository.read',
66 66 'repository.admin')(repo.name, 'main page check'):
67 67 repos_lst.append((repo.name, repo.dbrepo.private,))
68 68
69 69 return sorted(repos_lst, key=lambda k:k[0].lower())
70 70
71 71 @cache_region('long_term', 'full_changelog')
72 72 def _full_changelog_cached(repo_name):
73 73 log.info('getting full changelog for %s', repo_name)
74 74 return list(reversed(list(HgModel().get_repo(repo_name))))
75 75
76 76 class HgModel(object):
77 77 """Mercurial Model
78 78 """
79 79
80 80 def __init__(self):
81 81 pass
82 82
83 83 @staticmethod
84 84 def repo_scan(repos_prefix, repos_path, baseui, initial=False):
85 85 """
86 86 Listing of repositories in given path. This path should not be a
87 87 repository itself. Return a dictionary of repository objects
88 88 :param repos_path: path to directory it could take syntax with
89 89 * or ** for deep recursive displaying repositories
90 90 """
91 91 sa = meta.Session()
92 92 def check_repo_dir(path):
93 93 """Checks the repository
94 94 :param path:
95 95 """
96 96 repos_path = path.split('/')
97 97 if repos_path[-1] in ['*', '**']:
98 98 repos_path = repos_path[:-1]
99 99 if repos_path[0] != '/':
100 100 repos_path[0] = '/'
101 101 if not os.path.isdir(os.path.join(*repos_path)):
102 102 raise RepositoryError('Not a valid repository in %s' % path)
103 103 if not repos_path.endswith('*'):
104 104 raise VCSError('You need to specify * or ** at the end of path '
105 105 'for recursive scanning')
106 106
107 107 check_repo_dir(repos_path)
108 108 log.info('scanning for repositories in %s', repos_path)
109 109 repos = findrepos([(repos_prefix, repos_path)])
110 110 if not isinstance(baseui, ui.ui):
111 111 baseui = ui.ui()
112 112
113 113 repos_list = {}
114 114 for name, path in repos:
115 115 try:
116 116 #name = name.split('/')[-1]
117 117 if repos_list.has_key(name):
118 118 raise RepositoryError('Duplicate repository name %s found in'
119 119 ' %s' % (name, path))
120 120 else:
121 121
122 122 repos_list[name] = MercurialRepository(path, baseui=baseui)
123 123 repos_list[name].name = name
124 124
125 125 dbrepo = None
126 126 if not initial:
127 127 #for initial scann on application first run we don't
128 128 #have db repos yet.
129 129 dbrepo = sa.query(Repository)\
130 130 .options(joinedload(Repository.fork))\
131 131 .filter(Repository.repo_name == name)\
132 132 .scalar()
133 133
134 134 if dbrepo:
135 135 log.info('Adding db instance to cached list')
136 136 repos_list[name].dbrepo = dbrepo
137 137 repos_list[name].description = dbrepo.description
138 138 if dbrepo.user:
139 139 repos_list[name].contact = dbrepo.user.full_contact
140 140 else:
141 141 repos_list[name].contact = sa.query(User)\
142 142 .filter(User.admin == True).first().full_contact
143 143 except OSError:
144 144 continue
145 145 meta.Session.remove()
146 146 return repos_list
147 147
148 148 def get_repos(self):
149 149 for name, repo in _get_repos_cached().items():
150 150 if repo._get_hidden():
151 151 #skip hidden web repository
152 152 continue
153 153
154 154 last_change = repo.last_change
155 155 tip = h.get_changeset_safe(repo, 'tip')
156 156
157 157 tmp_d = {}
158 158 tmp_d['name'] = repo.name
159 159 tmp_d['name_sort'] = tmp_d['name'].lower()
160 160 tmp_d['description'] = repo.description
161 161 tmp_d['description_sort'] = tmp_d['description']
162 162 tmp_d['last_change'] = last_change
163 163 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
164 164 tmp_d['tip'] = tip.short_id
165 165 tmp_d['tip_sort'] = tip.revision
166 166 tmp_d['rev'] = tip.revision
167 167 tmp_d['contact'] = repo.contact
168 168 tmp_d['contact_sort'] = tmp_d['contact']
169 169 tmp_d['repo_archives'] = list(repo._get_archives())
170 170 tmp_d['last_msg'] = tip.message
171 171 tmp_d['repo'] = repo
172 172 yield tmp_d
173 173
174 174 def get_repo(self, repo_name):
175 175 try:
176 176 repo = _get_repos_cached()[repo_name]
177 177 return repo
178 178 except KeyError:
179 179 #i we're here and we got key errors let's try to invalidate the
180 180 #cahce and try again
181 181 invalidate_cache('cached_repo_list')
182 182 repo = _get_repos_cached()[repo_name]
183 183 return repo
184 184
185 185
186 186
@@ -1,269 +1,269 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 4 <head>
5 5 <title>${next.title()}</title>
6 6 <link rel="icon" href="/images/hgicon.png" type="image/png" />
7 7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
8 8 <meta name="robots" content="index, nofollow"/>
9 9 <!-- stylesheets -->
10 10 ${self.css()}
11 11 <!-- scripts -->
12 12 ${self.js()}
13 13 </head>
14 14 <body>
15 15 <!-- header -->
16 16 <div id="header">
17 17 <!-- user -->
18 18 <ul id="logged-user">
19 19 <li class="first">
20 20 <div class="gravatar">
21 21 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
22 22 </div>
23 23 <div class="account">
24 24 ${h.link_to('%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname),h.url('admin_settings_my_account'))}<br/>
25 25 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'))}
26 26 </div>
27 27 </li>
28 28 <li class="last highlight">${h.link_to(u'Logout',h.url('logout_home'))}</li>
29 29 </ul>
30 30 <!-- end user -->
31 31 <div id="header-inner">
32 32 <div id="home">
33 33 <a href="${h.url('hg_home')}"></a>
34 34 </div>
35 35 <!-- logo -->
36 36 <div id="logo">
37 37 <h1><a href="${h.url('hg_home')}">${c.rhodecode_name}</a></h1>
38 38 </div>
39 39 <!-- end logo -->
40 40 <!-- quick menu -->
41 41 ${self.page_nav()}
42 42 <!-- end quick -->
43 43 <div class="corner tl"></div>
44 44 <div class="corner tr"></div>
45 45 </div>
46 46 </div>
47 47 <!-- end header -->
48 48
49 49 <!-- CONTENT -->
50 50 <div id="content">
51 51 <div class="flash_msg">
52 52 <% messages = h.flash.pop_messages() %>
53 53 % if messages:
54 54 <ul id="flash-messages">
55 55 % for message in messages:
56 56 <li class="${message.category}_msg">${message}</li>
57 57 % endfor
58 58 </ul>
59 59 % endif
60 60 </div>
61 61 <div id="main">
62 62 ${next.main()}
63 63 </div>
64 64 </div>
65 65 <!-- END CONTENT -->
66 66
67 67 <!-- footer -->
68 68 <div id="footer">
69 <p>Hg App ${c.rhodecode_version} &copy; 2010 by Marcin Kuzminski</p>
69 <p>RhodeCode ${c.rhodecode_version} &copy; 2010 by Marcin Kuzminski</p>
70 70 <script type="text/javascript">${h.tooltip.activate()}</script>
71 71 </div>
72 72 <!-- end footer -->
73 73 </body>
74 74
75 75 </html>
76 76
77 77 ### MAKO DEFS ###
78 78 <%def name="page_nav()">
79 79 ${self.menu()}
80 80 </%def>
81 81
82 82 <%def name="menu(current=None)">
83 83 <%
84 84 def is_current(selected):
85 85 if selected == current:
86 86 return h.literal('class="current"')
87 87 %>
88 88 %if current not in ['home','admin']:
89 89 ##REGULAR MENU
90 90 <ul id="quick">
91 91 <!-- repo switcher -->
92 92 <li>
93 93 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
94 94 <span class="icon">
95 95 <img src="/images/icons/database.png" alt="${_('Products')}" />
96 96 </span>
97 97 <span>&darr;</span>
98 98 </a>
99 99 <ul class="repo_switcher">
100 100 %for repo,private in c.repo_switcher_list:
101 101 %if private:
102 102 <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="private_repo")}</li>
103 103 %else:
104 104 <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="public_repo")}</li>
105 105 %endif
106 106 %endfor
107 107 </ul>
108 108 </li>
109 109
110 110 <li ${is_current('summary')}>
111 111 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
112 112 <span class="icon">
113 113 <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" />
114 114 </span>
115 115 <span>${_('Summary')}</span>
116 116 </a>
117 117 </li>
118 118 <li ${is_current('shortlog')}>
119 119 <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
120 120 <span class="icon">
121 121 <img src="/images/icons/application_double.png" alt="${_('Shortlog')}" />
122 122 </span>
123 123 <span>${_('Shortlog')}</span>
124 124 </a>
125 125 </li>
126 126 <li ${is_current('changelog')}>
127 127 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
128 128 <span class="icon">
129 129 <img src="/images/icons/time.png" alt="${_('Changelog')}" />
130 130 </span>
131 131 <span>${_('Changelog')}</span>
132 132 </a>
133 133 </li>
134 134
135 135 <li ${is_current('switch_to')}>
136 136 <a title="${_('Switch to')}" href="#">
137 137 <span class="icon">
138 138 <img src="/images/icons/arrow_switch.png" alt="${_('Switch to')}" />
139 139 </span>
140 140 <span>${_('Switch to')}</span>
141 141 </a>
142 142 <ul>
143 143 <li>
144 144 ${h.link_to(_('branches'),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
145 145 <ul>
146 146 %if c.repository_branches.values():
147 147 %for cnt,branch in enumerate(c.repository_branches.items()):
148 148 <li>${h.link_to('%s - %s' % (branch[0],branch[1]),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
149 149 %endfor
150 150 %else:
151 151 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
152 152 %endif
153 153 </ul>
154 154 </li>
155 155 <li>
156 156 ${h.link_to(_('tags'),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
157 157 <ul>
158 158 %if c.repository_tags.values():
159 159 %for cnt,tag in enumerate(c.repository_tags.items()):
160 160 <li>${h.link_to('%s - %s' % (tag[0],tag[1]),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
161 161 %endfor
162 162 %else:
163 163 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
164 164 %endif
165 165 </ul>
166 166 </li>
167 167 </ul>
168 168 </li>
169 169 <li ${is_current('files')}>
170 170 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
171 171 <span class="icon">
172 172 <img src="/images/icons/file.png" alt="${_('Files')}" />
173 173 </span>
174 174 <span>${_('Files')}</span>
175 175 </a>
176 176 </li>
177 177
178 178 <li ${is_current('options')}>
179 179 <a title="${_('Options')}" href="#">
180 180 <span class="icon">
181 181 <img src="/images/icons/table_gear.png" alt="${_('Admin')}" />
182 182 </span>
183 183 <span>${_('Options')}</span>
184 184 </a>
185 185 <ul>
186 186 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
187 187 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
188 188 %endif
189 189 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
190 190 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
191 191 ## %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
192 192 ## <li class="last">
193 193 ## ${h.link_to(_('delete'),'#',class_='delete')}
194 194 ## ${h.form(url('repo_settings_delete', repo_name=c.repo_name),method='delete')}
195 195 ## ${h.submit('remove_%s' % c.repo_name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
196 196 ## ${h.end_form()}
197 197 ## </li>
198 198 ## %endif
199 199 </ul>
200 200 </li>
201 201 </ul>
202 202 %else:
203 203 ##ROOT MENU
204 204 <ul id="quick">
205 205 <li>
206 206 <a title="${_('Home')}" href="${h.url('hg_home')}">
207 207 <span class="icon">
208 208 <img src="/images/icons/home_16.png" alt="${_('Home')}" />
209 209 </span>
210 210 <span>${_('Home')}</span>
211 211 </a>
212 212 </li>
213 213
214 214 <li>
215 215 <a title="${_('Search')}" href="${h.url('search')}">
216 216 <span class="icon">
217 217 <img src="/images/icons/search_16.png" alt="${_('Search')}" />
218 218 </span>
219 219 <span>${_('Search')}</span>
220 220 </a>
221 221 </li>
222 222
223 223 %if h.HasPermissionAll('hg.admin')('access admin main page'):
224 224 <li ${is_current('admin')}>
225 225 <a title="${_('Admin')}" href="${h.url('admin_home')}">
226 226 <span class="icon">
227 227 <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" />
228 228 </span>
229 229 <span>${_('Admin')}</span>
230 230 </a>
231 231 <ul>
232 232 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
233 233 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
234 234 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
235 235 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
236 236 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
237 237 </ul>
238 238 </li>
239 239 %endif
240 240
241 241 </ul>
242 242 %endif
243 243 </%def>
244 244
245 245
246 246 <%def name="css()">
247 247 <link rel="stylesheet" type="text/css" href="/css/reset.css" />
248 248 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
249 249 <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" />
250 250 <link rel="stylesheet" type="text/css" href="/css/pygments.css" />
251 251 <link rel="stylesheet" type="text/css" href="/css/diff.css" />
252 252 </%def>
253 253
254 254 <%def name="js()">
255 255 ##<script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
256 256 ##<script type="text/javascript" src="/js/yui/container/container.js"></script>
257 257 ##<script type="text/javascript" src="/js/yui/datasource/datasource.js"></script>
258 258 ##<script type="text/javascript" src="/js/yui/autocomplete/autocomplete.js"></script>
259 259
260 260 <script type="text/javascript" src="/js/yui2.js"></script>
261 261 <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
262 262 <script type="text/javascript" src="/js/yui.flot.js"></script>
263 263 </%def>
264 264
265 265 <%def name="breadcrumbs()">
266 266 <div class="breadcrumbs">
267 267 ${self.breadcrumbs_links()}
268 268 </div>
269 269 </%def> No newline at end of file
@@ -1,49 +1,49 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 ${_('Repository managment')}
4 ${_('Repository management')}
5 5 </%def>
6 6 <%def name="breadcrumbs_links()">
7 7 ${h.link_to(u'Home',h.url('/'))}
8 8 &raquo;
9 9 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
10 10 &raquo;
11 11 ${'%s: %s %s %s' % (_('File diff'),c.diff2,'&rarr;',c.diff1)|n}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
15 15 ${self.menu('files')}
16 16 </%def>
17 17 <%def name="main()">
18 18 <div class="box">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 </div>
23 23 <div class="table">
24 24 <div id="body" class="diffblock">
25 25 <div class="code-header">
26 26 <div>
27 27 <span>${h.link_to(c.f_path,h.url('files_home',repo_name=c.repo_name,
28 28 revision=c.diff2.split(':')[1],f_path=c.f_path))}</span>
29 29 &raquo; <span>${h.link_to(_('diff'),
30 30 h.url.current(diff2=c.diff2.split(':')[-1],diff1=c.diff1.split(':')[-1],diff='diff'))}</span>
31 31 &raquo; <span>${h.link_to(_('raw diff'),
32 32 h.url.current(diff2=c.diff2.split(':')[-1],diff1=c.diff1.split(':')[-1],diff='raw'))}</span>
33 33 &raquo; <span>${h.link_to(_('download diff'),
34 34 h.url.current(diff2=c.diff2.split(':')[-1],diff1=c.diff1.split(':')[-1],diff='download'))}</span>
35 35 </div>
36 36 </div>
37 37 <div class="code-body">
38 38 %if c.no_changes:
39 39 ${_('No changes')}
40 40 %else:
41 41 ${c.cur_diff|n}
42 42 %endif
43 43 </div>
44 44 </div>
45 45 </div>
46 46 </div>
47 47 </%def>
48 48
49 49 No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now