##// END OF EJS Templates
docs/usage: rework section on non-changeable repository URLs and call them 'permanent'
Thomas De Schampheleire -
r4967:4185f87f default
parent child Browse files
Show More
@@ -1,177 +1,178 b''
1 1 .. _general:
2 2
3 3 =======================
4 4 General Kallithea usage
5 5 =======================
6 6
7 7
8 8 Repository deleting
9 9 -------------------
10 10
11 11 Currently when an admin or owner deletes a repository, Kallithea does
12 12 not physically delete said repository from the filesystem, but instead
13 13 renames it in a special way so that it is not possible to push, clone
14 14 or access the repository.
15 15
16 16 There is a special command for cleaning up such archived repos::
17 17
18 18 paster cleanup-repos --older-than=30d my.ini
19 19
20 20 This command scans for archived repositories that are older than
21 21 30 days, displays them, and asks if you want to delete them (unless given
22 22 the ``--dont-ask`` flag). If you host a large amount of repositories with
23 23 forks that are constantly being deleted, it is recommended that you run this
24 24 command via crontab.
25 25
26 26 It is worth noting that even if someone is given administrative access to
27 27 Kallithea and deletes a repository, you can easily restore such an action by
28 28 renaming the repository directory, removing the ``rm__<date>`` prefix.
29 29
30 30 File view: follow current branch
31 31 --------------------------------
32 32
33 33 In the file view, left and right arrows allow to jump to the previous and next
34 34 revision. Depending on the way revisions were created in the repository, this
35 35 could jump to a different branch. When the checkbox ``Follow current branch``
36 36 is checked, these arrows will only jump to revisions on the same branch as the
37 37 currently visible revision. So for example, if someone is viewing files in the
38 38 ``beta`` branch and marks the `Follow current branch` checkbox, the < and >
39 39 arrows will only show revisions on the ``beta`` branch.
40 40
41 41
42 42 Changelog features
43 43 ------------------
44 44
45 45 The core feature of a repository's ``changelog`` page is to show the revisions
46 46 in a repository. However, there are several other features available from the
47 47 changelog.
48 48
49 49 Branch filter
50 50 By default, the changelog shows revisions from all branches in the
51 51 repository. Use the branch filter to restrict to a given branch.
52 52
53 53 Viewing a changeset
54 54 A particular changeset can be opened by clicking on either the changeset
55 55 hash or the commit message, or by ticking the checkbox and clicking the
56 56 ``Show selected changeset`` button at the top.
57 57
58 58 Viewing all changes between two changesets
59 59 To get a list of all changesets between two selected changesets, along with
60 60 the changes in each one of them, tick the checkboxes of the first and
61 61 last changeset in the desired range and click the ``Show selected changesets``
62 62 button at the top. You can only show the range between the first and last
63 63 checkbox (no cherry-picking).
64 64
65 65 From that page, you can proceed to viewing the overall delta between the
66 66 selected changesets, by clicking the ``Compare revisions`` button.
67 67
68 68 Creating a pull request
69 69 You can create a new pull request for the changes of a particular changeset
70 70 (and its ancestors) by selecting it and clicking the ``Open new pull request
71 71 for selected changesets`` button.
72 72
73 Non changeable repository urls
74 ------------------------------
73 Permanent repository URLs
74 -------------------------
75 75
76 76 Due to the complicated nature of repository grouping, URLs of repositories
77 can often change.
78
79 example::
77 can often change. For example, a repository originally accessible from::
80 78
81 #before
82 79 http://server.com/repo_name
83 # after insertion to test_group group the url will be
80
81 would get a new URL after moving it to test_group::
82
84 83 http://server.com/test_group/repo_name
85 84
86 This can be an issue for build systems and any other hardcoded scripts, moving
87 a repository to a group leads to a need for changing external systems. To
88 overcome this Kallithea introduces a non-changable replacement URL. It's
89 simply a repository ID prefixed with ``_``. The above URLs are also accessible as::
85 Such moving of a repository to a group can be an issue for build systems and
86 other scripts where the repository paths are hardcoded. To mitigate this,
87 Kallithea provides permanent URLs using the repository ID prefixed with an
88 underscore. In all Kallithea URLs, for example those for the changelog and the
89 file view, a repository name can be replaced by this ``_ID`` string. Since IDs
90 are always the same, moving the repository to a different group will not affect
91 such URLs.
92
93 In the example, the repository could also be accessible as::
90 94
91 95 http://server.com/_<ID>
92 96
93 Since IDs are always the same, moving the repository will not affect
94 such a URL. the ``_<ID>`` syntax can be used anywhere in the system so
95 URLs with ``repo_name`` for changelogs and files can be exchanged
96 with the ``_<ID>`` syntax.
97
97 The ID of a given repository can be shown from the repository ``Summary`` page,
98 by selecting the ``Show by ID`` button next to ``Clone URL``.
98 99
99 100 E-mail notifications
100 101 --------------------
101 102
102 103 When the administrator correctly specified the e-mail settings in the Kallithea
103 104 configuration file, Kallithea will send e-mails on user registration and when
104 105 errors occur.
105 106
106 107 Mails are also sent for comments on changesets. In this case, an e-mail is sent
107 108 to the committer of the changeset (if known to Kallithea), to all reviewers of
108 109 the pull request (if applicable) and to all people mentioned in the comment
109 110 using @mention notation.
110 111
111 112
112 113 Trending source files
113 114 ---------------------
114 115
115 116 Trending source files are calculated based on a pre-defined dict of known
116 117 types and extensions. If you miss some extension or would like to scan some
117 118 custom files, it is possible to add new types in the ``LANGUAGES_EXTENSIONS_MAP`` dict
118 119 located in ``kallithea/lib/celerylib/tasks.py``.
119 120
120 121
121 122 Cloning remote repositories
122 123 ---------------------------
123 124
124 125 Kallithea has the ability to clone remote repos from given remote locations.
125 126 Currently it supports the following options:
126 127
127 128 - hg -> hg clone
128 129 - svn -> hg clone
129 130 - git -> git clone
130 131
131 132
132 133 .. note:: svn -> hg cloning requires tge ``hgsubversion`` library to be installed.
133 134
134 135 If you need to clone repositories that are protected via basic auth, you
135 136 might pass the url with stored credentials inside, e.g.,
136 137 ``http://user:passw@remote.server/repo``, Kallithea will try to login and clone
137 138 using the given credentials. Please take note that they will be stored as
138 139 plaintext inside the database. Kallithea will remove auth info when showing the
139 140 clone url in summary page.
140 141
141 142
142 143
143 144 Specific features configurable in the Admin settings
144 145 ----------------------------------------------------
145 146
146 147 In general, the Admin settings should be self-explanatory and will not be
147 148 described in more detail in this documentation. However, there are a few
148 149 features that merit further explanation.
149 150
150 151 Repository extra fields
151 152 ~~~~~~~~~~~~~~~~~~~~~~~
152 153
153 154 In the `Visual` tab, there is an option `Use repository extra
154 155 fields`, which allows to set custom fields for each repository in the system.
155 156 Each new field consists of 3 attributes: ``field key``, ``field label``,
156 157 ``field description``.
157 158
158 159 Example usage of such fields would be to define company-specific information
159 160 into repositories, e.g., defining a ``repo_manager`` key that would give info
160 161 about a manager of each repository. There's no limit for adding custom fields.
161 162 Newly created fields are accessible via the API.
162 163
163 164 Meta-Tagging
164 165 ~~~~~~~~~~~~
165 166
166 167 In the `Visual` tab, option `Stylify recognised meta tags` will cause Kallithea
167 168 to turn certain meta-tags, detected in repository and repository group
168 169 descriptions, into colored tags. Currently recognised tags are::
169 170
170 171 [featured]
171 172 [stale]
172 173 [dead]
173 174 [lang => lang]
174 175 [license => License]
175 176 [requires => Repo]
176 177 [recommends => Repo]
177 178 [see => URI]
@@ -1,459 +1,459 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14
15 15 """
16 16 kallithea.lib.base
17 17 ~~~~~~~~~~~~~~~~~~
18 18
19 19 The base Controller API
20 20 Provides the BaseController class for subclassing. And usage in different
21 21 controllers
22 22
23 23 This file was forked by the Kallithea project in July 2014.
24 24 Original author and date, and relevant copyright and licensing information is below:
25 25 :created_on: Oct 06, 2010
26 26 :author: marcink
27 27 :copyright: (c) 2013 RhodeCode GmbH, and others.
28 28 :license: GPLv3, see LICENSE.md for more details.
29 29 """
30 30
31 31 import logging
32 32 import time
33 33 import traceback
34 34
35 35 import webob.exc
36 36 import paste.httpexceptions
37 37 import paste.auth.basic
38 38 import paste.httpheaders
39 39
40 40 from pylons import config, tmpl_context as c, request, session, url
41 41 from pylons.controllers import WSGIController
42 42 from pylons.controllers.util import redirect
43 43 from pylons.templating import render_mako as render # don't remove this import
44 44 from pylons.i18n.translation import _
45 45
46 46 from kallithea import __version__, BACKENDS
47 47
48 48 from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
49 49 safe_str, safe_int
50 50 from kallithea.lib import auth_modules
51 51 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware, CookieStoreWrapper
52 52 from kallithea.lib.utils import get_repo_slug
53 53 from kallithea.lib.exceptions import UserCreationError
54 54 from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError
55 55 from kallithea.model import meta
56 56
57 57 from kallithea.model.db import Repository, Ui, User, Setting
58 58 from kallithea.model.notification import NotificationModel
59 59 from kallithea.model.scm import ScmModel
60 60 from kallithea.model.pull_request import PullRequestModel
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 def _filter_proxy(ip):
66 66 """
67 67 HEADERS can have multiple ips inside the left-most being the original
68 68 client, and each successive proxy that passed the request adding the IP
69 69 address where it received the request from.
70 70
71 71 :param ip:
72 72 """
73 73 if ',' in ip:
74 74 _ips = ip.split(',')
75 75 _first_ip = _ips[0].strip()
76 76 log.debug('Got multiple IPs %s, using %s' % (','.join(_ips), _first_ip))
77 77 return _first_ip
78 78 return ip
79 79
80 80
81 81 def _get_ip_addr(environ):
82 82 proxy_key = 'HTTP_X_REAL_IP'
83 83 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
84 84 def_key = 'REMOTE_ADDR'
85 85
86 86 ip = environ.get(proxy_key)
87 87 if ip:
88 88 return _filter_proxy(ip)
89 89
90 90 ip = environ.get(proxy_key2)
91 91 if ip:
92 92 return _filter_proxy(ip)
93 93
94 94 ip = environ.get(def_key, '0.0.0.0')
95 95 return _filter_proxy(ip)
96 96
97 97
98 98 def _get_access_path(environ):
99 99 path = environ.get('PATH_INFO')
100 100 org_req = environ.get('pylons.original_request')
101 101 if org_req:
102 102 path = org_req.environ.get('PATH_INFO')
103 103 return path
104 104
105 105
106 106 class BasicAuth(paste.auth.basic.AuthBasicAuthenticator):
107 107
108 108 def __init__(self, realm, authfunc, auth_http_code=None):
109 109 self.realm = realm
110 110 self.authfunc = authfunc
111 111 self._rc_auth_http_code = auth_http_code
112 112
113 113 def build_authentication(self):
114 114 head = paste.httpheaders.WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
115 115 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
116 116 # return 403 if alternative http return code is specified in
117 117 # Kallithea config
118 118 return paste.httpexceptions.HTTPForbidden(headers=head)
119 119 return paste.httpexceptions.HTTPUnauthorized(headers=head)
120 120
121 121 def authenticate(self, environ):
122 122 authorization = paste.httpheaders.AUTHORIZATION(environ)
123 123 if not authorization:
124 124 return self.build_authentication()
125 125 (authmeth, auth) = authorization.split(' ', 1)
126 126 if 'basic' != authmeth.lower():
127 127 return self.build_authentication()
128 128 auth = auth.strip().decode('base64')
129 129 _parts = auth.split(':', 1)
130 130 if len(_parts) == 2:
131 131 username, password = _parts
132 132 if self.authfunc(username, password, environ):
133 133 return username
134 134 return self.build_authentication()
135 135
136 136 __call__ = authenticate
137 137
138 138
139 139 class BaseVCSController(object):
140 140
141 141 def __init__(self, application, config):
142 142 self.application = application
143 143 self.config = config
144 144 # base path of repo locations
145 145 self.basepath = self.config['base_path']
146 146 #authenticate this VCS request using authfunc
147 147 self.authenticate = BasicAuth('', auth_modules.authenticate,
148 148 config.get('auth_ret_code'))
149 149 self.ip_addr = '0.0.0.0'
150 150
151 151 def _handle_request(self, environ, start_response):
152 152 raise NotImplementedError()
153 153
154 154 def _get_by_id(self, repo_name):
155 155 """
156 156 Gets a special pattern _<ID> from clone url and tries to replace it
157 with a repository_name for support of _<ID> non changeable urls
157 with a repository_name for support of _<ID> permanent URLs
158 158
159 159 :param repo_name:
160 160 """
161 161
162 162 data = repo_name.split('/')
163 163 if len(data) >= 2:
164 164 from kallithea.lib.utils import get_repo_by_id
165 165 by_id_match = get_repo_by_id(repo_name)
166 166 if by_id_match:
167 167 data[1] = by_id_match
168 168
169 169 return '/'.join(data)
170 170
171 171 def _invalidate_cache(self, repo_name):
172 172 """
173 173 Sets cache for this repository for invalidation on next access
174 174
175 175 :param repo_name: full repo name, also a cache key
176 176 """
177 177 ScmModel().mark_for_invalidation(repo_name)
178 178
179 179 def _check_permission(self, action, user, repo_name, ip_addr=None):
180 180 """
181 181 Checks permissions using action (push/pull) user and repository
182 182 name
183 183
184 184 :param action: push or pull action
185 185 :param user: user instance
186 186 :param repo_name: repository name
187 187 """
188 188 # check IP
189 189 inherit = user.inherit_default_permissions
190 190 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
191 191 inherit_from_default=inherit)
192 192 if ip_allowed:
193 193 log.info('Access for IP:%s allowed' % (ip_addr,))
194 194 else:
195 195 return False
196 196
197 197 if action == 'push':
198 198 if not HasPermissionAnyMiddleware('repository.write',
199 199 'repository.admin')(user,
200 200 repo_name):
201 201 return False
202 202
203 203 else:
204 204 #any other action need at least read permission
205 205 if not HasPermissionAnyMiddleware('repository.read',
206 206 'repository.write',
207 207 'repository.admin')(user,
208 208 repo_name):
209 209 return False
210 210
211 211 return True
212 212
213 213 def _get_ip_addr(self, environ):
214 214 return _get_ip_addr(environ)
215 215
216 216 def _check_ssl(self, environ):
217 217 """
218 218 Checks the SSL check flag and returns False if SSL is not present
219 219 and required True otherwise
220 220 """
221 221 #check if we have SSL required ! if not it's a bad request !
222 222 if str2bool(Ui.get_by_key('push_ssl').ui_value):
223 223 org_proto = environ.get('wsgi._org_proto', environ['wsgi.url_scheme'])
224 224 if org_proto != 'https':
225 225 log.debug('proto is %s and SSL is required BAD REQUEST !'
226 226 % org_proto)
227 227 return False
228 228 return True
229 229
230 230 def _check_locking_state(self, environ, action, repo, user_id):
231 231 """
232 232 Checks locking on this repository, if locking is enabled and lock is
233 233 present returns a tuple of make_lock, locked, locked_by.
234 234 make_lock can have 3 states None (do nothing) True, make lock
235 235 False release lock, This value is later propagated to hooks, which
236 236 do the locking. Think about this as signals passed to hooks what to do.
237 237
238 238 """
239 239 locked = False # defines that locked error should be thrown to user
240 240 make_lock = None
241 241 repo = Repository.get_by_repo_name(repo)
242 242 user = User.get(user_id)
243 243
244 244 # this is kind of hacky, but due to how mercurial handles client-server
245 245 # server see all operation on changeset; bookmarks, phases and
246 246 # obsolescence marker in different transaction, we don't want to check
247 247 # locking on those
248 248 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
249 249 locked_by = repo.locked
250 250 if repo and repo.enable_locking and not obsolete_call:
251 251 if action == 'push':
252 252 #check if it's already locked !, if it is compare users
253 253 user_id, _date = repo.locked
254 254 if user.user_id == user_id:
255 255 log.debug('Got push from user %s, now unlocking' % (user))
256 256 # unlock if we have push from user who locked
257 257 make_lock = False
258 258 else:
259 259 # we're not the same user who locked, ban with 423 !
260 260 locked = True
261 261 if action == 'pull':
262 262 if repo.locked[0] and repo.locked[1]:
263 263 locked = True
264 264 else:
265 265 log.debug('Setting lock on repo %s by %s' % (repo, user))
266 266 make_lock = True
267 267
268 268 else:
269 269 log.debug('Repository %s do not have locking enabled' % (repo))
270 270 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
271 271 % (make_lock, locked, locked_by))
272 272 return make_lock, locked, locked_by
273 273
274 274 def __call__(self, environ, start_response):
275 275 start = time.time()
276 276 try:
277 277 return self._handle_request(environ, start_response)
278 278 finally:
279 279 log = logging.getLogger('kallithea.' + self.__class__.__name__)
280 280 log.debug('Request time: %.3fs' % (time.time() - start))
281 281 meta.Session.remove()
282 282
283 283
284 284 class BaseController(WSGIController):
285 285
286 286 def __before__(self):
287 287 """
288 288 __before__ is called before controller methods and after __call__
289 289 """
290 290 c.kallithea_version = __version__
291 291 rc_config = Setting.get_app_settings()
292 292
293 293 # Visual options
294 294 c.visual = AttributeDict({})
295 295
296 296 ## DB stored
297 297 c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
298 298 c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
299 299 c.visual.stylify_metatags = str2bool(rc_config.get('stylify_metatags'))
300 300 c.visual.dashboard_items = safe_int(rc_config.get('dashboard_items', 100))
301 301 c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100))
302 302 c.visual.repository_fields = str2bool(rc_config.get('repository_fields'))
303 303 c.visual.show_version = str2bool(rc_config.get('show_version'))
304 304 c.visual.use_gravatar = str2bool(rc_config.get('use_gravatar'))
305 305 c.visual.gravatar_url = rc_config.get('gravatar_url')
306 306
307 307 c.ga_code = rc_config.get('ga_code')
308 308 # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
309 309 if c.ga_code and '<' not in c.ga_code:
310 310 c.ga_code = '''<script type="text/javascript">
311 311 var _gaq = _gaq || [];
312 312 _gaq.push(['_setAccount', '%s']);
313 313 _gaq.push(['_trackPageview']);
314 314
315 315 (function() {
316 316 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
317 317 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
318 318 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
319 319 })();
320 320 </script>''' % c.ga_code
321 321 c.site_name = rc_config.get('title')
322 322 c.clone_uri_tmpl = rc_config.get('clone_uri_tmpl')
323 323
324 324 ## INI stored
325 325 c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
326 326 c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True))
327 327
328 328 c.instance_id = config.get('instance_id')
329 329 c.issues_url = config.get('bugtracker', url('issues_url'))
330 330 # END CONFIG VARS
331 331
332 332 c.repo_name = get_repo_slug(request) # can be empty
333 333 c.backends = BACKENDS.keys()
334 334 c.unread_notifications = NotificationModel()\
335 335 .get_unread_cnt_for_user(c.authuser.user_id)
336 336
337 337 self.cut_off_limit = safe_int(config.get('cut_off_limit'))
338 338
339 339 c.my_pr_count = PullRequestModel().get_pullrequest_cnt_for_user(c.authuser.user_id)
340 340
341 341 self.sa = meta.Session
342 342 self.scm_model = ScmModel(self.sa)
343 343
344 344 def __call__(self, environ, start_response):
345 345 """Invoke the Controller"""
346 346 # WSGIController.__call__ dispatches to the Controller method
347 347 # the request is routed to. This routing information is
348 348 # available in environ['pylons.routes_dict']
349 349 try:
350 350 self.ip_addr = _get_ip_addr(environ)
351 351 # make sure that we update permissions each time we call controller
352 352 api_key = request.GET.get('api_key')
353 353
354 354 if api_key:
355 355 # when using API_KEY we are sure user exists.
356 356 auth_user = AuthUser(api_key=api_key, ip_addr=self.ip_addr)
357 357 authenticated = False
358 358 else:
359 359 cookie_store = CookieStoreWrapper(session.get('authuser'))
360 360 try:
361 361 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
362 362 ip_addr=self.ip_addr)
363 363 except UserCreationError, e:
364 364 from kallithea.lib import helpers as h
365 365 h.flash(e, 'error')
366 366 # container auth or other auth functions that create users on
367 367 # the fly can throw this exception signaling that there's issue
368 368 # with user creation, explanation should be provided in
369 369 # Exception itself
370 370 auth_user = AuthUser(ip_addr=self.ip_addr)
371 371
372 372 authenticated = cookie_store.get('is_authenticated')
373 373
374 374 if not auth_user.is_authenticated and auth_user.user_id is not None:
375 375 # user is not authenticated and not empty
376 376 auth_user.set_authenticated(authenticated)
377 377 request.user = auth_user
378 378 #set globals for auth user
379 379 self.authuser = c.authuser = auth_user
380 380 log.info('IP: %s User: %s accessed %s' % (
381 381 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
382 382 )
383 383 return WSGIController.__call__(self, environ, start_response)
384 384 finally:
385 385 meta.Session.remove()
386 386
387 387
388 388 class BaseRepoController(BaseController):
389 389 """
390 390 Base class for controllers responsible for loading all needed data for
391 391 repository loaded items are
392 392
393 393 c.db_repo_scm_instance: instance of scm repository
394 394 c.db_repo: instance of db
395 395 c.repository_followers: number of followers
396 396 c.repository_forks: number of forks
397 397 c.repository_following: weather the current user is following the current repo
398 398 """
399 399
400 400 def __before__(self):
401 401 super(BaseRepoController, self).__before__()
402 402 if c.repo_name: # extracted from routes
403 403 _dbr = Repository.get_by_repo_name(c.repo_name)
404 404 if not _dbr:
405 405 return
406 406
407 407 log.debug('Found repository in database %s with state `%s`'
408 408 % (safe_unicode(_dbr), safe_unicode(_dbr.repo_state)))
409 409 route = getattr(request.environ.get('routes.route'), 'name', '')
410 410
411 411 # allow to delete repos that are somehow damages in filesystem
412 412 if route in ['delete_repo']:
413 413 return
414 414
415 415 if _dbr.repo_state in [Repository.STATE_PENDING]:
416 416 if route in ['repo_creating_home']:
417 417 return
418 418 check_url = url('repo_creating_home', repo_name=c.repo_name)
419 419 return redirect(check_url)
420 420
421 421 dbr = c.db_repo = _dbr
422 422 c.db_repo_scm_instance = c.db_repo.scm_instance
423 423 if c.db_repo_scm_instance is None:
424 424 log.error('%s this repository is present in database but it '
425 425 'cannot be created as an scm instance', c.repo_name)
426 426 from kallithea.lib import helpers as h
427 427 h.flash(h.literal(_('Repository not found in the filesystem')),
428 428 category='error')
429 429 raise paste.httpexceptions.HTTPNotFound()
430 430
431 431 # some globals counter for menu
432 432 c.repository_followers = self.scm_model.get_followers(dbr)
433 433 c.repository_forks = self.scm_model.get_forks(dbr)
434 434 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
435 435 c.repository_following = self.scm_model.is_following_repo(
436 436 c.repo_name, self.authuser.user_id)
437 437
438 438 @staticmethod
439 439 def _get_ref_rev(repo, ref_type, ref_name, returnempty=False):
440 440 """
441 441 Safe way to get changeset. If error occurs show error.
442 442 """
443 443 from kallithea.lib import helpers as h
444 444 try:
445 445 return repo.scm_instance.get_ref_revision(ref_type, ref_name)
446 446 except EmptyRepositoryError as e:
447 447 if returnempty:
448 448 return repo.scm_instance.EMPTY_CHANGESET
449 449 h.flash(h.literal(_('There are no changesets yet')),
450 450 category='error')
451 451 raise webob.exc.HTTPNotFound()
452 452 except ChangesetDoesNotExistError as e:
453 453 h.flash(h.literal(_('Changeset not found')),
454 454 category='error')
455 455 raise webob.exc.HTTPNotFound()
456 456 except RepositoryError as e:
457 457 log.error(traceback.format_exc())
458 458 h.flash(safe_str(e), category='error')
459 459 raise webob.exc.HTTPBadRequest()
@@ -1,164 +1,164 b''
1 1 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
2 2 <div class="form">
3 3 <!-- fields -->
4 4 <div class="fields">
5 5 <div class="field">
6 6 <div class="label">
7 7 <label for="repo_name">${_('Name')}:</label>
8 8 </div>
9 9 <div class="input">
10 10 ${h.text('repo_name',class_="medium")}
11 <span class="help-block">${_('Non-changeable id')}: `_${c.repo_info.repo_id}` <span><a id="show_more_clone_id" href="#">${_('What is that?')}</a></span></span>
11 <span class="help-block">${_('Permanent Repository ID')}: `_${c.repo_info.repo_id}` <span><a id="show_more_clone_id" href="#">${_('What is that?')}</a></span></span>
12 12 <span id="clone_id" class="help-block" style="display: none">
13 13 ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}` </br>
14 14 ${_('''In case this repository is renamed or moved into another group the repository URL changes.
15 Using the above URL guarantees that this repository will always be accessible under such URL.
16 Useful for CI systems, or any other cases that you need to hardcode the URL into 3rd party service.''')}</span>
15 Using the above permanent URL guarantees that this repository always will be accessible on that URL.
16 This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service.''')}</span>
17 17 </div>
18 18 </div>
19 19 <div class="field">
20 20 <div class="label">
21 21 <label for="clone_uri">${_('Clone URL')}:</label>
22 22 </div>
23 23 <div class="input">
24 24 %if c.repo_info.clone_uri:
25 25 <div id="clone_uri_hidden" style="font-size: 14px">
26 26 <span id="clone_uri_hidden_value">${c.repo_info.clone_uri_hidden}</span>
27 27 <span style="cursor: pointer; padding: 0px 0px 5px 0px" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
28 28 </div>
29 29 <div id="alter_clone_uri" style="display: none">
30 30 ${h.text('clone_uri',class_="medium", placeholder=_('new value'))}
31 31 </div>
32 32 %else:
33 33 ## not set yet, display form to set it
34 34 ${h.text('clone_uri',class_="medium")}
35 35 ${h.hidden('clone_uri_change', 'NEW')}
36 36 %endif
37 37 <span id="alter_clone_uri_help_block" class="help-block">${_('URL used for doing remote pulls.')}</span>
38 38 </div>
39 39 </div>
40 40 <div class="field">
41 41 <div class="label">
42 42 <label for="repo_group">${_('Repository group')}:</label>
43 43 </div>
44 44 <div class="input">
45 45 ${h.select('repo_group','',c.repo_groups,class_="medium")}
46 46 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
47 47 </div>
48 48 </div>
49 49 <div class="field">
50 50 <div class="label">
51 51 <label for="repo_landing_rev">${_('Landing revision')}:</label>
52 52 </div>
53 53 <div class="input">
54 54 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
55 55 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
56 56 </div>
57 57 </div>
58 58 <div class="field">
59 59 <div class="label">
60 60 <label for="user">${_('Owner')}:</label>
61 61 </div>
62 62 <div class="input input-medium ac">
63 63 <div class="perm_ac">
64 64 ${h.text('user',class_='yui-ac-input')}
65 65 <span class="help-block">${_('Change owner of this repository.')}</span>
66 66 <div id="owner_container"></div>
67 67 </div>
68 68 </div>
69 69 </div>
70 70 <div class="field">
71 71 <div class="label label-textarea">
72 72 <label for="repo_description">${_('Description')}:</label>
73 73 </div>
74 74 <div class="textarea text-area editor">
75 75 ${h.textarea('repo_description', style="height:165px")}
76 76 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
77 77 </div>
78 78 </div>
79 79
80 80 <div class="field">
81 81 <div class="label label-checkbox">
82 82 <label for="repo_private">${_('Private repository')}:</label>
83 83 </div>
84 84 <div class="checkboxes">
85 85 ${h.checkbox('repo_private',value="True")}
86 86 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
87 87 </div>
88 88 </div>
89 89 <div class="field">
90 90 <div class="label label-checkbox">
91 91 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
92 92 </div>
93 93 <div class="checkboxes">
94 94 ${h.checkbox('repo_enable_statistics',value="True")}
95 95 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
96 96 </div>
97 97 </div>
98 98 <div class="field">
99 99 <div class="label label-checkbox">
100 100 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
101 101 </div>
102 102 <div class="checkboxes">
103 103 ${h.checkbox('repo_enable_downloads',value="True")}
104 104 <span class="help-block">${_('Enable download menu on summary page.')}</span>
105 105 </div>
106 106 </div>
107 107 <div class="field">
108 108 <div class="label label-checkbox">
109 109 <label for="repo_enable_locking">${_('Enable locking')}:</label>
110 110 </div>
111 111 <div class="checkboxes">
112 112 ${h.checkbox('repo_enable_locking',value="True")}
113 113 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
114 114 </div>
115 115 </div>
116 116
117 117 %if c.visual.repository_fields:
118 118 ## EXTRA FIELDS
119 119 %for field in c.repo_fields:
120 120 <div class="field">
121 121 <div class="label">
122 122 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
123 123 </div>
124 124 <div class="input input-medium">
125 125 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
126 126 %if field.field_desc:
127 127 <span class="help-block">${field.field_desc}</span>
128 128 %endif
129 129 </div>
130 130 </div>
131 131 %endfor
132 132 %endif
133 133 <div class="buttons">
134 134 ${h.submit('save',_('Save'),class_="btn")}
135 135 ${h.reset('reset',_('Reset'),class_="btn")}
136 136 </div>
137 137 </div>
138 138 </div>
139 139 ${h.end_form()}
140 140
141 141 <script>
142 142 $(document).ready(function(){
143 143 $('#show_more_clone_id').on('click', function(e){
144 144 $('#clone_id').show();
145 145 e.preventDefault();
146 146 })
147 147 $('#edit_clone_uri').on('click', function(e){
148 148 $('#alter_clone_uri').show();
149 149 $('#edit_clone_uri').hide();
150 150 $('#clone_uri_hidden').hide();
151 151 ## store hash of old value for change detection
152 152 var uri_change = '<input id="clone_uri_change" name="clone_uri_change" type="hidden" value="${h.md5(c.repo_info.clone_uri or "").hexdigest()}" />';
153 153 $('#alter_clone_uri_help_block').html($('#alter_clone_uri_help_block').html()+" ("+$('#clone_uri_hidden_value').html()+")")
154 154 })
155 155
156 156 $('#repo_landing_rev').select2({
157 157 'dropdownAutoWidth': true
158 158 });
159 159 $('#repo_group').select2({
160 160 'dropdownAutoWidth': true
161 161 });
162 162
163 163 })
164 164 </script>
General Comments 0
You need to be logged in to leave comments. Login now