##// END OF EJS Templates
show only open pull requests in the counter, and use repo context bar in pull requests view
marcink -
r3580:7b9d4f6b beta
parent child Browse files
Show More
@@ -1,338 +1,337 b''
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 import logging
5 import logging
6 import time
6 import time
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12
12
13 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
15 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
16 from pylons.templating import render_mako as render
17
17
18 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
19
19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 safe_str, safe_int
21 safe_str, safe_int
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 HasPermissionAnyMiddleware, CookieStoreWrapper
23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 from rhodecode.model import meta
25 from rhodecode.model import meta
26
26
27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
28 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.notification import NotificationModel
29 from rhodecode.model.scm import ScmModel
29 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 def _get_ip_addr(environ):
35 def _get_ip_addr(environ):
36 proxy_key = 'HTTP_X_REAL_IP'
36 proxy_key = 'HTTP_X_REAL_IP'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
38 def_key = 'REMOTE_ADDR'
38 def_key = 'REMOTE_ADDR'
39
39
40 ip = environ.get(proxy_key)
40 ip = environ.get(proxy_key)
41 if ip:
41 if ip:
42 return ip
42 return ip
43
43
44 ip = environ.get(proxy_key2)
44 ip = environ.get(proxy_key2)
45 if ip:
45 if ip:
46 return ip
46 return ip
47
47
48 ip = environ.get(def_key, '0.0.0.0')
48 ip = environ.get(def_key, '0.0.0.0')
49
49
50 # HEADERS can have mutliple ips inside
50 # HEADERS can have mutliple ips inside
51 # the left-most being the original client, and each successive proxy
51 # the left-most being the original client, and each successive proxy
52 # that passed the request adding the IP address where it received the
52 # that passed the request adding the IP address where it received the
53 # request from.
53 # request from.
54 if ',' in ip:
54 if ',' in ip:
55 ip = ip.split(',')[0].strip()
55 ip = ip.split(',')[0].strip()
56
56
57 return ip
57 return ip
58
58
59
59
60 def _get_access_path(environ):
60 def _get_access_path(environ):
61 path = environ.get('PATH_INFO')
61 path = environ.get('PATH_INFO')
62 org_req = environ.get('pylons.original_request')
62 org_req = environ.get('pylons.original_request')
63 if org_req:
63 if org_req:
64 path = org_req.environ.get('PATH_INFO')
64 path = org_req.environ.get('PATH_INFO')
65 return path
65 return path
66
66
67
67
68 class BasicAuth(AuthBasicAuthenticator):
68 class BasicAuth(AuthBasicAuthenticator):
69
69
70 def __init__(self, realm, authfunc, auth_http_code=None):
70 def __init__(self, realm, authfunc, auth_http_code=None):
71 self.realm = realm
71 self.realm = realm
72 self.authfunc = authfunc
72 self.authfunc = authfunc
73 self._rc_auth_http_code = auth_http_code
73 self._rc_auth_http_code = auth_http_code
74
74
75 def build_authentication(self):
75 def build_authentication(self):
76 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
76 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
77 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
77 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
78 # return 403 if alternative http return code is specified in
78 # return 403 if alternative http return code is specified in
79 # RhodeCode config
79 # RhodeCode config
80 return HTTPForbidden(headers=head)
80 return HTTPForbidden(headers=head)
81 return HTTPUnauthorized(headers=head)
81 return HTTPUnauthorized(headers=head)
82
82
83 def authenticate(self, environ):
83 def authenticate(self, environ):
84 authorization = AUTHORIZATION(environ)
84 authorization = AUTHORIZATION(environ)
85 if not authorization:
85 if not authorization:
86 return self.build_authentication()
86 return self.build_authentication()
87 (authmeth, auth) = authorization.split(' ', 1)
87 (authmeth, auth) = authorization.split(' ', 1)
88 if 'basic' != authmeth.lower():
88 if 'basic' != authmeth.lower():
89 return self.build_authentication()
89 return self.build_authentication()
90 auth = auth.strip().decode('base64')
90 auth = auth.strip().decode('base64')
91 _parts = auth.split(':', 1)
91 _parts = auth.split(':', 1)
92 if len(_parts) == 2:
92 if len(_parts) == 2:
93 username, password = _parts
93 username, password = _parts
94 if self.authfunc(environ, username, password):
94 if self.authfunc(environ, username, password):
95 return username
95 return username
96 return self.build_authentication()
96 return self.build_authentication()
97
97
98 __call__ = authenticate
98 __call__ = authenticate
99
99
100
100
101 class BaseVCSController(object):
101 class BaseVCSController(object):
102
102
103 def __init__(self, application, config):
103 def __init__(self, application, config):
104 self.application = application
104 self.application = application
105 self.config = config
105 self.config = config
106 # base path of repo locations
106 # base path of repo locations
107 self.basepath = self.config['base_path']
107 self.basepath = self.config['base_path']
108 #authenticate this mercurial request using authfunc
108 #authenticate this mercurial request using authfunc
109 self.authenticate = BasicAuth('', authfunc,
109 self.authenticate = BasicAuth('', authfunc,
110 config.get('auth_ret_code'))
110 config.get('auth_ret_code'))
111 self.ip_addr = '0.0.0.0'
111 self.ip_addr = '0.0.0.0'
112
112
113 def _handle_request(self, environ, start_response):
113 def _handle_request(self, environ, start_response):
114 raise NotImplementedError()
114 raise NotImplementedError()
115
115
116 def _get_by_id(self, repo_name):
116 def _get_by_id(self, repo_name):
117 """
117 """
118 Get's a special pattern _<ID> from clone url and tries to replace it
118 Get's a special pattern _<ID> from clone url and tries to replace it
119 with a repository_name for support of _<ID> non changable urls
119 with a repository_name for support of _<ID> non changable urls
120
120
121 :param repo_name:
121 :param repo_name:
122 """
122 """
123 try:
123 try:
124 data = repo_name.split('/')
124 data = repo_name.split('/')
125 if len(data) >= 2:
125 if len(data) >= 2:
126 by_id = data[1].split('_')
126 by_id = data[1].split('_')
127 if len(by_id) == 2 and by_id[1].isdigit():
127 if len(by_id) == 2 and by_id[1].isdigit():
128 _repo_name = Repository.get(by_id[1]).repo_name
128 _repo_name = Repository.get(by_id[1]).repo_name
129 data[1] = _repo_name
129 data[1] = _repo_name
130 except:
130 except:
131 log.debug('Failed to extract repo_name from id %s' % (
131 log.debug('Failed to extract repo_name from id %s' % (
132 traceback.format_exc()
132 traceback.format_exc()
133 )
133 )
134 )
134 )
135
135
136 return '/'.join(data)
136 return '/'.join(data)
137
137
138 def _invalidate_cache(self, repo_name):
138 def _invalidate_cache(self, repo_name):
139 """
139 """
140 Set's cache for this repository for invalidation on next access
140 Set's cache for this repository for invalidation on next access
141
141
142 :param repo_name: full repo name, also a cache key
142 :param repo_name: full repo name, also a cache key
143 """
143 """
144 invalidate_cache('get_repo_cached_%s' % repo_name)
144 invalidate_cache('get_repo_cached_%s' % repo_name)
145
145
146 def _check_permission(self, action, user, repo_name, ip_addr=None):
146 def _check_permission(self, action, user, repo_name, ip_addr=None):
147 """
147 """
148 Checks permissions using action (push/pull) user and repository
148 Checks permissions using action (push/pull) user and repository
149 name
149 name
150
150
151 :param action: push or pull action
151 :param action: push or pull action
152 :param user: user instance
152 :param user: user instance
153 :param repo_name: repository name
153 :param repo_name: repository name
154 """
154 """
155 #check IP
155 #check IP
156 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
156 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
157 if not authuser.ip_allowed:
157 if not authuser.ip_allowed:
158 return False
158 return False
159 else:
159 else:
160 log.info('Access for IP:%s allowed' % (ip_addr))
160 log.info('Access for IP:%s allowed' % (ip_addr))
161 if action == 'push':
161 if action == 'push':
162 if not HasPermissionAnyMiddleware('repository.write',
162 if not HasPermissionAnyMiddleware('repository.write',
163 'repository.admin')(user,
163 'repository.admin')(user,
164 repo_name):
164 repo_name):
165 return False
165 return False
166
166
167 else:
167 else:
168 #any other action need at least read permission
168 #any other action need at least read permission
169 if not HasPermissionAnyMiddleware('repository.read',
169 if not HasPermissionAnyMiddleware('repository.read',
170 'repository.write',
170 'repository.write',
171 'repository.admin')(user,
171 'repository.admin')(user,
172 repo_name):
172 repo_name):
173 return False
173 return False
174
174
175 return True
175 return True
176
176
177 def _get_ip_addr(self, environ):
177 def _get_ip_addr(self, environ):
178 return _get_ip_addr(environ)
178 return _get_ip_addr(environ)
179
179
180 def _check_ssl(self, environ, start_response):
180 def _check_ssl(self, environ, start_response):
181 """
181 """
182 Checks the SSL check flag and returns False if SSL is not present
182 Checks the SSL check flag and returns False if SSL is not present
183 and required True otherwise
183 and required True otherwise
184 """
184 """
185 org_proto = environ['wsgi._org_proto']
185 org_proto = environ['wsgi._org_proto']
186 #check if we have SSL required ! if not it's a bad request !
186 #check if we have SSL required ! if not it's a bad request !
187 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
187 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
188 if require_ssl and org_proto == 'http':
188 if require_ssl and org_proto == 'http':
189 log.debug('proto is %s and SSL is required BAD REQUEST !'
189 log.debug('proto is %s and SSL is required BAD REQUEST !'
190 % org_proto)
190 % org_proto)
191 return False
191 return False
192 return True
192 return True
193
193
194 def _check_locking_state(self, environ, action, repo, user_id):
194 def _check_locking_state(self, environ, action, repo, user_id):
195 """
195 """
196 Checks locking on this repository, if locking is enabled and lock is
196 Checks locking on this repository, if locking is enabled and lock is
197 present returns a tuple of make_lock, locked, locked_by.
197 present returns a tuple of make_lock, locked, locked_by.
198 make_lock can have 3 states None (do nothing) True, make lock
198 make_lock can have 3 states None (do nothing) True, make lock
199 False release lock, This value is later propagated to hooks, which
199 False release lock, This value is later propagated to hooks, which
200 do the locking. Think about this as signals passed to hooks what to do.
200 do the locking. Think about this as signals passed to hooks what to do.
201
201
202 """
202 """
203 locked = False # defines that locked error should be thrown to user
203 locked = False # defines that locked error should be thrown to user
204 make_lock = None
204 make_lock = None
205 repo = Repository.get_by_repo_name(repo)
205 repo = Repository.get_by_repo_name(repo)
206 user = User.get(user_id)
206 user = User.get(user_id)
207
207
208 # this is kind of hacky, but due to how mercurial handles client-server
208 # this is kind of hacky, but due to how mercurial handles client-server
209 # server see all operation on changeset; bookmarks, phases and
209 # server see all operation on changeset; bookmarks, phases and
210 # obsolescence marker in different transaction, we don't want to check
210 # obsolescence marker in different transaction, we don't want to check
211 # locking on those
211 # locking on those
212 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
212 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
213 locked_by = repo.locked
213 locked_by = repo.locked
214 if repo and repo.enable_locking and not obsolete_call:
214 if repo and repo.enable_locking and not obsolete_call:
215 if action == 'push':
215 if action == 'push':
216 #check if it's already locked !, if it is compare users
216 #check if it's already locked !, if it is compare users
217 user_id, _date = repo.locked
217 user_id, _date = repo.locked
218 if user.user_id == user_id:
218 if user.user_id == user_id:
219 log.debug('Got push from user %s, now unlocking' % (user))
219 log.debug('Got push from user %s, now unlocking' % (user))
220 # unlock if we have push from user who locked
220 # unlock if we have push from user who locked
221 make_lock = False
221 make_lock = False
222 else:
222 else:
223 # we're not the same user who locked, ban with 423 !
223 # we're not the same user who locked, ban with 423 !
224 locked = True
224 locked = True
225 if action == 'pull':
225 if action == 'pull':
226 if repo.locked[0] and repo.locked[1]:
226 if repo.locked[0] and repo.locked[1]:
227 locked = True
227 locked = True
228 else:
228 else:
229 log.debug('Setting lock on repo %s by %s' % (repo, user))
229 log.debug('Setting lock on repo %s by %s' % (repo, user))
230 make_lock = True
230 make_lock = True
231
231
232 else:
232 else:
233 log.debug('Repository %s do not have locking enabled' % (repo))
233 log.debug('Repository %s do not have locking enabled' % (repo))
234 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
234 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
235 % (make_lock, locked, locked_by))
235 % (make_lock, locked, locked_by))
236 return make_lock, locked, locked_by
236 return make_lock, locked, locked_by
237
237
238 def __call__(self, environ, start_response):
238 def __call__(self, environ, start_response):
239 start = time.time()
239 start = time.time()
240 try:
240 try:
241 return self._handle_request(environ, start_response)
241 return self._handle_request(environ, start_response)
242 finally:
242 finally:
243 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
243 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
244 log.debug('Request time: %.3fs' % (time.time() - start))
244 log.debug('Request time: %.3fs' % (time.time() - start))
245 meta.Session.remove()
245 meta.Session.remove()
246
246
247
247
248 class BaseController(WSGIController):
248 class BaseController(WSGIController):
249
249
250 def __before__(self):
250 def __before__(self):
251 """
251 """
252 __before__ is called before controller methods and after __call__
252 __before__ is called before controller methods and after __call__
253 """
253 """
254 c.rhodecode_version = __version__
254 c.rhodecode_version = __version__
255 c.rhodecode_instanceid = config.get('instance_id')
255 c.rhodecode_instanceid = config.get('instance_id')
256 c.rhodecode_name = config.get('rhodecode_title')
256 c.rhodecode_name = config.get('rhodecode_title')
257 c.use_gravatar = str2bool(config.get('use_gravatar'))
257 c.use_gravatar = str2bool(config.get('use_gravatar'))
258 c.ga_code = config.get('rhodecode_ga_code')
258 c.ga_code = config.get('rhodecode_ga_code')
259 # Visual options
259 # Visual options
260 c.visual = AttributeDict({})
260 c.visual = AttributeDict({})
261 rc_config = RhodeCodeSetting.get_app_settings()
261 rc_config = RhodeCodeSetting.get_app_settings()
262
262
263 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
263 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
264 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
264 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
265 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
265 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
266 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
266 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
267 c.visual.lightweight_dashboard_items = safe_int(config.get('dashboard_items', 100))
267 c.visual.lightweight_dashboard_items = safe_int(config.get('dashboard_items', 100))
268 c.visual.repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
268 c.visual.repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
269
269 c.repo_name = get_repo_slug(request) # can be empty
270 c.repo_name = get_repo_slug(request)
271 c.backends = BACKENDS.keys()
270 c.backends = BACKENDS.keys()
272 c.unread_notifications = NotificationModel()\
271 c.unread_notifications = NotificationModel()\
273 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
272 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
274 self.cut_off_limit = int(config.get('cut_off_limit'))
273 self.cut_off_limit = int(config.get('cut_off_limit'))
275
274
276 self.sa = meta.Session
275 self.sa = meta.Session
277 self.scm_model = ScmModel(self.sa)
276 self.scm_model = ScmModel(self.sa)
278
277
279 def __call__(self, environ, start_response):
278 def __call__(self, environ, start_response):
280 """Invoke the Controller"""
279 """Invoke the Controller"""
281 # WSGIController.__call__ dispatches to the Controller method
280 # WSGIController.__call__ dispatches to the Controller method
282 # the request is routed to. This routing information is
281 # the request is routed to. This routing information is
283 # available in environ['pylons.routes_dict']
282 # available in environ['pylons.routes_dict']
284 try:
283 try:
285 self.ip_addr = _get_ip_addr(environ)
284 self.ip_addr = _get_ip_addr(environ)
286 # make sure that we update permissions each time we call controller
285 # make sure that we update permissions each time we call controller
287 api_key = request.GET.get('api_key')
286 api_key = request.GET.get('api_key')
288 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
287 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
289 user_id = cookie_store.get('user_id', None)
288 user_id = cookie_store.get('user_id', None)
290 username = get_container_username(environ, config)
289 username = get_container_username(environ, config)
291 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
290 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
292 request.user = auth_user
291 request.user = auth_user
293 self.rhodecode_user = c.rhodecode_user = auth_user
292 self.rhodecode_user = c.rhodecode_user = auth_user
294 if not self.rhodecode_user.is_authenticated and \
293 if not self.rhodecode_user.is_authenticated and \
295 self.rhodecode_user.user_id is not None:
294 self.rhodecode_user.user_id is not None:
296 self.rhodecode_user.set_authenticated(
295 self.rhodecode_user.set_authenticated(
297 cookie_store.get('is_authenticated')
296 cookie_store.get('is_authenticated')
298 )
297 )
299 log.info('IP: %s User: %s accessed %s' % (
298 log.info('IP: %s User: %s accessed %s' % (
300 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
299 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
301 )
300 )
302 return WSGIController.__call__(self, environ, start_response)
301 return WSGIController.__call__(self, environ, start_response)
303 finally:
302 finally:
304 meta.Session.remove()
303 meta.Session.remove()
305
304
306
305
307 class BaseRepoController(BaseController):
306 class BaseRepoController(BaseController):
308 """
307 """
309 Base class for controllers responsible for loading all needed data for
308 Base class for controllers responsible for loading all needed data for
310 repository loaded items are
309 repository loaded items are
311
310
312 c.rhodecode_repo: instance of scm repository
311 c.rhodecode_repo: instance of scm repository
313 c.rhodecode_db_repo: instance of db
312 c.rhodecode_db_repo: instance of db
314 c.repository_followers: number of followers
313 c.repository_followers: number of followers
315 c.repository_forks: number of forks
314 c.repository_forks: number of forks
316 c.repository_following: weather the current user is following the current repo
315 c.repository_following: weather the current user is following the current repo
317 """
316 """
318
317
319 def __before__(self):
318 def __before__(self):
320 super(BaseRepoController, self).__before__()
319 super(BaseRepoController, self).__before__()
321 if c.repo_name:
320 if c.repo_name:
322
321
323 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
322 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
324 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
323 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
325 # update last change according to VCS data
324 # update last change according to VCS data
326 dbr.update_changeset_cache(dbr.get_changeset())
325 dbr.update_changeset_cache(dbr.get_changeset())
327 if c.rhodecode_repo is None:
326 if c.rhodecode_repo is None:
328 log.error('%s this repository is present in database but it '
327 log.error('%s this repository is present in database but it '
329 'cannot be created as an scm instance', c.repo_name)
328 'cannot be created as an scm instance', c.repo_name)
330
329
331 redirect(url('home'))
330 redirect(url('home'))
332
331
333 # some globals counter for menu
332 # some globals counter for menu
334 c.repository_followers = self.scm_model.get_followers(dbr)
333 c.repository_followers = self.scm_model.get_followers(dbr)
335 c.repository_forks = self.scm_model.get_forks(dbr)
334 c.repository_forks = self.scm_model.get_forks(dbr)
336 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
335 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
337 c.repository_following = self.scm_model.is_following_repo(c.repo_name,
336 c.repository_following = self.scm_model.is_following_repo(c.repo_name,
338 self.rhodecode_user.user_id)
337 self.rhodecode_user.user_id)
@@ -1,673 +1,674 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import re
27 import re
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31 import cStringIO
31 import cStringIO
32 import pkg_resources
32 import pkg_resources
33 from os.path import dirname as dn, join as jn
33 from os.path import dirname as dn, join as jn
34
34
35 from sqlalchemy import func
35 from sqlalchemy import func
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs.exceptions import RepositoryError
40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.nodes import FileNode
42 from rhodecode.lib.vcs.nodes import FileNode
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44
44
45 from rhodecode import BACKENDS
45 from rhodecode import BACKENDS
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
48 _set_extras
48 _set_extras
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
50 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
50 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
51 action_logger, REMOVED_REPO_PAT
51 action_logger, REMOVED_REPO_PAT
52 from rhodecode.model import BaseModel
52 from rhodecode.model import BaseModel
53 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
53 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
54 UserFollowing, UserLog, User, RepoGroup, PullRequest
54 UserFollowing, UserLog, User, RepoGroup, PullRequest
55 from rhodecode.lib.hooks import log_push_action
55 from rhodecode.lib.hooks import log_push_action
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class UserTemp(object):
60 class UserTemp(object):
61 def __init__(self, user_id):
61 def __init__(self, user_id):
62 self.user_id = user_id
62 self.user_id = user_id
63
63
64 def __repr__(self):
64 def __repr__(self):
65 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
65 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
66
66
67
67
68 class RepoTemp(object):
68 class RepoTemp(object):
69 def __init__(self, repo_id):
69 def __init__(self, repo_id):
70 self.repo_id = repo_id
70 self.repo_id = repo_id
71
71
72 def __repr__(self):
72 def __repr__(self):
73 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
73 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
74
74
75
75
76 class CachedRepoList(object):
76 class CachedRepoList(object):
77 """
77 """
78 Cached repo list, uses in-memory cache after initialization, that is
78 Cached repo list, uses in-memory cache after initialization, that is
79 super fast
79 super fast
80 """
80 """
81
81
82 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
82 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
83 self.db_repo_list = db_repo_list
83 self.db_repo_list = db_repo_list
84 self.repos_path = repos_path
84 self.repos_path = repos_path
85 self.order_by = order_by
85 self.order_by = order_by
86 self.reversed = (order_by or '').startswith('-')
86 self.reversed = (order_by or '').startswith('-')
87 if not perm_set:
87 if not perm_set:
88 perm_set = ['repository.read', 'repository.write',
88 perm_set = ['repository.read', 'repository.write',
89 'repository.admin']
89 'repository.admin']
90 self.perm_set = perm_set
90 self.perm_set = perm_set
91
91
92 def __len__(self):
92 def __len__(self):
93 return len(self.db_repo_list)
93 return len(self.db_repo_list)
94
94
95 def __repr__(self):
95 def __repr__(self):
96 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
96 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
97
97
98 def __iter__(self):
98 def __iter__(self):
99 # pre-propagated cache_map to save executing select statements
99 # pre-propagated cache_map to save executing select statements
100 # for each repo
100 # for each repo
101 cache_map = CacheInvalidation.get_cache_map()
101 cache_map = CacheInvalidation.get_cache_map()
102
102
103 for dbr in self.db_repo_list:
103 for dbr in self.db_repo_list:
104 scmr = dbr.scm_instance_cached(cache_map)
104 scmr = dbr.scm_instance_cached(cache_map)
105 # check permission at this level
105 # check permission at this level
106 if not HasRepoPermissionAny(
106 if not HasRepoPermissionAny(
107 *self.perm_set
107 *self.perm_set
108 )(dbr.repo_name, 'get repo check'):
108 )(dbr.repo_name, 'get repo check'):
109 continue
109 continue
110
110
111 try:
111 try:
112 last_change = scmr.last_change
112 last_change = scmr.last_change
113 tip = h.get_changeset_safe(scmr, 'tip')
113 tip = h.get_changeset_safe(scmr, 'tip')
114 except Exception:
114 except Exception:
115 log.error(
115 log.error(
116 '%s this repository is present in database but it '
116 '%s this repository is present in database but it '
117 'cannot be created as an scm instance, org_exc:%s'
117 'cannot be created as an scm instance, org_exc:%s'
118 % (dbr.repo_name, traceback.format_exc())
118 % (dbr.repo_name, traceback.format_exc())
119 )
119 )
120 continue
120 continue
121
121
122 tmp_d = {}
122 tmp_d = {}
123 tmp_d['name'] = dbr.repo_name
123 tmp_d['name'] = dbr.repo_name
124 tmp_d['name_sort'] = tmp_d['name'].lower()
124 tmp_d['name_sort'] = tmp_d['name'].lower()
125 tmp_d['raw_name'] = tmp_d['name'].lower()
125 tmp_d['raw_name'] = tmp_d['name'].lower()
126 tmp_d['description'] = dbr.description
126 tmp_d['description'] = dbr.description
127 tmp_d['description_sort'] = tmp_d['description'].lower()
127 tmp_d['description_sort'] = tmp_d['description'].lower()
128 tmp_d['last_change'] = last_change
128 tmp_d['last_change'] = last_change
129 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
129 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
130 tmp_d['tip'] = tip.raw_id
130 tmp_d['tip'] = tip.raw_id
131 tmp_d['tip_sort'] = tip.revision
131 tmp_d['tip_sort'] = tip.revision
132 tmp_d['rev'] = tip.revision
132 tmp_d['rev'] = tip.revision
133 tmp_d['contact'] = dbr.user.full_contact
133 tmp_d['contact'] = dbr.user.full_contact
134 tmp_d['contact_sort'] = tmp_d['contact']
134 tmp_d['contact_sort'] = tmp_d['contact']
135 tmp_d['owner_sort'] = tmp_d['contact']
135 tmp_d['owner_sort'] = tmp_d['contact']
136 tmp_d['repo_archives'] = list(scmr._get_archives())
136 tmp_d['repo_archives'] = list(scmr._get_archives())
137 tmp_d['last_msg'] = tip.message
137 tmp_d['last_msg'] = tip.message
138 tmp_d['author'] = tip.author
138 tmp_d['author'] = tip.author
139 tmp_d['dbrepo'] = dbr.get_dict()
139 tmp_d['dbrepo'] = dbr.get_dict()
140 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
140 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
141 yield tmp_d
141 yield tmp_d
142
142
143
143
144 class SimpleCachedRepoList(CachedRepoList):
144 class SimpleCachedRepoList(CachedRepoList):
145 """
145 """
146 Lighter version of CachedRepoList without the scm initialisation
146 Lighter version of CachedRepoList without the scm initialisation
147 """
147 """
148
148
149 def __iter__(self):
149 def __iter__(self):
150 for dbr in self.db_repo_list:
150 for dbr in self.db_repo_list:
151 # check permission at this level
151 # check permission at this level
152 if not HasRepoPermissionAny(
152 if not HasRepoPermissionAny(
153 *self.perm_set
153 *self.perm_set
154 )(dbr.repo_name, 'get repo check'):
154 )(dbr.repo_name, 'get repo check'):
155 continue
155 continue
156
156
157 tmp_d = {}
157 tmp_d = {}
158 tmp_d['name'] = dbr.repo_name
158 tmp_d['name'] = dbr.repo_name
159 tmp_d['name_sort'] = tmp_d['name'].lower()
159 tmp_d['name_sort'] = tmp_d['name'].lower()
160 tmp_d['raw_name'] = tmp_d['name'].lower()
160 tmp_d['raw_name'] = tmp_d['name'].lower()
161 tmp_d['description'] = dbr.description
161 tmp_d['description'] = dbr.description
162 tmp_d['description_sort'] = tmp_d['description'].lower()
162 tmp_d['description_sort'] = tmp_d['description'].lower()
163 tmp_d['dbrepo'] = dbr.get_dict()
163 tmp_d['dbrepo'] = dbr.get_dict()
164 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
164 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
165 yield tmp_d
165 yield tmp_d
166
166
167
167
168 class GroupList(object):
168 class GroupList(object):
169
169
170 def __init__(self, db_repo_group_list, perm_set=None):
170 def __init__(self, db_repo_group_list, perm_set=None):
171 """
171 """
172 Creates iterator from given list of group objects, additionally
172 Creates iterator from given list of group objects, additionally
173 checking permission for them from perm_set var
173 checking permission for them from perm_set var
174
174
175 :param db_repo_group_list:
175 :param db_repo_group_list:
176 :param perm_set: list of permissons to check
176 :param perm_set: list of permissons to check
177 """
177 """
178 self.db_repo_group_list = db_repo_group_list
178 self.db_repo_group_list = db_repo_group_list
179 if not perm_set:
179 if not perm_set:
180 perm_set = ['group.read', 'group.write', 'group.admin']
180 perm_set = ['group.read', 'group.write', 'group.admin']
181 self.perm_set = perm_set
181 self.perm_set = perm_set
182
182
183 def __len__(self):
183 def __len__(self):
184 return len(self.db_repo_group_list)
184 return len(self.db_repo_group_list)
185
185
186 def __repr__(self):
186 def __repr__(self):
187 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
187 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
188
188
189 def __iter__(self):
189 def __iter__(self):
190 for dbgr in self.db_repo_group_list:
190 for dbgr in self.db_repo_group_list:
191 # check permission at this level
191 # check permission at this level
192 if not HasReposGroupPermissionAny(
192 if not HasReposGroupPermissionAny(
193 *self.perm_set
193 *self.perm_set
194 )(dbgr.group_name, 'get group repo check'):
194 )(dbgr.group_name, 'get group repo check'):
195 continue
195 continue
196
196
197 yield dbgr
197 yield dbgr
198
198
199
199
200 class ScmModel(BaseModel):
200 class ScmModel(BaseModel):
201 """
201 """
202 Generic Scm Model
202 Generic Scm Model
203 """
203 """
204
204
205 def __get_repo(self, instance):
205 def __get_repo(self, instance):
206 cls = Repository
206 cls = Repository
207 if isinstance(instance, cls):
207 if isinstance(instance, cls):
208 return instance
208 return instance
209 elif isinstance(instance, int) or safe_str(instance).isdigit():
209 elif isinstance(instance, int) or safe_str(instance).isdigit():
210 return cls.get(instance)
210 return cls.get(instance)
211 elif isinstance(instance, basestring):
211 elif isinstance(instance, basestring):
212 return cls.get_by_repo_name(instance)
212 return cls.get_by_repo_name(instance)
213 elif instance:
213 elif instance:
214 raise Exception('given object must be int, basestr or Instance'
214 raise Exception('given object must be int, basestr or Instance'
215 ' of %s got %s' % (type(cls), type(instance)))
215 ' of %s got %s' % (type(cls), type(instance)))
216
216
217 @LazyProperty
217 @LazyProperty
218 def repos_path(self):
218 def repos_path(self):
219 """
219 """
220 Get's the repositories root path from database
220 Get's the repositories root path from database
221 """
221 """
222
222
223 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
223 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
224
224
225 return q.ui_value
225 return q.ui_value
226
226
227 def repo_scan(self, repos_path=None):
227 def repo_scan(self, repos_path=None):
228 """
228 """
229 Listing of repositories in given path. This path should not be a
229 Listing of repositories in given path. This path should not be a
230 repository itself. Return a dictionary of repository objects
230 repository itself. Return a dictionary of repository objects
231
231
232 :param repos_path: path to directory containing repositories
232 :param repos_path: path to directory containing repositories
233 """
233 """
234
234
235 if repos_path is None:
235 if repos_path is None:
236 repos_path = self.repos_path
236 repos_path = self.repos_path
237
237
238 log.info('scanning for repositories in %s' % repos_path)
238 log.info('scanning for repositories in %s' % repos_path)
239
239
240 baseui = make_ui('db')
240 baseui = make_ui('db')
241 repos = {}
241 repos = {}
242
242
243 for name, path in get_filesystem_repos(repos_path, recursive=True):
243 for name, path in get_filesystem_repos(repos_path, recursive=True):
244 # name need to be decomposed and put back together using the /
244 # name need to be decomposed and put back together using the /
245 # since this is internal storage separator for rhodecode
245 # since this is internal storage separator for rhodecode
246 name = Repository.normalize_repo_name(name)
246 name = Repository.normalize_repo_name(name)
247
247
248 try:
248 try:
249 if name in repos:
249 if name in repos:
250 raise RepositoryError('Duplicate repository name %s '
250 raise RepositoryError('Duplicate repository name %s '
251 'found in %s' % (name, path))
251 'found in %s' % (name, path))
252 else:
252 else:
253
253
254 klass = get_backend(path[0])
254 klass = get_backend(path[0])
255
255
256 if path[0] == 'hg' and path[0] in BACKENDS.keys():
256 if path[0] == 'hg' and path[0] in BACKENDS.keys():
257 repos[name] = klass(safe_str(path[1]), baseui=baseui)
257 repos[name] = klass(safe_str(path[1]), baseui=baseui)
258
258
259 if path[0] == 'git' and path[0] in BACKENDS.keys():
259 if path[0] == 'git' and path[0] in BACKENDS.keys():
260 repos[name] = klass(path[1])
260 repos[name] = klass(path[1])
261 except OSError:
261 except OSError:
262 continue
262 continue
263 log.debug('found %s paths with repositories' % (len(repos)))
263 log.debug('found %s paths with repositories' % (len(repos)))
264 return repos
264 return repos
265
265
266 def get_repos(self, all_repos=None, sort_key=None, simple=False):
266 def get_repos(self, all_repos=None, sort_key=None, simple=False):
267 """
267 """
268 Get all repos from db and for each repo create it's
268 Get all repos from db and for each repo create it's
269 backend instance and fill that backed with information from database
269 backend instance and fill that backed with information from database
270
270
271 :param all_repos: list of repository names as strings
271 :param all_repos: list of repository names as strings
272 give specific repositories list, good for filtering
272 give specific repositories list, good for filtering
273
273
274 :param sort_key: initial sorting of repos
274 :param sort_key: initial sorting of repos
275 :param simple: use SimpleCachedList - one without the SCM info
275 :param simple: use SimpleCachedList - one without the SCM info
276 """
276 """
277 if all_repos is None:
277 if all_repos is None:
278 all_repos = self.sa.query(Repository)\
278 all_repos = self.sa.query(Repository)\
279 .filter(Repository.group_id == None)\
279 .filter(Repository.group_id == None)\
280 .order_by(func.lower(Repository.repo_name)).all()
280 .order_by(func.lower(Repository.repo_name)).all()
281 if simple:
281 if simple:
282 repo_iter = SimpleCachedRepoList(all_repos,
282 repo_iter = SimpleCachedRepoList(all_repos,
283 repos_path=self.repos_path,
283 repos_path=self.repos_path,
284 order_by=sort_key)
284 order_by=sort_key)
285 else:
285 else:
286 repo_iter = CachedRepoList(all_repos,
286 repo_iter = CachedRepoList(all_repos,
287 repos_path=self.repos_path,
287 repos_path=self.repos_path,
288 order_by=sort_key)
288 order_by=sort_key)
289
289
290 return repo_iter
290 return repo_iter
291
291
292 def get_repos_groups(self, all_groups=None):
292 def get_repos_groups(self, all_groups=None):
293 if all_groups is None:
293 if all_groups is None:
294 all_groups = RepoGroup.query()\
294 all_groups = RepoGroup.query()\
295 .filter(RepoGroup.group_parent_id == None).all()
295 .filter(RepoGroup.group_parent_id == None).all()
296 return [x for x in GroupList(all_groups)]
296 return [x for x in GroupList(all_groups)]
297
297
298 def mark_for_invalidation(self, repo_name):
298 def mark_for_invalidation(self, repo_name):
299 """
299 """
300 Puts cache invalidation task into db for
300 Puts cache invalidation task into db for
301 further global cache invalidation
301 further global cache invalidation
302
302
303 :param repo_name: this repo that should invalidation take place
303 :param repo_name: this repo that should invalidation take place
304 """
304 """
305 invalidated_keys = CacheInvalidation.set_invalidate(repo_name=repo_name)
305 invalidated_keys = CacheInvalidation.set_invalidate(repo_name=repo_name)
306 repo = Repository.get_by_repo_name(repo_name)
306 repo = Repository.get_by_repo_name(repo_name)
307 if repo:
307 if repo:
308 repo.update_changeset_cache()
308 repo.update_changeset_cache()
309 return invalidated_keys
309 return invalidated_keys
310
310
311 def toggle_following_repo(self, follow_repo_id, user_id):
311 def toggle_following_repo(self, follow_repo_id, user_id):
312
312
313 f = self.sa.query(UserFollowing)\
313 f = self.sa.query(UserFollowing)\
314 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
314 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
315 .filter(UserFollowing.user_id == user_id).scalar()
315 .filter(UserFollowing.user_id == user_id).scalar()
316
316
317 if f is not None:
317 if f is not None:
318 try:
318 try:
319 self.sa.delete(f)
319 self.sa.delete(f)
320 action_logger(UserTemp(user_id),
320 action_logger(UserTemp(user_id),
321 'stopped_following_repo',
321 'stopped_following_repo',
322 RepoTemp(follow_repo_id))
322 RepoTemp(follow_repo_id))
323 return
323 return
324 except:
324 except:
325 log.error(traceback.format_exc())
325 log.error(traceback.format_exc())
326 raise
326 raise
327
327
328 try:
328 try:
329 f = UserFollowing()
329 f = UserFollowing()
330 f.user_id = user_id
330 f.user_id = user_id
331 f.follows_repo_id = follow_repo_id
331 f.follows_repo_id = follow_repo_id
332 self.sa.add(f)
332 self.sa.add(f)
333
333
334 action_logger(UserTemp(user_id),
334 action_logger(UserTemp(user_id),
335 'started_following_repo',
335 'started_following_repo',
336 RepoTemp(follow_repo_id))
336 RepoTemp(follow_repo_id))
337 except:
337 except:
338 log.error(traceback.format_exc())
338 log.error(traceback.format_exc())
339 raise
339 raise
340
340
341 def toggle_following_user(self, follow_user_id, user_id):
341 def toggle_following_user(self, follow_user_id, user_id):
342 f = self.sa.query(UserFollowing)\
342 f = self.sa.query(UserFollowing)\
343 .filter(UserFollowing.follows_user_id == follow_user_id)\
343 .filter(UserFollowing.follows_user_id == follow_user_id)\
344 .filter(UserFollowing.user_id == user_id).scalar()
344 .filter(UserFollowing.user_id == user_id).scalar()
345
345
346 if f is not None:
346 if f is not None:
347 try:
347 try:
348 self.sa.delete(f)
348 self.sa.delete(f)
349 return
349 return
350 except:
350 except:
351 log.error(traceback.format_exc())
351 log.error(traceback.format_exc())
352 raise
352 raise
353
353
354 try:
354 try:
355 f = UserFollowing()
355 f = UserFollowing()
356 f.user_id = user_id
356 f.user_id = user_id
357 f.follows_user_id = follow_user_id
357 f.follows_user_id = follow_user_id
358 self.sa.add(f)
358 self.sa.add(f)
359 except:
359 except:
360 log.error(traceback.format_exc())
360 log.error(traceback.format_exc())
361 raise
361 raise
362
362
363 def is_following_repo(self, repo_name, user_id, cache=False):
363 def is_following_repo(self, repo_name, user_id, cache=False):
364 r = self.sa.query(Repository)\
364 r = self.sa.query(Repository)\
365 .filter(Repository.repo_name == repo_name).scalar()
365 .filter(Repository.repo_name == repo_name).scalar()
366
366
367 f = self.sa.query(UserFollowing)\
367 f = self.sa.query(UserFollowing)\
368 .filter(UserFollowing.follows_repository == r)\
368 .filter(UserFollowing.follows_repository == r)\
369 .filter(UserFollowing.user_id == user_id).scalar()
369 .filter(UserFollowing.user_id == user_id).scalar()
370
370
371 return f is not None
371 return f is not None
372
372
373 def is_following_user(self, username, user_id, cache=False):
373 def is_following_user(self, username, user_id, cache=False):
374 u = User.get_by_username(username)
374 u = User.get_by_username(username)
375
375
376 f = self.sa.query(UserFollowing)\
376 f = self.sa.query(UserFollowing)\
377 .filter(UserFollowing.follows_user == u)\
377 .filter(UserFollowing.follows_user == u)\
378 .filter(UserFollowing.user_id == user_id).scalar()
378 .filter(UserFollowing.user_id == user_id).scalar()
379
379
380 return f is not None
380 return f is not None
381
381
382 def get_followers(self, repo):
382 def get_followers(self, repo):
383 repo = self._get_repo(repo)
383 repo = self._get_repo(repo)
384
384
385 return self.sa.query(UserFollowing)\
385 return self.sa.query(UserFollowing)\
386 .filter(UserFollowing.follows_repository == repo).count()
386 .filter(UserFollowing.follows_repository == repo).count()
387
387
388 def get_forks(self, repo):
388 def get_forks(self, repo):
389 repo = self._get_repo(repo)
389 repo = self._get_repo(repo)
390 return self.sa.query(Repository)\
390 return self.sa.query(Repository)\
391 .filter(Repository.fork == repo).count()
391 .filter(Repository.fork == repo).count()
392
392
393 def get_pull_requests(self, repo):
393 def get_pull_requests(self, repo):
394 repo = self._get_repo(repo)
394 repo = self._get_repo(repo)
395 return self.sa.query(PullRequest)\
395 return self.sa.query(PullRequest)\
396 .filter(PullRequest.other_repo == repo).count()
396 .filter(PullRequest.other_repo == repo)\
397 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
397
398
398 def mark_as_fork(self, repo, fork, user):
399 def mark_as_fork(self, repo, fork, user):
399 repo = self.__get_repo(repo)
400 repo = self.__get_repo(repo)
400 fork = self.__get_repo(fork)
401 fork = self.__get_repo(fork)
401 if fork and repo.repo_id == fork.repo_id:
402 if fork and repo.repo_id == fork.repo_id:
402 raise Exception("Cannot set repository as fork of itself")
403 raise Exception("Cannot set repository as fork of itself")
403 repo.fork = fork
404 repo.fork = fork
404 self.sa.add(repo)
405 self.sa.add(repo)
405 return repo
406 return repo
406
407
407 def _handle_push(self, repo, username, action, repo_name, revisions):
408 def _handle_push(self, repo, username, action, repo_name, revisions):
408 """
409 """
409 Triggers push action hooks
410 Triggers push action hooks
410
411
411 :param repo: SCM repo
412 :param repo: SCM repo
412 :param username: username who pushes
413 :param username: username who pushes
413 :param action: push/push_loca/push_remote
414 :param action: push/push_loca/push_remote
414 :param repo_name: name of repo
415 :param repo_name: name of repo
415 :param revisions: list of revisions that we pushed
416 :param revisions: list of revisions that we pushed
416 """
417 """
417 from rhodecode import CONFIG
418 from rhodecode import CONFIG
418 from rhodecode.lib.base import _get_ip_addr
419 from rhodecode.lib.base import _get_ip_addr
419 try:
420 try:
420 from pylons import request
421 from pylons import request
421 environ = request.environ
422 environ = request.environ
422 except TypeError:
423 except TypeError:
423 # we might use this outside of request context, let's fake the
424 # we might use this outside of request context, let's fake the
424 # environ data
425 # environ data
425 from webob import Request
426 from webob import Request
426 environ = Request.blank('').environ
427 environ = Request.blank('').environ
427
428
428 #trigger push hook
429 #trigger push hook
429 extras = {
430 extras = {
430 'ip': _get_ip_addr(environ),
431 'ip': _get_ip_addr(environ),
431 'username': username,
432 'username': username,
432 'action': 'push_local',
433 'action': 'push_local',
433 'repository': repo_name,
434 'repository': repo_name,
434 'scm': repo.alias,
435 'scm': repo.alias,
435 'config': CONFIG['__file__'],
436 'config': CONFIG['__file__'],
436 'server_url': get_server_url(environ),
437 'server_url': get_server_url(environ),
437 'make_lock': None,
438 'make_lock': None,
438 'locked_by': [None, None]
439 'locked_by': [None, None]
439 }
440 }
440 _scm_repo = repo._repo
441 _scm_repo = repo._repo
441 _set_extras(extras)
442 _set_extras(extras)
442 if repo.alias == 'hg':
443 if repo.alias == 'hg':
443 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
444 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
444 elif repo.alias == 'git':
445 elif repo.alias == 'git':
445 log_push_action(_scm_repo.ui, _scm_repo, _git_revs=revisions)
446 log_push_action(_scm_repo.ui, _scm_repo, _git_revs=revisions)
446
447
447 def _get_IMC_module(self, scm_type):
448 def _get_IMC_module(self, scm_type):
448 """
449 """
449 Returns InMemoryCommit class based on scm_type
450 Returns InMemoryCommit class based on scm_type
450
451
451 :param scm_type:
452 :param scm_type:
452 """
453 """
453 if scm_type == 'hg':
454 if scm_type == 'hg':
454 from rhodecode.lib.vcs.backends.hg import \
455 from rhodecode.lib.vcs.backends.hg import \
455 MercurialInMemoryChangeset as IMC
456 MercurialInMemoryChangeset as IMC
456 elif scm_type == 'git':
457 elif scm_type == 'git':
457 from rhodecode.lib.vcs.backends.git import \
458 from rhodecode.lib.vcs.backends.git import \
458 GitInMemoryChangeset as IMC
459 GitInMemoryChangeset as IMC
459 return IMC
460 return IMC
460
461
461 def pull_changes(self, repo, username):
462 def pull_changes(self, repo, username):
462 dbrepo = self.__get_repo(repo)
463 dbrepo = self.__get_repo(repo)
463 clone_uri = dbrepo.clone_uri
464 clone_uri = dbrepo.clone_uri
464 if not clone_uri:
465 if not clone_uri:
465 raise Exception("This repository doesn't have a clone uri")
466 raise Exception("This repository doesn't have a clone uri")
466
467
467 repo = dbrepo.scm_instance
468 repo = dbrepo.scm_instance
468 repo_name = dbrepo.repo_name
469 repo_name = dbrepo.repo_name
469 try:
470 try:
470 if repo.alias == 'git':
471 if repo.alias == 'git':
471 repo.fetch(clone_uri)
472 repo.fetch(clone_uri)
472 else:
473 else:
473 repo.pull(clone_uri)
474 repo.pull(clone_uri)
474 self.mark_for_invalidation(repo_name)
475 self.mark_for_invalidation(repo_name)
475 except:
476 except:
476 log.error(traceback.format_exc())
477 log.error(traceback.format_exc())
477 raise
478 raise
478
479
479 def commit_change(self, repo, repo_name, cs, user, author, message,
480 def commit_change(self, repo, repo_name, cs, user, author, message,
480 content, f_path):
481 content, f_path):
481 """
482 """
482 Commits changes
483 Commits changes
483
484
484 :param repo: SCM instance
485 :param repo: SCM instance
485
486
486 """
487 """
487 user = self._get_user(user)
488 user = self._get_user(user)
488 IMC = self._get_IMC_module(repo.alias)
489 IMC = self._get_IMC_module(repo.alias)
489
490
490 # decoding here will force that we have proper encoded values
491 # decoding here will force that we have proper encoded values
491 # in any other case this will throw exceptions and deny commit
492 # in any other case this will throw exceptions and deny commit
492 content = safe_str(content)
493 content = safe_str(content)
493 path = safe_str(f_path)
494 path = safe_str(f_path)
494 # message and author needs to be unicode
495 # message and author needs to be unicode
495 # proper backend should then translate that into required type
496 # proper backend should then translate that into required type
496 message = safe_unicode(message)
497 message = safe_unicode(message)
497 author = safe_unicode(author)
498 author = safe_unicode(author)
498 m = IMC(repo)
499 m = IMC(repo)
499 m.change(FileNode(path, content))
500 m.change(FileNode(path, content))
500 tip = m.commit(message=message,
501 tip = m.commit(message=message,
501 author=author,
502 author=author,
502 parents=[cs], branch=cs.branch)
503 parents=[cs], branch=cs.branch)
503
504
504 self.mark_for_invalidation(repo_name)
505 self.mark_for_invalidation(repo_name)
505 self._handle_push(repo,
506 self._handle_push(repo,
506 username=user.username,
507 username=user.username,
507 action='push_local',
508 action='push_local',
508 repo_name=repo_name,
509 repo_name=repo_name,
509 revisions=[tip.raw_id])
510 revisions=[tip.raw_id])
510 return tip
511 return tip
511
512
512 def create_node(self, repo, repo_name, cs, user, author, message, content,
513 def create_node(self, repo, repo_name, cs, user, author, message, content,
513 f_path):
514 f_path):
514 user = self._get_user(user)
515 user = self._get_user(user)
515 IMC = self._get_IMC_module(repo.alias)
516 IMC = self._get_IMC_module(repo.alias)
516
517
517 # decoding here will force that we have proper encoded values
518 # decoding here will force that we have proper encoded values
518 # in any other case this will throw exceptions and deny commit
519 # in any other case this will throw exceptions and deny commit
519 if isinstance(content, (basestring,)):
520 if isinstance(content, (basestring,)):
520 content = safe_str(content)
521 content = safe_str(content)
521 elif isinstance(content, (file, cStringIO.OutputType,)):
522 elif isinstance(content, (file, cStringIO.OutputType,)):
522 content = content.read()
523 content = content.read()
523 else:
524 else:
524 raise Exception('Content is of unrecognized type %s' % (
525 raise Exception('Content is of unrecognized type %s' % (
525 type(content)
526 type(content)
526 ))
527 ))
527
528
528 message = safe_unicode(message)
529 message = safe_unicode(message)
529 author = safe_unicode(author)
530 author = safe_unicode(author)
530 path = safe_str(f_path)
531 path = safe_str(f_path)
531 m = IMC(repo)
532 m = IMC(repo)
532
533
533 if isinstance(cs, EmptyChangeset):
534 if isinstance(cs, EmptyChangeset):
534 # EmptyChangeset means we we're editing empty repository
535 # EmptyChangeset means we we're editing empty repository
535 parents = None
536 parents = None
536 else:
537 else:
537 parents = [cs]
538 parents = [cs]
538
539
539 m.add(FileNode(path, content=content))
540 m.add(FileNode(path, content=content))
540 tip = m.commit(message=message,
541 tip = m.commit(message=message,
541 author=author,
542 author=author,
542 parents=parents, branch=cs.branch)
543 parents=parents, branch=cs.branch)
543
544
544 self.mark_for_invalidation(repo_name)
545 self.mark_for_invalidation(repo_name)
545 self._handle_push(repo,
546 self._handle_push(repo,
546 username=user.username,
547 username=user.username,
547 action='push_local',
548 action='push_local',
548 repo_name=repo_name,
549 repo_name=repo_name,
549 revisions=[tip.raw_id])
550 revisions=[tip.raw_id])
550 return tip
551 return tip
551
552
552 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
553 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
553 """
554 """
554 recursive walk in root dir and return a set of all path in that dir
555 recursive walk in root dir and return a set of all path in that dir
555 based on repository walk function
556 based on repository walk function
556
557
557 :param repo_name: name of repository
558 :param repo_name: name of repository
558 :param revision: revision for which to list nodes
559 :param revision: revision for which to list nodes
559 :param root_path: root path to list
560 :param root_path: root path to list
560 :param flat: return as a list, if False returns a dict with decription
561 :param flat: return as a list, if False returns a dict with decription
561
562
562 """
563 """
563 _files = list()
564 _files = list()
564 _dirs = list()
565 _dirs = list()
565 try:
566 try:
566 _repo = self.__get_repo(repo_name)
567 _repo = self.__get_repo(repo_name)
567 changeset = _repo.scm_instance.get_changeset(revision)
568 changeset = _repo.scm_instance.get_changeset(revision)
568 root_path = root_path.lstrip('/')
569 root_path = root_path.lstrip('/')
569 for topnode, dirs, files in changeset.walk(root_path):
570 for topnode, dirs, files in changeset.walk(root_path):
570 for f in files:
571 for f in files:
571 _files.append(f.path if flat else {"name": f.path,
572 _files.append(f.path if flat else {"name": f.path,
572 "type": "file"})
573 "type": "file"})
573 for d in dirs:
574 for d in dirs:
574 _dirs.append(d.path if flat else {"name": d.path,
575 _dirs.append(d.path if flat else {"name": d.path,
575 "type": "dir"})
576 "type": "dir"})
576 except RepositoryError:
577 except RepositoryError:
577 log.debug(traceback.format_exc())
578 log.debug(traceback.format_exc())
578 raise
579 raise
579
580
580 return _dirs, _files
581 return _dirs, _files
581
582
582 def get_unread_journal(self):
583 def get_unread_journal(self):
583 return self.sa.query(UserLog).count()
584 return self.sa.query(UserLog).count()
584
585
585 def get_repo_landing_revs(self, repo=None):
586 def get_repo_landing_revs(self, repo=None):
586 """
587 """
587 Generates select option with tags branches and bookmarks (for hg only)
588 Generates select option with tags branches and bookmarks (for hg only)
588 grouped by type
589 grouped by type
589
590
590 :param repo:
591 :param repo:
591 :type repo:
592 :type repo:
592 """
593 """
593
594
594 hist_l = []
595 hist_l = []
595 choices = []
596 choices = []
596 repo = self.__get_repo(repo)
597 repo = self.__get_repo(repo)
597 hist_l.append(['tip', _('latest tip')])
598 hist_l.append(['tip', _('latest tip')])
598 choices.append('tip')
599 choices.append('tip')
599 if not repo:
600 if not repo:
600 return choices, hist_l
601 return choices, hist_l
601
602
602 repo = repo.scm_instance
603 repo = repo.scm_instance
603
604
604 branches_group = ([(k, k) for k, v in
605 branches_group = ([(k, k) for k, v in
605 repo.branches.iteritems()], _("Branches"))
606 repo.branches.iteritems()], _("Branches"))
606 hist_l.append(branches_group)
607 hist_l.append(branches_group)
607 choices.extend([x[0] for x in branches_group[0]])
608 choices.extend([x[0] for x in branches_group[0]])
608
609
609 if repo.alias == 'hg':
610 if repo.alias == 'hg':
610 bookmarks_group = ([(k, k) for k, v in
611 bookmarks_group = ([(k, k) for k, v in
611 repo.bookmarks.iteritems()], _("Bookmarks"))
612 repo.bookmarks.iteritems()], _("Bookmarks"))
612 hist_l.append(bookmarks_group)
613 hist_l.append(bookmarks_group)
613 choices.extend([x[0] for x in bookmarks_group[0]])
614 choices.extend([x[0] for x in bookmarks_group[0]])
614
615
615 tags_group = ([(k, k) for k, v in
616 tags_group = ([(k, k) for k, v in
616 repo.tags.iteritems()], _("Tags"))
617 repo.tags.iteritems()], _("Tags"))
617 hist_l.append(tags_group)
618 hist_l.append(tags_group)
618 choices.extend([x[0] for x in tags_group[0]])
619 choices.extend([x[0] for x in tags_group[0]])
619
620
620 return choices, hist_l
621 return choices, hist_l
621
622
622 def install_git_hook(self, repo, force_create=False):
623 def install_git_hook(self, repo, force_create=False):
623 """
624 """
624 Creates a rhodecode hook inside a git repository
625 Creates a rhodecode hook inside a git repository
625
626
626 :param repo: Instance of VCS repo
627 :param repo: Instance of VCS repo
627 :param force_create: Create even if same name hook exists
628 :param force_create: Create even if same name hook exists
628 """
629 """
629
630
630 loc = jn(repo.path, 'hooks')
631 loc = jn(repo.path, 'hooks')
631 if not repo.bare:
632 if not repo.bare:
632 loc = jn(repo.path, '.git', 'hooks')
633 loc = jn(repo.path, '.git', 'hooks')
633 if not os.path.isdir(loc):
634 if not os.path.isdir(loc):
634 os.makedirs(loc)
635 os.makedirs(loc)
635
636
636 tmpl_post = pkg_resources.resource_string(
637 tmpl_post = pkg_resources.resource_string(
637 'rhodecode', jn('config', 'post_receive_tmpl.py')
638 'rhodecode', jn('config', 'post_receive_tmpl.py')
638 )
639 )
639 tmpl_pre = pkg_resources.resource_string(
640 tmpl_pre = pkg_resources.resource_string(
640 'rhodecode', jn('config', 'pre_receive_tmpl.py')
641 'rhodecode', jn('config', 'pre_receive_tmpl.py')
641 )
642 )
642
643
643 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
644 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
644 _hook_file = jn(loc, '%s-receive' % h_type)
645 _hook_file = jn(loc, '%s-receive' % h_type)
645 _rhodecode_hook = False
646 _rhodecode_hook = False
646 log.debug('Installing git hook in repo %s' % repo)
647 log.debug('Installing git hook in repo %s' % repo)
647 if os.path.exists(_hook_file):
648 if os.path.exists(_hook_file):
648 # let's take a look at this hook, maybe it's rhodecode ?
649 # let's take a look at this hook, maybe it's rhodecode ?
649 log.debug('hook exists, checking if it is from rhodecode')
650 log.debug('hook exists, checking if it is from rhodecode')
650 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
651 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
651 with open(_hook_file, 'rb') as f:
652 with open(_hook_file, 'rb') as f:
652 data = f.read()
653 data = f.read()
653 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
654 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
654 % 'RC_HOOK_VER').search(data)
655 % 'RC_HOOK_VER').search(data)
655 if matches:
656 if matches:
656 try:
657 try:
657 ver = matches.groups()[0]
658 ver = matches.groups()[0]
658 log.debug('got %s it is rhodecode' % (ver))
659 log.debug('got %s it is rhodecode' % (ver))
659 _rhodecode_hook = True
660 _rhodecode_hook = True
660 except:
661 except:
661 log.error(traceback.format_exc())
662 log.error(traceback.format_exc())
662 else:
663 else:
663 # there is no hook in this dir, so we want to create one
664 # there is no hook in this dir, so we want to create one
664 _rhodecode_hook = True
665 _rhodecode_hook = True
665
666
666 if _rhodecode_hook or force_create:
667 if _rhodecode_hook or force_create:
667 log.debug('writing %s hook file !' % h_type)
668 log.debug('writing %s hook file !' % h_type)
668 with open(_hook_file, 'wb') as f:
669 with open(_hook_file, 'wb') as f:
669 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
670 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
670 f.write(tmpl)
671 f.write(tmpl)
671 os.chmod(_hook_file, 0755)
672 os.chmod(_hook_file, 0755)
672 else:
673 else:
673 log.debug('skipping writing hook file')
674 log.debug('skipping writing hook file')
@@ -1,209 +1,209 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
10 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
11 &raquo;
11 &raquo;
12 ${_('new pull request')}
12 ${_('new pull request')}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16 ${self.context_bar('showpullrequest')}
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 <div style="float:left;padding:0px 30px 30px 30px">
23 <div style="float:left;padding:0px 30px 30px 30px">
24 ##ORG
24 ##ORG
25 <div style="float:left">
25 <div style="float:left">
26 <div>
26 <div>
27 <span style="font-size: 20px">
27 <span style="font-size: 20px">
28 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref',c.default_org_ref,c.org_refs,class_='refs')}
28 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref',c.default_org_ref,c.org_refs,class_='refs')}
29 </span>
29 </span>
30 <div style="padding:5px 3px 3px 20px;">${c.rhodecode_db_repo.description}</div>
30 <div style="padding:5px 3px 3px 20px;">${c.rhodecode_db_repo.description}</div>
31 </div>
31 </div>
32 <div style="clear:both;padding-top: 10px"></div>
32 <div style="clear:both;padding-top: 10px"></div>
33 </div>
33 </div>
34 <div style="float:left;font-size:24px;padding:0px 20px">
34 <div style="float:left;font-size:24px;padding:0px 20px">
35 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
35 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
36 </div>
36 </div>
37
37
38 ##OTHER, most Probably the PARENT OF THIS FORK
38 ##OTHER, most Probably the PARENT OF THIS FORK
39 <div style="float:left">
39 <div style="float:left">
40 <div>
40 <div>
41 <span style="font-size: 20px">
41 <span style="font-size: 20px">
42 ${h.select('other_repo',c.default_other_repo,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_other_ref,c.default_other_refs,class_='refs')}
42 ${h.select('other_repo',c.default_other_repo,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_other_ref,c.default_other_refs,class_='refs')}
43 </span>
43 </span>
44 <div id="other_repo_desc" style="padding:5px 3px 3px 20px;"></div>
44 <div id="other_repo_desc" style="padding:5px 3px 3px 20px;"></div>
45 </div>
45 </div>
46 <div style="clear:both;padding-top: 10px"></div>
46 <div style="clear:both;padding-top: 10px"></div>
47 </div>
47 </div>
48 <div style="clear:both;padding-top: 10px"></div>
48 <div style="clear:both;padding-top: 10px"></div>
49 ## overview pulled by ajax
49 ## overview pulled by ajax
50 <div style="float:left" id="pull_request_overview"></div>
50 <div style="float:left" id="pull_request_overview"></div>
51 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
51 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
52 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
52 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
53 </div>
53 </div>
54 </div>
54 </div>
55 <div style="float:left; border-left:1px dashed #eee">
55 <div style="float:left; border-left:1px dashed #eee">
56 <h4>${_('Pull request reviewers')}</h4>
56 <h4>${_('Pull request reviewers')}</h4>
57 <div id="reviewers" style="padding:0px 0px 0px 15px">
57 <div id="reviewers" style="padding:0px 0px 0px 15px">
58 ## members goes here !
58 ## members goes here !
59 <div class="group_members_wrap">
59 <div class="group_members_wrap">
60 <ul id="review_members" class="group_members">
60 <ul id="review_members" class="group_members">
61 %for member in c.review_members:
61 %for member in c.review_members:
62 <li id="reviewer_${member.user_id}">
62 <li id="reviewer_${member.user_id}">
63 <div class="reviewers_member">
63 <div class="reviewers_member">
64 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
64 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
65 <div style="float:left">${member.full_name} (${_('owner')})</div>
65 <div style="float:left">${member.full_name} (${_('owner')})</div>
66 <input type="hidden" value="${member.user_id}" name="review_members" />
66 <input type="hidden" value="${member.user_id}" name="review_members" />
67 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
67 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
68 </div>
68 </div>
69 </li>
69 </li>
70 %endfor
70 %endfor
71 </ul>
71 </ul>
72 </div>
72 </div>
73
73
74 <div class='ac'>
74 <div class='ac'>
75 <div class="reviewer_ac">
75 <div class="reviewer_ac">
76 ${h.text('user', class_='yui-ac-input')}
76 ${h.text('user', class_='yui-ac-input')}
77 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
77 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
78 <div id="reviewers_container"></div>
78 <div id="reviewers_container"></div>
79 </div>
79 </div>
80 </div>
80 </div>
81 </div>
81 </div>
82 </div>
82 </div>
83 <h3>${_('Create new pull request')}</h3>
83 <h3>${_('Create new pull request')}</h3>
84
84
85 <div class="form">
85 <div class="form">
86 <!-- fields -->
86 <!-- fields -->
87
87
88 <div class="fields">
88 <div class="fields">
89
89
90 <div class="field">
90 <div class="field">
91 <div class="label">
91 <div class="label">
92 <label for="pullrequest_title">${_('Title')}:</label>
92 <label for="pullrequest_title">${_('Title')}:</label>
93 </div>
93 </div>
94 <div class="input">
94 <div class="input">
95 ${h.text('pullrequest_title',size=30)}
95 ${h.text('pullrequest_title',size=30)}
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 <div class="field">
99 <div class="field">
100 <div class="label label-textarea">
100 <div class="label label-textarea">
101 <label for="pullrequest_desc">${_('Description')}:</label>
101 <label for="pullrequest_desc">${_('Description')}:</label>
102 </div>
102 </div>
103 <div class="textarea text-area editor">
103 <div class="textarea text-area editor">
104 ${h.textarea('pullrequest_desc',size=30)}
104 ${h.textarea('pullrequest_desc',size=30)}
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="buttons">
108 <div class="buttons">
109 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
109 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
110 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
110 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
111 </div>
111 </div>
112 </div>
112 </div>
113 </div>
113 </div>
114 ${h.end_form()}
114 ${h.end_form()}
115
115
116 </div>
116 </div>
117
117
118 <script type="text/javascript">
118 <script type="text/javascript">
119 var _USERS_AC_DATA = ${c.users_array|n};
119 var _USERS_AC_DATA = ${c.users_array|n};
120 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
120 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
121 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
121 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
122
122
123 var other_repos_info = ${c.other_repos_info|n};
123 var other_repos_info = ${c.other_repos_info|n};
124
124
125 var otherrepoChanged = function(){
125 var otherrepoChanged = function(){
126 var sel_box = YUQ('#pull_request_form #other_repo')[0];
126 var sel_box = YUQ('#pull_request_form #other_repo')[0];
127 var repo_name = sel_box.options[sel_box.selectedIndex].value;
127 var repo_name = sel_box.options[sel_box.selectedIndex].value;
128
128
129 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
129 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
130 // replace options of other_ref with the ones for the current other_repo
130 // replace options of other_ref with the ones for the current other_repo
131 var other_ref_selector = YUD.get('other_ref');
131 var other_ref_selector = YUD.get('other_ref');
132 var new_select = YUD.createElementFromMarkup(other_repos_info[repo_name]['revs']);
132 var new_select = YUD.createElementFromMarkup(other_repos_info[repo_name]['revs']);
133 var new_selectedIndex = new_select.selectedIndex;
133 var new_selectedIndex = new_select.selectedIndex;
134 other_ref_selector.innerHTML = ""; // clear old options
134 other_ref_selector.innerHTML = ""; // clear old options
135 while (new_select.length > 0){ // children will be popped when appened to other_ref_selector
135 while (new_select.length > 0){ // children will be popped when appened to other_ref_selector
136 other_ref_selector.appendChild(new_select.children[0]);
136 other_ref_selector.appendChild(new_select.children[0]);
137 }
137 }
138 // browsers lost track of selected when appendChild was used
138 // browsers lost track of selected when appendChild was used
139 other_ref_selector.selectedIndex = new_selectedIndex;
139 other_ref_selector.selectedIndex = new_selectedIndex;
140
140
141 // reset && add the reviewer based on selected repo
141 // reset && add the reviewer based on selected repo
142 var _data = other_repos_info[repo_name];
142 var _data = other_repos_info[repo_name];
143 YUD.get('review_members').innerHTML = '';
143 YUD.get('review_members').innerHTML = '';
144 addReviewMember(_data.user.user_id, _data.user.firstname,
144 addReviewMember(_data.user.user_id, _data.user.firstname,
145 _data.user.lastname, _data.user.username,
145 _data.user.lastname, _data.user.username,
146 _data.user.gravatar_link);
146 _data.user.gravatar_link);
147 }
147 }
148
148
149 var loadPreview = function(){
149 var loadPreview = function(){
150 //url template
150 //url template
151 var url = "${h.url('compare_url',
151 var url = "${h.url('compare_url',
152 repo_name='__other_repo__',
152 repo_name='__other_repo__',
153 org_ref_type='__other_ref_type__',
153 org_ref_type='__other_ref_type__',
154 org_ref='__other_ref__',
154 org_ref='__other_ref__',
155 other_repo='__org_repo__',
155 other_repo='__org_repo__',
156 other_ref_type='__org_ref_type__',
156 other_ref_type='__org_ref_type__',
157 other_ref='__org_ref__',
157 other_ref='__org_ref__',
158 as_form=True,
158 as_form=True,
159 merge=True,
159 merge=True,
160 )}";
160 )}";
161 var org_repo = YUQ('#pull_request_form #org_repo')[0].value;
161 var org_repo = YUQ('#pull_request_form #org_repo')[0].value;
162 var org_ref = YUQ('#pull_request_form #org_ref')[0].value.split(':');
162 var org_ref = YUQ('#pull_request_form #org_ref')[0].value.split(':');
163
163
164 var other_repo = YUQ('#pull_request_form #other_repo')[0].value;
164 var other_repo = YUQ('#pull_request_form #other_repo')[0].value;
165 var other_ref = YUQ('#pull_request_form #other_ref')[0].value.split(':');
165 var other_ref = YUQ('#pull_request_form #other_ref')[0].value.split(':');
166
166
167 var select_refs = YUQ('#pull_request_form select.refs')
167 var select_refs = YUQ('#pull_request_form select.refs')
168 var rev_data = {
168 var rev_data = {
169 'org_repo': org_repo,
169 'org_repo': org_repo,
170 'org_ref': org_ref[2],
170 'org_ref': org_ref[2],
171 'org_ref_type': 'rev',
171 'org_ref_type': 'rev',
172 'other_repo': other_repo,
172 'other_repo': other_repo,
173 'other_ref': other_ref[2],
173 'other_ref': other_ref[2],
174 'other_ref_type': 'rev',
174 'other_ref_type': 'rev',
175 }; // gather the org/other ref and repo here
175 }; // gather the org/other ref and repo here
176
176
177 for (k in rev_data){
177 for (k in rev_data){
178 url = url.replace('__'+k+'__',rev_data[k]);
178 url = url.replace('__'+k+'__',rev_data[k]);
179 }
179 }
180
180
181 YUD.get('pull_request_overview').innerHTML = "${_('Loading ...')}";
181 YUD.get('pull_request_overview').innerHTML = "${_('Loading ...')}";
182 ypjax(url,'pull_request_overview');
182 ypjax(url,'pull_request_overview');
183
183
184 YUD.get('pull_request_overview_url').href = url; // shouldn't have as_form ... but ...
184 YUD.get('pull_request_overview_url').href = url; // shouldn't have as_form ... but ...
185 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
185 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
186 }
186 }
187
187
188 ## refresh automatically when something changes (org_repo can't change)
188 ## refresh automatically when something changes (org_repo can't change)
189
189
190 YUE.on('org_ref', 'change', function(e){
190 YUE.on('org_ref', 'change', function(e){
191 loadPreview();
191 loadPreview();
192 });
192 });
193
193
194 YUE.on('other_repo', 'change', function(e){
194 YUE.on('other_repo', 'change', function(e){
195 otherrepoChanged();
195 otherrepoChanged();
196 loadPreview();
196 loadPreview();
197 });
197 });
198
198
199 YUE.on('other_ref', 'change', function(e){
199 YUE.on('other_ref', 'change', function(e){
200 loadPreview();
200 loadPreview();
201 });
201 });
202
202
203 otherrepoChanged();
203 otherrepoChanged();
204 //lazy load overview after 0.5s
204 //lazy load overview after 0.5s
205 setTimeout(loadPreview, 500);
205 setTimeout(loadPreview, 500);
206
206
207 </script>
207 </script>
208
208
209 </%def>
209 </%def>
@@ -1,248 +1,248 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
10 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
11 &raquo;
11 &raquo;
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16 ${self.context_bar('showpullrequest')}
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 %if c.pull_request.is_closed():
22 %if c.pull_request.is_closed():
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
24 %endif
24 %endif
25 <h3>
25 <h3>
26 %if c.pull_request.is_closed():
26 %if c.pull_request.is_closed():
27 <img src="${h.url('/images/icons/lock_go.png')}" title="${_('Closed')}"/>
27 <img src="${h.url('/images/icons/lock_go.png')}" title="${_('Closed')}"/>
28 %endif
28 %endif
29 <img src="${h.url('/images/icons/flag_status_%s.png' % str(c.pull_request.last_review_status))}" />
29 <img src="${h.url('/images/icons/flag_status_%s.png' % str(c.pull_request.last_review_status))}" />
30 ${_('Title')}: ${c.pull_request.title}</h3>
30 ${_('Title')}: ${c.pull_request.title}</h3>
31
31
32 <div class="form">
32 <div class="form">
33 <div id="summary" class="fields">
33 <div id="summary" class="fields">
34 <div class="field">
34 <div class="field">
35 <div class="label-summary">
35 <div class="label-summary">
36 <label>${_('Review status')}:</label>
36 <label>${_('Review status')}:</label>
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 <div class="changeset-status-container" style="float:none;clear:both">
39 <div class="changeset-status-container" style="float:none;clear:both">
40 %if c.current_changeset_status:
40 %if c.current_changeset_status:
41 <div title="${_('Pull request status')}" class="changeset-status-lbl">${h.changeset_status_lbl(c.current_changeset_status)}</div>
41 <div title="${_('Pull request status')}" class="changeset-status-lbl">${h.changeset_status_lbl(c.current_changeset_status)}</div>
42 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
42 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
43 %endif
43 %endif
44 </div>
44 </div>
45 </div>
45 </div>
46 </div>
46 </div>
47 <div class="field">
47 <div class="field">
48 <div class="label-summary">
48 <div class="label-summary">
49 <label>${_('Still not reviewed by')}:</label>
49 <label>${_('Still not reviewed by')}:</label>
50 </div>
50 </div>
51 <div class="input">
51 <div class="input">
52 % if len(c.pull_request_pending_reviewers) > 0:
52 % if len(c.pull_request_pending_reviewers) > 0:
53 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
53 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
54 %else:
54 %else:
55 <div>${_('pull request was reviewed by all reviewers')}</div>
55 <div>${_('pull request was reviewed by all reviewers')}</div>
56 %endif
56 %endif
57 </div>
57 </div>
58 </div>
58 </div>
59 <div class="field">
59 <div class="field">
60 <div class="label-summary">
60 <div class="label-summary">
61 <label>${_('Origin repository')}:</label>
61 <label>${_('Origin repository')}:</label>
62 </div>
62 </div>
63 <div class="input">
63 <div class="input">
64 <div>
64 <div>
65 ##%if h.is_hg(c.pull_request.org_repo):
65 ##%if h.is_hg(c.pull_request.org_repo):
66 ## <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
66 ## <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
67 ##%elif h.is_git(c.pull_request.org_repo):
67 ##%elif h.is_git(c.pull_request.org_repo):
68 ## <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
68 ## <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
69 ##%endif
69 ##%endif
70 <span class="spantag">${c.pull_request.org_ref_parts[0]}: ${c.pull_request.org_ref_parts[1]}</span>
70 <span class="spantag">${c.pull_request.org_ref_parts[0]}: ${c.pull_request.org_ref_parts[1]}</span>
71 <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span>
71 <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span>
72 </div>
72 </div>
73 </div>
73 </div>
74 </div>
74 </div>
75 <div class="field">
75 <div class="field">
76 <div class="label-summary">
76 <div class="label-summary">
77 <label>${_('Summary')}:</label>
77 <label>${_('Summary')}:</label>
78 </div>
78 </div>
79 <div class="input">
79 <div class="input">
80 <div style="white-space:pre-wrap">${h.literal(c.pull_request.description)}</div>
80 <div style="white-space:pre-wrap">${h.literal(c.pull_request.description)}</div>
81 </div>
81 </div>
82 </div>
82 </div>
83 <div class="field">
83 <div class="field">
84 <div class="label-summary">
84 <div class="label-summary">
85 <label>${_('Created on')}:</label>
85 <label>${_('Created on')}:</label>
86 </div>
86 </div>
87 <div class="input">
87 <div class="input">
88 <div>${h.fmt_date(c.pull_request.created_on)}</div>
88 <div>${h.fmt_date(c.pull_request.created_on)}</div>
89 </div>
89 </div>
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 <div style="overflow: auto;">
94 <div style="overflow: auto;">
95 ##DIFF
95 ##DIFF
96 <div class="table" style="float:left;clear:none">
96 <div class="table" style="float:left;clear:none">
97 <div id="body" class="diffblock">
97 <div id="body" class="diffblock">
98 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
98 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
99 </div>
99 </div>
100 <div id="changeset_compare_view_content">
100 <div id="changeset_compare_view_content">
101 ##CS
101 ##CS
102 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div>
102 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div>
103 <%include file="/compare/compare_cs.html" />
103 <%include file="/compare/compare_cs.html" />
104
104
105 ## FILES
105 ## FILES
106 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
106 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
107
107
108 % if c.limited_diff:
108 % if c.limited_diff:
109 ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
109 ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
110 % else:
110 % else:
111 ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
111 ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
112 %endif
112 %endif
113
113
114 </div>
114 </div>
115 <div class="cs_files">
115 <div class="cs_files">
116 %if not c.files:
116 %if not c.files:
117 <span class="empty_data">${_('No files')}</span>
117 <span class="empty_data">${_('No files')}</span>
118 %endif
118 %endif
119 %for fid, change, f, stat in c.files:
119 %for fid, change, f, stat in c.files:
120 <div class="cs_${change}">
120 <div class="cs_${change}">
121 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
121 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
122 <div class="changes">${h.fancy_file_stats(stat)}</div>
122 <div class="changes">${h.fancy_file_stats(stat)}</div>
123 </div>
123 </div>
124 %endfor
124 %endfor
125 </div>
125 </div>
126 % if c.limited_diff:
126 % if c.limited_diff:
127 <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("confirm to show potentially huge diff")}')">${_('Show full diff')}</a></h5>
127 <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("confirm to show potentially huge diff")}')">${_('Show full diff')}</a></h5>
128 % endif
128 % endif
129 </div>
129 </div>
130 </div>
130 </div>
131 ## REVIEWERS
131 ## REVIEWERS
132 <div style="float:left; border-left:1px dashed #eee">
132 <div style="float:left; border-left:1px dashed #eee">
133 <h4>${_('Pull request reviewers')}</h4>
133 <h4>${_('Pull request reviewers')}</h4>
134 <div id="reviewers" style="padding:0px 0px 5px 10px">
134 <div id="reviewers" style="padding:0px 0px 5px 10px">
135 ## members goes here !
135 ## members goes here !
136 <div class="group_members_wrap" style="min-height:45px">
136 <div class="group_members_wrap" style="min-height:45px">
137 <ul id="review_members" class="group_members">
137 <ul id="review_members" class="group_members">
138 %for member,status in c.pull_request_reviewers:
138 %for member,status in c.pull_request_reviewers:
139 <li id="reviewer_${member.user_id}">
139 <li id="reviewer_${member.user_id}">
140 <div class="reviewers_member">
140 <div class="reviewers_member">
141 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
141 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
142 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
142 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
143 </div>
143 </div>
144 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
144 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
145 <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
145 <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
146 <input type="hidden" value="${member.user_id}" name="review_members" />
146 <input type="hidden" value="${member.user_id}" name="review_members" />
147 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.user_id == c.rhodecode_user.user_id):
147 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.user_id == c.rhodecode_user.user_id):
148 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
148 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
149 %endif
149 %endif
150 </div>
150 </div>
151 </li>
151 </li>
152 %endfor
152 %endfor
153 </ul>
153 </ul>
154 </div>
154 </div>
155 %if not c.pull_request.is_closed():
155 %if not c.pull_request.is_closed():
156 <div class='ac'>
156 <div class='ac'>
157 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
157 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
158 <div class="reviewer_ac">
158 <div class="reviewer_ac">
159 ${h.text('user', class_='yui-ac-input')}
159 ${h.text('user', class_='yui-ac-input')}
160 <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span>
160 <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span>
161 <div id="reviewers_container"></div>
161 <div id="reviewers_container"></div>
162 </div>
162 </div>
163 <div style="padding:0px 10px">
163 <div style="padding:0px 10px">
164 <span id="update_pull_request" class="ui-btn xsmall">${_('Save changes')}</span>
164 <span id="update_pull_request" class="ui-btn xsmall">${_('Save changes')}</span>
165 </div>
165 </div>
166 %endif
166 %endif
167 </div>
167 </div>
168 %endif
168 %endif
169 </div>
169 </div>
170 </div>
170 </div>
171 </div>
171 </div>
172 <script>
172 <script>
173 var _USERS_AC_DATA = ${c.users_array|n};
173 var _USERS_AC_DATA = ${c.users_array|n};
174 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
174 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
175 // TODO: switch this to pyroutes
175 // TODO: switch this to pyroutes
176 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
176 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
177 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
177 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
178
178
179 pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
179 pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
180 pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']);
180 pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']);
181 pyroutes.register('pullrequest_update', "${url('pullrequest_update',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
181 pyroutes.register('pullrequest_update', "${url('pullrequest_update',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
182
182
183 </script>
183 </script>
184
184
185 ## diff block
185 ## diff block
186 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
186 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
187 %for fid, change, f, stat in c.files:
187 %for fid, change, f, stat in c.files:
188 ${diff_block.diff_block_simple([c.changes[fid]])}
188 ${diff_block.diff_block_simple([c.changes[fid]])}
189 %endfor
189 %endfor
190 % if c.limited_diff:
190 % if c.limited_diff:
191 <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("confirm to show potentially huge diff")}')">${_('Show full diff')}</a></h4>
191 <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("confirm to show potentially huge diff")}')">${_('Show full diff')}</a></h4>
192 % endif
192 % endif
193
193
194
194
195 ## template for inline comment form
195 ## template for inline comment form
196 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
196 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
197 ${comment.comment_inline_form()}
197 ${comment.comment_inline_form()}
198
198
199 ## render comments and inlines
199 ## render comments and inlines
200 ${comment.generate_comments(include_pr=True)}
200 ${comment.generate_comments(include_pr=True)}
201
201
202 % if not c.pull_request.is_closed():
202 % if not c.pull_request.is_closed():
203 ## main comment form and it status
203 ## main comment form and it status
204 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
204 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
205 pull_request_id=c.pull_request.pull_request_id),
205 pull_request_id=c.pull_request.pull_request_id),
206 c.current_changeset_status,
206 c.current_changeset_status,
207 close_btn=True, change_status=c.allowed_to_change_status)}
207 close_btn=True, change_status=c.allowed_to_change_status)}
208 %endif
208 %endif
209
209
210 <script type="text/javascript">
210 <script type="text/javascript">
211 YUE.onDOMReady(function(){
211 YUE.onDOMReady(function(){
212 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
212 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
213
213
214 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
214 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
215 var show = 'none';
215 var show = 'none';
216 var target = e.currentTarget;
216 var target = e.currentTarget;
217 if(target.checked){
217 if(target.checked){
218 var show = ''
218 var show = ''
219 }
219 }
220 var boxid = YUD.getAttribute(target,'id_for');
220 var boxid = YUD.getAttribute(target,'id_for');
221 var comments = YUQ('#{0} .inline-comments'.format(boxid));
221 var comments = YUQ('#{0} .inline-comments'.format(boxid));
222 for(c in comments){
222 for(c in comments){
223 YUD.setStyle(comments[c],'display',show);
223 YUD.setStyle(comments[c],'display',show);
224 }
224 }
225 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
225 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
226 for(c in btns){
226 for(c in btns){
227 YUD.setStyle(btns[c],'display',show);
227 YUD.setStyle(btns[c],'display',show);
228 }
228 }
229 })
229 })
230
230
231 YUE.on(YUQ('.line'),'click',function(e){
231 YUE.on(YUQ('.line'),'click',function(e){
232 var tr = e.currentTarget;
232 var tr = e.currentTarget;
233 injectInlineForm(tr);
233 injectInlineForm(tr);
234 });
234 });
235
235
236 // inject comments into they proper positions
236 // inject comments into they proper positions
237 var file_comments = YUQ('.inline-comment-placeholder');
237 var file_comments = YUQ('.inline-comment-placeholder');
238 renderInlineComments(file_comments);
238 renderInlineComments(file_comments);
239
239
240 YUE.on(YUD.get('update_pull_request'),'click',function(e){
240 YUE.on(YUD.get('update_pull_request'),'click',function(e){
241 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
241 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
242 })
242 })
243 })
243 })
244 </script>
244 </script>
245
245
246 </div>
246 </div>
247
247
248 </%def>
248 </%def>
@@ -1,40 +1,38 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('all pull requests')}
4 ${c.repo_name} ${_('all pull requests')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('Pull requests')}
8 ${_('Pull requests')}
9 </%def>
9 </%def>
10
10
11 <%def name="main()">
11 <%def name="main()">
12 ${self.context_bar('showpullrequest')}
12 ${self.context_bar('showpullrequest')}
13 <div class="box">
13 <div class="box">
14 <!-- box / title -->
14 <!-- box / title -->
15 <div class="title">
15 <div class="title">
16 ${self.breadcrumbs()}
16 ${self.breadcrumbs()}
17 </div>
17 </div>
18
18
19 %for pr in c.pull_requests:
19 %for pr in c.pull_requests:
20 <div>
20 <div>
21 <h4 style="border:0px;padding:0px">
21 <h4 style="border:0px;padding:0px">
22 %if pr.is_closed():
22 %if pr.is_closed():
23 <img src="${h.url('/images/icons/lock_go.png')}" title="${_('Closed')}"/>
23 <img src="${h.url('/images/icons/lock_go.png')}" title="${_('Closed')}"/>
24 %endif
24 %endif
25 <img src="${h.url('/images/icons/flag_status_%s.png' % str(pr.last_review_status))}" />
25 <img src="${h.url('/images/icons/flag_status_%s.png' % str(pr.last_review_status))}" />
26 <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">
26 <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">
27 ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))}
27 ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))}
28 </a>
28 </a>
29 </h4>
29 </h4>
30 <h5 style="border:0px;padding-bottom:0px">${_('Title')}: ${pr.title}</h5>
30 <h5 style="border:0px;padding-bottom:0px">${_('Title')}: ${pr.title}</h5>
31 <div style="padding:0px 24px">${pr.description}</div>
31 <div style="padding:0px 24px">${pr.description}</div>
32 <div style="border-bottom: 1px solid #DDD;margin:10px 20px;padding-bottom:10px"></div>
32 <div style="border-bottom: 1px solid #DDD;margin:10px 20px;padding-bottom:10px"></div>
33 </div>
33 </div>
34 %endfor
34 %endfor
35
35
36 </div>
36 </div>
37
37
38 <script type="text/javascript"></script>
39
40 </%def>
38 </%def>
General Comments 0
You need to be logged in to leave comments. Login now