##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r1849:5440352d merge stable
parent child Browse files
Show More
@@ -0,0 +1,97 b''
1 .. _integrations-ci:
2
3 CI Server integration
4 =====================
5
6
7 RhodeCode :ref:`integrations-webhook` integration is a powerfull tool to allow
8 interaction with systems like Jenkin, Bamboo, TeamCity, CircleCi or any other
9 CI server that allows triggering a build using HTTP call.
10
11 Below are few examples on how to use :ref:`integrations-webhook` to trigger
12 a CI build.
13
14
15 General Webhook
16 +++++++++++++++
17
18 :ref:`integrations-webhook` allows sending a JSON payload information to specified
19 url with GET or POST methods. There are several variables that could be used
20 in the URL greatly extending the flexibility of this type of integration.
21
22 Most of the modern CI systems such as Jenkins, TeamCity, Bamboo or CircleCi
23 allows triggering builds via GET or POST calls.
24
25 :ref:`integrations-webhook` can be either specified per each repository or
26 globally, if your CI maps directly to all your projects a global
27 :ref:`integrations-webhook` integration can be created and will trigger builds
28 for each change in projects. If only some projects allow triggering builds a
29 global integration will also work because mostly a CI system will ignore a
30 call for unspecified builds.
31
32
33 .. note::
34
35 A quick note on security. It's recommended to allow IP restrictions
36 to only allow RhodeCode server to trigger builds. If you need to
37 specify username and password this could be done by embedding it into a
38 trigger URL, e.g. `http://user:password@server.com/job/${project_id}
39
40
41 If users require to provide any custom parameters, they can be stored for each
42 project inside the :ref:`repo-xtra`. For example to migrate a current job that
43 has a numeric build id, storing this as `jenkins_build_id` key extra field
44 the url would look like that::
45
46 http://server/job/${extra:jenkins_build_id}/
47
48
49 .. note::
50
51 Please note that some variables will result in multiple calls.
52 e.g. for |HG| specifying `${branch}` will trigger as many builds as how
53 many branches the suer actually pushed. Same applies to `${commit_id}`
54 This will trigger many builds if many commits are pushed. This allows
55 triggering individual builds for each pushed commit.
56
57
58 Jenkins
59 +++++++
60
61 To use Jenkins CI with RhodeCode, a Jenkins Build with Parameters should be used.
62 Plugin details are available here: https://wiki.jenkins.io/display/JENKINS/Build+With+Parameters+Plugin
63
64 If the plugin is configured, RhodeCode can trigger builds automatically by
65 calling such example url provided in :ref:`integrations-webhook` integration::
66
67 http://server/job/${project_id}/build-branch-${branch}/buildWithParameters?token=TOKEN&PARAMETER=value&PARAMETER2=value2
68
69
70 Team City
71 +++++++++
72
73 To use TeamCity CI it's enough to call the API and provide a buildId.
74 Example url after configuring :ref:`repo-xtra` would look like that::
75
76 http://teacmtiyserver/viewType.html?buildTypeId=${extra:tc_build_id}
77
78
79 Each project can have many build configurations.
80 buildTypeId which is a unique ID for each build configuration (job).
81
82
83 CircleCi
84 ++++++++
85
86 To use CircleCi, a POST call needs to be triggered. Example build url would
87 look like this::
88
89 http://cicleCiServer/project/${repo_type}/${username}/${repo_id}/tree/${branch}
90
91
92 Circle Ci expects format of::
93
94 POST: /project/:vcs-type/:username/:project/tree/:branch
95
96
97 CircleCi API documentation can be found here: https://circleci.com/docs/api/v1-reference/
@@ -0,0 +1,103 b''
1 |RCE| 4.8.0 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2017-06-30
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13 - Code Review: added new reviewers logic. This features now is Common Criteria
14 compatible and allows to define Mandatory (non-removable) reviewers.
15 In addition new options were added to forbid adding new reviewers or forbid
16 author of commits or the pull request itself to be a reviewer of the code.
17 - Audit logs: introducing new audit logs tracking most important actions in
18 the system. Admins can track important events such as deletion of resources,
19 permissions changes, user groups changes. Each event tracks users with his
20 IP and user agent.
21 - Mercurial: enabled evolve extensions. Each repository can be now configured
22 to support evolve, commit phases, and evolve state are also shown in
23 commit and changelog views.
24 - VCS: expose newly pushed bookmarks or branches as quick links to open a
25 pull request on client output. Allows easier pull request creation via CLI.
26
27
28 General
29 ^^^^^^^
30
31 - Core: ported many views into pure pyramid code with python3.6 compatibility.
32 Now almost 80% of the code is ported, and future ready. It's our ongoing
33 effort to allow support for modern python version.
34 - Comments: show author tag in pull request comments to easily
35 discover the author of changes in discussions.
36 - Files: allow specifying custom filename for uploaded files via web interface.
37 - Pull requests: changed who is allowed to close a pull request. Now it's only
38 super-admin, owner or person who can merge.
39 Before it was every reviewer can close. Which really doesn't make sense.
40 - Users: show that user is disabled when editing his properties.
41 - Integrations: expose user_id, and username in Webhook integration
42 templates arguments.
43 - Integrations: exposed extra repo variables in template arguments of
44 Webhook integration.
45 - Login: add link when using external auth to make it easier to login
46 using oauth providers, such as Google or Github.
47 - Maintenance: added svn verify command to tasks to be able to verify the
48 filesystem and repo formats from web interface. Allows much easier tracking
49 of incompatible filesystem storage of subversion repositories.
50 - Events: expose permalink urls for pull requests, and repositories.
51 Permalink url should provide a non-changeable url that can be used in
52 external system.
53 - Svn: increase possibility to specify compatibility to pre 1.9 version.
54
55
56 Security
57 ^^^^^^^^
58
59 - security(high): fixed possibility to delete other users inline comments
60 for users who were repository admins.
61 - security(med): fixed XSS inside the tooltip for author string.
62 - security(med): fixed stored XSS in notifications inbox.
63 - security(med): use custom writer for RST rendering to prevent injection of javascript: tags.
64 - security(med): escape flash messaged VCS errors to prevent reflected XSS attacks.
65 - security(low): use 404 instead of 403 code on permission decorator to
66 prevent brute force resource discovery attacks.
67 - security(low): fixed self XSS inside autocomplete files view.
68 - security(low): fixed self Xss inside repo strip view.
69 - security(low): fixed self Xss inside the email add functionality.
70 - security(none): use new safe escaped user attributes across the application.
71 Will prevent all possible XSS attack vectors from user stored attributes.
72 This specially can come from external authentication systems which doesn't
73 validate the data.
74
75
76 Performance
77 ^^^^^^^^^^^
78
79
80
81
82 Fixes
83 ^^^^^
84
85 - Pull requests: make sure we process comments in the order of IDS when
86 linking them. In some edge cases it could lead to comments not displaying
87 correctly.
88 - Emails: fixed newlines in email templates that can break email sending code.
89 - Markdown: fixed hr and strong tags styling.
90 - Notifications: fixed problem with 500 errors on non-numeric entries in url.
91 - API: use simple schema validator to be consistent how we validate between
92 API and web views for create user and create user_group calls.
93 - Users: fixed problem with personal repo group wasn't shown for disabled users.
94 - Oauth: improve Google extraction of first/last name from returned data.
95
96
97 Upgrade notes
98 ^^^^^^^^^^^^^
99
100
101 - API: the `update_pull_request` method will no longer support a close action.
102 Users should use the existing `close_pull_request` method which allows
103 specifying a message and status while closing a pull request. No newline at end of file
@@ -0,0 +1,187 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22 import csv
23 import datetime
24
25 import pytest
26
27 from rhodecode.tests import *
28 from rhodecode.tests.fixture import FIXTURES
29 from rhodecode.model.db import UserLog
30 from rhodecode.model.meta import Session
31 from rhodecode.lib.utils2 import safe_unicode
32
33
34 def route_path(name, params=None, **kwargs):
35 import urllib
36 from rhodecode.apps._base import ADMIN_PREFIX
37
38 base_url = {
39 'admin_home': ADMIN_PREFIX,
40 'admin_audit_logs': ADMIN_PREFIX + '/audit_logs',
41
42 }[name].format(**kwargs)
43
44 if params:
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 return base_url
47
48
49 class TestAdminController(TestController):
50
51 @pytest.fixture(scope='class', autouse=True)
52 def prepare(self, request, pylonsapp):
53 UserLog.query().delete()
54 Session().commit()
55
56 def strptime(val):
57 fmt = '%Y-%m-%d %H:%M:%S'
58 if '.' not in val:
59 return datetime.datetime.strptime(val, fmt)
60
61 nofrag, frag = val.split(".")
62 date = datetime.datetime.strptime(nofrag, fmt)
63
64 frag = frag[:6] # truncate to microseconds
65 frag += (6 - len(frag)) * '0' # add 0s
66 return date.replace(microsecond=int(frag))
67
68 with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
69 for row in csv.DictReader(f):
70 ul = UserLog()
71 for k, v in row.iteritems():
72 v = safe_unicode(v)
73 if k == 'action_date':
74 v = strptime(v)
75 if k in ['user_id', 'repository_id']:
76 # nullable due to FK problems
77 v = None
78 setattr(ul, k, v)
79 Session().add(ul)
80 Session().commit()
81
82 @request.addfinalizer
83 def cleanup():
84 UserLog.query().delete()
85 Session().commit()
86
87 def test_index(self):
88 self.log_user()
89 response = self.app.get(route_path('admin_audit_logs'))
90 response.mustcontain('Admin audit logs')
91
92 def test_filter_all_entries(self):
93 self.log_user()
94 response = self.app.get(route_path('admin_audit_logs'))
95 all_count = UserLog.query().count()
96 response.mustcontain('%s entries' % all_count)
97
98 def test_filter_journal_filter_exact_match_on_repository(self):
99 self.log_user()
100 response = self.app.get(route_path('admin_audit_logs',
101 params=dict(filter='repository:rhodecode')))
102 response.mustcontain('3 entries')
103
104 def test_filter_journal_filter_exact_match_on_repository_CamelCase(self):
105 self.log_user()
106 response = self.app.get(route_path('admin_audit_logs',
107 params=dict(filter='repository:RhodeCode')))
108 response.mustcontain('3 entries')
109
110 def test_filter_journal_filter_wildcard_on_repository(self):
111 self.log_user()
112 response = self.app.get(route_path('admin_audit_logs',
113 params=dict(filter='repository:*test*')))
114 response.mustcontain('862 entries')
115
116 def test_filter_journal_filter_prefix_on_repository(self):
117 self.log_user()
118 response = self.app.get(route_path('admin_audit_logs',
119 params=dict(filter='repository:test*')))
120 response.mustcontain('257 entries')
121
122 def test_filter_journal_filter_prefix_on_repository_CamelCase(self):
123 self.log_user()
124 response = self.app.get(route_path('admin_audit_logs',
125 params=dict(filter='repository:Test*')))
126 response.mustcontain('257 entries')
127
128 def test_filter_journal_filter_prefix_on_repository_and_user(self):
129 self.log_user()
130 response = self.app.get(route_path('admin_audit_logs',
131 params=dict(filter='repository:test* AND username:demo')))
132 response.mustcontain('130 entries')
133
134 def test_filter_journal_filter_prefix_on_repository_or_target_repo(self):
135 self.log_user()
136 response = self.app.get(route_path('admin_audit_logs',
137 params=dict(filter='repository:test* OR repository:rhodecode')))
138 response.mustcontain('260 entries') # 257 + 3
139
140 def test_filter_journal_filter_exact_match_on_username(self):
141 self.log_user()
142 response = self.app.get(route_path('admin_audit_logs',
143 params=dict(filter='username:demo')))
144 response.mustcontain('1087 entries')
145
146 def test_filter_journal_filter_exact_match_on_username_camelCase(self):
147 self.log_user()
148 response = self.app.get(route_path('admin_audit_logs',
149 params=dict(filter='username:DemO')))
150 response.mustcontain('1087 entries')
151
152 def test_filter_journal_filter_wildcard_on_username(self):
153 self.log_user()
154 response = self.app.get(route_path('admin_audit_logs',
155 params=dict(filter='username:*test*')))
156 entries_count = UserLog.query().filter(UserLog.username.ilike('%test%')).count()
157 response.mustcontain('{} entries'.format(entries_count))
158
159 def test_filter_journal_filter_prefix_on_username(self):
160 self.log_user()
161 response = self.app.get(route_path('admin_audit_logs',
162 params=dict(filter='username:demo*')))
163 response.mustcontain('1101 entries')
164
165 def test_filter_journal_filter_prefix_on_user_or_other_user(self):
166 self.log_user()
167 response = self.app.get(route_path('admin_audit_logs',
168 params=dict(filter='username:demo OR username:volcan')))
169 response.mustcontain('1095 entries') # 1087 + 8
170
171 def test_filter_journal_filter_wildcard_on_action(self):
172 self.log_user()
173 response = self.app.get(route_path('admin_audit_logs',
174 params=dict(filter='action:*pull_request*')))
175 response.mustcontain('187 entries')
176
177 def test_filter_journal_filter_on_date(self):
178 self.log_user()
179 response = self.app.get(route_path('admin_audit_logs',
180 params=dict(filter='date:20121010')))
181 response.mustcontain('47 entries')
182
183 def test_filter_journal_filter_on_date_2(self):
184 self.log_user()
185 response = self.app.get(route_path('admin_audit_logs',
186 params=dict(filter='date:20121020')))
187 response.mustcontain('17 entries')
@@ -0,0 +1,82 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests import TestController
24 from rhodecode.tests.fixture import Fixture
25
26 fixture = Fixture()
27
28
29 def route_path(name, params=None, **kwargs):
30 import urllib
31 from rhodecode.apps._base import ADMIN_PREFIX
32
33 base_url = {
34 'admin_home': ADMIN_PREFIX,
35 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
36 'pull_requests_global': ADMIN_PREFIX + '/pull-request/{pull_request_id}',
37 'pull_requests_global_0': ADMIN_PREFIX + '/pull_requests/{pull_request_id}',
38 'pull_requests_global_1': ADMIN_PREFIX + '/pull-requests/{pull_request_id}',
39
40 }[name].format(**kwargs)
41
42 if params:
43 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
44 return base_url
45
46
47 class TestAdminMainView(TestController):
48
49 def test_redirect_admin_home(self):
50 self.log_user()
51 response = self.app.get(route_path('admin_home'), status=302)
52 assert response.location.endswith('/audit_logs')
53
54 def test_redirect_pull_request_view(self, view):
55 self.log_user()
56 self.app.get(
57 route_path(view, pull_request_id='xxxx'),
58 status=404)
59
60 @pytest.mark.backends("git", "hg")
61 @pytest.mark.parametrize('view', [
62 'pull_requests_global',
63 'pull_requests_global_0',
64 'pull_requests_global_1',
65 ])
66 def test_redirect_pull_request_view(self, view, pr_util):
67 self.log_user()
68 pull_request = pr_util.create_pull_request()
69 pull_request_id = pull_request.pull_request_id
70
71 response = self.app.get(
72 route_path(view, pull_request_id=pull_request_id),
73 status=302)
74 assert response.location.endswith(
75 'pull-request/{}'.format(pull_request_id))
76
77 repo_name = pull_request.target_repo.repo_name
78 redirect_url = route_path(
79 'pullrequest_show', repo_name=repo_name,
80 pull_request_id=pull_request.pull_request_id)
81
82 assert redirect_url in response.location
@@ -0,0 +1,73 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24 from sqlalchemy.orm import joinedload
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.model.db import UserLog
28 from rhodecode.lib.user_log_filter import user_log_filter
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib.helpers import Page
32
33 log = logging.getLogger(__name__)
34
35
36 class AdminAuditLogsView(BaseAppView):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
39 self._register_global_c(c)
40 return c
41
42 @LoginRequired()
43 @HasPermissionAllDecorator('hg.admin')
44 @view_config(
45 route_name='admin_audit_logs', request_method='GET',
46 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
47 def admin_audit_logs(self):
48 c = self.load_default_context()
49
50 users_log = UserLog.query()\
51 .options(joinedload(UserLog.user))\
52 .options(joinedload(UserLog.repository))
53
54 # FILTERING
55 c.search_term = self.request.GET.get('filter')
56 try:
57 users_log = user_log_filter(users_log, c.search_term)
58 except Exception:
59 # we want this to crash for now
60 raise
61
62 users_log = users_log.order_by(UserLog.action_date.desc())
63
64 p = safe_int(self.request.GET.get('page', 1), 1)
65
66 def url_generator(**kw):
67 if c.search_term:
68 kw['filter'] = c.search_term
69 return self.request.current_route_path(_query=kw)
70
71 c.audit_logs = Page(users_log, page=p, items_per_page=10,
72 url=url_generator)
73 return self._get_template_context(c)
@@ -0,0 +1,63 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
30 from rhodecode.model.db import PullRequest
31
32
33 log = logging.getLogger(__name__)
34
35
36 class AdminMainView(BaseAppView):
37
38 @LoginRequired()
39 @HasPermissionAllDecorator('hg.admin')
40 @view_config(
41 route_name='admin_home', request_method='GET')
42 def admin_main(self):
43 # redirect _admin to audit logs...
44 raise HTTPFound(h.route_path('admin_audit_logs'))
45
46 @LoginRequired()
47 @view_config(route_name='pull_requests_global_0', request_method='GET')
48 @view_config(route_name='pull_requests_global_1', request_method='GET')
49 @view_config(route_name='pull_requests_global', request_method='GET')
50 def pull_requests(self):
51 """
52 Global redirect for Pull Requests
53
54 :param pull_request_id: id of pull requests in the system
55 """
56
57 pull_request_id = self.request.matchdict.get('pull_request_id')
58 pull_request = PullRequest.get_or_404(pull_request_id, pyramid_exc=True)
59 repo_name = pull_request.target_repo.repo_name
60
61 raise HTTPFound(
62 h.route_path('pullrequest_show', repo_name=repo_name,
63 pull_request_id=pull_request_id))
@@ -0,0 +1,49 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.config import routing_links
21
22
23 def includeme(config):
24
25 config.add_route(
26 name='home',
27 pattern='/')
28
29 config.add_route(
30 name='user_autocomplete_data',
31 pattern='/_users')
32
33 config.add_route(
34 name='user_group_autocomplete_data',
35 pattern='/_user_groups')
36
37 config.add_route(
38 name='repo_list_data',
39 pattern='/_repos')
40
41 config.add_route(
42 name='goto_switcher_data',
43 pattern='/_goto_data')
44
45 # register our static links via redirection mechanism
46 routing_links.connect_redirection_links(config)
47
48 # Scan module for configuration decorators.
49 config.scan()
@@ -0,0 +1,40 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 def assert_and_get_content(result):
23 repos = []
24 groups = []
25 commits = []
26 for data in result:
27 for data_item in data['children']:
28 assert data_item['id']
29 assert data_item['text']
30 assert data_item['url']
31 if data_item['type'] == 'repo':
32 repos.append(data_item)
33 elif data_item['type'] == 'group':
34 groups.append(data_item)
35 elif data_item['type'] == 'commit':
36 commits.append(data_item)
37 else:
38 raise Exception('invalid type %s' % data_item['type'])
39
40 return repos, groups, commits No newline at end of file
@@ -0,0 +1,151 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import json
22
23 import pytest
24
25 from . import assert_and_get_content
26 from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests.fixture import Fixture
28
29 from rhodecode.lib.utils import map_groups
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.db import Session, Repository, RepoGroup
33
34 fixture = Fixture()
35
36
37 def route_path(name, params=None, **kwargs):
38 import urllib
39
40 base_url = {
41 'goto_switcher_data': '/_goto_data',
42 }[name].format(**kwargs)
43
44 if params:
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 return base_url
47
48
49 class TestGotoSwitcherData(TestController):
50
51 required_repos_with_groups = [
52 'abc',
53 'abc-fork',
54 'forks/abcd',
55 'abcd',
56 'abcde',
57 'a/abc',
58 'aa/abc',
59 'aaa/abc',
60 'aaaa/abc',
61 'repos_abc/aaa/abc',
62 'abc_repos/abc',
63 'abc_repos/abcd',
64 'xxx/xyz',
65 'forked-abc/a/abc'
66 ]
67
68 @pytest.fixture(autouse=True, scope='class')
69 def prepare(self, request, pylonsapp):
70 for repo_and_group in self.required_repos_with_groups:
71 # create structure of groups and return the last group
72
73 repo_group = map_groups(repo_and_group)
74
75 RepoModel()._create_repo(
76 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
77 repo_group=getattr(repo_group, 'group_id', None))
78
79 Session().commit()
80
81 request.addfinalizer(self.cleanup)
82
83 def cleanup(self):
84 # first delete all repos
85 for repo_and_groups in self.required_repos_with_groups:
86 repo = Repository.get_by_repo_name(repo_and_groups)
87 if repo:
88 RepoModel().delete(repo)
89 Session().commit()
90
91 # then delete all empty groups
92 for repo_and_groups in self.required_repos_with_groups:
93 if '/' in repo_and_groups:
94 r_group = repo_and_groups.rsplit('/', 1)[0]
95 repo_group = RepoGroup.get_by_group_name(r_group)
96 if not repo_group:
97 continue
98 parents = repo_group.parents
99 RepoGroupModel().delete(repo_group, force_delete=True)
100 Session().commit()
101
102 for el in reversed(parents):
103 RepoGroupModel().delete(el, force_delete=True)
104 Session().commit()
105
106 def test_returns_list_of_repos_and_groups(self, xhr_header):
107 self.log_user()
108
109 response = self.app.get(
110 route_path('goto_switcher_data'),
111 extra_environ=xhr_header, status=200)
112 result = json.loads(response.body)['results']
113
114 repos, groups, commits = assert_and_get_content(result)
115
116 assert len(repos) == len(Repository.get_all())
117 assert len(groups) == len(RepoGroup.get_all())
118 assert len(commits) == 0
119
120 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
121 self.log_user()
122
123 response = self.app.get(
124 route_path('goto_switcher_data'),
125 params={'query': 'abc'},
126 extra_environ=xhr_header, status=200)
127 result = json.loads(response.body)['results']
128
129 repos, groups, commits = assert_and_get_content(result)
130
131 assert len(repos) == 13
132 assert len(groups) == 5
133 assert len(commits) == 0
134
135 def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
136 self.log_user()
137
138 response = self.app.get(
139 route_path('goto_switcher_data'),
140 params={'query': 'abc'},
141 extra_environ=xhr_header, status=200)
142 result = json.loads(response.body)['results']
143
144 repos, groups, commits = assert_and_get_content(result)
145
146 test_repos = [x['text'] for x in repos[:4]]
147 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
148
149 test_groups = [x['text'] for x in groups[:4]]
150 assert ['abc_repos', 'repos_abc',
151 'forked-abc', 'forked-abc/a'] == test_groups
@@ -0,0 +1,103 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import json
22
23 from . import assert_and_get_content
24 from rhodecode.tests import TestController
25 from rhodecode.tests.fixture import Fixture
26 from rhodecode.model.db import Repository
27
28 fixture = Fixture()
29
30
31 def route_path(name, params=None, **kwargs):
32 import urllib
33
34 base_url = {
35 'repo_list_data': '/_repos',
36 }[name].format(**kwargs)
37
38 if params:
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 return base_url
41
42
43 class TestRepoListData(TestController):
44
45 def test_returns_list_of_repos_and_groups(self, xhr_header):
46 self.log_user()
47
48 response = self.app.get(
49 route_path('repo_list_data'),
50 extra_environ=xhr_header, status=200)
51 result = json.loads(response.body)['results']
52
53 repos, groups, commits = assert_and_get_content(result)
54
55 assert len(repos) == len(Repository.get_all())
56 assert len(groups) == 0
57 assert len(commits) == 0
58
59 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
60 self.log_user()
61
62 response = self.app.get(
63 route_path('repo_list_data'),
64 params={'query': 'vcs_test_git'},
65 extra_environ=xhr_header, status=200)
66 result = json.loads(response.body)['results']
67
68 repos, groups, commits = assert_and_get_content(result)
69
70 assert len(repos) == len(Repository.query().filter(
71 Repository.repo_name.ilike('%vcs_test_git%')).all())
72 assert len(groups) == 0
73 assert len(commits) == 0
74
75 def test_returns_list_of_repos_and_groups_filtered_with_type(self, xhr_header):
76 self.log_user()
77
78 response = self.app.get(
79 route_path('repo_list_data'),
80 params={'query': 'vcs_test_git', 'repo_type': 'git'},
81 extra_environ=xhr_header, status=200)
82 result = json.loads(response.body)['results']
83
84 repos, groups, commits = assert_and_get_content(result)
85
86 assert len(repos) == len(Repository.query().filter(
87 Repository.repo_name.ilike('%vcs_test_git%')).all())
88 assert len(groups) == 0
89 assert len(commits) == 0
90
91 def test_returns_list_of_repos_non_ascii_query(self, xhr_header):
92 self.log_user()
93 response = self.app.get(
94 route_path('repo_list_data'),
95 params={'query': 'ć_vcs_test_ą', 'repo_type': 'git'},
96 extra_environ=xhr_header, status=200)
97 result = json.loads(response.body)['results']
98
99 repos, groups, commits = assert_and_get_content(result)
100
101 assert len(repos) == 0
102 assert len(groups) == 0
103 assert len(commits) == 0
@@ -0,0 +1,112 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import json
22 import pytest
23
24 from rhodecode.tests import TestController
25 from rhodecode.tests.fixture import Fixture
26
27
28 fixture = Fixture()
29
30
31 def route_path(name, params=None, **kwargs):
32 import urllib
33
34 base_url = {
35 'user_autocomplete_data': '/_users',
36 'user_group_autocomplete_data': '/_user_groups'
37 }[name].format(**kwargs)
38
39 if params:
40 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
41 return base_url
42
43
44 class TestUserAutocompleteData(TestController):
45
46 def test_returns_list_of_users(self, user_util, xhr_header):
47 self.log_user()
48 user = user_util.create_user(active=True)
49 user_name = user.username
50 response = self.app.get(
51 route_path('user_autocomplete_data'),
52 extra_environ=xhr_header, status=200)
53 result = json.loads(response.body)
54 values = [suggestion['value'] for suggestion in result['suggestions']]
55 assert user_name in values
56
57 def test_returns_inactive_users_when_active_flag_sent(
58 self, user_util, xhr_header):
59 self.log_user()
60 user = user_util.create_user(active=False)
61 user_name = user.username
62
63 response = self.app.get(
64 route_path('user_autocomplete_data',
65 params=dict(user_groups='true', active='0')),
66 extra_environ=xhr_header, status=200)
67 result = json.loads(response.body)
68 values = [suggestion['value'] for suggestion in result['suggestions']]
69 assert user_name in values
70
71 response = self.app.get(
72 route_path('user_autocomplete_data',
73 params=dict(user_groups='true', active='1')),
74 extra_environ=xhr_header, status=200)
75 result = json.loads(response.body)
76 values = [suggestion['value'] for suggestion in result['suggestions']]
77 assert user_name not in values
78
79 def test_returns_groups_when_user_groups_flag_sent(
80 self, user_util, xhr_header):
81 self.log_user()
82 group = user_util.create_user_group(user_groups_active=True)
83 group_name = group.users_group_name
84 response = self.app.get(
85 route_path('user_autocomplete_data',
86 params=dict(user_groups='true')),
87 extra_environ=xhr_header, status=200)
88 result = json.loads(response.body)
89 values = [suggestion['value'] for suggestion in result['suggestions']]
90 assert group_name in values
91
92 @pytest.mark.parametrize('query, count', [
93 ('hello1', 0),
94 ('dev', 2),
95 ])
96 def test_result_is_limited_when_query_is_sent(self, user_util, xhr_header,
97 query, count):
98 self.log_user()
99
100 user_util._test_name = 'dev-test'
101 user_util.create_user()
102
103 user_util._test_name = 'dev-group-test'
104 user_util.create_user_group()
105
106 response = self.app.get(
107 route_path('user_autocomplete_data',
108 params=dict(user_groups='true', query=query)),
109 extra_environ=xhr_header, status=200)
110
111 result = json.loads(response.body)
112 assert len(result['suggestions']) == count
@@ -0,0 +1,117 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 # -*- coding: utf-8 -*-
21
22 # Copyright (C) 2016-2017 RhodeCode GmbH
23 #
24 # This program is free software: you can redistribute it and/or modify
25 # it under the terms of the GNU Affero General Public License, version 3
26 # (only), as published by the Free Software Foundation.
27 #
28 # This program is distributed in the hope that it will be useful,
29 # but WITHOUT ANY WARRANTY; without even the implied warranty of
30 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 # GNU General Public License for more details.
32 #
33 # You should have received a copy of the GNU Affero General Public License
34 # along with this program. If not, see <http://www.gnu.org/licenses/>.
35 #
36 # This program is dual-licensed. If you wish to learn more about the
37 # RhodeCode Enterprise Edition, including its added features, Support services,
38 # and proprietary license terms, please see https://rhodecode.com/licenses/
39
40 import json
41
42 import pytest
43
44 from rhodecode.tests import TestController
45 from rhodecode.tests.fixture import Fixture
46
47
48 fixture = Fixture()
49
50
51 def route_path(name, params=None, **kwargs):
52 import urllib
53
54 base_url = {
55 'user_autocomplete_data': '/_users',
56 'user_group_autocomplete_data': '/_user_groups'
57 }[name].format(**kwargs)
58
59 if params:
60 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
61 return base_url
62
63
64 class TestUserGroupAutocompleteData(TestController):
65
66 def test_returns_list_of_user_groups(self, user_util, xhr_header):
67 self.log_user()
68 user_group = user_util.create_user_group(active=True)
69 user_group_name = user_group.users_group_name
70 response = self.app.get(
71 route_path('user_group_autocomplete_data'),
72 extra_environ=xhr_header, status=200)
73 result = json.loads(response.body)
74 values = [suggestion['value'] for suggestion in result['suggestions']]
75 assert user_group_name in values
76
77 def test_returns_inactive_user_groups_when_active_flag_sent(
78 self, user_util, xhr_header):
79 self.log_user()
80 user_group = user_util.create_user_group(active=False)
81 user_group_name = user_group.users_group_name
82
83 response = self.app.get(
84 route_path('user_group_autocomplete_data',
85 params=dict(active='0')),
86 extra_environ=xhr_header, status=200)
87 result = json.loads(response.body)
88 values = [suggestion['value'] for suggestion in result['suggestions']]
89 assert user_group_name in values
90
91 response = self.app.get(
92 route_path('user_group_autocomplete_data',
93 params=dict(active='1')),
94 extra_environ=xhr_header, status=200)
95 result = json.loads(response.body)
96 values = [suggestion['value'] for suggestion in result['suggestions']]
97 assert user_group_name not in values
98
99 @pytest.mark.parametrize('query, count', [
100 ('hello1', 0),
101 ('dev', 1),
102 ])
103 def test_result_is_limited_when_query_is_sent(self, user_util, xhr_header, query, count):
104 self.log_user()
105
106 user_util._test_name = 'dev-test'
107 user_util.create_user_group()
108
109 response = self.app.get(
110 route_path('user_group_autocomplete_data',
111 params=dict(user_groups='true',
112 query=query)),
113 extra_environ=xhr_header, status=200)
114
115 result = json.loads(response.body)
116
117 assert len(result['suggestions']) == count
@@ -0,0 +1,304 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import re
22 import logging
23
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, \
29 HasRepoGroupPermissionAnyDecorator
30 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.lib.utils2 import safe_unicode, str2bool
32 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import func, Repository, RepoGroup
34 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
37 from rhodecode.model.user import UserModel
38 from rhodecode.model.user_group import UserGroupModel
39
40 log = logging.getLogger(__name__)
41
42
43 class HomeView(BaseAppView):
44
45 def load_default_context(self):
46 c = self._get_local_tmpl_context()
47 c.user = c.auth_user.get_instance()
48 self._register_global_c(c)
49 return c
50
51 @LoginRequired()
52 @view_config(
53 route_name='user_autocomplete_data', request_method='GET',
54 renderer='json_ext', xhr=True)
55 def user_autocomplete_data(self):
56 query = self.request.GET.get('query')
57 active = str2bool(self.request.GET.get('active') or True)
58 include_groups = str2bool(self.request.GET.get('user_groups'))
59 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
60 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
61
62 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
63 query, active, include_groups)
64
65 _users = UserModel().get_users(
66 name_contains=query, only_active=active)
67
68 def maybe_skip_default_user(usr):
69 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
70 return False
71 return True
72 _users = filter(maybe_skip_default_user, _users)
73
74 if include_groups:
75 # extend with user groups
76 _user_groups = UserGroupModel().get_user_groups(
77 name_contains=query, only_active=active,
78 expand_groups=expand_groups)
79 _users = _users + _user_groups
80
81 return {'suggestions': _users}
82
83 @LoginRequired()
84 @NotAnonymous()
85 @view_config(
86 route_name='user_group_autocomplete_data', request_method='GET',
87 renderer='json_ext', xhr=True)
88 def user_group_autocomplete_data(self):
89 query = self.request.GET.get('query')
90 active = str2bool(self.request.GET.get('active') or True)
91 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
92
93 log.debug('generating user group list, query:%s, active:%s',
94 query, active)
95
96 _user_groups = UserGroupModel().get_user_groups(
97 name_contains=query, only_active=active,
98 expand_groups=expand_groups)
99 _user_groups = _user_groups
100
101 return {'suggestions': _user_groups}
102
103 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
104 query = Repository.query()\
105 .order_by(func.length(Repository.repo_name))\
106 .order_by(Repository.repo_name)
107
108 if repo_type:
109 query = query.filter(Repository.repo_type == repo_type)
110
111 if name_contains:
112 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
113 query = query.filter(
114 Repository.repo_name.ilike(ilike_expression))
115 query = query.limit(limit)
116
117 all_repos = query.all()
118 # permission checks are inside this function
119 repo_iter = ScmModel().get_repos(all_repos)
120 return [
121 {
122 'id': obj['name'],
123 'text': obj['name'],
124 'type': 'repo',
125 'obj': obj['dbrepo'],
126 'url': h.route_path('repo_summary', repo_name=obj['name'])
127 }
128 for obj in repo_iter]
129
130 def _get_repo_group_list(self, name_contains=None, limit=20):
131 query = RepoGroup.query()\
132 .order_by(func.length(RepoGroup.group_name))\
133 .order_by(RepoGroup.group_name)
134
135 if name_contains:
136 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
137 query = query.filter(
138 RepoGroup.group_name.ilike(ilike_expression))
139 query = query.limit(limit)
140
141 all_groups = query.all()
142 repo_groups_iter = ScmModel().get_repo_groups(all_groups)
143 return [
144 {
145 'id': obj.group_name,
146 'text': obj.group_name,
147 'type': 'group',
148 'obj': {},
149 'url': h.route_path('repo_group_home', repo_group_name=obj.group_name)
150 }
151 for obj in repo_groups_iter]
152
153 def _get_hash_commit_list(self, auth_user, hash_starts_with=None):
154 if not hash_starts_with or len(hash_starts_with) < 3:
155 return []
156
157 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
158
159 if len(commit_hashes) != 1:
160 return []
161
162 commit_hash_prefix = commit_hashes[0]
163
164 searcher = searcher_from_config(self.request.registry.settings)
165 result = searcher.search(
166 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
167 raise_on_exc=False)
168
169 return [
170 {
171 'id': entry['commit_id'],
172 'text': entry['commit_id'],
173 'type': 'commit',
174 'obj': {'repo': entry['repository']},
175 'url': h.url('changeset_home',
176 repo_name=entry['repository'],
177 revision=entry['commit_id'])
178 }
179 for entry in result['results']]
180
181 @LoginRequired()
182 @view_config(
183 route_name='repo_list_data', request_method='GET',
184 renderer='json_ext', xhr=True)
185 def repo_list_data(self):
186 _ = self.request.translate
187
188 query = self.request.GET.get('query')
189 repo_type = self.request.GET.get('repo_type')
190 log.debug('generating repo list, query:%s, repo_type:%s',
191 query, repo_type)
192
193 res = []
194 repos = self._get_repo_list(query, repo_type=repo_type)
195 if repos:
196 res.append({
197 'text': _('Repositories'),
198 'children': repos
199 })
200
201 data = {
202 'more': False,
203 'results': res
204 }
205 return data
206
207 @LoginRequired()
208 @view_config(
209 route_name='goto_switcher_data', request_method='GET',
210 renderer='json_ext', xhr=True)
211 def goto_switcher_data(self):
212 c = self.load_default_context()
213
214 _ = self.request.translate
215
216 query = self.request.GET.get('query')
217 log.debug('generating goto switcher list, query %s', query)
218
219 res = []
220 repo_groups = self._get_repo_group_list(query)
221 if repo_groups:
222 res.append({
223 'text': _('Groups'),
224 'children': repo_groups
225 })
226
227 repos = self._get_repo_list(query)
228 if repos:
229 res.append({
230 'text': _('Repositories'),
231 'children': repos
232 })
233
234 commits = self._get_hash_commit_list(c.auth_user, query)
235 if commits:
236 unique_repos = {}
237 for commit in commits:
238 unique_repos.setdefault(commit['obj']['repo'], []
239 ).append(commit)
240
241 for repo in unique_repos:
242 res.append({
243 'text': _('Commits in %(repo)s') % {'repo': repo},
244 'children': unique_repos[repo]
245 })
246
247 data = {
248 'more': False,
249 'results': res
250 }
251 return data
252
253 def _get_groups_and_repos(self, repo_group_id=None):
254 # repo groups groups
255 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
256 _perms = ['group.read', 'group.write', 'group.admin']
257 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
258 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
259 repo_group_list=repo_group_list_acl, admin=False)
260
261 # repositories
262 repo_list = Repository.get_all_repos(group_id=repo_group_id)
263 _perms = ['repository.read', 'repository.write', 'repository.admin']
264 repo_list_acl = RepoList(repo_list, perm_set=_perms)
265 repo_data = RepoModel().get_repos_as_dict(
266 repo_list=repo_list_acl, admin=False)
267
268 return repo_data, repo_group_data
269
270 @LoginRequired()
271 @view_config(
272 route_name='home', request_method='GET',
273 renderer='rhodecode:templates/index.mako')
274 def main_page(self):
275 c = self.load_default_context()
276 c.repo_group = None
277
278 repo_data, repo_group_data = self._get_groups_and_repos()
279 # json used to render the grids
280 c.repos_data = json.dumps(repo_data)
281 c.repo_groups_data = json.dumps(repo_group_data)
282
283 return self._get_template_context(c)
284
285 @LoginRequired()
286 @HasRepoGroupPermissionAnyDecorator(
287 'group.read', 'group.write', 'group.admin')
288 @view_config(
289 route_name='repo_group_home', request_method='GET',
290 renderer='rhodecode:templates/index_repo_group.mako')
291 @view_config(
292 route_name='repo_group_home_slash', request_method='GET',
293 renderer='rhodecode:templates/index_repo_group.mako')
294 def repo_group_main_page(self):
295 c = self.load_default_context()
296 c.repo_group = self.request.db_repo_group
297 repo_data, repo_group_data = self._get_groups_and_repos(
298 c.repo_group.group_id)
299
300 # json used to render the grids
301 c.repos_data = json.dumps(repo_data)
302 c.repo_groups_data = json.dumps(repo_group_data)
303
304 return self._get_template_context(c)
@@ -0,0 +1,93 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import User, UserEmailMap
25 from rhodecode.tests import (
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
27 assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29
30 fixture = Fixture()
31
32
33 def route_path(name, **kwargs):
34 return {
35 'my_account_emails':
36 ADMIN_PREFIX + '/my_account/emails',
37 'my_account_emails_add':
38 ADMIN_PREFIX + '/my_account/emails/new',
39 'my_account_emails_delete':
40 ADMIN_PREFIX + '/my_account/emails/delete',
41 }[name].format(**kwargs)
42
43
44 class TestMyAccountEmails(TestController):
45 def test_my_account_my_emails(self):
46 self.log_user()
47 response = self.app.get(route_path('my_account_emails'))
48 response.mustcontain('No additional emails specified')
49
50 def test_my_account_my_emails_add_existing_email(self):
51 self.log_user()
52 response = self.app.get(route_path('my_account_emails'))
53 response.mustcontain('No additional emails specified')
54 response = self.app.post(route_path('my_account_emails_add'),
55 {'new_email': TEST_USER_REGULAR_EMAIL,
56 'csrf_token': self.csrf_token})
57 assert_session_flash(response, 'This e-mail address is already taken')
58
59 def test_my_account_my_emails_add_mising_email_in_form(self):
60 self.log_user()
61 response = self.app.get(route_path('my_account_emails'))
62 response.mustcontain('No additional emails specified')
63 response = self.app.post(route_path('my_account_emails_add'),
64 {'csrf_token': self.csrf_token})
65 assert_session_flash(response, 'Please enter an email address')
66
67 def test_my_account_my_emails_add_remove(self):
68 self.log_user()
69 response = self.app.get(route_path('my_account_emails'))
70 response.mustcontain('No additional emails specified')
71
72 response = self.app.post(route_path('my_account_emails_add'),
73 {'new_email': 'foo@barz.com',
74 'csrf_token': self.csrf_token})
75
76 response = self.app.get(route_path('my_account_emails'))
77
78 email_id = UserEmailMap.query().filter(
79 UserEmailMap.user == User.get_by_username(
80 TEST_USER_ADMIN_LOGIN)).filter(
81 UserEmailMap.email == 'foo@barz.com').one().email_id
82
83 response.mustcontain('foo@barz.com')
84 response.mustcontain('<input id="del_email_id" name="del_email_id" '
85 'type="hidden" value="%s" />' % email_id)
86
87 response = self.app.post(
88 route_path('my_account_emails_delete'), {
89 'del_email_id': email_id,
90 'csrf_token': self.csrf_token})
91 assert_session_flash(response, 'Email successfully deleted')
92 response = self.app.get(route_path('my_account_emails'))
93 response.mustcontain('No additional emails specified')
@@ -0,0 +1,76 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import User, UserEmailMap, Repository, UserFollowing
25 from rhodecode.tests import (
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
27 assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29
30 fixture = Fixture()
31
32
33 def route_path(name, **kwargs):
34 return {
35 'my_account_repos':
36 ADMIN_PREFIX + '/my_account/repos',
37 'my_account_watched':
38 ADMIN_PREFIX + '/my_account/watched',
39 'my_account_perms':
40 ADMIN_PREFIX + '/my_account/perms',
41 'my_account_notifications':
42 ADMIN_PREFIX + '/my_account/notifications',
43 }[name].format(**kwargs)
44
45
46 class TestMyAccountSimpleViews(TestController):
47
48 def test_my_account_my_repos(self, autologin_user):
49 response = self.app.get(route_path('my_account_repos'))
50 repos = Repository.query().filter(
51 Repository.user == User.get_by_username(
52 TEST_USER_ADMIN_LOGIN)).all()
53 for repo in repos:
54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
55
56 def test_my_account_my_watched(self, autologin_user):
57 response = self.app.get(route_path('my_account_watched'))
58
59 repos = UserFollowing.query().filter(
60 UserFollowing.user == User.get_by_username(
61 TEST_USER_ADMIN_LOGIN)).all()
62 for repo in repos:
63 response.mustcontain(
64 '"name_raw": "%s"' % repo.follows_repository.repo_name)
65
66 def test_my_account_perms(self, autologin_user):
67 response = self.app.get(route_path('my_account_perms'))
68 assert_response = response.assert_response()
69 assert assert_response.get_elements('.perm_tag.none')
70 assert assert_response.get_elements('.perm_tag.read')
71 assert assert_response.get_elements('.perm_tag.write')
72 assert assert_response.get_elements('.perm_tag.admin')
73
74 def test_my_account_notifications(self, autologin_user):
75 response = self.app.get(route_path('my_account_notifications'))
76 response.mustcontain('Test flash message')
@@ -0,0 +1,35 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from rhodecode.config.routing import ADMIN_PREFIX
22
23
24 def admin_routes(config):
25 config.add_route(
26 name='ops_ping',
27 pattern='/ping')
28
29
30 def includeme(config):
31
32 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
33
34 # Scan module for configuration decorators.
35 config.scan()
@@ -0,0 +1,54 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24
25 from rhodecode.apps._base import BaseAppView
26
27
28 log = logging.getLogger(__name__)
29
30
31 class OpsView(BaseAppView):
32
33 def load_default_context(self):
34 c = self._get_local_tmpl_context()
35 c.user = c.auth_user.get_instance()
36 self._register_global_c(c)
37 return c
38
39 @view_config(
40 route_name='ops_ping', request_method='GET',
41 renderer='json_ext')
42 def ops_ping(self):
43 data = {
44 'instance': self.request.registry.settings.get('instance_id'),
45 }
46 if getattr(self.request, 'user'):
47 data.update({
48 'caller_ip': self.request.user.ip_addr,
49 'caller_name': self.request.user.username,
50 })
51 return {'ok': data}
52
53
54
@@ -0,0 +1,33 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
21
22
23 def includeme(config):
24
25 # Summary
26 add_route_with_slash(
27 config,
28 name='repo_group_home',
29 pattern='/{repo_group_name:.*?[^/]}', repo_group_route=True)
30
31 # Scan module for configuration decorators.
32 config.scan()
33
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,83 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22 from rhodecode.model.db import Repository
23
24
25 def route_path(name, params=None, **kwargs):
26 import urllib
27
28 base_url = {
29 'pullrequest_show_all': '/{repo_name}/pull-request',
30 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
31 }[name].format(**kwargs)
32
33 if params:
34 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
35 return base_url
36
37
38 @pytest.mark.backends("git", "hg")
39 @pytest.mark.usefixtures('autologin_user', 'app')
40 class TestPullRequestList(object):
41
42 @pytest.mark.parametrize('params, expected_title', [
43 ({'source': 0, 'closed': 1}, 'Closed Pull Requests'),
44 ({'source': 0, 'my': 1}, 'opened by me'),
45 ({'source': 0, 'awaiting_review': 1}, 'awaiting review'),
46 ({'source': 0, 'awaiting_my_review': 1}, 'awaiting my review'),
47 ({'source': 1}, 'Pull Requests from'),
48 ])
49 def test_showing_list_page(self, backend, pr_util, params, expected_title):
50 pull_request = pr_util.create_pull_request()
51
52 response = self.app.get(
53 route_path('pullrequest_show_all',
54 repo_name=pull_request.target_repo.repo_name,
55 params=params))
56
57 assert_response = response.assert_response()
58 assert_response.element_equals_to('.panel-title', expected_title)
59 element = assert_response.get_element('.panel-title')
60 element_text = assert_response._element_to_string(element)
61
62 def test_showing_list_page_data(self, backend, pr_util, xhr_header):
63 pull_request = pr_util.create_pull_request()
64 response = self.app.get(
65 route_path('pullrequest_show_all_data',
66 repo_name=pull_request.target_repo.repo_name),
67 extra_environ=xhr_header)
68
69 assert response.json['recordsTotal'] == 1
70 assert response.json['data'][0]['description'] == 'Description'
71
72 def test_description_is_escaped_on_index_page(self, backend, pr_util, xhr_header):
73 xss_description = "<script>alert('Hi!')</script>"
74 pull_request = pr_util.create_pull_request(description=xss_description)
75
76 response = self.app.get(
77 route_path('pullrequest_show_all_data',
78 repo_name=pull_request.target_repo.repo_name),
79 extra_environ=xhr_header)
80
81 assert response.json['recordsTotal'] == 1
82 assert response.json['data'][0]['description'] == \
83 "&lt;script&gt;alert(&#39;Hi!&#39;)&lt;/script&gt;"
@@ -0,0 +1,233 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import mock
22 import pytest
23
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import (
29 url, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN,
30 assert_session_flash)
31 from rhodecode.tests.fixture import Fixture
32
33 fixture = Fixture()
34
35
36 def route_path(name, params=None, **kwargs):
37 import urllib
38
39 base_url = {
40 'edit_repo': '/{repo_name}/settings',
41 'edit_repo_advanced': '/{repo_name}/settings/advanced',
42 'edit_repo_caches': '/{repo_name}/settings/caches',
43 'edit_repo_perms': '/{repo_name}/settings/permissions',
44 }[name].format(**kwargs)
45
46 if params:
47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
48 return base_url
49
50
51 def _get_permission_for_user(user, repo):
52 perm = UserRepoToPerm.query()\
53 .filter(UserRepoToPerm.repository ==
54 Repository.get_by_repo_name(repo))\
55 .filter(UserRepoToPerm.user == User.get_by_username(user))\
56 .all()
57 return perm
58
59
60 @pytest.mark.usefixtures('autologin_user', 'app')
61 class TestAdminRepoSettings(object):
62 @pytest.mark.parametrize('urlname', [
63 'edit_repo',
64 'edit_repo_caches',
65 'edit_repo_perms',
66 'edit_repo_advanced',
67 ])
68 def test_show_page(self, urlname, app, backend):
69 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
70
71 def test_edit_accessible_when_missing_requirements(
72 self, backend_hg, autologin_user):
73 scm_patcher = mock.patch.object(
74 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
75 with scm_patcher:
76 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
77
78 @pytest.mark.parametrize('urlname', [
79 'repo_vcs_settings',
80 'repo_settings_issuetracker',
81 'edit_repo_fields',
82 'edit_repo_remote',
83 'edit_repo_statistics',
84 ])
85 def test_show_page_pylons(self, urlname, app):
86 app.get(url(urlname, repo_name=HG_REPO))
87
88 @pytest.mark.parametrize('update_settings', [
89 {'repo_description': 'alter-desc'},
90 {'repo_owner': TEST_USER_REGULAR_LOGIN},
91 {'repo_private': 'true'},
92 {'repo_enable_locking': 'true'},
93 {'repo_enable_downloads': 'true'},
94 ])
95 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
96 repo = user_util.create_repo(repo_type=backend.alias)
97 repo_name = repo.repo_name
98
99 params = fixture._get_repo_create_params(
100 csrf_token=csrf_token,
101 repo_name=repo_name,
102 repo_type=backend.alias,
103 repo_owner=TEST_USER_ADMIN_LOGIN,
104 repo_description='DESC',
105
106 repo_private='false',
107 repo_enable_locking='false',
108 repo_enable_downloads='false')
109 params.update(update_settings)
110 self.app.post(
111 route_path('edit_repo', repo_name=repo_name),
112 params=params, status=302)
113
114 repo = Repository.get_by_repo_name(repo_name)
115 assert repo.user.username == \
116 update_settings.get('repo_owner', repo.user.username)
117
118 assert repo.description == \
119 update_settings.get('repo_description', repo.description)
120
121 assert repo.private == \
122 str2bool(update_settings.get(
123 'repo_private', repo.private))
124
125 assert repo.enable_locking == \
126 str2bool(update_settings.get(
127 'repo_enable_locking', repo.enable_locking))
128
129 assert repo.enable_downloads == \
130 str2bool(update_settings.get(
131 'repo_enable_downloads', repo.enable_downloads))
132
133 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
134 repo = user_util.create_repo(repo_type=backend.alias)
135 repo_name = repo.repo_name
136
137 repo_group = user_util.create_repo_group()
138 repo_group_name = repo_group.group_name
139 new_name = repo_group_name + '_' + repo_name
140
141 params = fixture._get_repo_create_params(
142 csrf_token=csrf_token,
143 repo_name=new_name,
144 repo_type=backend.alias,
145 repo_owner=TEST_USER_ADMIN_LOGIN,
146 repo_description='DESC',
147 repo_private='false',
148 repo_enable_locking='false',
149 repo_enable_downloads='false')
150 self.app.post(
151 route_path('edit_repo', repo_name=repo_name),
152 params=params, status=302)
153 repo = Repository.get_by_repo_name(new_name)
154 assert repo.repo_name == new_name
155
156 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
157 repo = user_util.create_repo(repo_type=backend.alias)
158 repo_name = repo.repo_name
159
160 repo_group = user_util.create_repo_group()
161 repo_group_name = repo_group.group_name
162 repo_group_id = repo_group.group_id
163
164 new_name = repo_group_name + '/' + repo_name
165 params = fixture._get_repo_create_params(
166 csrf_token=csrf_token,
167 repo_name=repo_name,
168 repo_type=backend.alias,
169 repo_owner=TEST_USER_ADMIN_LOGIN,
170 repo_description='DESC',
171 repo_group=repo_group_id,
172 repo_private='false',
173 repo_enable_locking='false',
174 repo_enable_downloads='false')
175 self.app.post(
176 route_path('edit_repo', repo_name=repo_name),
177 params=params, status=302)
178 repo = Repository.get_by_repo_name(new_name)
179 assert repo.repo_name == new_name
180
181 def test_set_private_flag_sets_default_user_permissions_to_none(
182 self, autologin_user, backend, csrf_token):
183
184 # initially repository perm should be read
185 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
186 assert len(perm) == 1
187 assert perm[0].permission.permission_name == 'repository.read'
188 assert not backend.repo.private
189
190 response = self.app.post(
191 route_path('edit_repo', repo_name=backend.repo_name),
192 params=fixture._get_repo_create_params(
193 repo_private='true',
194 repo_name=backend.repo_name,
195 repo_type=backend.alias,
196 repo_owner=TEST_USER_ADMIN_LOGIN,
197 csrf_token=csrf_token), status=302)
198
199 assert_session_flash(
200 response,
201 msg='Repository %s updated successfully' % (backend.repo_name))
202
203 repo = Repository.get_by_repo_name(backend.repo_name)
204 assert repo.private is True
205
206 # now the repo default permission should be None
207 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
208 assert len(perm) == 1
209 assert perm[0].permission.permission_name == 'repository.none'
210
211 response = self.app.post(
212 route_path('edit_repo', repo_name=backend.repo_name),
213 params=fixture._get_repo_create_params(
214 repo_private='false',
215 repo_name=backend.repo_name,
216 repo_type=backend.alias,
217 repo_owner=TEST_USER_ADMIN_LOGIN,
218 csrf_token=csrf_token), status=302)
219
220 assert_session_flash(
221 response,
222 msg='Repository %s updated successfully' % (backend.repo_name))
223 assert backend.repo.private is False
224
225 # we turn off private now the repo default permission should stay None
226 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
227 assert len(perm) == 1
228 assert perm[0].permission.permission_name == 'repository.none'
229
230 # update this permission back
231 perm[0].permission = Permission.get_by_key('repository.read')
232 Session().add(perm[0])
233 Session().commit()
@@ -0,0 +1,150 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.lib.utils2 import safe_unicode, safe_str
24 from rhodecode.model.db import Repository
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.tests import (
27 HG_REPO, GIT_REPO, assert_session_flash, no_newline_id_generator)
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import repo_on_filesystem
30
31 fixture = Fixture()
32
33
34 def route_path(name, params=None, **kwargs):
35 import urllib
36
37 base_url = {
38 'repo_summary_explicit': '/{repo_name}/summary',
39 'repo_summary': '/{repo_name}',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
41 'edit_repo_advanced_delete': '/{repo_name}/settings/advanced/delete',
42 'edit_repo_advanced_fork': '/{repo_name}/settings/advanced/fork',
43 'edit_repo_advanced_locking': '/{repo_name}/settings/advanced/locking',
44 'edit_repo_advanced_journal': '/{repo_name}/settings/advanced/journal',
45
46 }[name].format(**kwargs)
47
48 if params:
49 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
50 return base_url
51
52
53 @pytest.mark.usefixtures('autologin_user', 'app')
54 class TestAdminRepoSettingsAdvanced(object):
55
56 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
57 repo = backend.repo
58 response = self.app.get(
59 route_path('edit_repo_advanced', repo_name=backend.repo_name))
60 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
61 response.mustcontain(no=[opt])
62
63 def test_set_fork_of_target_repo(
64 self, autologin_user, backend, csrf_token):
65 target_repo = 'target_%s' % backend.alias
66 fixture.create_repo(target_repo, repo_type=backend.alias)
67 repo2 = Repository.get_by_repo_name(target_repo)
68 response = self.app.post(
69 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
70 params={'id_fork_of': repo2.repo_id,
71 'csrf_token': csrf_token})
72 repo = Repository.get_by_repo_name(backend.repo_name)
73 repo2 = Repository.get_by_repo_name(target_repo)
74 assert_session_flash(
75 response,
76 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
77
78 assert repo.fork == repo2
79 response = response.follow()
80 # check if given repo is selected
81
82 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
83 route_path('repo_summary', repo_name=repo2.repo_name),
84 repo2.repo_name)
85
86 response.mustcontain(opt)
87
88 fixture.destroy_repo(target_repo, forks='detach')
89
90 @pytest.mark.backends("hg", "git")
91 def test_set_fork_of_other_type_repo(
92 self, autologin_user, backend, csrf_token):
93 TARGET_REPO_MAP = {
94 'git': {
95 'type': 'hg',
96 'repo_name': HG_REPO},
97 'hg': {
98 'type': 'git',
99 'repo_name': GIT_REPO},
100 }
101 target_repo = TARGET_REPO_MAP[backend.alias]
102
103 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
104 response = self.app.post(
105 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
106 params={'id_fork_of': repo2.repo_id,
107 'csrf_token': csrf_token})
108 assert_session_flash(
109 response,
110 'Cannot set repository as fork of repository with other type')
111
112 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
113 # mark it as None
114 response = self.app.post(
115 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
116 params={'id_fork_of': None, '_method': 'put',
117 'csrf_token': csrf_token})
118 assert_session_flash(
119 response,
120 'Marked repo %s as fork of %s'
121 % (backend.repo_name, "Nothing"))
122 assert backend.repo.fork is None
123
124 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
125 repo = Repository.get_by_repo_name(backend.repo_name)
126 response = self.app.post(
127 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
128 params={'id_fork_of': repo.repo_id, 'csrf_token': csrf_token})
129 assert_session_flash(
130 response, 'An error occurred during this operation')
131
132 @pytest.mark.parametrize(
133 "suffix",
134 ['', u'ąęł' , '123'],
135 ids=no_newline_id_generator)
136 def test_advanced_delete(self, autologin_user, backend, suffix, csrf_token):
137 repo = backend.create_repo(name_suffix=suffix)
138 repo_name = repo.repo_name
139 repo_name_str = safe_str(repo.repo_name)
140
141 response = self.app.post(
142 route_path('edit_repo_advanced_delete', repo_name=repo_name_str),
143 params={'csrf_token': csrf_token})
144 assert_session_flash(response,
145 u'Deleted repository `{}`'.format(repo_name))
146 response.follow()
147
148 # check if repo was deleted from db
149 assert RepoModel().get_by_repo_name(repo_name) is None
150 assert not repo_on_filesystem(repo_name_str)
@@ -0,0 +1,117 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import mock
22 import pytest
23
24 import rhodecode
25 from rhodecode.model.settings import SettingsModel
26 from rhodecode.tests import url
27 from rhodecode.tests.utils import AssertResponse
28
29
30 def route_path(name, params=None, **kwargs):
31 import urllib
32
33 base_url = {
34 'edit_repo': '/{repo_name}/settings',
35 }[name].format(**kwargs)
36
37 if params:
38 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
39 return base_url
40
41
42 @pytest.mark.usefixtures('autologin_user', 'app')
43 class TestAdminRepoVcsSettings(object):
44
45 @pytest.mark.parametrize('setting_name, setting_backends', [
46 ('hg_use_rebase_for_merging', ['hg']),
47 ])
48 def test_labs_settings_visible_if_enabled(
49 self, setting_name, setting_backends, backend):
50 if backend.alias not in setting_backends:
51 pytest.skip('Setting not available for backend {}'.format(backend))
52
53 vcs_settings_url = url(
54 'repo_vcs_settings', repo_name=backend.repo.repo_name)
55
56 with mock.patch.dict(
57 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
58 response = self.app.get(vcs_settings_url)
59
60 assertr = AssertResponse(response)
61 assertr.one_element_exists('#rhodecode_{}'.format(setting_name))
62
63 @pytest.mark.parametrize('setting_name, setting_backends', [
64 ('hg_use_rebase_for_merging', ['hg']),
65 ])
66 def test_labs_settings_not_visible_if_disabled(
67 self, setting_name, setting_backends, backend):
68 if backend.alias not in setting_backends:
69 pytest.skip('Setting not available for backend {}'.format(backend))
70
71 vcs_settings_url = url(
72 'repo_vcs_settings', repo_name=backend.repo.repo_name)
73
74 with mock.patch.dict(
75 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
76 response = self.app.get(vcs_settings_url)
77
78 assertr = AssertResponse(response)
79 assertr.no_element_exists('#rhodecode_{}'.format(setting_name))
80
81 @pytest.mark.parametrize('setting_name, setting_backends', [
82 ('hg_use_rebase_for_merging', ['hg']),
83 ])
84 def test_update_boolean_settings(
85 self, csrf_token, setting_name, setting_backends, backend):
86 if backend.alias not in setting_backends:
87 pytest.skip('Setting not available for backend {}'.format(backend))
88
89 repo = backend.create_repo()
90
91 settings_model = SettingsModel(repo=repo)
92 vcs_settings_url = url(
93 'repo_vcs_settings', repo_name=repo.repo_name)
94
95 self.app.post(
96 vcs_settings_url,
97 params={
98 'inherit_global_settings': False,
99 'new_svn_branch': 'dummy-value-for-testing',
100 'new_svn_tag': 'dummy-value-for-testing',
101 'rhodecode_{}'.format(setting_name): 'true',
102 'csrf_token': csrf_token,
103 })
104 setting = settings_model.get_setting_by_name(setting_name)
105 assert setting.app_settings_value
106
107 self.app.post(
108 vcs_settings_url,
109 params={
110 'inherit_global_settings': False,
111 'new_svn_branch': 'dummy-value-for-testing',
112 'new_svn_tag': 'dummy-value-for-testing',
113 'rhodecode_{}'.format(setting_name): 'false',
114 'csrf_token': csrf_token,
115 })
116 setting = settings_model.get_setting_by_name(setting_name)
117 assert not setting.app_settings_value
@@ -0,0 +1,76 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from rhodecode.lib import helpers as h
22 from rhodecode.lib.utils2 import safe_int
23
24
25 def reviewer_as_json(user, reasons=None, mandatory=False):
26 """
27 Returns json struct of a reviewer for frontend
28
29 :param user: the reviewer
30 :param reasons: list of strings of why they are reviewers
31 :param mandatory: bool, to set user as mandatory
32 """
33
34 return {
35 'user_id': user.user_id,
36 'reasons': reasons or [],
37 'mandatory': mandatory,
38 'username': user.username,
39 'first_name': user.first_name,
40 'last_name': user.last_name,
41 'gravatar_link': h.gravatar_url(user.email, 14),
42 }
43
44
45 def get_default_reviewers_data(
46 current_user, source_repo, source_commit, target_repo, target_commit):
47
48 """ Return json for default reviewers of a repository """
49
50 reasons = ['Default reviewer', 'Repository owner']
51 default = reviewer_as_json(
52 user=current_user, reasons=reasons, mandatory=False)
53
54 return {
55 'api_ver': 'v1', # define version for later possible schema upgrade
56 'reviewers': [default],
57 'rules': {},
58 'rules_data': {},
59 }
60
61
62 def validate_default_reviewers(review_members, reviewer_rules):
63 """
64 Function to validate submitted reviewers against the saved rules
65
66 """
67 reviewers = []
68 reviewer_by_id = {}
69 for r in review_members:
70 reviewer_user_id = safe_int(r['user_id'])
71 entry = (reviewer_user_id, r['reasons'], r['mandatory'])
72
73 reviewer_by_id[reviewer_user_id] = entry
74 reviewers.append(entry)
75
76 return reviewers
@@ -0,0 +1,50 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import logging
21
22 from pyramid.httpexceptions import HTTPNotFound
23 from pyramid.view import view_config
24
25 from rhodecode.apps._base import BaseReferencesView
26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
27 from rhodecode.lib import helpers as h
28
29 log = logging.getLogger(__name__)
30
31
32 class RepoBookmarksView(BaseReferencesView):
33
34 @LoginRequired()
35 @HasRepoPermissionAnyDecorator(
36 'repository.read', 'repository.write', 'repository.admin')
37 @view_config(
38 route_name='bookmarks_home', request_method='GET',
39 renderer='rhodecode:templates/bookmarks/bookmarks.mako')
40 def bookmarks(self):
41 c = self.load_default_context()
42
43 if not h.is_hg(self.db_repo):
44 raise HTTPNotFound()
45
46 ref_items = self.rhodecode_vcs_repo.bookmarks.items()
47 self.load_refs_context(
48 ref_items=ref_items, partials_template='bookmarks/bookmarks_data.mako')
49
50 return self._get_template_context(c)
@@ -0,0 +1,51 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 from pyramid.view import view_config
23
24 from rhodecode.apps._base import BaseReferencesView
25 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
26
27
28 log = logging.getLogger(__name__)
29
30
31 class RepoBranchesView(BaseReferencesView):
32
33 @LoginRequired()
34 @HasRepoPermissionAnyDecorator(
35 'repository.read', 'repository.write', 'repository.admin')
36 @view_config(
37 route_name='branches_home', request_method='GET',
38 renderer='rhodecode:templates/branches/branches.mako')
39 def branches(self):
40 c = self.load_default_context()
41 c.closed_branches = self.rhodecode_vcs_repo.branches_closed
42 # NOTE(marcink):
43 # we need this trick because of PartialRenderer still uses the
44 # global 'c', we might not need this after full pylons migration
45 self._register_global_c(c)
46
47 ref_items = self.rhodecode_vcs_repo.branches_all.items()
48 self.load_refs_context(
49 ref_items=ref_items, partials_template='branches/branches_data.mako')
50
51 return self._get_template_context(c)
@@ -0,0 +1,78 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
28 CSRFRequired
29 from rhodecode.lib import helpers as h
30 from rhodecode.model.meta import Session
31 from rhodecode.model.scm import ScmModel
32
33 log = logging.getLogger(__name__)
34
35
36 class RepoCachesView(RepoAppView):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
39
40 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
41 c.repo_info = self.db_repo
42
43 self._register_global_c(c)
44 return c
45
46 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.admin')
48 @view_config(
49 route_name='edit_repo_caches', request_method='GET',
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
51 def repo_caches(self):
52 c = self.load_default_context()
53 c.active = 'caches'
54
55 return self._get_template_context(c)
56
57 @LoginRequired()
58 @HasRepoPermissionAnyDecorator('repository.admin')
59 @CSRFRequired()
60 @view_config(
61 route_name='edit_repo_caches', request_method='POST')
62 def repo_caches_purge(self):
63 _ = self.request.translate
64 c = self.load_default_context()
65 c.active = 'caches'
66
67 try:
68 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
69 Session().commit()
70 h.flash(_('Cache invalidation successful'),
71 category='success')
72 except Exception:
73 log.exception("Exception during cache invalidation")
74 h.flash(_('An error occurred during cache invalidation'),
75 category='error')
76
77 raise HTTPFound(h.route_path(
78 'edit_repo_caches', repo_name=self.db_repo_name)) No newline at end of file
@@ -0,0 +1,98 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 import deform
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
33 HasRepoPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.forms import RepoPermsForm
36 from rhodecode.model.meta import Session
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.scm import RepoGroupList, ScmModel
39 from rhodecode.model.validation_schema.schemas import repo_schema
40
41 log = logging.getLogger(__name__)
42
43
44 class RepoSettingsPermissionsView(RepoAppView):
45
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
48
49 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
50 c.repo_info = self.db_repo
51
52 self._register_global_c(c)
53 return c
54
55 @LoginRequired()
56 @HasRepoPermissionAnyDecorator('repository.admin')
57 @view_config(
58 route_name='edit_repo_perms', request_method='GET',
59 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
60 def edit_permissions(self):
61 c = self.load_default_context()
62 c.active = 'permissions'
63 return self._get_template_context(c)
64
65 @LoginRequired()
66 @HasRepoPermissionAllDecorator('repository.admin')
67 @CSRFRequired()
68 @view_config(
69 route_name='edit_repo_perms', request_method='POST',
70 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
71 def edit_permissions_update(self):
72 _ = self.request.translate
73 c = self.load_default_context()
74 c.active = 'permissions'
75 data = self.request.POST
76 # store private flag outside of HTML to verify if we can modify
77 # default user permissions, prevents submition of FAKE post data
78 # into the form for private repos
79 data['repo_private'] = self.db_repo.private
80 form = RepoPermsForm()().to_python(data)
81 changes = RepoModel().update_permissions(
82 self.db_repo_name, form['perm_additions'], form['perm_updates'],
83 form['perm_deletions'])
84
85 action_data = {
86 'added': changes['added'],
87 'updated': changes['updated'],
88 'deleted': changes['deleted'],
89 }
90 audit_logger.store_web(
91 'repo.edit.permissions', action_data=action_data,
92 user=self._rhodecode_user, repo=self.db_repo)
93
94 Session().commit()
95 h.flash(_('Repository permissions updated'), category='success')
96
97 raise HTTPFound(
98 self.request.route_path('edit_repo_perms', repo_name=self.db_repo_name))
This diff has been collapsed as it changes many lines, (584 lines changed) Show them Hide them
@@ -0,0 +1,584 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 import collections
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import RepoAppView, DataGridAppView
28 from rhodecode.lib import helpers as h, diffs, codeblocks
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator)
31 from rhodecode.lib.utils import PartialRenderer
32 from rhodecode.lib.utils2 import str2bool, safe_int, safe_str
33 from rhodecode.lib.vcs.backends.base import EmptyCommit
34 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError, \
35 RepositoryRequirementError, NodeDoesNotExistError
36 from rhodecode.model.comment import CommentsModel
37 from rhodecode.model.db import PullRequest, PullRequestVersion, \
38 ChangesetComment, ChangesetStatus
39 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
40
41 log = logging.getLogger(__name__)
42
43
44 class RepoPullRequestsView(RepoAppView, DataGridAppView):
45
46 def load_default_context(self):
47 c = self._get_local_tmpl_context(include_app_defaults=True)
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 c.repo_info = self.db_repo
50 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
51 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
52 self._register_global_c(c)
53 return c
54
55 def _get_pull_requests_list(
56 self, repo_name, source, filter_type, opened_by, statuses):
57
58 draw, start, limit = self._extract_chunk(self.request)
59 search_q, order_by, order_dir = self._extract_ordering(self.request)
60 _render = PartialRenderer('data_table/_dt_elements.mako')
61
62 # pagination
63
64 if filter_type == 'awaiting_review':
65 pull_requests = PullRequestModel().get_awaiting_review(
66 repo_name, source=source, opened_by=opened_by,
67 statuses=statuses, offset=start, length=limit,
68 order_by=order_by, order_dir=order_dir)
69 pull_requests_total_count = PullRequestModel().count_awaiting_review(
70 repo_name, source=source, statuses=statuses,
71 opened_by=opened_by)
72 elif filter_type == 'awaiting_my_review':
73 pull_requests = PullRequestModel().get_awaiting_my_review(
74 repo_name, source=source, opened_by=opened_by,
75 user_id=self._rhodecode_user.user_id, statuses=statuses,
76 offset=start, length=limit, order_by=order_by,
77 order_dir=order_dir)
78 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
79 repo_name, source=source, user_id=self._rhodecode_user.user_id,
80 statuses=statuses, opened_by=opened_by)
81 else:
82 pull_requests = PullRequestModel().get_all(
83 repo_name, source=source, opened_by=opened_by,
84 statuses=statuses, offset=start, length=limit,
85 order_by=order_by, order_dir=order_dir)
86 pull_requests_total_count = PullRequestModel().count_all(
87 repo_name, source=source, statuses=statuses,
88 opened_by=opened_by)
89
90 data = []
91 comments_model = CommentsModel()
92 for pr in pull_requests:
93 comments = comments_model.get_all_comments(
94 self.db_repo.repo_id, pull_request=pr)
95
96 data.append({
97 'name': _render('pullrequest_name',
98 pr.pull_request_id, pr.target_repo.repo_name),
99 'name_raw': pr.pull_request_id,
100 'status': _render('pullrequest_status',
101 pr.calculated_review_status()),
102 'title': _render(
103 'pullrequest_title', pr.title, pr.description),
104 'description': h.escape(pr.description),
105 'updated_on': _render('pullrequest_updated_on',
106 h.datetime_to_time(pr.updated_on)),
107 'updated_on_raw': h.datetime_to_time(pr.updated_on),
108 'created_on': _render('pullrequest_updated_on',
109 h.datetime_to_time(pr.created_on)),
110 'created_on_raw': h.datetime_to_time(pr.created_on),
111 'author': _render('pullrequest_author',
112 pr.author.full_contact, ),
113 'author_raw': pr.author.full_name,
114 'comments': _render('pullrequest_comments', len(comments)),
115 'comments_raw': len(comments),
116 'closed': pr.is_closed(),
117 })
118
119 data = ({
120 'draw': draw,
121 'data': data,
122 'recordsTotal': pull_requests_total_count,
123 'recordsFiltered': pull_requests_total_count,
124 })
125 return data
126
127 @LoginRequired()
128 @HasRepoPermissionAnyDecorator(
129 'repository.read', 'repository.write', 'repository.admin')
130 @view_config(
131 route_name='pullrequest_show_all', request_method='GET',
132 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
133 def pull_request_list(self):
134 c = self.load_default_context()
135
136 req_get = self.request.GET
137 c.source = str2bool(req_get.get('source'))
138 c.closed = str2bool(req_get.get('closed'))
139 c.my = str2bool(req_get.get('my'))
140 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
141 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
142
143 c.active = 'open'
144 if c.my:
145 c.active = 'my'
146 if c.closed:
147 c.active = 'closed'
148 if c.awaiting_review and not c.source:
149 c.active = 'awaiting'
150 if c.source and not c.awaiting_review:
151 c.active = 'source'
152 if c.awaiting_my_review:
153 c.active = 'awaiting_my'
154
155 return self._get_template_context(c)
156
157 @LoginRequired()
158 @HasRepoPermissionAnyDecorator(
159 'repository.read', 'repository.write', 'repository.admin')
160 @view_config(
161 route_name='pullrequest_show_all_data', request_method='GET',
162 renderer='json_ext', xhr=True)
163 def pull_request_list_data(self):
164
165 # additional filters
166 req_get = self.request.GET
167 source = str2bool(req_get.get('source'))
168 closed = str2bool(req_get.get('closed'))
169 my = str2bool(req_get.get('my'))
170 awaiting_review = str2bool(req_get.get('awaiting_review'))
171 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
172
173 filter_type = 'awaiting_review' if awaiting_review \
174 else 'awaiting_my_review' if awaiting_my_review \
175 else None
176
177 opened_by = None
178 if my:
179 opened_by = [self._rhodecode_user.user_id]
180
181 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
182 if closed:
183 statuses = [PullRequest.STATUS_CLOSED]
184
185 data = self._get_pull_requests_list(
186 repo_name=self.db_repo_name, source=source,
187 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
188
189 return data
190
191 def _get_pr_version(self, pull_request_id, version=None):
192 pull_request_id = safe_int(pull_request_id)
193 at_version = None
194
195 if version and version == 'latest':
196 pull_request_ver = PullRequest.get(pull_request_id)
197 pull_request_obj = pull_request_ver
198 _org_pull_request_obj = pull_request_obj
199 at_version = 'latest'
200 elif version:
201 pull_request_ver = PullRequestVersion.get_or_404(version)
202 pull_request_obj = pull_request_ver
203 _org_pull_request_obj = pull_request_ver.pull_request
204 at_version = pull_request_ver.pull_request_version_id
205 else:
206 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
207 pull_request_id)
208
209 pull_request_display_obj = PullRequest.get_pr_display_object(
210 pull_request_obj, _org_pull_request_obj)
211
212 return _org_pull_request_obj, pull_request_obj, \
213 pull_request_display_obj, at_version
214
215 def _get_diffset(self, source_repo_name, source_repo,
216 source_ref_id, target_ref_id,
217 target_commit, source_commit, diff_limit, fulldiff,
218 file_limit, display_inline_comments):
219
220 vcs_diff = PullRequestModel().get_diff(
221 source_repo, source_ref_id, target_ref_id)
222
223 diff_processor = diffs.DiffProcessor(
224 vcs_diff, format='newdiff', diff_limit=diff_limit,
225 file_limit=file_limit, show_full_diff=fulldiff)
226
227 _parsed = diff_processor.prepare()
228
229 def _node_getter(commit):
230 def get_node(fname):
231 try:
232 return commit.get_node(fname)
233 except NodeDoesNotExistError:
234 return None
235
236 return get_node
237
238 diffset = codeblocks.DiffSet(
239 repo_name=self.db_repo_name,
240 source_repo_name=source_repo_name,
241 source_node_getter=_node_getter(target_commit),
242 target_node_getter=_node_getter(source_commit),
243 comments=display_inline_comments
244 )
245 diffset = diffset.render_patchset(
246 _parsed, target_commit.raw_id, source_commit.raw_id)
247
248 return diffset
249
250 @LoginRequired()
251 @HasRepoPermissionAnyDecorator(
252 'repository.read', 'repository.write', 'repository.admin')
253 # @view_config(
254 # route_name='pullrequest_show', request_method='GET',
255 # renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
256 def pull_request_show(self):
257 pull_request_id = safe_int(
258 self.request.matchdict.get('pull_request_id'))
259 c = self.load_default_context()
260
261 version = self.request.GET.get('version')
262 from_version = self.request.GET.get('from_version') or version
263 merge_checks = self.request.GET.get('merge_checks')
264 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
265
266 (pull_request_latest,
267 pull_request_at_ver,
268 pull_request_display_obj,
269 at_version) = self._get_pr_version(
270 pull_request_id, version=version)
271 pr_closed = pull_request_latest.is_closed()
272
273 if pr_closed and (version or from_version):
274 # not allow to browse versions
275 raise HTTPFound(h.route_path(
276 'pullrequest_show', repo_name=self.db_repo_name,
277 pull_request_id=pull_request_id))
278
279 versions = pull_request_display_obj.versions()
280
281 c.at_version = at_version
282 c.at_version_num = (at_version
283 if at_version and at_version != 'latest'
284 else None)
285 c.at_version_pos = ChangesetComment.get_index_from_version(
286 c.at_version_num, versions)
287
288 (prev_pull_request_latest,
289 prev_pull_request_at_ver,
290 prev_pull_request_display_obj,
291 prev_at_version) = self._get_pr_version(
292 pull_request_id, version=from_version)
293
294 c.from_version = prev_at_version
295 c.from_version_num = (prev_at_version
296 if prev_at_version and prev_at_version != 'latest'
297 else None)
298 c.from_version_pos = ChangesetComment.get_index_from_version(
299 c.from_version_num, versions)
300
301 # define if we're in COMPARE mode or VIEW at version mode
302 compare = at_version != prev_at_version
303
304 # pull_requests repo_name we opened it against
305 # ie. target_repo must match
306 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
307 raise HTTPNotFound()
308
309 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
310 pull_request_at_ver)
311
312 c.pull_request = pull_request_display_obj
313 c.pull_request_latest = pull_request_latest
314
315 if compare or (at_version and not at_version == 'latest'):
316 c.allowed_to_change_status = False
317 c.allowed_to_update = False
318 c.allowed_to_merge = False
319 c.allowed_to_delete = False
320 c.allowed_to_comment = False
321 c.allowed_to_close = False
322 else:
323 can_change_status = PullRequestModel().check_user_change_status(
324 pull_request_at_ver, self._rhodecode_user)
325 c.allowed_to_change_status = can_change_status and not pr_closed
326
327 c.allowed_to_update = PullRequestModel().check_user_update(
328 pull_request_latest, self._rhodecode_user) and not pr_closed
329 c.allowed_to_merge = PullRequestModel().check_user_merge(
330 pull_request_latest, self._rhodecode_user) and not pr_closed
331 c.allowed_to_delete = PullRequestModel().check_user_delete(
332 pull_request_latest, self._rhodecode_user) and not pr_closed
333 c.allowed_to_comment = not pr_closed
334 c.allowed_to_close = c.allowed_to_merge and not pr_closed
335
336 c.forbid_adding_reviewers = False
337 c.forbid_author_to_review = False
338 c.forbid_commit_author_to_review = False
339
340 if pull_request_latest.reviewer_data and \
341 'rules' in pull_request_latest.reviewer_data:
342 rules = pull_request_latest.reviewer_data['rules'] or {}
343 try:
344 c.forbid_adding_reviewers = rules.get(
345 'forbid_adding_reviewers')
346 c.forbid_author_to_review = rules.get(
347 'forbid_author_to_review')
348 c.forbid_commit_author_to_review = rules.get(
349 'forbid_commit_author_to_review')
350 except Exception:
351 pass
352
353 # check merge capabilities
354 _merge_check = MergeCheck.validate(
355 pull_request_latest, user=self._rhodecode_user)
356 c.pr_merge_errors = _merge_check.error_details
357 c.pr_merge_possible = not _merge_check.failed
358 c.pr_merge_message = _merge_check.merge_msg
359
360 c.pull_request_review_status = _merge_check.review_status
361 if merge_checks:
362 self.request.override_renderer = \
363 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
364 return self._get_template_context(c)
365
366 comments_model = CommentsModel()
367
368 # reviewers and statuses
369 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
370 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
371
372 # GENERAL COMMENTS with versions #
373 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
374 q = q.order_by(ChangesetComment.comment_id.asc())
375 general_comments = q
376
377 # pick comments we want to render at current version
378 c.comment_versions = comments_model.aggregate_comments(
379 general_comments, versions, c.at_version_num)
380 c.comments = c.comment_versions[c.at_version_num]['until']
381
382 # INLINE COMMENTS with versions #
383 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
384 q = q.order_by(ChangesetComment.comment_id.asc())
385 inline_comments = q
386
387 c.inline_versions = comments_model.aggregate_comments(
388 inline_comments, versions, c.at_version_num, inline=True)
389
390 # inject latest version
391 latest_ver = PullRequest.get_pr_display_object(
392 pull_request_latest, pull_request_latest)
393
394 c.versions = versions + [latest_ver]
395
396 # if we use version, then do not show later comments
397 # than current version
398 display_inline_comments = collections.defaultdict(
399 lambda: collections.defaultdict(list))
400 for co in inline_comments:
401 if c.at_version_num:
402 # pick comments that are at least UPTO given version, so we
403 # don't render comments for higher version
404 should_render = co.pull_request_version_id and \
405 co.pull_request_version_id <= c.at_version_num
406 else:
407 # showing all, for 'latest'
408 should_render = True
409
410 if should_render:
411 display_inline_comments[co.f_path][co.line_no].append(co)
412
413 # load diff data into template context, if we use compare mode then
414 # diff is calculated based on changes between versions of PR
415
416 source_repo = pull_request_at_ver.source_repo
417 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
418
419 target_repo = pull_request_at_ver.target_repo
420 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
421
422 if compare:
423 # in compare switch the diff base to latest commit from prev version
424 target_ref_id = prev_pull_request_display_obj.revisions[0]
425
426 # despite opening commits for bookmarks/branches/tags, we always
427 # convert this to rev to prevent changes after bookmark or branch change
428 c.source_ref_type = 'rev'
429 c.source_ref = source_ref_id
430
431 c.target_ref_type = 'rev'
432 c.target_ref = target_ref_id
433
434 c.source_repo = source_repo
435 c.target_repo = target_repo
436
437 c.commit_ranges = []
438 source_commit = EmptyCommit()
439 target_commit = EmptyCommit()
440 c.missing_requirements = False
441
442 source_scm = source_repo.scm_instance()
443 target_scm = target_repo.scm_instance()
444
445 # try first shadow repo, fallback to regular repo
446 try:
447 commits_source_repo = pull_request_latest.get_shadow_repo()
448 except Exception:
449 log.debug('Failed to get shadow repo', exc_info=True)
450 commits_source_repo = source_scm
451
452 c.commits_source_repo = commits_source_repo
453 commit_cache = {}
454 try:
455 pre_load = ["author", "branch", "date", "message"]
456 show_revs = pull_request_at_ver.revisions
457 for rev in show_revs:
458 comm = commits_source_repo.get_commit(
459 commit_id=rev, pre_load=pre_load)
460 c.commit_ranges.append(comm)
461 commit_cache[comm.raw_id] = comm
462
463 # Order here matters, we first need to get target, and then
464 # the source
465 target_commit = commits_source_repo.get_commit(
466 commit_id=safe_str(target_ref_id))
467
468 source_commit = commits_source_repo.get_commit(
469 commit_id=safe_str(source_ref_id))
470
471 except CommitDoesNotExistError:
472 log.warning(
473 'Failed to get commit from `{}` repo'.format(
474 commits_source_repo), exc_info=True)
475 except RepositoryRequirementError:
476 log.warning(
477 'Failed to get all required data from repo', exc_info=True)
478 c.missing_requirements = True
479
480 c.ancestor = None # set it to None, to hide it from PR view
481
482 try:
483 ancestor_id = source_scm.get_common_ancestor(
484 source_commit.raw_id, target_commit.raw_id, target_scm)
485 c.ancestor_commit = source_scm.get_commit(ancestor_id)
486 except Exception:
487 c.ancestor_commit = None
488
489 c.statuses = source_repo.statuses(
490 [x.raw_id for x in c.commit_ranges])
491
492 # auto collapse if we have more than limit
493 collapse_limit = diffs.DiffProcessor._collapse_commits_over
494 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
495 c.compare_mode = compare
496
497 # diff_limit is the old behavior, will cut off the whole diff
498 # if the limit is applied otherwise will just hide the
499 # big files from the front-end
500 diff_limit = c.visual.cut_off_limit_diff
501 file_limit = c.visual.cut_off_limit_file
502
503 c.missing_commits = False
504 if (c.missing_requirements
505 or isinstance(source_commit, EmptyCommit)
506 or source_commit == target_commit):
507
508 c.missing_commits = True
509 else:
510
511 c.diffset = self._get_diffset(
512 c.source_repo.repo_name, commits_source_repo,
513 source_ref_id, target_ref_id,
514 target_commit, source_commit,
515 diff_limit, c.fulldiff, file_limit, display_inline_comments)
516
517 c.limited_diff = c.diffset.limited_diff
518
519 # calculate removed files that are bound to comments
520 comment_deleted_files = [
521 fname for fname in display_inline_comments
522 if fname not in c.diffset.file_stats]
523
524 c.deleted_files_comments = collections.defaultdict(dict)
525 for fname, per_line_comments in display_inline_comments.items():
526 if fname in comment_deleted_files:
527 c.deleted_files_comments[fname]['stats'] = 0
528 c.deleted_files_comments[fname]['comments'] = list()
529 for lno, comments in per_line_comments.items():
530 c.deleted_files_comments[fname]['comments'].extend(
531 comments)
532
533 # this is a hack to properly display links, when creating PR, the
534 # compare view and others uses different notation, and
535 # compare_commits.mako renders links based on the target_repo.
536 # We need to swap that here to generate it properly on the html side
537 c.target_repo = c.source_repo
538
539 c.commit_statuses = ChangesetStatus.STATUSES
540
541 c.show_version_changes = not pr_closed
542 if c.show_version_changes:
543 cur_obj = pull_request_at_ver
544 prev_obj = prev_pull_request_at_ver
545
546 old_commit_ids = prev_obj.revisions
547 new_commit_ids = cur_obj.revisions
548 commit_changes = PullRequestModel()._calculate_commit_id_changes(
549 old_commit_ids, new_commit_ids)
550 c.commit_changes_summary = commit_changes
551
552 # calculate the diff for commits between versions
553 c.commit_changes = []
554 mark = lambda cs, fw: list(
555 h.itertools.izip_longest([], cs, fillvalue=fw))
556 for c_type, raw_id in mark(commit_changes.added, 'a') \
557 + mark(commit_changes.removed, 'r') \
558 + mark(commit_changes.common, 'c'):
559
560 if raw_id in commit_cache:
561 commit = commit_cache[raw_id]
562 else:
563 try:
564 commit = commits_source_repo.get_commit(raw_id)
565 except CommitDoesNotExistError:
566 # in case we fail extracting still use "dummy" commit
567 # for display in commit diff
568 commit = h.AttributeDict(
569 {'raw_id': raw_id,
570 'message': 'EMPTY or MISSING COMMIT'})
571 c.commit_changes.append([c_type, commit])
572
573 # current user review statuses for each version
574 c.review_versions = {}
575 if self._rhodecode_user.user_id in allowed_reviewers:
576 for co in general_comments:
577 if co.author.user_id == self._rhodecode_user.user_id:
578 # each comment has a status change
579 status = co.status_change
580 if status:
581 _ver_pr = status[0].comment.pull_request_version_id
582 c.review_versions[_ver_pr] = status[0]
583
584 return self._get_template_context(c)
@@ -0,0 +1,64 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24
25 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps.repository.utils import get_default_reviewers_data
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28
29 log = logging.getLogger(__name__)
30
31
32 class RepoReviewRulesView(RepoAppView):
33 def load_default_context(self):
34 c = self._get_local_tmpl_context()
35
36 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
37 c.repo_info = self.db_repo
38
39 self._register_global_c(c)
40 return c
41
42 @LoginRequired()
43 @HasRepoPermissionAnyDecorator('repository.admin')
44 @view_config(
45 route_name='repo_reviewers', request_method='GET',
46 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 def repo_review_rules(self):
48 c = self.load_default_context()
49 c.active = 'reviewers'
50
51 return self._get_template_context(c)
52
53 @LoginRequired()
54 @HasRepoPermissionAnyDecorator(
55 'repository.read', 'repository.write', 'repository.admin')
56 @view_config(
57 route_name='repo_default_reviewers_data', request_method='GET',
58 renderer='json_ext')
59 def repo_default_reviewers_data(self):
60 review_data = get_default_reviewers_data(
61 self.db_repo.user, None, None, None, None)
62 return review_data
63
64
@@ -0,0 +1,179 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 import deform
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
33 HasRepoPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.meta import Session
36 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.scm import RepoGroupList, ScmModel
38 from rhodecode.model.validation_schema.schemas import repo_schema
39
40 log = logging.getLogger(__name__)
41
42
43 class RepoSettingsView(RepoAppView):
44
45 def load_default_context(self):
46 c = self._get_local_tmpl_context()
47
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 c.repo_info = self.db_repo
50
51 acl_groups = RepoGroupList(
52 RepoGroup.query().all(),
53 perm_set=['group.write', 'group.admin'])
54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
55 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
56
57 # in case someone no longer have a group.write access to a repository
58 # pre fill the list with this entry, we don't care if this is the same
59 # but it will allow saving repo data properly.
60 repo_group = self.db_repo.group
61 if repo_group and repo_group.group_id not in c.repo_groups_choices:
62 c.repo_groups_choices.append(repo_group.group_id)
63 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
64
65 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
66 # we might be in missing requirement state, so we load things
67 # without touching scm_instance()
68 c.landing_revs_choices, c.landing_revs = \
69 ScmModel().get_repo_landing_revs()
70 else:
71 c.landing_revs_choices, c.landing_revs = \
72 ScmModel().get_repo_landing_revs(self.db_repo)
73
74 c.personal_repo_group = c.auth_user.personal_repo_group
75 c.repo_fields = RepositoryField.query()\
76 .filter(RepositoryField.repository == self.db_repo).all()
77
78 self._register_global_c(c)
79 return c
80
81 def _get_schema(self, c, old_values=None):
82 return repo_schema.RepoSettingsSchema().bind(
83 repo_type=self.db_repo.repo_type,
84 repo_type_options=[self.db_repo.repo_type],
85 repo_ref_options=c.landing_revs_choices,
86 repo_ref_items=c.landing_revs,
87 repo_repo_group_options=c.repo_groups_choices,
88 repo_repo_group_items=c.repo_groups,
89 # user caller
90 user=self._rhodecode_user,
91 old_values=old_values
92 )
93
94 @LoginRequired()
95 @HasRepoPermissionAnyDecorator('repository.admin')
96 @view_config(
97 route_name='edit_repo', request_method='GET',
98 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
99 def edit_settings(self):
100 c = self.load_default_context()
101 c.active = 'settings'
102
103 defaults = RepoModel()._get_defaults(self.db_repo_name)
104 defaults['repo_owner'] = defaults['user']
105 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
106
107 schema = self._get_schema(c)
108 c.form = RcForm(schema, appstruct=defaults)
109 return self._get_template_context(c)
110
111 @LoginRequired()
112 @HasRepoPermissionAllDecorator('repository.admin')
113 @CSRFRequired()
114 @view_config(
115 route_name='edit_repo', request_method='POST',
116 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
117 def edit_settings_update(self):
118 _ = self.request.translate
119 c = self.load_default_context()
120 c.active = 'settings'
121 old_repo_name = self.db_repo_name
122
123 old_values = self.db_repo.get_api_data()
124 schema = self._get_schema(c, old_values=old_values)
125
126 c.form = RcForm(schema)
127 pstruct = self.request.POST.items()
128 pstruct.append(('repo_type', self.db_repo.repo_type))
129 try:
130 schema_data = c.form.validate(pstruct)
131 except deform.ValidationFailure as err_form:
132 return self._get_template_context(c)
133
134 # data is now VALID, proceed with updates
135 # save validated data back into the updates dict
136 validated_updates = dict(
137 repo_name=schema_data['repo_group']['repo_name_without_group'],
138 repo_group=schema_data['repo_group']['repo_group_id'],
139
140 user=schema_data['repo_owner'],
141 repo_description=schema_data['repo_description'],
142 repo_private=schema_data['repo_private'],
143 clone_uri=schema_data['repo_clone_uri'],
144 repo_landing_rev=schema_data['repo_landing_commit_ref'],
145 repo_enable_statistics=schema_data['repo_enable_statistics'],
146 repo_enable_locking=schema_data['repo_enable_locking'],
147 repo_enable_downloads=schema_data['repo_enable_downloads'],
148 )
149 # detect if CLONE URI changed, if we get OLD means we keep old values
150 if schema_data['repo_clone_uri_change'] == 'OLD':
151 validated_updates['clone_uri'] = self.db_repo.clone_uri
152
153 # use the new full name for redirect
154 new_repo_name = schema_data['repo_group']['repo_name_with_group']
155
156 # save extra fields into our validated data
157 for key, value in pstruct:
158 if key.startswith(RepositoryField.PREFIX):
159 validated_updates[key] = value
160
161 try:
162 RepoModel().update(self.db_repo, **validated_updates)
163 ScmModel().mark_for_invalidation(new_repo_name)
164
165 audit_logger.store_web(
166 'repo.edit', action_data={'old_data': old_values},
167 user=self._rhodecode_user, repo=self.db_repo)
168
169 Session().commit()
170
171 h.flash(_('Repository {} updated successfully').format(
172 old_repo_name), category='success')
173 except Exception:
174 log.exception("Exception during update of repository")
175 h.flash(_('Error occurred during update of repository {}').format(
176 old_repo_name), category='error')
177
178 raise HTTPFound(
179 self.request.route_path('edit_repo', repo_name=new_repo_name))
@@ -0,0 +1,226 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
25
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.lib.exceptions import AttachedForksError
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.vcs import RepositoryError
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import ScmModel
37
38 log = logging.getLogger(__name__)
39
40
41 class RepoSettingsView(RepoAppView):
42
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
48
49 self._register_global_c(c)
50 return c
51
52 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.admin')
54 @view_config(
55 route_name='edit_repo_advanced', request_method='GET',
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 def edit_advanced(self):
58 c = self.load_default_context()
59 c.active = 'advanced'
60
61 c.default_user_id = User.get_default_user().user_id
62 c.in_public_journal = UserFollowing.query() \
63 .filter(UserFollowing.user_id == c.default_user_id) \
64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
65
66 c.has_origin_repo_read_perm = False
67 if self.db_repo.fork:
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 'repository.write', 'repository.read', 'repository.admin')(
70 self.db_repo.fork.repo_name, 'repo set as fork page')
71
72 return self._get_template_context(c)
73
74 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
76 @CSRFRequired()
77 @view_config(
78 route_name='edit_repo_advanced_delete', request_method='POST',
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 def edit_advanced_delete(self):
81 """
82 Deletes the repository, or shows warnings if deletion is not possible
83 because of attached forks or other errors.
84 """
85 _ = self.request.translate
86 handle_forks = self.request.POST.get('forks', None)
87
88 try:
89 _forks = self.db_repo.forks.count()
90 if _forks and handle_forks:
91 if handle_forks == 'detach_forks':
92 handle_forks = 'detach'
93 h.flash(_('Detached %s forks') % _forks, category='success')
94 elif handle_forks == 'delete_forks':
95 handle_forks = 'delete'
96 h.flash(_('Deleted %s forks') % _forks, category='success')
97
98 old_data = self.db_repo.get_api_data()
99 RepoModel().delete(self.db_repo, forks=handle_forks)
100
101 repo = audit_logger.RepoWrap(repo_id=None,
102 repo_name=self.db_repo.repo_name)
103 audit_logger.store_web(
104 'repo.delete', action_data={'old_data': old_data},
105 user=self._rhodecode_user, repo=repo)
106
107 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
108 h.flash(
109 _('Deleted repository `%s`') % self.db_repo_name,
110 category='success')
111 Session().commit()
112 except AttachedForksError:
113 repo_advanced_url = h.route_path(
114 'edit_repo_advanced', repo_name=self.db_repo_name,
115 _anchor='advanced-delete')
116 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
117 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
118 'Try using {delete_or_detach} option.')
119 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
120 category='warning')
121
122 # redirect to advanced for forks handle action ?
123 raise HTTPFound(repo_advanced_url)
124
125 except Exception:
126 log.exception("Exception during deletion of repository")
127 h.flash(_('An error occurred during deletion of `%s`')
128 % self.db_repo_name, category='error')
129 # redirect to advanced for more deletion options
130 raise HTTPFound(
131 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
132 _anchor='advanced-delete')
133
134 raise HTTPFound(h.route_path('home'))
135
136 @LoginRequired()
137 @HasRepoPermissionAnyDecorator('repository.admin')
138 @CSRFRequired()
139 @view_config(
140 route_name='edit_repo_advanced_journal', request_method='POST',
141 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
142 def edit_advanced_journal(self):
143 """
144 Set's this repository to be visible in public journal,
145 in other words making default user to follow this repo
146 """
147 _ = self.request.translate
148
149 try:
150 user_id = User.get_default_user().user_id
151 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
152 h.flash(_('Updated repository visibility in public journal'),
153 category='success')
154 Session().commit()
155 except Exception:
156 h.flash(_('An error occurred during setting this '
157 'repository in public journal'),
158 category='error')
159
160 raise HTTPFound(
161 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
162
163 @LoginRequired()
164 @HasRepoPermissionAnyDecorator('repository.admin')
165 @CSRFRequired()
166 @view_config(
167 route_name='edit_repo_advanced_fork', request_method='POST',
168 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
169 def edit_advanced_fork(self):
170 """
171 Mark given repository as a fork of another
172 """
173 _ = self.request.translate
174
175 new_fork_id = self.request.POST.get('id_fork_of')
176 try:
177
178 if new_fork_id and not new_fork_id.isdigit():
179 log.error('Given fork id %s is not an INT', new_fork_id)
180
181 fork_id = safe_int(new_fork_id)
182 repo = ScmModel().mark_as_fork(
183 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
184 fork = repo.fork.repo_name if repo.fork else _('Nothing')
185 Session().commit()
186 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
187 category='success')
188 except RepositoryError as e:
189 log.exception("Repository Error occurred")
190 h.flash(str(e), category='error')
191 except Exception as e:
192 log.exception("Exception while editing fork")
193 h.flash(_('An error occurred during this operation'),
194 category='error')
195
196 raise HTTPFound(
197 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
198
199 @LoginRequired()
200 @HasRepoPermissionAnyDecorator('repository.admin')
201 @CSRFRequired()
202 @view_config(
203 route_name='edit_repo_advanced_locking', request_method='POST',
204 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
205 def edit_advanced_locking(self):
206 """
207 Toggle locking of repository
208 """
209 _ = self.request.translate
210 set_lock = self.request.POST.get('set_lock')
211 set_unlock = self.request.POST.get('set_unlock')
212
213 try:
214 if set_lock:
215 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
216 lock_reason=Repository.LOCK_WEB)
217 h.flash(_('Locked repository'), category='success')
218 elif set_unlock:
219 Repository.unlock(self.db_repo)
220 h.flash(_('Unlocked repository'), category='success')
221 except Exception as e:
222 log.exception("Exception during unlocking")
223 h.flash(_('An error occurred during unlocking'), category='error')
224
225 raise HTTPFound(
226 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -0,0 +1,368 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import string
23
24 from pyramid.view import view_config
25
26 from beaker.cache import cache_region
27
28
29 from rhodecode.controllers import utils
30
31 from rhodecode.apps._base import RepoAppView
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
33 from rhodecode.lib import caches, helpers as h
34 from rhodecode.lib.helpers import RepoPage
35 from rhodecode.lib.utils2 import safe_str, safe_int
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.vcs.backends.base import EmptyCommit
40 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
41 from rhodecode.model.db import Statistics, CacheKey, User
42 from rhodecode.model.meta import Session
43 from rhodecode.model.repo import ReadmeFinder
44 from rhodecode.model.scm import ScmModel
45
46 log = logging.getLogger(__name__)
47
48
49 class RepoSummaryView(RepoAppView):
50
51 def load_default_context(self):
52 c = self._get_local_tmpl_context(include_app_defaults=True)
53
54 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
55 c.repo_info = self.db_repo
56 c.rhodecode_repo = None
57 if not c.repository_requirements_missing:
58 c.rhodecode_repo = self.rhodecode_vcs_repo
59
60 self._register_global_c(c)
61 return c
62
63 def _get_readme_data(self, db_repo, default_renderer):
64 repo_name = db_repo.repo_name
65 log.debug('Looking for README file')
66
67 @cache_region('long_term')
68 def _generate_readme(cache_key):
69 readme_data = None
70 readme_node = None
71 readme_filename = None
72 commit = self._get_landing_commit_or_none(db_repo)
73 if commit:
74 log.debug("Searching for a README file.")
75 readme_node = ReadmeFinder(default_renderer).search(commit)
76 if readme_node:
77 relative_url = h.url('files_raw_home',
78 repo_name=repo_name,
79 revision=commit.raw_id,
80 f_path=readme_node.path)
81 readme_data = self._render_readme_or_none(
82 commit, readme_node, relative_url)
83 readme_filename = readme_node.path
84 return readme_data, readme_filename
85
86 invalidator_context = CacheKey.repo_context_cache(
87 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
88
89 with invalidator_context as context:
90 context.invalidate()
91 computed = context.compute()
92
93 return computed
94
95 def _get_landing_commit_or_none(self, db_repo):
96 log.debug("Getting the landing commit.")
97 try:
98 commit = db_repo.get_landing_commit()
99 if not isinstance(commit, EmptyCommit):
100 return commit
101 else:
102 log.debug("Repository is empty, no README to render.")
103 except CommitError:
104 log.exception(
105 "Problem getting commit when trying to render the README.")
106
107 def _render_readme_or_none(self, commit, readme_node, relative_url):
108 log.debug(
109 'Found README file `%s` rendering...', readme_node.path)
110 renderer = MarkupRenderer()
111 try:
112 html_source = renderer.render(
113 readme_node.content, filename=readme_node.path)
114 if relative_url:
115 return relative_links(html_source, relative_url)
116 return html_source
117 except Exception:
118 log.exception(
119 "Exception while trying to render the README")
120
121 def _load_commits_context(self, c):
122 p = safe_int(self.request.GET.get('page'), 1)
123 size = safe_int(self.request.GET.get('size'), 10)
124
125 def url_generator(**kw):
126 query_params = {
127 'size': size
128 }
129 query_params.update(kw)
130 return h.route_path(
131 'repo_summary_commits',
132 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
133
134 pre_load = ['author', 'branch', 'date', 'message']
135 try:
136 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
137 except EmptyRepositoryError:
138 collection = self.rhodecode_vcs_repo
139
140 c.repo_commits = RepoPage(
141 collection, page=p, items_per_page=size, url=url_generator)
142 page_ids = [x.raw_id for x in c.repo_commits]
143 c.comments = self.db_repo.get_comments(page_ids)
144 c.statuses = self.db_repo.statuses(page_ids)
145
146 @LoginRequired()
147 @HasRepoPermissionAnyDecorator(
148 'repository.read', 'repository.write', 'repository.admin')
149 @view_config(
150 route_name='repo_summary_commits', request_method='GET',
151 renderer='rhodecode:templates/summary/summary_commits.mako')
152 def summary_commits(self):
153 c = self.load_default_context()
154 self._load_commits_context(c)
155 return self._get_template_context(c)
156
157 @LoginRequired()
158 @HasRepoPermissionAnyDecorator(
159 'repository.read', 'repository.write', 'repository.admin')
160 @view_config(
161 route_name='repo_summary', request_method='GET',
162 renderer='rhodecode:templates/summary/summary.mako')
163 @view_config(
164 route_name='repo_summary_slash', request_method='GET',
165 renderer='rhodecode:templates/summary/summary.mako')
166 def summary(self):
167 c = self.load_default_context()
168
169 # Prepare the clone URL
170 username = ''
171 if self._rhodecode_user.username != User.DEFAULT_USER:
172 username = safe_str(self._rhodecode_user.username)
173
174 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
175 if '{repo}' in _def_clone_uri:
176 _def_clone_uri_by_id = _def_clone_uri.replace(
177 '{repo}', '_{repoid}')
178 elif '{repoid}' in _def_clone_uri:
179 _def_clone_uri_by_id = _def_clone_uri.replace(
180 '_{repoid}', '{repo}')
181
182 c.clone_repo_url = self.db_repo.clone_url(
183 user=username, uri_tmpl=_def_clone_uri)
184 c.clone_repo_url_id = self.db_repo.clone_url(
185 user=username, uri_tmpl=_def_clone_uri_by_id)
186
187 # If enabled, get statistics data
188
189 c.show_stats = bool(self.db_repo.enable_statistics)
190
191 stats = Session().query(Statistics) \
192 .filter(Statistics.repository == self.db_repo) \
193 .scalar()
194
195 c.stats_percentage = 0
196
197 if stats and stats.languages:
198 c.no_data = False is self.db_repo.enable_statistics
199 lang_stats_d = json.loads(stats.languages)
200
201 # Sort first by decreasing count and second by the file extension,
202 # so we have a consistent output.
203 lang_stats_items = sorted(lang_stats_d.iteritems(),
204 key=lambda k: (-k[1], k[0]))[:10]
205 lang_stats = [(x, {"count": y,
206 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
207 for x, y in lang_stats_items]
208
209 c.trending_languages = json.dumps(lang_stats)
210 else:
211 c.no_data = True
212 c.trending_languages = json.dumps({})
213
214 scm_model = ScmModel()
215 c.enable_downloads = self.db_repo.enable_downloads
216 c.repository_followers = scm_model.get_followers(self.db_repo)
217 c.repository_forks = scm_model.get_forks(self.db_repo)
218 c.repository_is_user_following = scm_model.is_following_repo(
219 self.db_repo_name, self._rhodecode_user.user_id)
220
221 # first interaction with the VCS instance after here...
222 if c.repository_requirements_missing:
223 self.request.override_renderer = \
224 'rhodecode:templates/summary/missing_requirements.mako'
225 return self._get_template_context(c)
226
227 c.readme_data, c.readme_file = \
228 self._get_readme_data(self.db_repo, c.visual.default_renderer)
229
230 # loads the summary commits template context
231 self._load_commits_context(c)
232
233 return self._get_template_context(c)
234
235 def get_request_commit_id(self):
236 return self.request.matchdict['commit_id']
237
238 @LoginRequired()
239 @HasRepoPermissionAnyDecorator(
240 'repository.read', 'repository.write', 'repository.admin')
241 @view_config(
242 route_name='repo_stats', request_method='GET',
243 renderer='json_ext')
244 def repo_stats(self):
245 commit_id = self.get_request_commit_id()
246
247 _namespace = caches.get_repo_namespace_key(
248 caches.SUMMARY_STATS, self.db_repo_name)
249 show_stats = bool(self.db_repo.enable_statistics)
250 cache_manager = caches.get_cache_manager(
251 'repo_cache_long', _namespace)
252 _cache_key = caches.compute_key_from_params(
253 self.db_repo_name, commit_id, show_stats)
254
255 def compute_stats():
256 code_stats = {}
257 size = 0
258 try:
259 scm_instance = self.db_repo.scm_instance()
260 commit = scm_instance.get_commit(commit_id)
261
262 for node in commit.get_filenodes_generator():
263 size += node.size
264 if not show_stats:
265 continue
266 ext = string.lower(node.extension)
267 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
268 if ext_info:
269 if ext in code_stats:
270 code_stats[ext]['count'] += 1
271 else:
272 code_stats[ext] = {"count": 1, "desc": ext_info}
273 except EmptyRepositoryError:
274 pass
275 return {'size': h.format_byte_size_binary(size),
276 'code_stats': code_stats}
277
278 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
279 return stats
280
281 @LoginRequired()
282 @HasRepoPermissionAnyDecorator(
283 'repository.read', 'repository.write', 'repository.admin')
284 @view_config(
285 route_name='repo_refs_data', request_method='GET',
286 renderer='json_ext')
287 def repo_refs_data(self):
288 _ = self.request.translate
289 self.load_default_context()
290
291 repo = self.rhodecode_vcs_repo
292 refs_to_create = [
293 (_("Branch"), repo.branches, 'branch'),
294 (_("Tag"), repo.tags, 'tag'),
295 (_("Bookmark"), repo.bookmarks, 'book'),
296 ]
297 res = self._create_reference_data(
298 repo, self.db_repo_name, refs_to_create)
299 data = {
300 'more': False,
301 'results': res
302 }
303 return data
304
305 @LoginRequired()
306 @HasRepoPermissionAnyDecorator(
307 'repository.read', 'repository.write', 'repository.admin')
308 @view_config(
309 route_name='repo_refs_changelog_data', request_method='GET',
310 renderer='json_ext')
311 def repo_refs_changelog_data(self):
312 _ = self.request.translate
313 self.load_default_context()
314
315 repo = self.rhodecode_vcs_repo
316
317 refs_to_create = [
318 (_("Branches"), repo.branches, 'branch'),
319 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
320 # TODO: enable when vcs can handle bookmarks filters
321 # (_("Bookmarks"), repo.bookmarks, "book"),
322 ]
323 res = self._create_reference_data(
324 repo, self.db_repo_name, refs_to_create)
325 data = {
326 'more': False,
327 'results': res
328 }
329 return data
330
331 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
332 format_ref_id = utils.get_format_ref_id(repo)
333
334 result = []
335 for title, refs, ref_type in refs_to_create:
336 if refs:
337 result.append({
338 'text': title,
339 'children': self._create_reference_items(
340 repo, full_repo_name, refs, ref_type,
341 format_ref_id),
342 })
343 return result
344
345 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
346 format_ref_id):
347 result = []
348 is_svn = h.is_svn(repo)
349 for ref_name, raw_id in refs.iteritems():
350 files_url = self._create_files_url(
351 repo, full_repo_name, ref_name, raw_id, is_svn)
352 result.append({
353 'text': ref_name,
354 'id': format_ref_id(ref_name, raw_id),
355 'raw_id': raw_id,
356 'type': ref_type,
357 'files_url': files_url,
358 })
359 return result
360
361 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
362 use_commit_id = '/' in ref_name or is_svn
363 return h.url(
364 'files_home',
365 repo_name=full_repo_name,
366 f_path=ref_name if is_svn else '',
367 revision=raw_id if use_commit_id else ref_name,
368 at=ref_name)
@@ -0,0 +1,45 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 from pyramid.view import view_config
23
24 from rhodecode.apps._base import BaseReferencesView
25 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
26
27 log = logging.getLogger(__name__)
28
29
30 class RepoTagsView(BaseReferencesView):
31
32 @LoginRequired()
33 @HasRepoPermissionAnyDecorator(
34 'repository.read', 'repository.write', 'repository.admin')
35 @view_config(
36 route_name='tags_home', request_method='GET',
37 renderer='rhodecode:templates/tags/tags.mako')
38 def tags(self):
39 c = self.load_default_context()
40
41 ref_items = self.rhodecode_vcs_repo.tags.items()
42 self.load_refs_context(
43 ref_items=ref_items, partials_template='tags/tags_data.mako')
44
45 return self._get_template_context(c)
@@ -0,0 +1,44 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import ADMIN_PREFIX
21
22
23 def includeme(config):
24
25 config.add_route(
26 name='search',
27 pattern=ADMIN_PREFIX + '/search')
28
29 config.add_route(
30 name='search_repo',
31 pattern='/{repo_name:.*?[^/]}/search', repo_route=True)
32
33 # Scan module for configuration decorators.
34 config.scan()
35
36
37 # # FULL TEXT SEARCH
38 # rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
39 # controller='search')
40 # rmap.connect('search_repo_home', '/{repo_name}/search',
41 # controller='search',
42 # action='index',
43 # conditions={'function': check_repo},
44 # requirements=URL_NAME_REQUIREMENTS) No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,202 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22
23 import mock
24 import pytest
25 from whoosh import query
26
27 from rhodecode.tests import (
28 TestController, SkipTest, HG_REPO,
29 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
30 from rhodecode.tests.utils import AssertResponse
31
32
33 def route_path(name, **kwargs):
34 from rhodecode.apps._base import ADMIN_PREFIX
35 return {
36 'search':
37 ADMIN_PREFIX + '/search',
38 'search_repo':
39 '/{repo_name}/search',
40
41 }[name].format(**kwargs)
42
43
44 class TestSearchController(TestController):
45
46 def test_index(self):
47 self.log_user()
48 response = self.app.get(route_path('search'))
49 assert_response = AssertResponse(response)
50 assert_response.one_element_exists('input#q')
51
52 def test_search_files_empty_search(self):
53 if os.path.isdir(self.index_location):
54 raise SkipTest('skipped due to existing index')
55 else:
56 self.log_user()
57 response = self.app.get(route_path('search'),
58 {'q': HG_REPO})
59 response.mustcontain('There is no index to search in. '
60 'Please run whoosh indexer')
61
62 def test_search_validation(self):
63 self.log_user()
64 response = self.app.get(route_path('search'),
65 {'q': query, 'type': 'content', 'page_limit': 1000})
66
67 response.mustcontain(
68 'page_limit - 1000 is greater than maximum value 500')
69
70 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
71 ('todo', 23, [
72 'vcs/backends/hg/inmemory.py',
73 'vcs/tests/test_git.py']),
74 ('extension:rst installation', 6, [
75 'docs/index.rst',
76 'docs/installation.rst']),
77 ('def repo', 87, [
78 'vcs/tests/test_git.py',
79 'vcs/tests/test_changesets.py']),
80 ('repository:%s def test' % HG_REPO, 18, [
81 'vcs/tests/test_git.py',
82 'vcs/tests/test_changesets.py']),
83 ('"def main"', 9, [
84 'vcs/__init__.py',
85 'vcs/tests/__init__.py',
86 'vcs/utils/progressbar.py']),
87 ('owner:test_admin', 358, [
88 'vcs/tests/base.py',
89 'MANIFEST.in',
90 'vcs/utils/termcolors.py',
91 'docs/theme/ADC/static/documentation.png']),
92 ('owner:test_admin def main', 72, [
93 'vcs/__init__.py',
94 'vcs/tests/test_utils_filesize.py',
95 'vcs/tests/test_cli.py']),
96 ('owner:michał test', 0, []),
97 ])
98 def test_search_files(self, query, expected_hits, expected_paths):
99 self.log_user()
100 response = self.app.get(route_path('search'),
101 {'q': query, 'type': 'content', 'page_limit': 500})
102
103 response.mustcontain('%s results' % expected_hits)
104 for path in expected_paths:
105 response.mustcontain(path)
106
107 @pytest.mark.parametrize("query, expected_hits, expected_commits", [
108 ('bother to ask where to fetch repo during tests', 3, [
109 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1'),
110 ('git', 'c6eb379775c578a95dad8ddab53f963b80894850'),
111 ('svn', '98')]),
112 ('michał', 0, []),
113 ('changed:tests/utils.py', 36, [
114 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')]),
115 ('changed:vcs/utils/archivers.py', 11, [
116 ('hg', '25213a5fbb048dff8ba65d21e466a835536e5b70'),
117 ('hg', '47aedd538bf616eedcb0e7d630ea476df0e159c7'),
118 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
119 ('hg', '04ad456aefd6461aea24f90b63954b6b1ce07b3e'),
120 ('git', 'c994f0de03b2a0aa848a04fc2c0d7e737dba31fc'),
121 ('git', 'd1f898326327e20524fe22417c22d71064fe54a1'),
122 ('git', 'fe568b4081755c12abf6ba673ba777fc02a415f3'),
123 ('git', 'bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1')]),
124 ('added:README.rst', 3, [
125 ('hg', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'),
126 ('git', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
127 ('svn', '8')]),
128 ('changed:lazy.py', 15, [
129 ('hg', 'eaa291c5e6ae6126a203059de9854ccf7b5baa12'),
130 ('git', '17438a11f72b93f56d0e08e7d1fa79a378578a82'),
131 ('svn', '82'),
132 ('svn', '262'),
133 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
134 ('git', '33fa3223355104431402a888fa77a4e9956feb3e')
135 ]),
136 ('author:marcin@python-blog.com '
137 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
138 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
139 ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
140 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
141 ('b986218b', 1, [
142 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
143 ])
144 def test_search_commit_messages(
145 self, query, expected_hits, expected_commits, enabled_backends):
146 self.log_user()
147 response = self.app.get(route_path('search'),
148 {'q': query, 'type': 'commit', 'page_limit': 500})
149
150 response.mustcontain('%s results' % expected_hits)
151 for backend, commit_id in expected_commits:
152 if backend in enabled_backends:
153 response.mustcontain(commit_id)
154
155 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
156 ('readme.rst', 3, []),
157 ('test*', 75, []),
158 ('*model*', 1, []),
159 ('extension:rst', 48, []),
160 ('extension:rst api', 24, []),
161 ])
162 def test_search_file_paths(self, query, expected_hits, expected_paths):
163 self.log_user()
164 response = self.app.get(route_path('search'),
165 {'q': query, 'type': 'path', 'page_limit': 500})
166
167 response.mustcontain('%s results' % expected_hits)
168 for path in expected_paths:
169 response.mustcontain(path)
170
171 def test_search_commit_message_specific_repo(self, backend):
172 self.log_user()
173 response = self.app.get(
174 route_path('search_repo',repo_name=backend.repo_name),
175 {'q': 'bother to ask where to fetch repo during tests',
176 'type': 'commit'})
177
178 response.mustcontain('1 results')
179
180 def test_filters_are_not_applied_for_admin_user(self):
181 self.log_user()
182 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
183 self.app.get(route_path('search'),
184 {'q': 'test query', 'type': 'commit'})
185 assert search_mock.call_count == 1
186 _, kwargs = search_mock.call_args
187 assert kwargs['filter'] is None
188
189 def test_filters_are_applied_for_normal_user(self, enabled_backends):
190 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
191 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
192 self.app.get(route_path('search'),
193 {'q': 'test query', 'type': 'commit'})
194 assert search_mock.call_count == 1
195 _, kwargs = search_mock.call_args
196 assert isinstance(kwargs['filter'], query.Or)
197 expected_repositories = [
198 'vcs_test_{}'.format(b) for b in enabled_backends]
199 queried_repositories = [
200 name for type_, name in kwargs['filter'].all_terms()]
201 for repository in expected_repositories:
202 assert repository in queried_repositories
@@ -0,0 +1,133 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import urllib
23 from pyramid.view import view_config
24 from webhelpers.util import update_params
25
26 from rhodecode.apps._base import BaseAppView, RepoAppView
27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
28 from rhodecode.lib.helpers import Page
29 from rhodecode.lib.utils2 import safe_str, safe_int
30 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.model import validation_schema
32 from rhodecode.model.validation_schema.schemas import search_schema
33
34 log = logging.getLogger(__name__)
35
36
37 def search(request, tmpl_context, repo_name):
38 searcher = searcher_from_config(request.registry.settings)
39 formatted_results = []
40 execution_time = ''
41
42 schema = search_schema.SearchParamsSchema()
43
44 search_params = {}
45 errors = []
46 try:
47 search_params = schema.deserialize(
48 dict(search_query=request.GET.get('q'),
49 search_type=request.GET.get('type'),
50 search_sort=request.GET.get('sort'),
51 page_limit=request.GET.get('page_limit'),
52 requested_page=request.GET.get('page'))
53 )
54 except validation_schema.Invalid as e:
55 errors = e.children
56
57 def url_generator(**kw):
58 q = urllib.quote(safe_str(search_query))
59 return update_params(
60 "?q=%s&type=%s" % (q, safe_str(search_type)), **kw)
61
62 c = tmpl_context
63 search_query = search_params.get('search_query')
64 search_type = search_params.get('search_type')
65 search_sort = search_params.get('search_sort')
66 if search_params.get('search_query'):
67 page_limit = search_params['page_limit']
68 requested_page = search_params['requested_page']
69
70 try:
71 search_result = searcher.search(
72 search_query, search_type, c.auth_user, repo_name,
73 requested_page, page_limit, search_sort)
74
75 formatted_results = Page(
76 search_result['results'], page=requested_page,
77 item_count=search_result['count'],
78 items_per_page=page_limit, url=url_generator)
79 finally:
80 searcher.cleanup()
81
82 if not search_result['error']:
83 execution_time = '%s results (%.3f seconds)' % (
84 search_result['count'],
85 search_result['runtime'])
86 elif not errors:
87 node = schema['search_query']
88 errors = [
89 validation_schema.Invalid(node, search_result['error'])]
90
91 c.perm_user = c.auth_user
92 c.repo_name = repo_name
93 c.sort = search_sort
94 c.url_generator = url_generator
95 c.errors = errors
96 c.formatted_results = formatted_results
97 c.runtime = execution_time
98 c.cur_query = search_query
99 c.search_type = search_type
100 c.searcher = searcher
101
102
103 class SearchView(BaseAppView):
104 def load_default_context(self):
105 c = self._get_local_tmpl_context()
106 self._register_global_c(c)
107 return c
108
109 @LoginRequired()
110 @view_config(
111 route_name='search', request_method='GET',
112 renderer='rhodecode:templates/search/search.mako')
113 def search(self):
114 c = self.load_default_context()
115 search(self.request, c, repo_name=None)
116 return self._get_template_context(c)
117
118
119 class SearchRepoView(RepoAppView):
120 def load_default_context(self):
121 c = self._get_local_tmpl_context()
122 self._register_global_c(c)
123 return c
124
125 @LoginRequired()
126 @HasRepoPermissionAnyDecorator('repository.admin')
127 @view_config(
128 route_name='search_repo', request_method='GET',
129 renderer='rhodecode:templates/search/search.mako')
130 def search_repo(self):
131 c = self.load_default_context()
132 search(self.request, c, repo_name=self.db_repo_name)
133 return self._get_template_context(c)
@@ -0,0 +1,255 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import datetime
23
24 from rhodecode.model import meta
25 from rhodecode.model.db import User, UserLog, Repository
26
27
28 log = logging.getLogger(__name__)
29
30 # action as key, and expected action_data as value
31 ACTIONS_V1 = {
32 'user.login.success': {'user_agent': ''},
33 'user.login.failure': {'user_agent': ''},
34 'user.logout': {'user_agent': ''},
35 'user.password.reset_request': {},
36 'user.push': {'user_agent': '', 'commit_ids': []},
37 'user.pull': {'user_agent': ''},
38
39 'user.create': {'data': {}},
40 'user.delete': {'old_data': {}},
41 'user.edit': {'old_data': {}},
42 'user.edit.permissions': {},
43 'user.edit.ip.add': {'ip': {}, 'user': {}},
44 'user.edit.ip.delete': {'ip': {}, 'user': {}},
45 'user.edit.token.add': {'token': {}, 'user': {}},
46 'user.edit.token.delete': {'token': {}, 'user': {}},
47 'user.edit.email.add': {'email': ''},
48 'user.edit.email.delete': {'email': ''},
49 'user.edit.password_reset.enabled': {},
50 'user.edit.password_reset.disabled': {},
51
52 'user_group.create': {'data': {}},
53 'user_group.delete': {'old_data': {}},
54 'user_group.edit': {'old_data': {}},
55 'user_group.edit.permissions': {},
56 'user_group.edit.member.add': {'user': {}},
57 'user_group.edit.member.delete': {'user': {}},
58
59 'repo.create': {'data': {}},
60 'repo.fork': {'data': {}},
61 'repo.edit': {'old_data': {}},
62 'repo.edit.permissions': {},
63 'repo.delete': {'old_data': {}},
64 'repo.commit.strip': {'commit_id': ''},
65 'repo.archive.download': {'user_agent': '', 'archive_name': '',
66 'archive_spec': '', 'archive_cached': ''},
67 'repo.pull_request.create': '',
68 'repo.pull_request.edit': '',
69 'repo.pull_request.delete': '',
70 'repo.pull_request.close': '',
71 'repo.pull_request.merge': '',
72 'repo.pull_request.vote': '',
73 'repo.pull_request.comment.create': '',
74 'repo.pull_request.comment.delete': '',
75
76 'repo.pull_request.reviewer.add': '',
77 'repo.pull_request.reviewer.delete': '',
78
79 'repo.commit.comment.create': {'data': {}},
80 'repo.commit.comment.delete': {'data': {}},
81 'repo.commit.vote': '',
82
83 'repo_group.create': {'data': {}},
84 'repo_group.edit': {'old_data': {}},
85 'repo_group.edit.permissions': {},
86 'repo_group.delete': {'old_data': {}},
87 }
88 ACTIONS = ACTIONS_V1
89
90 SOURCE_WEB = 'source_web'
91 SOURCE_API = 'source_api'
92
93
94 class UserWrap(object):
95 """
96 Fake object used to imitate AuthUser
97 """
98
99 def __init__(self, user_id=None, username=None, ip_addr=None):
100 self.user_id = user_id
101 self.username = username
102 self.ip_addr = ip_addr
103
104
105 class RepoWrap(object):
106 """
107 Fake object used to imitate RepoObject that audit logger requires
108 """
109
110 def __init__(self, repo_id=None, repo_name=None):
111 self.repo_id = repo_id
112 self.repo_name = repo_name
113
114
115 def _store_log(action_name, action_data, user_id, username, user_data,
116 ip_address, repository_id, repository_name):
117 user_log = UserLog()
118 user_log.version = UserLog.VERSION_2
119
120 user_log.action = action_name
121 user_log.action_data = action_data
122
123 user_log.user_ip = ip_address
124
125 user_log.user_id = user_id
126 user_log.username = username
127 user_log.user_data = user_data
128
129 user_log.repository_id = repository_id
130 user_log.repository_name = repository_name
131
132 user_log.action_date = datetime.datetime.now()
133
134 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
135 action_name, user_id, username, ip_address)
136
137 return user_log
138
139
140 def store_web(*args, **kwargs):
141 if 'action_data' not in kwargs:
142 kwargs['action_data'] = {}
143 kwargs['action_data'].update({
144 'source': SOURCE_WEB
145 })
146 return store(*args, **kwargs)
147
148
149 def store_api(*args, **kwargs):
150 if 'action_data' not in kwargs:
151 kwargs['action_data'] = {}
152 kwargs['action_data'].update({
153 'source': SOURCE_API
154 })
155 return store(*args, **kwargs)
156
157
158 def store(action, user, action_data=None, user_data=None, ip_addr=None,
159 repo=None, sa_session=None, commit=False):
160 """
161 Audit logger for various actions made by users, typically this
162 results in a call such::
163
164 from rhodecode.lib import audit_logger
165
166 audit_logger.store(
167 'repo.edit', user=self._rhodecode_user)
168 audit_logger.store(
169 'repo.delete', action_data={'data': repo_data},
170 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
171
172 # repo action
173 audit_logger.store(
174 'repo.delete',
175 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
176 repo=audit_logger.RepoWrap(repo_name='some-repo'))
177
178 # repo action, when we know and have the repository object already
179 audit_logger.store(
180 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
181 user=self._rhodecode_user,
182 repo=repo_object)
183
184 # alternative wrapper to the above
185 audit_logger.store_web(
186 'repo.delete', action_data={},
187 user=self._rhodecode_user,
188 repo=repo_object)
189
190 # without an user ?
191 audit_logger.store(
192 'user.login.failure',
193 user=audit_logger.UserWrap(
194 username=self.request.params.get('username'),
195 ip_addr=self.request.remote_addr))
196
197 """
198 from rhodecode.lib.utils2 import safe_unicode
199 from rhodecode.lib.auth import AuthUser
200
201 action_spec = ACTIONS.get(action, None)
202 if action_spec is None:
203 raise ValueError('Action `{}` is not supported'.format(action))
204
205 if not sa_session:
206 sa_session = meta.Session()
207
208 try:
209 username = getattr(user, 'username', None)
210 if not username:
211 pass
212
213 user_id = getattr(user, 'user_id', None)
214 if not user_id:
215 # maybe we have username ? Try to figure user_id from username
216 if username:
217 user_id = getattr(
218 User.get_by_username(username), 'user_id', None)
219
220 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
221 if not ip_addr:
222 pass
223
224 if not user_data:
225 # try to get this from the auth user
226 if isinstance(user, AuthUser):
227 user_data = {
228 'username': user.username,
229 'email': user.email,
230 }
231
232 repository_name = getattr(repo, 'repo_name', None)
233 repository_id = getattr(repo, 'repo_id', None)
234 if not repository_id:
235 # maybe we have repo_name ? Try to figure repo_id from repo_name
236 if repository_name:
237 repository_id = getattr(
238 Repository.get_by_repo_name(repository_name), 'repo_id', None)
239
240 user_log = _store_log(
241 action_name=safe_unicode(action),
242 action_data=action_data or {},
243 user_id=user_id,
244 username=username,
245 user_data=user_data or {},
246 ip_address=safe_unicode(ip_addr),
247 repository_id=repository_id,
248 repository_name=repository_name
249 )
250 sa_session.add(user_log)
251 if commit:
252 sa_session.commit()
253
254 except Exception:
255 log.exception('AUDIT: failed to store audit log')
@@ -0,0 +1,61 b''
1 import os
2 import logging
3 import datetime
4
5 from sqlalchemy import *
6 from sqlalchemy.exc import DatabaseError
7 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
8 from sqlalchemy.orm.session import Session
9 from sqlalchemy.ext.declarative import declarative_base
10
11 from rhodecode.lib.dbmigrate.migrate import *
12 from rhodecode.lib.dbmigrate.migrate.changeset import *
13 from rhodecode.lib.utils2 import str2bool
14
15 from rhodecode.model.meta import Base
16 from rhodecode.model import meta
17 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
18
19 log = logging.getLogger(__name__)
20
21
22 def get_by_key(cls, key):
23 return cls.query().filter(cls.ui_key == key).scalar()
24
25
26 def get_repos_location(cls):
27 return get_by_key(cls, '/').ui_value
28
29
30 def upgrade(migrate_engine):
31 """
32 Upgrade operations go here.
33 Don't create your own engine; bind migrate_engine to your metadata
34 """
35 _reset_base(migrate_engine)
36 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
37
38 # add last_activity
39 user_log_table = db.UserLog.__table__
40
41 user_data = Column('user_data_json', db.JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
42 user_data.create(table=user_log_table)
43
44 version = Column("version", String(255), nullable=True, default='v2')
45 version.create(table=user_log_table)
46
47 action_data = Column('action_data_json', db.JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
48 action_data.create(table=user_log_table)
49
50 # issue fixups
51 fixups(db, meta.Session)
52
53
54 def downgrade(migrate_engine):
55 meta = MetaData()
56 meta.bind = migrate_engine
57
58
59 def fixups(models, _SESSION):
60 pass
61
@@ -0,0 +1,63 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def get_by_key(cls, key):
11 return cls.query().filter(cls.ui_key == key).scalar()
12
13
14 def create_or_update_hook(cls, key, val, SESSION):
15 new_ui = get_by_key(cls, key) or cls()
16 new_ui.ui_section = 'hooks'
17 new_ui.ui_active = True
18 new_ui.ui_key = key
19 new_ui.ui_value = val
20
21 SESSION().add(new_ui)
22
23
24 def upgrade(migrate_engine):
25 """
26 Upgrade operations go here.
27 Don't create your own engine; bind migrate_engine to your metadata
28 """
29 _reset_base(migrate_engine)
30 from rhodecode.lib.dbmigrate.schema import db_4_7_0_0 as db
31
32 # issue fixups
33 fixups(db, meta.Session)
34
35
36 def downgrade(migrate_engine):
37 meta = MetaData()
38 meta.bind = migrate_engine
39
40
41 def fixups(models, _SESSION):
42
43 cleanup_if_present = (
44 'pushkey.key_push',
45 )
46
47 for hook in cleanup_if_present:
48 ui_cfg = models.RhodeCodeUi.query().filter(
49 models.RhodeCodeUi.ui_key == hook).scalar()
50 if ui_cfg is not None:
51 log.info('Removing RhodeCodeUI for hook "%s".', hook)
52 _SESSION().delete(ui_cfg)
53
54 to_add = [
55 ('pushkey.key_push',
56 'python:vcsserver.hooks.key_push'),
57 ]
58
59 for hook, value in to_add:
60 log.info('Adding RhodeCodeUI for hook "%s".', hook)
61 create_or_update_hook(models.RhodeCodeUi, hook, value, _SESSION)
62
63 _SESSION().commit()
@@ -0,0 +1,37 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
17
18 repo_review_rule_table = db.RepoReviewRule.__table__
19
20 forbid_author_to_review = Column(
21 "forbid_author_to_review", Boolean(), nullable=True, default=False)
22 forbid_author_to_review.create(table=repo_review_rule_table)
23
24 forbid_adding_reviewers = Column(
25 "forbid_adding_reviewers", Boolean(), nullable=True, default=False)
26 forbid_adding_reviewers.create(table=repo_review_rule_table)
27
28 fixups(db, meta.Session)
29
30
31 def downgrade(migrate_engine):
32 meta = MetaData()
33 meta.bind = migrate_engine
34
35
36 def fixups(models, _SESSION):
37 pass
@@ -0,0 +1,38 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
17
18 repo_review_rule_user_table = db.RepoReviewRuleUser.__table__
19 repo_review_rule_user_group_table = db.RepoReviewRuleUserGroup.__table__
20
21 mandatory_user = Column(
22 "mandatory", Boolean(), nullable=True, default=False)
23 mandatory_user.create(table=repo_review_rule_user_table)
24
25 mandatory_user_group = Column(
26 "mandatory", Boolean(), nullable=True, default=False)
27 mandatory_user_group.create(table=repo_review_rule_user_group_table)
28
29 fixups(db, meta.Session)
30
31
32 def downgrade(migrate_engine):
33 meta = MetaData()
34 meta.bind = migrate_engine
35
36
37 def fixups(models, _SESSION):
38 pass
@@ -0,0 +1,33 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
17
18 pull_request_reviewers = db.PullRequestReviewers.__table__
19
20 mandatory = Column(
21 "mandatory", Boolean(), nullable=True, default=False)
22 mandatory.create(table=pull_request_reviewers)
23
24 fixups(db, meta.Session)
25
26
27 def downgrade(migrate_engine):
28 meta = MetaData()
29 meta.bind = migrate_engine
30
31
32 def fixups(models, _SESSION):
33 pass
@@ -0,0 +1,40 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
17
18 pull_request = db.PullRequest.__table__
19 pull_request_version = db.PullRequestVersion.__table__
20
21 reviewer_data_1 = Column(
22 'reviewer_data_json',
23 db.JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
24 reviewer_data_1.create(table=pull_request)
25
26 reviewer_data_2 = Column(
27 'reviewer_data_json',
28 db.JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
29 reviewer_data_2.create(table=pull_request_version)
30
31 fixups(db, meta.Session)
32
33
34 def downgrade(migrate_engine):
35 meta = MetaData()
36 meta.bind = migrate_engine
37
38
39 def fixups(models, _SESSION):
40 pass
@@ -0,0 +1,33 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
17
18 repo_review_rule_table = db.RepoReviewRule.__table__
19
20 forbid_commit_author_to_review = Column(
21 "forbid_commit_author_to_review", Boolean(), nullable=True, default=False)
22 forbid_commit_author_to_review.create(table=repo_review_rule_table)
23
24 fixups(db, meta.Session)
25
26
27 def downgrade(migrate_engine):
28 meta = MetaData()
29 meta.bind = migrate_engine
30
31
32 def fixups(models, _SESSION):
33 pass
@@ -0,0 +1,37 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from rhodecode.lib.ext_json import json
22
23
24 def pyramid_ext_json(info):
25 """
26 Custom json renderer for pyramid to use our ext_json lib
27 """
28 def _render(value, system):
29 request = system.get('request')
30 if request is not None:
31 response = request.response
32 ct = response.content_type
33 if ct == response.default_content_type:
34 response.content_type = 'application/json'
35 return json.dumps(value)
36
37 return _render
@@ -0,0 +1,34 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22 from rhodecode.model.validation_schema import validators, preparers, types
23
24
25 class ReviewerSchema(colander.MappingSchema):
26 username = colander.SchemaNode(types.StrOrIntType())
27 reasons = colander.SchemaNode(colander.List(), missing=['no reason specified'])
28 mandatory = colander.SchemaNode(colander.Boolean(), missing=False)
29
30
31 class ReviewerListSchema(colander.SequenceSchema):
32 reviewers = ReviewerSchema()
33
34
@@ -0,0 +1,78 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import re
21 import colander
22
23 from rhodecode.model.validation_schema import types, validators
24 from rhodecode.translation import _
25
26
27 @colander.deferred
28 def deferred_user_group_name_validator(node, kw):
29
30 def name_validator(node, value):
31
32 msg = _('Allowed in name are letters, numbers, and `-`, `_`, `.` '
33 'Name must start with a letter or number. Got `{}`').format(value)
34
35 if not re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value):
36 raise colander.Invalid(node, msg)
37
38 return name_validator
39
40
41 @colander.deferred
42 def deferred_user_group_owner_validator(node, kw):
43
44 def owner_validator(node, value):
45 from rhodecode.model.db import User
46 existing = User.get_by_username(value)
47 if not existing:
48 msg = _(u'User group owner with id `{}` does not exists').format(value)
49 raise colander.Invalid(node, msg)
50
51 return owner_validator
52
53
54 class UserGroupSchema(colander.Schema):
55
56 user_group_name = colander.SchemaNode(
57 colander.String(),
58 validator=deferred_user_group_name_validator)
59
60 user_group_description = colander.SchemaNode(
61 colander.String(), missing='')
62
63 user_group_owner = colander.SchemaNode(
64 colander.String(),
65 validator=deferred_user_group_owner_validator)
66
67 user_group_active = colander.SchemaNode(
68 types.StringBooleanType(),
69 missing=False)
70
71 def deserialize(self, cstruct):
72 """
73 Custom deserialize that allows to chain validation, and verify
74 permissions, and as last step uniqueness
75 """
76
77 appstruct = super(UserGroupSchema, self).deserialize(cstruct)
78 return appstruct
@@ -0,0 +1,49 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import deform.widget
22
23
24 def convert_to_optgroup(items):
25 """
26 Convert such format::
27
28 [
29 ['rev:tip', u'latest tip'],
30 ([(u'branch:default', u'default')], u'Branches'),
31 ]
32
33 into one used by deform Select widget::
34
35 (
36 ('rev:tip', 'latest tip'),
37 OptGroup('Branches',
38 ('branch:default', 'default'),
39 )
40 """
41 result = []
42 for value, label in items:
43 # option group
44 if isinstance(value, (tuple, list)):
45 result.append(deform.widget.OptGroup(label, *value))
46 else:
47 result.append((value, label))
48
49 return result
@@ -0,0 +1,252 b''
1 /** README styling **/
2 div.readme_box {
3 clear: both;
4 overflow: hidden;
5 margin: 0;
6 padding: 3px 15px 3px;
7 }
8
9 div.readme_box h1,
10 div.readme_box h2,
11 div.readme_box h3,
12 div.readme_box h4,
13 div.readme_box h5,
14 div.readme_box h6 {
15 border-bottom: none !important;
16 padding: 0 !important;
17 overflow: visible !important;
18 }
19
20 div.readme_box h1 {
21 font-size: 32px;
22 margin: 15px 0 15px 0 !important;
23 padding-bottom: 5px !important;
24 }
25
26 div.readme_box h2 {
27 font-size: 24px !important;
28 margin: 34px 0 10px 0 !important;
29 border-top: 3px #e6e5e5 solid !important;
30 padding-top: 15px !important;
31 padding-bottom: 8px !important;
32 }
33
34 div.readme_box h3 {
35 font-size: 18px !important;
36 margin: 30px 0 8px 0 !important;
37 padding-bottom: 2px !important;
38 }
39
40 div.readme_box h4 {
41 font-size: 13px !important;
42 margin: 18px 0 3px 0 !important;
43 }
44
45 div.readme_box h5 {
46 font-size: 12px !important;
47 margin: 15px 0 3px 0 !important;
48 }
49
50 div.readme_box h6 {
51 font-size: 12px;
52 color: #777777;
53 margin: 15px 0 3px 0 !important;
54 }
55
56 div.readme_box hr {
57 border: 0;
58 color: #e6e5e5;
59 background-color: #e6e5e5;
60 height: 3px;
61 margin-bottom: 13px;
62 }
63
64 div.readme_box ol,
65 div.readme_box ul,
66 div.readme_box p,
67 div.readme_box blockquote,
68 div.readme_box dl,
69 div.readme_box li,
70 div.readme_box table {
71 margin: 3px 0px 13px 0px !important;
72 color: #424242 !important;
73 font-size: 13px !important;
74 font-family: "Helvetica" !important;
75 font-weight: normal !important;
76 overflow: visible !important;
77 line-height: 140% !important;
78 }
79
80 div.readme_box pre {
81 margin: 3px 0px 13px 0px !important;
82 padding: .5em;
83 color: #424242 !important;
84 font-size: 13px !important;
85 overflow: visible !important;
86 line-height: 140% !important;
87 background-color: @grey7;
88 }
89
90 div.readme_box img {
91 border-style: none;
92 background-color: #fff;
93 }
94
95
96 div.readme_box strong {
97 font-weight: 600;
98 margin: 0;
99 }
100
101 div.readme_box ul,
102 div.readme_box ol {
103 padding-left: 30px !important;
104 margin-top: 0px !important;
105 margin-bottom: 18px !important;
106 }
107
108 div.readme_box ul li,
109 div.readme_box ol li {
110 list-style: bullet !important;
111 margin: 6px !important;
112 padding: 0 !important;
113 }
114
115 div.readme_box ol li {
116 list-style: decimal !important;
117 }
118
119 /*
120 div.readme_box a,
121 div.readme_box a:visited {
122 color: #4183C4 !important;
123 background-color: inherit;
124 text-decoration: none;
125 }
126 */
127
128
129 div.readme_box button {
130 font-size: @basefontsize;
131 padding: 4px 6px;
132 .border-radius(@border-radius);
133 border: @border-thickness solid @grey5;
134 background-color: @grey6;
135 }
136
137 div.readme_box code,
138 div.readme_box pre {
139 font-family: Monaco;
140 font-size: 11px;
141 .border-radius(@border-radius);
142 background-color: white;
143 color: @grey3;
144 }
145
146
147 div.readme_box code {
148 border: @border-thickness solid @grey6;
149 margin: 0 2px;
150 padding: 0 5px;
151 }
152
153 div.readme_box pre {
154 border: @border-thickness solid @grey5;
155 overflow: auto;
156 padding: .5em;
157 background-color: @grey7;
158 }
159
160 div.readme_box pre > code {
161 border: 0;
162 margin: 0;
163 padding: 0;
164 }
165
166 /** RST STYLE **/
167 div.rst-block {
168 clear: both;
169 overflow: hidden;
170 margin: 0;
171 padding: 3px 15px 3px;
172 }
173
174 div.rst-block h2 {
175 font-weight: normal;
176 }
177
178 div.rst-block h1,
179 div.rst-block h2,
180 div.rst-block h3,
181 div.rst-block h4,
182 div.rst-block h5,
183 div.rst-block h6 {
184 border-bottom: 0 !important;
185 margin: 0 !important;
186 padding: 0 !important;
187 line-height: 1.5em !important;
188 }
189
190
191 div.rst-block h1:first-child {
192 padding-top: .25em !important;
193 }
194
195 div.rst-block h2,
196 div.rst-block h3 {
197 margin: 1em 0 !important;
198 }
199
200 div.rst-block h2 {
201 margin-top: 1.5em !important;
202 border-top: 4px solid #e0e0e0 !important;
203 padding-top: .5em !important;
204 }
205
206 div.rst-block p {
207 color: black !important;
208 margin: 1em 0 !important;
209 line-height: 1.5em !important;
210 }
211
212 div.rst-block ul {
213 list-style: disc !important;
214 margin: 1em 0 1em 2em !important;
215 clear: both;
216 }
217
218 div.rst-block ol {
219 list-style: decimal;
220 margin: 1em 0 1em 2em !important;
221 }
222
223 div.rst-block pre,
224 div.rst-block code {
225 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
226 }
227
228 div.rst-block code {
229 font-size: 12px !important;
230 background-color: ghostWhite !important;
231 color: #444 !important;
232 padding: 0 .2em !important;
233 border: 1px solid #dedede !important;
234 }
235
236 div.rst-block pre code {
237 padding: 0 !important;
238 font-size: 12px !important;
239 background-color: #eee !important;
240 border: none !important;
241 }
242
243 div.rst-block pre {
244 margin: 1em 0;
245 padding: @padding;
246 border: 1px solid @grey6;
247 .border-radius(@border-radius);
248 overflow: auto;
249 font-size: 12px;
250 color: #444;
251 background-color: @grey7;
252 } No newline at end of file
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,65 b''
1 <%namespace name="base" file="/base/base.mako"/>
2
3 %if c.audit_logs:
4 <table class="rctable admin_log">
5 <tr>
6 <th>${_('Username')}</th>
7 <th>${_('Action')}</th>
8 <th>${_('Action Data')}</th>
9 <th>${_('Repository')}</th>
10 <th>${_('Date')}</th>
11 <th>${_('IP')}</th>
12 </tr>
13
14 %for cnt,l in enumerate(c.audit_logs):
15 <tr class="parity${cnt%2}">
16 <td class="td-user">
17 %if l.user is not None:
18 ${base.gravatar_with_user(l.user.email)}
19 %else:
20 ${l.username}
21 %endif
22 </td>
23 <td class="td-journalaction">
24 % if l.version == l.VERSION_1:
25 ${h.action_parser(l)[0]()}
26 % else:
27 ${h.literal(l.action)}
28 % endif
29
30 <div class="journal_action_params">
31 % if l.version == l.VERSION_1:
32 ${h.literal(h.action_parser(l)[1]())}
33 % endif
34 </div>
35 </td>
36 <td>
37 % if l.version == l.VERSION_2:
38 <a href="#" onclick="$('#entry-'+${l.user_log_id}).toggle();return false">${_('toggle')}</a>
39 <div id="entry-${l.user_log_id}" style="display: none">
40 <pre>${h.json.dumps(l.action_data, indent=4, sort_keys=True)}</pre>
41 </div>
42 % else:
43 <pre title="${_('data not available for v1 entries type')}">-</pre>
44 % endif
45 </td>
46 <td class="td-componentname">
47 %if l.repository is not None:
48 ${h.link_to(l.repository.repo_name, h.route_path('repo_summary',repo_name=l.repository.repo_name))}
49 %else:
50 ${l.repository_name}
51 %endif
52 </td>
53
54 <td class="td-time">${h.format_date(l.action_date)}</td>
55 <td class="td-ip">${l.user_ip}</td>
56 </tr>
57 %endfor
58 </table>
59
60 <div class="pagination-wh pagination-left">
61 ${c.audit_logs.pager('$link_previous ~2~ $link_next')}
62 </div>
63 %else:
64 ${_('No actions yet')}
65 %endif No newline at end of file
@@ -0,0 +1,9 b''
1 <div class="panel panel-default">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Default Reviewer Rules')}</h3>
4 </div>
5 <div class="panel-body">
6 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
7 <img style="width: 100%; height: 100%" src="${h.asset('images/ee_features/default_reviewers.png')}"/>
8 </div>
9 </div>
@@ -0,0 +1,136 b''
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
3 %if c.repo_commits:
4 <table class="rctable repo_summary table_disp">
5 <tr>
6
7 <th class="status" colspan="2"></th>
8 <th>${_('Commit')}</th>
9 <th>${_('Commit message')}</th>
10 <th>${_('Age')}</th>
11 <th>${_('Author')}</th>
12 <th>${_('Refs')}</th>
13 </tr>
14 %for cnt,cs in enumerate(c.repo_commits):
15 <tr class="parity${cnt%2}">
16
17 <td class="td-status">
18 %if c.statuses.get(cs.raw_id):
19 <div class="changeset-status-ico shortlog">
20 %if c.statuses.get(cs.raw_id)[2]:
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
23 </a>
24 %else:
25 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
27 </a>
28 %endif
29 </div>
30 %else:
31 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
32 %endif
33 </td>
34 <td class="td-comments">
35 %if c.comments.get(cs.raw_id,[]):
36 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
37 <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])}
38 </a>
39 %endif
40 </td>
41 <td class="td-commit">
42 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
43 </td>
44
45 <td class="td-description mid">
46 <div class="log-container truncate-wrap">
47 <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div>
48 </div>
49 </td>
50
51 <td class="td-time">
52 ${h.age_component(cs.date)}
53 </td>
54 <td class="td-user author">
55 ${base.gravatar_with_user(cs.author)}
56 </td>
57
58 <td class="td-tags">
59 <div class="autoexpand">
60 %if h.is_hg(c.rhodecode_repo):
61 %for book in cs.bookmarks:
62 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
63 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
64 </span>
65 %endfor
66 %endif
67 ## tags
68 %for tag in cs.tags:
69 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
70 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
71 </span>
72 %endfor
73
74 ## branch
75 %if cs.branch:
76 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
77 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch)}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
78 </span>
79 %endif
80 </div>
81 </td>
82 </tr>
83 %endfor
84
85 </table>
86
87 <script type="text/javascript">
88 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
89 $(document).on('pjax:success', function(){ timeagoActivate(); });
90 </script>
91
92 <div class="pagination-wh pagination-left">
93 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
94 </div>
95 %else:
96
97 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
98 <div class="quick_start">
99 <div class="fieldset">
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
101 <div class="right-content">
102 <div id="add_node_id" class="add_node">
103 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
104 </div>
105 </div>
106 %endif
107 </div>
108
109 %if not h.is_svn(c.rhodecode_repo):
110 <div class="fieldset">
111 <div class="left-label">${_('Push new repo:')}</div>
112 <div class="right-content">
113 <pre>
114 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
115 ${c.rhodecode_repo.alias} add README # add first file
116 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
117 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
118 </pre>
119 </div>
120 </div>
121 <div class="fieldset">
122 <div class="left-label">${_('Existing repository?')}</div>
123 <div class="right-content">
124 <pre>
125 %if h.is_git(c.rhodecode_repo):
126 git remote add origin ${c.clone_repo_url}
127 git push -u origin master
128 %else:
129 hg push ${c.clone_repo_url}
130 %endif
131 </pre>
132 </div>
133 </div>
134 %endif
135 </div>
136 %endif
@@ -1,5 +1,5 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 4.7.2
2 current_version = 4.8.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:rhodecode/VERSION]
5 [bumpversion:file:rhodecode/VERSION]
@@ -5,25 +5,20 b' done = false'
5 done = true
5 done = true
6
6
7 [task:rc_tools_pinned]
7 [task:rc_tools_pinned]
8 done = true
9
8
10 [task:fixes_on_stable]
9 [task:fixes_on_stable]
11 done = true
12
10
13 [task:pip2nix_generated]
11 [task:pip2nix_generated]
14 done = true
15
12
16 [task:changelog_updated]
13 [task:changelog_updated]
17 done = true
18
14
19 [task:generate_api_docs]
15 [task:generate_api_docs]
20 done = true
16
17 [task:updated_translation]
21
18
22 [release]
19 [release]
23 state = prepared
20 state = in_progress
24 version = 4.7.2
21 version = 4.8.0
25
26 [task:updated_translation]
27
22
28 [task:generate_js_routes]
23 [task:generate_js_routes]
29
24
@@ -33,6 +33,7 b' include rhodecode/public/502.html'
33 # images, css
33 # images, css
34 include rhodecode/public/css/*.css
34 include rhodecode/public/css/*.css
35 include rhodecode/public/images/*.*
35 include rhodecode/public/images/*.*
36 include rhodecode/public/images/ee_features/*.*
36
37
37 # sound files
38 # sound files
38 include rhodecode/public/sounds/*.mp3
39 include rhodecode/public/sounds/*.mp3
@@ -174,32 +174,33 b' let'
174 '';
174 '';
175
175
176 postInstall = ''
176 postInstall = ''
177 echo "Writing meta information for rccontrol to nix-support/rccontrol"
178 mkdir -p $out/nix-support/rccontrol
179 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
180 echo "DONE: Meta information for rccontrol written"
181
177 # python based programs need to be wrapped
182 # python based programs need to be wrapped
183 ln -s ${self.pyramid}/bin/* $out/bin/
184 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
178 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
185 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
179 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
180 ln -s ${self.PasteScript}/bin/paster $out/bin/
186 ln -s ${self.PasteScript}/bin/paster $out/bin/
181 ln -s ${self.channelstream}/bin/channelstream $out/bin/
187 ln -s ${self.channelstream}/bin/channelstream $out/bin/
182 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
183
188
184 # rhodecode-tools
189 # rhodecode-tools
185 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
186 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
190 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
187
191
188 # note that condition should be restricted when adding further tools
192 # note that condition should be restricted when adding further tools
189 for file in $out/bin/*; do #*/
193 for file in $out/bin/*;
194 do
190 wrapProgram $file \
195 wrapProgram $file \
196 --prefix PATH : $PATH \
191 --prefix PYTHONPATH : $PYTHONPATH \
197 --prefix PYTHONPATH : $PYTHONPATH \
192 --prefix PATH : $PATH \
193 --set PYTHONHASHSEED random
198 --set PYTHONHASHSEED random
194 done
199 done
195
200
196 mkdir $out/etc
201 mkdir $out/etc
197 cp configs/production.ini $out/etc
202 cp configs/production.ini $out/etc
198
203
199 echo "Writing meta information for rccontrol to nix-support/rccontrol"
200 mkdir -p $out/nix-support/rccontrol
201 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
202 echo "DONE: Meta information for rccontrol written"
203
204
204 # TODO: johbo: Make part of ac-tests
205 # TODO: johbo: Make part of ac-tests
205 if [ ! -f rhodecode/public/js/scripts.js ]; then
206 if [ ! -f rhodecode/public/js/scripts.js ]; then
@@ -62,6 +62,9 b' Below config if for an Apache Reverse Pr'
62 ProxyPass / http://127.0.0.1:10002/ timeout=7200 Keepalive=On
62 ProxyPass / http://127.0.0.1:10002/ timeout=7200 Keepalive=On
63 ProxyPassReverse / http://127.0.0.1:10002/
63 ProxyPassReverse / http://127.0.0.1:10002/
64
64
65 # Increase headers for large Mercurial headers
66 LimitRequestLine 16380
67
65 # strict http prevents from https -> http downgrade
68 # strict http prevents from https -> http downgrade
66 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
69 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
67
70
@@ -5,7 +5,10 b' Use the following example to configure N'
5
5
6
6
7 .. code-block:: nginx
7 .. code-block:: nginx
8 ## rate limiter for certain pages to prevent brute force attacks
9 limit_req_zone $binary_remote_addr zone=dl_limit:10m rate=1r/s;
8
10
11 ## custom log format
9 log_format log_custom '$remote_addr - $remote_user [$time_local] '
12 log_format log_custom '$remote_addr - $remote_user [$time_local] '
10 '"$request" $status $body_bytes_sent '
13 '"$request" $status $body_bytes_sent '
11 '"$http_referer" "$http_user_agent" '
14 '"$http_referer" "$http_user_agent" '
@@ -109,6 +112,12 b' Use the following example to configure N'
109 proxy_set_header Connection "upgrade";
112 proxy_set_header Connection "upgrade";
110 }
113 }
111
114
115 location /_admin/login {
116 ## rate limit this endpoint
117 limit_req zone=dl_limit burst=10 nodelay;
118 try_files $uri @rhode;
119 }
120
112 location / {
121 location / {
113 try_files $uri @rhode;
122 try_files $uri @rhode;
114 }
123 }
@@ -3,8 +3,12 b''
3 Repository Extra Fields
3 Repository Extra Fields
4 =======================
4 =======================
5
5
6 Extra fields attached to a |repo| allow you to configure additional actions for
6 Extra fields attached to a |repo| allow you to configure additional fields for
7 |RCX|. To install and read more about |RCX|, see the :ref:`install-rcx` section.
7 each repository. This allows storing custom data per-repository.
8
9 It can be used in :ref:`integrations-webhook` or in |RCX|.
10 To install and read more about |RCX|, see the :ref:`install-rcx` section.
11
8
12
9 Enabling Extra Fields
13 Enabling Extra Fields
10 ---------------------
14 ---------------------
@@ -27,9 +31,14 b' 2. On the |repo| settings page, select t'
27
31
28 .. image:: ../images/extra-repo-fields.png
32 .. image:: ../images/extra-repo-fields.png
29
33
34 The most important is the `New field key` variable which under the value will
35 be stored. It needs to be unique for each repository. The label and description
36 will be generated in repository settings where users can actually save some
37 values inside generated extra fields.
30
38
31 Example Usage
39
32 -------------
40 Example Usage in extensions
41 ---------------------------
33
42
34 To use the extra fields in an extension, see the example below. For more
43 To use the extra fields in an extension, see the example below. For more
35 information and examples, see the :ref:`extensions-hooks-ref` section.
44 information and examples, see the :ref:`extensions-hooks-ref` section.
@@ -7,7 +7,7 b' The VCS Server handles |RCM| backend fun'
7 a VCS Server to run with a |RCM| instance. If you do not, you will be missing
7 a VCS Server to run with a |RCM| instance. If you do not, you will be missing
8 the connection between |RCM| and its |repos|. This will cause error messages
8 the connection between |RCM| and its |repos|. This will cause error messages
9 on the web interface. You can run your setup in the following configurations,
9 on the web interface. You can run your setup in the following configurations,
10 currently the best performance is one VCS Server per |RCM| instance:
10 currently the best performance is one of following:
11
11
12 * One VCS Server per |RCM| instance.
12 * One VCS Server per |RCM| instance.
13 * One VCS Server handling multiple instances.
13 * One VCS Server handling multiple instances.
@@ -59,7 +59,8 b' instance in the'
59 \vcs.backends <available-vcs-systems>
59 \vcs.backends <available-vcs-systems>
60 Set a comma-separated list of the |repo| options available from the
60 Set a comma-separated list of the |repo| options available from the
61 web interface. The default is ``hg, git, svn``,
61 web interface. The default is ``hg, git, svn``,
62 which is all |repo| types available.
62 which is all |repo| types available. The order of backends is also the
63 order backend will try to detect requests type.
63
64
64 \vcs.connection_timeout <seconds>
65 \vcs.connection_timeout <seconds>
65 Set the length of time in seconds that the VCS Server waits for
66 Set the length of time in seconds that the VCS Server waits for
@@ -159,9 +160,10 b' for full details see the :ref:`RhodeCode'
159
160
160 - NAME: vcsserver-1
161 - NAME: vcsserver-1
161 - STATUS: RUNNING
162 - STATUS: RUNNING
162 - TYPE: VCSServer
163 logs:/home/ubuntu/.rccontrol/vcsserver-1/vcsserver.log
163 - VERSION: 1.0.0
164 - VERSION: 4.7.2 VCSServer
164 - URL: http://127.0.0.1:10001
165 - URL: http://127.0.0.1:10008
166 - CONFIG: /home/ubuntu/.rccontrol/vcsserver-1/vcsserver.ini
165
167
166 $ rccontrol restart vcsserver-1
168 $ rccontrol restart vcsserver-1
167 Instance "vcsserver-1" successfully stopped.
169 Instance "vcsserver-1" successfully stopped.
@@ -181,7 +183,9 b' For a more detailed explanation of the l'
181 .. rst-class:: dl-horizontal
183 .. rst-class:: dl-horizontal
182
184
183 \host <ip-address>
185 \host <ip-address>
184 Set the host on which the VCS Server will run.
186 Set the host on which the VCS Server will run. VCSServer is not
187 protected by any authentication, so we *highly* recommend running it
188 under localhost ip that is `127.0.0.1`
185
189
186 \port <int>
190 \port <int>
187 Set the port number on which the VCS Server will be available.
191 Set the port number on which the VCS Server will be available.
@@ -189,13 +193,22 b' For a more detailed explanation of the l'
189 \locale <locale_utf>
193 \locale <locale_utf>
190 Set the locale the VCS Server expects.
194 Set the locale the VCS Server expects.
191
195
192 \threadpool_size <int>
196 \workers <int>
193 Set the size of the threadpool used to communicate
197 Set the number of process workers.Recommended
194 with the WSGI workers. This should be at least 6 times the number of
198 value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
195 WSGI worker processes.
196
199
197 \timeout <seconds>
200 \max_requests <int>
198 Set the timeout for RPC communication in seconds.
201 The maximum number of requests a worker will process before restarting.
202 Any value greater than zero will limit the number of requests a work
203 will process before automatically restarting. This is a simple method
204 to help limit the damage of memory leaks.
205
206 \max_requests_jitter <int>
207 The maximum jitter to add to the max_requests setting.
208 The jitter causes the restart per worker to be randomized by
209 randint(0, max_requests_jitter). This is intended to stagger worker
210 restarts to avoid all workers restarting at the same time.
211
199
212
200 .. note::
213 .. note::
201
214
@@ -204,27 +217,54 b' For a more detailed explanation of the l'
204 .. code-block:: ini
217 .. code-block:: ini
205
218
206 ################################################################################
219 ################################################################################
207 # RhodeCode VCSServer - configuration #
220 # RhodeCode VCSServer with HTTP Backend - configuration #
208 # #
221 # #
209 ################################################################################
222 ################################################################################
210
223
211 [DEFAULT]
224
225 [server:main]
226 ## COMMON ##
212 host = 127.0.0.1
227 host = 127.0.0.1
213 port = 9900
228 port = 10002
229
230 ##########################
231 ## GUNICORN WSGI SERVER ##
232 ##########################
233 ## run with gunicorn --log-config vcsserver.ini --paste vcsserver.ini
234 use = egg:gunicorn#main
235 ## Sets the number of process workers. Recommended
236 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
237 workers = 3
238 ## process name
239 proc_name = rhodecode_vcsserver
240 ## type of worker class, one of sync, gevent
241 ## recommended for bigger setup is using of of other than sync one
242 worker_class = sync
243 ## The maximum number of simultaneous clients. Valid only for Gevent
244 #worker_connections = 10
245 ## max number of requests that worker will handle before being gracefully
246 ## restarted, could prevent memory leaks
247 max_requests = 1000
248 max_requests_jitter = 30
249 ## amount of time a worker can spend with handling a request before it
250 ## gets killed and restarted. Set to 6hrs
251 timeout = 21600
252
253 [app:main]
254 use = egg:rhodecode-vcsserver
255
256 pyramid.default_locale_name = en
257 pyramid.includes =
258
259 ## default locale used by VCS systems
214 locale = en_US.UTF-8
260 locale = en_US.UTF-8
215 # number of worker threads, this should be set based on a formula threadpool=N*6
216 # where N is number of RhodeCode Enterprise workers, eg. running 2 instances
217 # 8 gunicorn workers each would be 2 * 8 * 6 = 96, threadpool_size = 96
218 threadpool_size = 16
219 timeout = 0
220
261
221 # cache regions, please don't change
262 # cache regions, please don't change
222 beaker.cache.regions = repo_object
263 beaker.cache.regions = repo_object
223 beaker.cache.repo_object.type = memorylru
264 beaker.cache.repo_object.type = memorylru
224 beaker.cache.repo_object.max_items = 1000
265 beaker.cache.repo_object.max_items = 100
225
226 # cache auto-expires after N seconds
266 # cache auto-expires after N seconds
227 beaker.cache.repo_object.expire = 10
267 beaker.cache.repo_object.expire = 300
228 beaker.cache.repo_object.enabled = true
268 beaker.cache.repo_object.enabled = true
229
269
230
270
@@ -270,20 +310,6 b' For a more detailed explanation of the l'
270 level = DEBUG
310 level = DEBUG
271 formatter = generic
311 formatter = generic
272
312
273 [handler_file]
274 class = FileHandler
275 args = ('vcsserver.log', 'a',)
276 level = DEBUG
277 formatter = generic
278
279 [handler_file_rotating]
280 class = logging.handlers.TimedRotatingFileHandler
281 # 'D', 5 - rotate every 5days
282 # you can set 'h', 'midnight'
283 args = ('vcsserver.log', 'D', 5, 10,)
284 level = DEBUG
285 formatter = generic
286
287 ################
313 ################
288 ## FORMATTERS ##
314 ## FORMATTERS ##
289 ################
315 ################
@@ -6,7 +6,7 b' pull_request methods'
6 close_pull_request
6 close_pull_request
7 ------------------
7 ------------------
8
8
9 .. py:function:: close_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
9 .. py:function:: close_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>, message=<Optional:''>)
10
10
11 Close the pull request specified by `pullrequestid`.
11 Close the pull request specified by `pullrequestid`.
12
12
@@ -19,6 +19,9 b' close_pull_request'
19 :type pullrequestid: int
19 :type pullrequestid: int
20 :param userid: Close the pull request as this user.
20 :param userid: Close the pull request as this user.
21 :type userid: Optional(str or int)
21 :type userid: Optional(str or int)
22 :param message: Optional message to close the Pull Request with. If not
23 specified it will be generated automatically.
24 :type message: Optional(str)
22
25
23 Example output:
26 Example output:
24
27
@@ -27,6 +30,7 b' close_pull_request'
27 "id": <id_given_in_input>,
30 "id": <id_given_in_input>,
28 "result": {
31 "result": {
29 "pull_request_id": "<int>",
32 "pull_request_id": "<int>",
33 "close_status": "<str:status_lbl>,
30 "closed": "<bool>"
34 "closed": "<bool>"
31 },
35 },
32 "error": null
36 "error": null
@@ -105,10 +109,12 b' create_pull_request'
105 :param description: Set the pull request description.
109 :param description: Set the pull request description.
106 :type description: Optional(str)
110 :type description: Optional(str)
107 :param reviewers: Set the new pull request reviewers list.
111 :param reviewers: Set the new pull request reviewers list.
112 Reviewer defined by review rules will be added automatically to the
113 defined list.
108 :type reviewers: Optional(list)
114 :type reviewers: Optional(list)
109 Accepts username strings or objects of the format:
115 Accepts username strings or objects of the format:
110
116
111 {'username': 'nick', 'reasons': ['original author']}
117 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
112
118
113
119
114 get_pull_request
120 get_pull_request
@@ -320,7 +326,7 b' merge_pull_request'
320 update_pull_request
326 update_pull_request
321 -------------------
327 -------------------
322
328
323 .. py:function:: update_pull_request(apiuser, repoid, pullrequestid, title=<Optional:''>, description=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>, close_pull_request=<Optional:None>)
329 .. py:function:: update_pull_request(apiuser, repoid, pullrequestid, title=<Optional:''>, description=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>)
324
330
325 Updates a pull request.
331 Updates a pull request.
326
332
@@ -336,10 +342,12 b' update_pull_request'
336 :type description: Optional(str)
342 :type description: Optional(str)
337 :param reviewers: Update pull request reviewers list with new value.
343 :param reviewers: Update pull request reviewers list with new value.
338 :type reviewers: Optional(list)
344 :type reviewers: Optional(list)
345 Accepts username strings or objects of the format:
346
347 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
348
339 :param update_commits: Trigger update of commits for this pull request
349 :param update_commits: Trigger update of commits for this pull request
340 :type: update_commits: Optional(bool)
350 :type: update_commits: Optional(bool)
341 :param close_pull_request: Close this pull request with rejected state
342 :type: close_pull_request: Optional(bool)
343
351
344 Example output:
352 Example output:
345
353
@@ -527,6 +527,7 b' get_repo_settings'
527 "id": 237,
527 "id": 237,
528 "result": {
528 "result": {
529 "extensions_largefiles": true,
529 "extensions_largefiles": true,
530 "extensions_evolve": true,
530 "hooks_changegroup_push_logger": true,
531 "hooks_changegroup_push_logger": true,
531 "hooks_changegroup_repo_size": false,
532 "hooks_changegroup_repo_size": false,
532 "hooks_outgoing_pull_logger": true,
533 "hooks_outgoing_pull_logger": true,
@@ -762,6 +763,49 b' lock'
762 }
763 }
763
764
764
765
766 maintenance
767 -----------
768
769 .. py:function:: maintenance(apiuser, repoid)
770
771 Triggers a maintenance on the given repository.
772
773 This command can only be run using an |authtoken| with admin
774 rights to the specified repository. For more information,
775 see :ref:`config-token-ref`.
776
777 This command takes the following options:
778
779 :param apiuser: This is filled automatically from the |authtoken|.
780 :type apiuser: AuthUser
781 :param repoid: The repository name or repository ID.
782 :type repoid: str or int
783
784 Example output:
785
786 .. code-block:: bash
787
788 id : <id_given_in_input>
789 result : {
790 "msg": "executed maintenance command",
791 "executed_actions": [
792 <action_message>, <action_message2>...
793 ],
794 "repository": "<repository name>"
795 }
796 error : null
797
798 Example error output:
799
800 .. code-block:: bash
801
802 id : <id_given_in_input>
803 result : null
804 error : {
805 "Unable to execute maintenance on `<reponame>`"
806 }
807
808
765 pull
809 pull
766 ----
810 ----
767
811
@@ -66,7 +66,7 b' RhodeCode VCSServer repositories into th'
66 RhodeCode currently is using Mercurial Version Control System, please make sure
66 RhodeCode currently is using Mercurial Version Control System, please make sure
67 you have it installed before continuing.
67 you have it installed before continuing.
68
68
69 To obtain the required sources, use the following commands:
69 To obtain the required sources, use the following commands::
70
70
71 mkdir rhodecode-develop && cd rhodecode-develop
71 mkdir rhodecode-develop && cd rhodecode-develop
72 hg clone https://code.rhodecode.com/rhodecode-enterprise-ce
72 hg clone https://code.rhodecode.com/rhodecode-enterprise-ce
@@ -80,9 +80,9 b' To obtain the required sources, use the '
80 Install some required libraries
80 Install some required libraries
81 -------------------------------
81 -------------------------------
82
82
83 There are some required drivers that we need to install to test RhodeCode
83 There are some required drivers and dev libraries that we need to install to
84 under different types of databases. For example in Ubuntu we need to install
84 test RhodeCode under different types of databases. For example in Ubuntu we
85 the following.
85 need to install the following.
86
86
87 required libraries::
87 required libraries::
88
88
@@ -20,7 +20,7 b' and commit files and |repos| while manag'
20 * Migration from existing databases.
20 * Migration from existing databases.
21 * |RCM| SDK.
21 * |RCM| SDK.
22 * Built-in analytics
22 * Built-in analytics
23 * Built in integrations including: Slack, Jenkins, Webhooks, Jira, Redmine, Hipchat
23 * Built in integrations including: Slack, Webhooks (used for Jenkins/TeamCity and other CIs), Jira, Redmine, Hipchat
24 * Pluggable authentication system.
24 * Pluggable authentication system.
25 * Support for AD, |LDAP|, Crowd, CAS, PAM.
25 * Support for AD, |LDAP|, Crowd, CAS, PAM.
26 * Support for external authentication via Oauth Google, Github, Bitbucket, Twitter.
26 * Support for external authentication via Oauth Google, Github, Bitbucket, Twitter.
@@ -17,7 +17,8 b' Type/Name |RC| Edi'
17 :ref:`integrations-slack` |RCCEshort| https://slack.com/
17 :ref:`integrations-slack` |RCCEshort| https://slack.com/
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
20 :ref:`integrations-email` |RCEEshort| Send repo push commits by email
20 :ref:`integrations-ci` |RCCEshort| Trigger Builds for Common CI Systems
21 :ref:`integrations-email` |RCCEshort| Send repo push commits by email
21 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
22 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
22 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
23 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
23 ============================ ============ =====================================
24 ============================ ============ =====================================
@@ -3,9 +3,9 b''
3 Webhook integration
3 Webhook integration
4 ===================
4 ===================
5
5
6 The Webhook integration allows you to POST events such as repository pushes
6 The :ref:`creating-integrations` integration allows you to POST events such as
7 or pull requests to a custom http endpoint as a json dict with details of the
7 repository pushes or pull requests to a custom http endpoint as a JSON dict
8 event.
8 with details of the event.
9
9
10 Starting from 4.5.0 release, webhook integration allows to use variables
10 Starting from 4.5.0 release, webhook integration allows to use variables
11 inside the URL. For example in URL `https://server-example.com/${repo_name}`
11 inside the URL. For example in URL `https://server-example.com/${repo_name}`
@@ -14,8 +14,10 b' triggered from. Some of the variables li'
14 `${branch}` will result in webhook be called multiple times when multiple
14 `${branch}` will result in webhook be called multiple times when multiple
15 branches are pushed.
15 branches are pushed.
16
16
17 Some of the variables like `${pull_request_id}` will be replaced only in
17 Starting from 4.8.0 also repository extra fields can be used. A format to use
18 the pull request related events.
18 them is `${extra:field_key}`. It's usefull to use them to specify custom
19 repo only parameters. Some of the variables like `${pull_request_id}`
20 will be replaced only in the pull request related events.
19
21
20 To create a webhook integration, select "webhook" in the integration settings
22 To create a webhook integration, select "webhook" in the integration settings
21 and use the URL and key from your any previous custom webhook created. See
23 and use the URL and key from your any previous custom webhook created. See
@@ -9,6 +9,7 b' Release Notes'
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12 release-notes-4.8.0.rst
12 release-notes-4.7.2.rst
13 release-notes-4.7.2.rst
13 release-notes-4.7.1.rst
14 release-notes-4.7.1.rst
14 release-notes-4.7.0.rst
15 release-notes-4.7.0.rst
@@ -3,8 +3,13 b''
3 Getting Started with VCS
3 Getting Started with VCS
4 ------------------------
4 ------------------------
5
5
6 When using |RCM|, you will be working with |git| or |hg| |repos| from the
6 When using |RCM|, you will be working with |git|, |svn| or |hg| |repos| from the
7 command line.
7 command line or using a GUI client such as Tortoise, Tower or SourceTree.
8
9 |RCM| uses a standard |git|, |svn| and |hg| protocols. So all tools that
10 can interact with there protocols are supported, including Eclipse or PyCharm
11 plugins.
12
8
13
9 If you have never used either before, the following information should
14 If you have never used either before, the following information should
10 help you set up your local machine so that you can sync changes with the
15 help you set up your local machine so that you can sync changes with the
@@ -7,7 +7,7 b' buildEnv { name = "bower-env"; ignoreCol'
7 (fetchbower "paper-tooltip" "PolymerElements/paper-tooltip#1.1.3" "PolymerElements/paper-tooltip#^1.1.2" "0vmrm1n8k9sk9nvqy03q177axy22pia6i3j1gxbk72j3pqiqvg6k")
7 (fetchbower "paper-tooltip" "PolymerElements/paper-tooltip#1.1.3" "PolymerElements/paper-tooltip#^1.1.2" "0vmrm1n8k9sk9nvqy03q177axy22pia6i3j1gxbk72j3pqiqvg6k")
8 (fetchbower "paper-toast" "PolymerElements/paper-toast#1.3.0" "PolymerElements/paper-toast#^1.3.0" "0x9rqxsks5455s8pk4aikpp99ijdn6kxr9gvhwh99nbcqdzcxq1m")
8 (fetchbower "paper-toast" "PolymerElements/paper-toast#1.3.0" "PolymerElements/paper-toast#^1.3.0" "0x9rqxsks5455s8pk4aikpp99ijdn6kxr9gvhwh99nbcqdzcxq1m")
9 (fetchbower "paper-toggle-button" "PolymerElements/paper-toggle-button#1.2.0" "PolymerElements/paper-toggle-button#^1.2.0" "0mphcng3ngspbpg4jjn0mb91nvr4xc1phq3qswib15h6sfww1b2w")
9 (fetchbower "paper-toggle-button" "PolymerElements/paper-toggle-button#1.2.0" "PolymerElements/paper-toggle-button#^1.2.0" "0mphcng3ngspbpg4jjn0mb91nvr4xc1phq3qswib15h6sfww1b2w")
10 (fetchbower "iron-ajax" "PolymerElements/iron-ajax#1.4.3" "PolymerElements/iron-ajax#^1.4.3" "0m3dx27arwmlcp00b7n516sc5a51f40p9vapr1nvd57l3i3z0pzm")
10 (fetchbower "iron-ajax" "PolymerElements/iron-ajax#1.4.3" "PolymerElements/iron-ajax#^1.4.3" "1b1z3112ggjdflgrwbpmnbsh3kgcm4hn255wshvrlzds4w069gja")
11 (fetchbower "iron-autogrow-textarea" "PolymerElements/iron-autogrow-textarea#1.0.13" "PolymerElements/iron-autogrow-textarea#^1.0.13" "0zwhpl97vii1s8k0lgain8i9dnw29b0mxc5ixdscx9las13n2lqq")
11 (fetchbower "iron-autogrow-textarea" "PolymerElements/iron-autogrow-textarea#1.0.13" "PolymerElements/iron-autogrow-textarea#^1.0.13" "0zwhpl97vii1s8k0lgain8i9dnw29b0mxc5ixdscx9las13n2lqq")
12 (fetchbower "iron-a11y-keys" "PolymerElements/iron-a11y-keys#1.0.6" "PolymerElements/iron-a11y-keys#^1.0.6" "1xz3mgghfcxixq28sdb654iaxj4nyi1bzcwf77ydkms6fviqs9mv")
12 (fetchbower "iron-a11y-keys" "PolymerElements/iron-a11y-keys#1.0.6" "PolymerElements/iron-a11y-keys#^1.0.6" "1xz3mgghfcxixq28sdb654iaxj4nyi1bzcwf77ydkms6fviqs9mv")
13 (fetchbower "iron-flex-layout" "PolymerElements/iron-flex-layout#1.3.1" "PolymerElements/iron-flex-layout#^1.0.0" "0nswv3ih3bhflgcd2wjfmddqswzgqxb2xbq65jk9w3rkj26hplbl")
13 (fetchbower "iron-flex-layout" "PolymerElements/iron-flex-layout#1.3.1" "PolymerElements/iron-flex-layout#^1.0.0" "0nswv3ih3bhflgcd2wjfmddqswzgqxb2xbq65jk9w3rkj26hplbl")
@@ -601,13 +601,13 b''
601 };
601 };
602 };
602 };
603 deform = super.buildPythonPackage {
603 deform = super.buildPythonPackage {
604 name = "deform-2.0a2";
604 name = "deform-2.0.4";
605 buildInputs = with self; [];
605 buildInputs = with self; [];
606 doCheck = false;
606 doCheck = false;
607 propagatedBuildInputs = with self; [Chameleon colander peppercorn translationstring zope.deprecation];
607 propagatedBuildInputs = with self; [Chameleon colander iso8601 peppercorn translationstring zope.deprecation];
608 src = fetchurl {
608 src = fetchurl {
609 url = "https://pypi.python.org/packages/8d/b3/aab57e81da974a806dc9c5fa024a6404720f890a6dcf2e80885e3cb4609a/deform-2.0a2.tar.gz";
609 url = "https://pypi.python.org/packages/66/3b/eefcb07abcab7a97f6665aa2d0cf1af741d9d6e78a2e4657fd2b89f89880/deform-2.0.4.tar.gz";
610 md5 = "7a90d41f7fbc18002ce74f39bd90a5e4";
610 md5 = "34756e42cf50dd4b4430809116c4ec0a";
611 };
611 };
612 meta = {
612 meta = {
613 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
613 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
@@ -887,13 +887,13 b''
887 };
887 };
888 };
888 };
889 ipython-genutils = super.buildPythonPackage {
889 ipython-genutils = super.buildPythonPackage {
890 name = "ipython-genutils-0.1.0";
890 name = "ipython-genutils-0.2.0";
891 buildInputs = with self; [];
891 buildInputs = with self; [];
892 doCheck = false;
892 doCheck = false;
893 propagatedBuildInputs = with self; [];
893 propagatedBuildInputs = with self; [];
894 src = fetchurl {
894 src = fetchurl {
895 url = "https://pypi.python.org/packages/71/b7/a64c71578521606edbbce15151358598f3dfb72a3431763edc2baf19e71f/ipython_genutils-0.1.0.tar.gz";
895 url = "https://pypi.python.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
896 md5 = "9a8afbe0978adbcbfcb3b35b2d015a56";
896 md5 = "5a4f9781f78466da0ea1a648f3e1f79f";
897 };
897 };
898 meta = {
898 meta = {
899 license = [ pkgs.lib.licenses.bsdOriginal ];
899 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -1004,13 +1004,13 b''
1004 };
1004 };
1005 };
1005 };
1006 mistune = super.buildPythonPackage {
1006 mistune = super.buildPythonPackage {
1007 name = "mistune-0.7.3";
1007 name = "mistune-0.7.4";
1008 buildInputs = with self; [];
1008 buildInputs = with self; [];
1009 doCheck = false;
1009 doCheck = false;
1010 propagatedBuildInputs = with self; [];
1010 propagatedBuildInputs = with self; [];
1011 src = fetchurl {
1011 src = fetchurl {
1012 url = "https://pypi.python.org/packages/88/1e/be99791262b3a794332fda598a07c2749a433b9378586361ba9d8e824607/mistune-0.7.3.tar.gz";
1012 url = "https://pypi.python.org/packages/25/a4/12a584c0c59c9fed529f8b3c47ca8217c0cf8bcc5e1089d3256410cfbdbc/mistune-0.7.4.tar.gz";
1013 md5 = "4eba50bd121b83716fa4be6a4049004b";
1013 md5 = "92d01cb717e9e74429e9bde9d29ac43b";
1014 };
1014 };
1015 meta = {
1015 meta = {
1016 license = [ pkgs.lib.licenses.bsdOriginal ];
1016 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -1082,13 +1082,13 b''
1082 };
1082 };
1083 };
1083 };
1084 objgraph = super.buildPythonPackage {
1084 objgraph = super.buildPythonPackage {
1085 name = "objgraph-2.0.0";
1085 name = "objgraph-3.1.0";
1086 buildInputs = with self; [];
1086 buildInputs = with self; [];
1087 doCheck = false;
1087 doCheck = false;
1088 propagatedBuildInputs = with self; [];
1088 propagatedBuildInputs = with self; [];
1089 src = fetchurl {
1089 src = fetchurl {
1090 url = "https://pypi.python.org/packages/d7/33/ace750b59247496ed769b170586c5def7202683f3d98e737b75b767ff29e/objgraph-2.0.0.tar.gz";
1090 url = "https://pypi.python.org/packages/f4/b3/082e54e62094cb2ec84f8d5a49e0142cef99016491cecba83309cff920ae/objgraph-3.1.0.tar.gz";
1091 md5 = "25b0d5e5adc74aa63ead15699614159c";
1091 md5 = "eddbd96039796bfbd13eee403701e64a";
1092 };
1092 };
1093 meta = {
1093 meta = {
1094 license = [ pkgs.lib.licenses.mit ];
1094 license = [ pkgs.lib.licenses.mit ];
@@ -1186,13 +1186,13 b''
1186 };
1186 };
1187 };
1187 };
1188 prompt-toolkit = super.buildPythonPackage {
1188 prompt-toolkit = super.buildPythonPackage {
1189 name = "prompt-toolkit-1.0.13";
1189 name = "prompt-toolkit-1.0.14";
1190 buildInputs = with self; [];
1190 buildInputs = with self; [];
1191 doCheck = false;
1191 doCheck = false;
1192 propagatedBuildInputs = with self; [six wcwidth];
1192 propagatedBuildInputs = with self; [six wcwidth];
1193 src = fetchurl {
1193 src = fetchurl {
1194 url = "https://pypi.python.org/packages/23/be/4876b52d5cc159cbd4b0ff6e7aa419a26470849a43a8f647857a4a24467b/prompt_toolkit-1.0.13.tar.gz";
1194 url = "https://pypi.python.org/packages/55/56/8c39509b614bda53e638b7500f12577d663ac1b868aef53426fc6a26c3f5/prompt_toolkit-1.0.14.tar.gz";
1195 md5 = "427b496d2c147bd3819bc3a7f6e0d493";
1195 md5 = "f24061ae133ed32c6b764e92bd48c496";
1196 };
1196 };
1197 meta = {
1197 meta = {
1198 license = [ pkgs.lib.licenses.bsdOriginal ];
1198 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -1641,7 +1641,7 b''
1641 };
1641 };
1642 };
1642 };
1643 rhodecode-enterprise-ce = super.buildPythonPackage {
1643 rhodecode-enterprise-ce = super.buildPythonPackage {
1644 name = "rhodecode-enterprise-ce-4.7.2";
1644 name = "rhodecode-enterprise-ce-4.8.0";
1645 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
1645 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
1646 doCheck = true;
1646 doCheck = true;
1647 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1647 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
@@ -16,7 +16,7 b' colander==1.2'
16 configobj==5.0.6
16 configobj==5.0.6
17 cssselect==1.0.1
17 cssselect==1.0.1
18 decorator==4.0.11
18 decorator==4.0.11
19 deform==2.0a2
19 deform==2.0.4
20 docutils==0.12
20 docutils==0.12
21 dogpile.cache==0.6.1
21 dogpile.cache==0.6.1
22 dogpile.core==0.4.1
22 dogpile.core==0.4.1
@@ -38,7 +38,7 b' meld3==1.0.2'
38 msgpack-python==0.4.8
38 msgpack-python==0.4.8
39 MySQL-python==1.2.5
39 MySQL-python==1.2.5
40 nose==1.3.6
40 nose==1.3.6
41 objgraph==2.0.0
41 objgraph==3.1.0
42 packaging==15.2
42 packaging==15.2
43 paramiko==1.15.1
43 paramiko==1.15.1
44 Paste==2.0.3
44 Paste==2.0.3
@@ -1,1 +1,1 b''
1 4.7.2 No newline at end of file
1 4.8.0 No newline at end of file
@@ -51,7 +51,7 b' PYRAMID_SETTINGS = {}'
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 71 # defines current db version for migrations
54 __dbversion__ = 78 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
@@ -35,8 +35,9 b' from pyramid.httpexceptions import HTTPN'
35
35
36 from rhodecode.api.exc import (
36 from rhodecode.api.exc import (
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.lib.plugins.utils import get_plugin_settings
@@ -278,6 +279,11 b' def request_view(request):'
278 'request': request,
279 'request': request,
279 'apiuser': auth_u
280 'apiuser': auth_u
280 })
281 })
282
283 # register some common functions for usage
284 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id,
285 attach_to_request=True)
286
281 try:
287 try:
282 ret_value = func(**call_params)
288 ret_value = func(**call_params)
283 return jsonrpc_response(request, ret_value)
289 return jsonrpc_response(request, ret_value)
@@ -43,16 +43,16 b' class TestClosePullRequest(object):'
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44 expected = {
44 expected = {
45 'pull_request_id': pull_request_id,
45 'pull_request_id': pull_request_id,
46 'close_status': 'Rejected',
46 'closed': True,
47 'closed': True,
47 }
48 }
48 assert_ok(id_, expected, response.body)
49 assert_ok(id_, expected, response.body)
49 action = 'user_closed_pull_request:%d' % pull_request_id
50 journal = UserLog.query()\
50 journal = UserLog.query()\
51 .filter(UserLog.user_id == author)\
51 .filter(UserLog.user_id == author) \
52 .order_by('user_log_id') \
52 .filter(UserLog.repository_id == repo)\
53 .filter(UserLog.repository_id == repo)\
53 .filter(UserLog.action == action)\
54 .all()
54 .all()
55 assert len(journal) == 1
55 assert journal[-1].action == 'repo.pull_request.close'
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
@@ -62,13 +62,12 b' class TestCommentPullRequest(object):'
62 }
62 }
63 assert_ok(id_, expected, response.body)
63 assert_ok(id_, expected, response.body)
64
64
65 action = 'user_commented_pull_request:%d' % pull_request_id
66 journal = UserLog.query()\
65 journal = UserLog.query()\
67 .filter(UserLog.user_id == author)\
66 .filter(UserLog.user_id == author)\
68 .filter(UserLog.repository_id == repo)\
67 .filter(UserLog.repository_id == repo) \
69 .filter(UserLog.action == action)\
68 .order_by('user_log_id') \
70 .all()
69 .all()
71 assert len(journal) == 2
70 assert journal[-1].action == 'repo.pull_request.comment.create'
72
71
73 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
74 def test_api_comment_pull_request_change_status(
73 def test_api_comment_pull_request_change_status(
@@ -77,7 +77,7 b' class TestCreatePullRequestApi(object):'
77 assert pull_request.source_repo.repo_name == data['source_repo']
77 assert pull_request.source_repo.repo_name == data['source_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
79 assert pull_request.revisions == [self.commit_ids['change']]
79 assert pull_request.revisions == [self.commit_ids['change']]
80 assert pull_request.reviewers == []
80 assert len(pull_request.reviewers) == 1
81
81
82 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
83 def test_create_with_empty_description(self, backend):
83 def test_create_with_empty_description(self, backend):
@@ -98,7 +98,12 b' class TestCreatePullRequestApi(object):'
98 def test_create_with_reviewers_specified_by_names(
98 def test_create_with_reviewers_specified_by_names(
99 self, backend, no_notifications):
99 self, backend, no_notifications):
100 data = self._prepare_data(backend)
100 data = self._prepare_data(backend)
101 reviewers = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
101 reviewers = [
102 {'username': TEST_USER_REGULAR_LOGIN,
103 'reasons': ['added manually']},
104 {'username': TEST_USER_ADMIN_LOGIN,
105 'reasons': ['added manually']},
106 ]
102 data['reviewers'] = reviewers
107 data['reviewers'] = reviewers
103 id_, params = build_data(
108 id_, params = build_data(
104 self.apikey_regular, 'create_pull_request', **data)
109 self.apikey_regular, 'create_pull_request', **data)
@@ -110,16 +115,26 b' class TestCreatePullRequestApi(object):'
110 assert result['result']['msg'] == expected_message
115 assert result['result']['msg'] == expected_message
111 pull_request_id = result['result']['pull_request_id']
116 pull_request_id = result['result']['pull_request_id']
112 pull_request = PullRequestModel().get(pull_request_id)
117 pull_request = PullRequestModel().get(pull_request_id)
113 actual_reviewers = [r.user.username for r in pull_request.reviewers]
118 actual_reviewers = [
119 {'username': r.user.username,
120 'reasons': ['added manually'],
121 } for r in pull_request.reviewers
122 ]
114 assert sorted(actual_reviewers) == sorted(reviewers)
123 assert sorted(actual_reviewers) == sorted(reviewers)
115
124
116 @pytest.mark.backends("git", "hg")
125 @pytest.mark.backends("git", "hg")
117 def test_create_with_reviewers_specified_by_ids(
126 def test_create_with_reviewers_specified_by_ids(
118 self, backend, no_notifications):
127 self, backend, no_notifications):
119 data = self._prepare_data(backend)
128 data = self._prepare_data(backend)
120 reviewer_names = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
121 reviewers = [
129 reviewers = [
122 UserModel().get_by_username(n).user_id for n in reviewer_names]
130 {'username': UserModel().get_by_username(
131 TEST_USER_REGULAR_LOGIN).user_id,
132 'reasons': ['added manually']},
133 {'username': UserModel().get_by_username(
134 TEST_USER_ADMIN_LOGIN).user_id,
135 'reasons': ['added manually']},
136 ]
137
123 data['reviewers'] = reviewers
138 data['reviewers'] = reviewers
124 id_, params = build_data(
139 id_, params = build_data(
125 self.apikey_regular, 'create_pull_request', **data)
140 self.apikey_regular, 'create_pull_request', **data)
@@ -131,14 +146,17 b' class TestCreatePullRequestApi(object):'
131 assert result['result']['msg'] == expected_message
146 assert result['result']['msg'] == expected_message
132 pull_request_id = result['result']['pull_request_id']
147 pull_request_id = result['result']['pull_request_id']
133 pull_request = PullRequestModel().get(pull_request_id)
148 pull_request = PullRequestModel().get(pull_request_id)
134 actual_reviewers = [r.user.username for r in pull_request.reviewers]
149 actual_reviewers = [
135 assert sorted(actual_reviewers) == sorted(reviewer_names)
150 {'username': r.user.user_id,
151 'reasons': ['added manually'],
152 } for r in pull_request.reviewers
153 ]
154 assert sorted(actual_reviewers) == sorted(reviewers)
136
155
137 @pytest.mark.backends("git", "hg")
156 @pytest.mark.backends("git", "hg")
138 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
157 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
139 data = self._prepare_data(backend)
158 data = self._prepare_data(backend)
140 reviewers = ['somebody']
159 data['reviewers'] = [{'username': 'somebody'}]
141 data['reviewers'] = reviewers
142 id_, params = build_data(
160 id_, params = build_data(
143 self.apikey_regular, 'create_pull_request', **data)
161 self.apikey_regular, 'create_pull_request', **data)
144 response = api_call(self.app, params)
162 response = api_call(self.app, params)
@@ -153,7 +171,7 b' class TestCreatePullRequestApi(object):'
153 id_, params = build_data(
171 id_, params = build_data(
154 self.apikey_regular, 'create_pull_request', **data)
172 self.apikey_regular, 'create_pull_request', **data)
155 response = api_call(self.app, params)
173 response = api_call(self.app, params)
156 expected_message = 'reviewers should be specified as a list'
174 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
157 assert_error(id_, expected_message, given=response.body)
175 assert_error(id_, expected_message, given=response.body)
158
176
159 @pytest.mark.backends("git", "hg")
177 @pytest.mark.backends("git", "hg")
@@ -59,6 +59,21 b' class TestCreateUser(object):'
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 assert_error(id_, expected, given=response.body)
60 assert_error(id_, expected, given=response.body)
61
61
62 def test_api_create_user_with_wrong_username(self):
63 bad_username = '<> HELLO WORLD <>'
64 id_, params = build_data(
65 self.apikey, 'create_user',
66 username=bad_username,
67 email='new@email.com',
68 password='trololo')
69 response = api_call(self.app, params)
70
71 expected = {'username':
72 "Username may only contain alphanumeric characters "
73 "underscores, periods or dashes and must begin with "
74 "alphanumeric character or underscore"}
75 assert_error(id_, expected, given=response.body)
76
62 def test_api_create_user(self):
77 def test_api_create_user(self):
63 username = 'test_new_api_user'
78 username = 'test_new_api_user'
64 email = username + "@foo.com"
79 email = username + "@foo.com"
@@ -175,7 +190,6 b' class TestCreateUser(object):'
175 fixture.destroy_repo_group(username)
190 fixture.destroy_repo_group(username)
176 fixture.destroy_user(usr.user_id)
191 fixture.destroy_user(usr.user_id)
177
192
178
179 @mock.patch.object(UserModel, 'create_or_update', crash)
193 @mock.patch.object(UserModel, 'create_or_update', crash)
180 def test_api_create_user_when_exception_happened(self):
194 def test_api_create_user_when_exception_happened(self):
181
195
@@ -112,3 +112,16 b' class TestCreateUserGroup(object):'
112
112
113 expected = 'failed to create group `%s`' % (group_name,)
113 expected = 'failed to create group `%s`' % (group_name,)
114 assert_error(id_, expected, given=response.body)
114 assert_error(id_, expected, given=response.body)
115
116 def test_api_create_user_group_with_wrong_name(self, user_util):
117
118 group_name = 'wrong NAME <>'
119 id_, params = build_data(
120 self.apikey, 'create_user_group', group_name=group_name)
121 response = api_call(self.app, params)
122
123 expected = {"user_group_name":
124 "Allowed in name are letters, numbers, and `-`, `_`, "
125 "`.` Name must start with a letter or number. "
126 "Got `{}`".format(group_name)}
127 assert_error(id_, expected, given=response.body)
@@ -30,43 +30,45 b' from rhodecode.api.tests.utils import ('
30 class TestApiDeleteRepo(object):
30 class TestApiDeleteRepo(object):
31 def test_api_delete_repo(self, backend):
31 def test_api_delete_repo(self, backend):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33
33 repo_name = repo.repo_name
34 id_, params = build_data(
34 id_, params = build_data(
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37
37
38 expected = {
38 expected = {
39 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
39 'msg': 'Deleted repository `%s`' % (repo_name,),
40 'success': True
40 'success': True
41 }
41 }
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 repo = backend.create_repo(cur_user=user_regular.username)
45 repo = backend.create_repo(cur_user=user_regular.username)
46 repo_name = repo.repo_name
46 id_, params = build_data(
47 id_, params = build_data(
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 response = api_call(self.app, params)
49 response = api_call(self.app, params)
49
50
50 expected = {
51 expected = {
51 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
52 'msg': 'Deleted repository `%s`' % (repo_name,),
52 'success': True
53 'success': True
53 }
54 }
54 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
55
56
56 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 repo = backend.create_repo()
58 repo = backend.create_repo()
59 repo_name = repo.repo_name
58 id_, params = build_data(
60 id_, params = build_data(
59 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
61 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
60 response = api_call(self.app, params)
62 response = api_call(self.app, params)
61 expected = 'repository `%s` does not exist' % (repo.repo_name)
63 expected = 'repository `%s` does not exist' % (repo_name)
62 assert_error(id_, expected, given=response.body)
64 assert_error(id_, expected, given=response.body)
63
65
64 def test_api_delete_repo_exception_occurred(self, backend):
66 def test_api_delete_repo_exception_occurred(self, backend):
65 repo = backend.create_repo()
67 repo = backend.create_repo()
68 repo_name = repo.repo_name
66 id_, params = build_data(
69 id_, params = build_data(
67 self.apikey, 'delete_repo', repoid=repo.repo_name, )
70 self.apikey, 'delete_repo', repoid=repo.repo_name, )
68 with mock.patch.object(RepoModel, 'delete', crash):
71 with mock.patch.object(RepoModel, 'delete', crash):
69 response = api_call(self.app, params)
72 response = api_call(self.app, params)
70 expected = 'failed to delete repository `%s`' % (
73 expected = 'failed to delete repository `%s`' % (repo_name,)
71 repo.repo_name,)
72 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
@@ -28,7 +28,7 b' from rhodecode.api.tests.utils import ('
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gist(self, gist_util):
31 def test_api_get_gist(self, gist_util, http_host_stub):
32 gist = gist_util.create_gist()
32 gist = gist_util.create_gist()
33 gist_id = gist.gist_access_id
33 gist_id = gist.gist_access_id
34 gist_created_on = gist.created_on
34 gist_created_on = gist.created_on
@@ -45,14 +45,14 b' class TestApiGetGist(object):'
45 'expires': -1.0,
45 'expires': -1.0,
46 'gist_id': int(gist_id),
46 'gist_id': int(gist_id),
47 'type': 'public',
47 'type': 'public',
48 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
48 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 'content': None,
50 'content': None,
51 }
51 }
52
52
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_get_gist_with_content(self, gist_util):
55 def test_api_get_gist_with_content(self, gist_util, http_host_stub):
56 mapping = {
56 mapping = {
57 u'filename1.txt': {'content': u'hello world'},
57 u'filename1.txt': {'content': u'hello world'},
58 u'filename1ą.txt': {'content': u'hello worldę'}
58 u'filename1ą.txt': {'content': u'hello worldę'}
@@ -73,7 +73,7 b' class TestApiGetGist(object):'
73 'expires': -1.0,
73 'expires': -1.0,
74 'gist_id': int(gist_id),
74 'gist_id': int(gist_id),
75 'type': 'public',
75 'type': 'public',
76 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
76 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 'content': {
78 'content': {
79 u'filename1.txt': u'hello world',
79 u'filename1.txt': u'hello world',
@@ -19,13 +19,13 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
23 import pytest
22 import pytest
24 import urlobject
23 import urlobject
25 from pylons import url
24 from pylons import url
26
25
27 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28 from rhodecode.lib.utils2 import safe_unicode
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
@@ -33,7 +33,7 b' pytestmark = pytest.mark.backends("git",'
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequest(object):
34 class TestGetPullRequest(object):
35
35
36 def test_api_get_pull_request(self, pr_util):
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38 pull_request = pr_util.create_pull_request(mergeable=True)
38 pull_request = pr_util.create_pull_request(mergeable=True)
39 id_, params = build_data(
39 id_, params = build_data(
@@ -50,16 +50,16 b' class TestGetPullRequest(object):'
50 'pullrequest_show',
50 'pullrequest_show',
51 repo_name=pull_request.target_repo.repo_name,
51 repo_name=pull_request.target_repo.repo_name,
52 pull_request_id=pull_request.pull_request_id, qualified=True))
52 pull_request_id=pull_request.pull_request_id, qualified=True))
53 pr_url = unicode(
53
54 url_obj.with_netloc('test.example.com:80'))
54 pr_url = safe_unicode(
55 source_url = unicode(
55 url_obj.with_netloc(http_host_only_stub))
56 pull_request.source_repo.clone_url()
56 source_url = safe_unicode(
57 .with_netloc('test.example.com:80'))
57 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
58 target_url = unicode(
58 target_url = safe_unicode(
59 pull_request.target_repo.clone_url()
59 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
60 .with_netloc('test.example.com:80'))
60 shadow_url = safe_unicode(
61 shadow_url = unicode(
62 PullRequestModel().get_shadow_clone_url(pull_request))
61 PullRequestModel().get_shadow_clone_url(pull_request))
62
63 expected = {
63 expected = {
64 'pull_request_id': pull_request.pull_request_id,
64 'pull_request_id': pull_request.pull_request_id,
65 'url': pr_url,
65 'url': pr_url,
@@ -109,7 +109,8 b' class TestGetPullRequest(object):'
109 'reasons': reasons,
109 'reasons': reasons,
110 'review_status': st[0][1].status if st else 'not_reviewed',
110 'review_status': st[0][1].status if st else 'not_reviewed',
111 }
111 }
112 for reviewer, reasons, st in pull_request.reviewers_statuses()
112 for reviewer, reasons, mandatory, st in
113 pull_request.reviewers_statuses()
113 ]
114 ]
114 }
115 }
115 assert_ok(id_, expected, response.body)
116 assert_ok(id_, expected, response.body)
@@ -95,13 +95,13 b' class TestMergePullRequest(object):'
95
95
96 assert_ok(id_, expected, response.body)
96 assert_ok(id_, expected, response.body)
97
97
98 action = 'user_merged_pull_request:%d' % (pull_request_id, )
99 journal = UserLog.query()\
98 journal = UserLog.query()\
100 .filter(UserLog.user_id == author)\
99 .filter(UserLog.user_id == author)\
101 .filter(UserLog.repository_id == repo)\
100 .filter(UserLog.repository_id == repo) \
102 .filter(UserLog.action == action)\
101 .order_by('user_log_id') \
103 .all()
102 .all()
104 assert len(journal) == 1
103 assert journal[-2].action == 'repo.pull_request.merge'
104 assert journal[-1].action == 'repo.pull_request.close'
105
105
106 id_, params = build_data(
106 id_, params = build_data(
107 self.apikey, 'merge_pull_request',
107 self.apikey, 'merge_pull_request',
@@ -33,7 +33,7 b' class TestUpdatePullRequest(object):'
33
33
34 @pytest.mark.backends("git", "hg")
34 @pytest.mark.backends("git", "hg")
35 def test_api_update_pull_request_title_or_description(
35 def test_api_update_pull_request_title_or_description(
36 self, pr_util, silence_action_logger, no_notifications):
36 self, pr_util, no_notifications):
37 pull_request = pr_util.create_pull_request()
37 pull_request = pr_util.create_pull_request()
38
38
39 id_, params = build_data(
39 id_, params = build_data(
@@ -61,7 +61,7 b' class TestUpdatePullRequest(object):'
61
61
62 @pytest.mark.backends("git", "hg")
62 @pytest.mark.backends("git", "hg")
63 def test_api_try_update_closed_pull_request(
63 def test_api_try_update_closed_pull_request(
64 self, pr_util, silence_action_logger, no_notifications):
64 self, pr_util, no_notifications):
65 pull_request = pr_util.create_pull_request()
65 pull_request = pr_util.create_pull_request()
66 PullRequestModel().close_pull_request(
66 PullRequestModel().close_pull_request(
67 pull_request, TEST_USER_ADMIN_LOGIN)
67 pull_request, TEST_USER_ADMIN_LOGIN)
@@ -78,8 +78,7 b' class TestUpdatePullRequest(object):'
78 assert_error(id_, expected, response.body)
78 assert_error(id_, expected, response.body)
79
79
80 @pytest.mark.backends("git", "hg")
80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(
81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 self, pr_util, silence_action_logger, no_notifications):
83 commits = [
82 commits = [
84 {'message': 'a'},
83 {'message': 'a'},
85 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
@@ -119,20 +118,28 b' class TestUpdatePullRequest(object):'
119
118
120 @pytest.mark.backends("git", "hg")
119 @pytest.mark.backends("git", "hg")
121 def test_api_update_change_reviewers(
120 def test_api_update_change_reviewers(
122 self, pr_util, silence_action_logger, no_notifications):
121 self, user_util, pr_util, no_notifications):
122 a = user_util.create_user()
123 b = user_util.create_user()
124 c = user_util.create_user()
125 new_reviewers = [
126 {'username': b.username,'reasons': ['updated via API'],
127 'mandatory':False},
128 {'username': c.username, 'reasons': ['updated via API'],
129 'mandatory':False},
130 ]
123
131
124 users = [x.username for x in User.get_all()]
132 added = [b.username, c.username]
125 new = [users.pop(0)]
133 removed = [a.username]
126 removed = sorted(new)
127 added = sorted(users)
128
134
129 pull_request = pr_util.create_pull_request(reviewers=new)
135 pull_request = pr_util.create_pull_request(
136 reviewers=[(a.username, ['added via API'], False)])
130
137
131 id_, params = build_data(
138 id_, params = build_data(
132 self.apikey, 'update_pull_request',
139 self.apikey, 'update_pull_request',
133 repoid=pull_request.target_repo.repo_name,
140 repoid=pull_request.target_repo.repo_name,
134 pullrequestid=pull_request.pull_request_id,
141 pullrequestid=pull_request.pull_request_id,
135 reviewers=added)
142 reviewers=new_reviewers)
136 response = api_call(self.app, params)
143 response = api_call(self.app, params)
137 expected = {
144 expected = {
138 "msg": "Updated pull request `{}`".format(
145 "msg": "Updated pull request `{}`".format(
@@ -152,7 +159,7 b' class TestUpdatePullRequest(object):'
152 self.apikey, 'update_pull_request',
159 self.apikey, 'update_pull_request',
153 repoid=pull_request.target_repo.repo_name,
160 repoid=pull_request.target_repo.repo_name,
154 pullrequestid=pull_request.pull_request_id,
161 pullrequestid=pull_request.pull_request_id,
155 reviewers=['bad_name'])
162 reviewers=[{'username': 'bad_name'}])
156 response = api_call(self.app, params)
163 response = api_call(self.app, params)
157
164
158 expected = 'user `bad_name` does not exist'
165 expected = 'user `bad_name` does not exist'
@@ -165,7 +172,7 b' class TestUpdatePullRequest(object):'
165 self.apikey, 'update_pull_request',
172 self.apikey, 'update_pull_request',
166 repoid='fake',
173 repoid='fake',
167 pullrequestid='fake',
174 pullrequestid='fake',
168 reviewers=['bad_name'])
175 reviewers=[{'username': 'bad_name'}])
169 response = api_call(self.app, params)
176 response = api_call(self.app, params)
170
177
171 expected = 'repository `fake` does not exist'
178 expected = 'repository `fake` does not exist'
@@ -181,7 +188,7 b' class TestUpdatePullRequest(object):'
181 self.apikey, 'update_pull_request',
188 self.apikey, 'update_pull_request',
182 repoid=pull_request.target_repo.repo_name,
189 repoid=pull_request.target_repo.repo_name,
183 pullrequestid=999999,
190 pullrequestid=999999,
184 reviewers=['bad_name'])
191 reviewers=[{'username': 'bad_name'}])
185 response = api_call(self.app, params)
192 response = api_call(self.app, params)
186
193
187 expected = 'pull request `999999` does not exist'
194 expected = 'pull request `999999` does not exist'
@@ -26,7 +26,7 b' from rhodecode.tests import TEST_USER_AD'
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29 from rhodecode.tests.plugin import http_host_stub, http_host_only_stub
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
@@ -71,14 +71,15 b' class TestApiUpdateRepo(object):'
71 ({'repo_name': 'new_repo_name'},
71 ({'repo_name': 'new_repo_name'},
72 {
72 {
73 'repo_name': 'new_repo_name',
73 'repo_name': 'new_repo_name',
74 'url': 'http://test.example.com:80/new_repo_name'
74 'url': 'http://{}/new_repo_name'.format(http_host_only_stub())
75 }),
75 }),
76
76
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
78 '_group': 'test_group_for_update'},
78 '_group': 'test_group_for_update'},
79 {
79 {
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
81 'url': 'http://test.example.com:80/test_group_for_update/{}'.format(UPDATE_REPO_NAME)
81 'url': 'http://{}/test_group_for_update/{}'.format(
82 http_host_only_stub(), UPDATE_REPO_NAME)
82 }),
83 }),
83 ])
84 ])
84 def test_api_update_repo(self, updates, expected, backend):
85 def test_api_update_repo(self, updates, expected, backend):
@@ -115,7 +116,8 b' class TestApiUpdateRepo(object):'
115 master_repo = backend.create_repo()
116 master_repo = backend.create_repo()
116 repo = backend.create_repo()
117 repo = backend.create_repo()
117 updates = {
118 updates = {
118 'fork_of': master_repo.repo_name
119 'fork_of': master_repo.repo_name,
120 'fork_of_id': master_repo.repo_id
119 }
121 }
120 expected_api_data = repo.get_api_data(include_secrets=True)
122 expected_api_data = repo.get_api_data(include_secrets=True)
121 expected_api_data.update(updates)
123 expected_api_data.update(updates)
@@ -130,6 +132,7 b' class TestApiUpdateRepo(object):'
130 assert_ok(id_, expected, given=response.body)
132 assert_ok(id_, expected, given=response.body)
131 result = response.json['result']['repository']
133 result = response.json['result']['repository']
132 assert result['fork_of'] == master_repo.repo_name
134 assert result['fork_of'] == master_repo.repo_name
135 assert result['fork_of_id'] == master_repo.repo_id
133
136
134 def test_api_update_repo_fork_of_not_found(self, backend):
137 def test_api_update_repo_fork_of_not_found(self, backend):
135 master_repo_name = 'fake-parent-repo'
138 master_repo_name = 'fake-parent-repo'
@@ -21,7 +21,8 b''
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError
24 from rhodecode import events
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
25 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
@@ -34,6 +35,9 b' from rhodecode.model.comment import Comm'
34 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 ReviewerListSchema)
37
41
38 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
39
43
@@ -224,8 +228,9 b' def get_pull_requests(request, apiuser, '
224
228
225
229
226 @jsonrpc_method()
230 @jsonrpc_method()
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
231 def merge_pull_request(
228 userid=Optional(OAttr('apiuser'))):
232 request, apiuser, repoid, pullrequestid,
233 userid=Optional(OAttr('apiuser'))):
229 """
234 """
230 Merge the pull request specified by `pullrequestid` into its target
235 Merge the pull request specified by `pullrequestid` into its target
231 repository.
236 repository.
@@ -273,7 +278,12 b' def merge_pull_request(request, apiuser,'
273 merge_possible = not check.failed
278 merge_possible = not check.failed
274
279
275 if not merge_possible:
280 if not merge_possible:
276 reasons = ','.join([msg for _e, msg in check.errors])
281 error_messages = []
282 for err_type, error_msg in check.errors:
283 error_msg = request.translate(error_msg)
284 error_messages.append(error_msg)
285
286 reasons = ','.join(error_messages)
277 raise JSONRPCError(
287 raise JSONRPCError(
278 'merge not possible for following reasons: {}'.format(reasons))
288 'merge not possible for following reasons: {}'.format(reasons))
279
289
@@ -300,63 +310,6 b' def merge_pull_request(request, apiuser,'
300
310
301
311
302 @jsonrpc_method()
312 @jsonrpc_method()
303 def close_pull_request(request, apiuser, repoid, pullrequestid,
304 userid=Optional(OAttr('apiuser'))):
305 """
306 Close the pull request specified by `pullrequestid`.
307
308 :param apiuser: This is filled automatically from the |authtoken|.
309 :type apiuser: AuthUser
310 :param repoid: Repository name or repository ID to which the pull
311 request belongs.
312 :type repoid: str or int
313 :param pullrequestid: ID of the pull request to be closed.
314 :type pullrequestid: int
315 :param userid: Close the pull request as this user.
316 :type userid: Optional(str or int)
317
318 Example output:
319
320 .. code-block:: bash
321
322 "id": <id_given_in_input>,
323 "result": {
324 "pull_request_id": "<int>",
325 "closed": "<bool>"
326 },
327 "error": null
328
329 """
330 repo = get_repo_or_error(repoid)
331 if not isinstance(userid, Optional):
332 if (has_superadmin_permission(apiuser) or
333 HasRepoPermissionAnyApi('repository.admin')(
334 user=apiuser, repo_name=repo.repo_name)):
335 apiuser = get_user_or_error(userid)
336 else:
337 raise JSONRPCError('userid is not the same as your user')
338
339 pull_request = get_pull_request_or_error(pullrequestid)
340 if not PullRequestModel().check_user_update(
341 pull_request, apiuser, api=True):
342 raise JSONRPCError(
343 'pull request `%s` close failed, no permission to close.' % (
344 pullrequestid,))
345 if pull_request.is_closed():
346 raise JSONRPCError(
347 'pull request `%s` is already closed' % (pullrequestid,))
348
349 PullRequestModel().close_pull_request(
350 pull_request.pull_request_id, apiuser)
351 Session().commit()
352 data = {
353 'pull_request_id': pull_request.pull_request_id,
354 'closed': True,
355 }
356 return data
357
358
359 @jsonrpc_method()
360 def comment_pull_request(
313 def comment_pull_request(
361 request, apiuser, repoid, pullrequestid, message=Optional(None),
314 request, apiuser, repoid, pullrequestid, message=Optional(None),
362 commit_id=Optional(None), status=Optional(None),
315 commit_id=Optional(None), status=Optional(None),
@@ -529,24 +482,26 b' def create_pull_request('
529 :param description: Set the pull request description.
482 :param description: Set the pull request description.
530 :type description: Optional(str)
483 :type description: Optional(str)
531 :param reviewers: Set the new pull request reviewers list.
484 :param reviewers: Set the new pull request reviewers list.
485 Reviewer defined by review rules will be added automatically to the
486 defined list.
532 :type reviewers: Optional(list)
487 :type reviewers: Optional(list)
533 Accepts username strings or objects of the format:
488 Accepts username strings or objects of the format:
534
489
535 {'username': 'nick', 'reasons': ['original author']}
490 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
536 """
491 """
537
492
538 source = get_repo_or_error(source_repo)
493 source_db_repo = get_repo_or_error(source_repo)
539 target = get_repo_or_error(target_repo)
494 target_db_repo = get_repo_or_error(target_repo)
540 if not has_superadmin_permission(apiuser):
495 if not has_superadmin_permission(apiuser):
541 _perms = ('repository.admin', 'repository.write', 'repository.read',)
496 _perms = ('repository.admin', 'repository.write', 'repository.read',)
542 validate_repo_permissions(apiuser, source_repo, source, _perms)
497 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
543
498
544 full_source_ref = resolve_ref_or_error(source_ref, source)
499 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
545 full_target_ref = resolve_ref_or_error(target_ref, target)
500 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
546 source_commit = get_commit_or_error(full_source_ref, source)
501 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
547 target_commit = get_commit_or_error(full_target_ref, target)
502 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
548 source_scm = source.scm_instance()
503 source_scm = source_db_repo.scm_instance()
549 target_scm = target.scm_instance()
504 target_scm = target_db_repo.scm_instance()
550
505
551 commit_ranges = target_scm.compare(
506 commit_ranges = target_scm.compare(
552 target_commit.raw_id, source_commit.raw_id, source_scm,
507 target_commit.raw_id, source_commit.raw_id, source_scm,
@@ -562,20 +517,36 b' def create_pull_request('
562 raise JSONRPCError('no common ancestor found')
517 raise JSONRPCError('no common ancestor found')
563
518
564 reviewer_objects = Optional.extract(reviewers) or []
519 reviewer_objects = Optional.extract(reviewers) or []
565 if not isinstance(reviewer_objects, list):
520
566 raise JSONRPCError('reviewers should be specified as a list')
521 if reviewer_objects:
522 schema = ReviewerListSchema()
523 try:
524 reviewer_objects = schema.deserialize(reviewer_objects)
525 except Invalid as err:
526 raise JSONRPCValidationError(colander_exc=err)
527
528 # validate users
529 for reviewer_object in reviewer_objects:
530 user = get_user_or_error(reviewer_object['username'])
531 reviewer_object['user_id'] = user.user_id
567
532
568 reviewers_reasons = []
533 get_default_reviewers_data, get_validated_reviewers = \
569 for reviewer_object in reviewer_objects:
534 PullRequestModel().get_reviewer_functions()
570 reviewer_reasons = []
535
571 if isinstance(reviewer_object, (basestring, int)):
536 reviewer_rules = get_default_reviewers_data(
572 reviewer_username = reviewer_object
537 apiuser.get_instance(), source_db_repo,
573 else:
538 source_commit, target_db_repo, target_commit)
574 reviewer_username = reviewer_object['username']
575 reviewer_reasons = reviewer_object.get('reasons', [])
576
539
577 user = get_user_or_error(reviewer_username)
540 # specified rules are later re-validated, thus we can assume users will
578 reviewers_reasons.append((user.user_id, reviewer_reasons))
541 # eventually provide those that meet the reviewer criteria.
542 if not reviewer_objects:
543 reviewer_objects = reviewer_rules['reviewers']
544
545 try:
546 reviewers = get_validated_reviewers(
547 reviewer_objects, reviewer_rules)
548 except ValueError as e:
549 raise JSONRPCError('Reviewers Validation: {}'.format(e))
579
550
580 pull_request_model = PullRequestModel()
551 pull_request_model = PullRequestModel()
581 pull_request = pull_request_model.create(
552 pull_request = pull_request_model.create(
@@ -586,7 +557,7 b' def create_pull_request('
586 target_ref=full_target_ref,
557 target_ref=full_target_ref,
587 revisions=reversed(
558 revisions=reversed(
588 [commit.raw_id for commit in reversed(commit_ranges)]),
559 [commit.raw_id for commit in reversed(commit_ranges)]),
589 reviewers=reviewers_reasons,
560 reviewers=reviewers,
590 title=title,
561 title=title,
591 description=Optional.extract(description)
562 description=Optional.extract(description)
592 )
563 )
@@ -603,7 +574,7 b' def create_pull_request('
603 def update_pull_request(
574 def update_pull_request(
604 request, apiuser, repoid, pullrequestid, title=Optional(''),
575 request, apiuser, repoid, pullrequestid, title=Optional(''),
605 description=Optional(''), reviewers=Optional(None),
576 description=Optional(''), reviewers=Optional(None),
606 update_commits=Optional(None), close_pull_request=Optional(None)):
577 update_commits=Optional(None)):
607 """
578 """
608 Updates a pull request.
579 Updates a pull request.
609
580
@@ -619,10 +590,12 b' def update_pull_request('
619 :type description: Optional(str)
590 :type description: Optional(str)
620 :param reviewers: Update pull request reviewers list with new value.
591 :param reviewers: Update pull request reviewers list with new value.
621 :type reviewers: Optional(list)
592 :type reviewers: Optional(list)
593 Accepts username strings or objects of the format:
594
595 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
596
622 :param update_commits: Trigger update of commits for this pull request
597 :param update_commits: Trigger update of commits for this pull request
623 :type: update_commits: Optional(bool)
598 :type: update_commits: Optional(bool)
624 :param close_pull_request: Close this pull request with rejected state
625 :type: close_pull_request: Optional(bool)
626
599
627 Example output:
600 Example output:
628
601
@@ -665,29 +638,38 b' def update_pull_request('
665 pullrequestid,))
638 pullrequestid,))
666
639
667 reviewer_objects = Optional.extract(reviewers) or []
640 reviewer_objects = Optional.extract(reviewers) or []
668 if not isinstance(reviewer_objects, list):
641
669 raise JSONRPCError('reviewers should be specified as a list')
642 if reviewer_objects:
643 schema = ReviewerListSchema()
644 try:
645 reviewer_objects = schema.deserialize(reviewer_objects)
646 except Invalid as err:
647 raise JSONRPCValidationError(colander_exc=err)
648
649 # validate users
650 for reviewer_object in reviewer_objects:
651 user = get_user_or_error(reviewer_object['username'])
652 reviewer_object['user_id'] = user.user_id
670
653
671 reviewers_reasons = []
654 get_default_reviewers_data, get_validated_reviewers = \
672 reviewer_ids = set()
655 PullRequestModel().get_reviewer_functions()
673 for reviewer_object in reviewer_objects:
674 reviewer_reasons = []
675 if isinstance(reviewer_object, (int, basestring)):
676 reviewer_username = reviewer_object
677 else:
678 reviewer_username = reviewer_object['username']
679 reviewer_reasons = reviewer_object.get('reasons', [])
680
656
681 user = get_user_or_error(reviewer_username)
657 # re-use stored rules
682 reviewer_ids.add(user.user_id)
658 reviewer_rules = pull_request.reviewer_data
683 reviewers_reasons.append((user.user_id, reviewer_reasons))
659 try:
660 reviewers = get_validated_reviewers(
661 reviewer_objects, reviewer_rules)
662 except ValueError as e:
663 raise JSONRPCError('Reviewers Validation: {}'.format(e))
664 else:
665 reviewers = []
684
666
685 title = Optional.extract(title)
667 title = Optional.extract(title)
686 description = Optional.extract(description)
668 description = Optional.extract(description)
687 if title or description:
669 if title or description:
688 PullRequestModel().edit(
670 PullRequestModel().edit(
689 pull_request, title or pull_request.title,
671 pull_request, title or pull_request.title,
690 description or pull_request.description)
672 description or pull_request.description, apiuser)
691 Session().commit()
673 Session().commit()
692
674
693 commit_changes = {"added": [], "common": [], "removed": []}
675 commit_changes = {"added": [], "common": [], "removed": []}
@@ -699,9 +681,9 b' def update_pull_request('
699 Session().commit()
681 Session().commit()
700
682
701 reviewers_changes = {"added": [], "removed": []}
683 reviewers_changes = {"added": [], "removed": []}
702 if reviewer_ids:
684 if reviewers:
703 added_reviewers, removed_reviewers = \
685 added_reviewers, removed_reviewers = \
704 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
686 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
705
687
706 reviewers_changes['added'] = sorted(
688 reviewers_changes['added'] = sorted(
707 [get_user_or_error(n).username for n in added_reviewers])
689 [get_user_or_error(n).username for n in added_reviewers])
@@ -709,11 +691,6 b' def update_pull_request('
709 [get_user_or_error(n).username for n in removed_reviewers])
691 [get_user_or_error(n).username for n in removed_reviewers])
710 Session().commit()
692 Session().commit()
711
693
712 if str2bool(Optional.extract(close_pull_request)):
713 PullRequestModel().close_pull_request_with_comment(
714 pull_request, apiuser, repo)
715 Session().commit()
716
717 data = {
694 data = {
718 'msg': 'Updated pull request `{}`'.format(
695 'msg': 'Updated pull request `{}`'.format(
719 pull_request.pull_request_id),
696 pull_request.pull_request_id),
@@ -723,3 +700,80 b' def update_pull_request('
723 }
700 }
724
701
725 return data
702 return data
703
704
705 @jsonrpc_method()
706 def close_pull_request(
707 request, apiuser, repoid, pullrequestid,
708 userid=Optional(OAttr('apiuser')), message=Optional('')):
709 """
710 Close the pull request specified by `pullrequestid`.
711
712 :param apiuser: This is filled automatically from the |authtoken|.
713 :type apiuser: AuthUser
714 :param repoid: Repository name or repository ID to which the pull
715 request belongs.
716 :type repoid: str or int
717 :param pullrequestid: ID of the pull request to be closed.
718 :type pullrequestid: int
719 :param userid: Close the pull request as this user.
720 :type userid: Optional(str or int)
721 :param message: Optional message to close the Pull Request with. If not
722 specified it will be generated automatically.
723 :type message: Optional(str)
724
725 Example output:
726
727 .. code-block:: bash
728
729 "id": <id_given_in_input>,
730 "result": {
731 "pull_request_id": "<int>",
732 "close_status": "<str:status_lbl>,
733 "closed": "<bool>"
734 },
735 "error": null
736
737 """
738 _ = request.translate
739
740 repo = get_repo_or_error(repoid)
741 if not isinstance(userid, Optional):
742 if (has_superadmin_permission(apiuser) or
743 HasRepoPermissionAnyApi('repository.admin')(
744 user=apiuser, repo_name=repo.repo_name)):
745 apiuser = get_user_or_error(userid)
746 else:
747 raise JSONRPCError('userid is not the same as your user')
748
749 pull_request = get_pull_request_or_error(pullrequestid)
750
751 if pull_request.is_closed():
752 raise JSONRPCError(
753 'pull request `%s` is already closed' % (pullrequestid,))
754
755 # only owner or admin or person with write permissions
756 allowed_to_close = PullRequestModel().check_user_update(
757 pull_request, apiuser, api=True)
758
759 if not allowed_to_close:
760 raise JSONRPCError(
761 'pull request `%s` close failed, no permission to close.' % (
762 pullrequestid,))
763
764 # message we're using to close the PR, else it's automatically generated
765 message = Optional.extract(message)
766
767 # finally close the PR, with proper message comment
768 comment, status = PullRequestModel().close_pull_request_with_comment(
769 pull_request, apiuser, repo, message=message)
770 status_lbl = ChangesetStatus.get_status_lbl(status)
771
772 Session().commit()
773
774 data = {
775 'pull_request_id': pull_request.pull_request_id,
776 'close_status': status_lbl,
777 'closed': True,
778 }
779 return data
@@ -29,6 +29,8 b' from rhodecode.api.utils import ('
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib import repo_maintenance
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
@@ -915,12 +917,13 b' def update_repo('
915
917
916 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
917
919
920 old_values = repo.get_api_data()
918 schema = repo_schema.RepoSchema().bind(
921 schema = repo_schema.RepoSchema().bind(
919 repo_type_options=rhodecode.BACKENDS.keys(),
922 repo_type_options=rhodecode.BACKENDS.keys(),
920 repo_ref_options=ref_choices,
923 repo_ref_options=ref_choices,
921 # user caller
924 # user caller
922 user=apiuser,
925 user=apiuser,
923 old_values=repo.get_api_data())
926 old_values=old_values)
924 try:
927 try:
925 schema_data = schema.deserialize(dict(
928 schema_data = schema.deserialize(dict(
926 # we save old value, users cannot change type
929 # we save old value, users cannot change type
@@ -965,6 +968,9 b' def update_repo('
965
968
966 try:
969 try:
967 RepoModel().update(repo, **validated_updates)
970 RepoModel().update(repo, **validated_updates)
971 audit_logger.store_api(
972 'repo.edit', action_data={'old_data': old_values},
973 user=apiuser, repo=repo)
968 Session().commit()
974 Session().commit()
969 return {
975 return {
970 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
976 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
@@ -1153,6 +1159,7 b' def delete_repo(request, apiuser, repoid'
1153 """
1159 """
1154
1160
1155 repo = get_repo_or_error(repoid)
1161 repo = get_repo_or_error(repoid)
1162 repo_name = repo.repo_name
1156 if not has_superadmin_permission(apiuser):
1163 if not has_superadmin_permission(apiuser):
1157 _perms = ('repository.admin',)
1164 _perms = ('repository.admin',)
1158 validate_repo_permissions(apiuser, repoid, repo, _perms)
1165 validate_repo_permissions(apiuser, repoid, repo, _perms)
@@ -1170,18 +1177,26 b' def delete_repo(request, apiuser, repoid'
1170 'Cannot delete `%s` it still contains attached forks' %
1177 'Cannot delete `%s` it still contains attached forks' %
1171 (repo.repo_name,)
1178 (repo.repo_name,)
1172 )
1179 )
1180 old_data = repo.get_api_data()
1181 RepoModel().delete(repo, forks=forks)
1173
1182
1174 RepoModel().delete(repo, forks=forks)
1183 repo = audit_logger.RepoWrap(repo_id=None,
1184 repo_name=repo.repo_name)
1185
1186 audit_logger.store_api(
1187 'repo.delete', action_data={'old_data': old_data},
1188 user=apiuser, repo=repo)
1189
1190 ScmModel().mark_for_invalidation(repo_name, delete=True)
1175 Session().commit()
1191 Session().commit()
1176 return {
1192 return {
1177 'msg': 'Deleted repository `%s`%s' % (
1193 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1178 repo.repo_name, _forks_msg),
1179 'success': True
1194 'success': True
1180 }
1195 }
1181 except Exception:
1196 except Exception:
1182 log.exception("Exception occurred while trying to delete repo")
1197 log.exception("Exception occurred while trying to delete repo")
1183 raise JSONRPCError(
1198 raise JSONRPCError(
1184 'failed to delete repository `%s`' % (repo.repo_name,)
1199 'failed to delete repository `%s`' % (repo_name,)
1185 )
1200 )
1186
1201
1187
1202
@@ -1460,7 +1475,7 b' def comment_commit('
1460 rc_config = SettingsModel().get_all_settings()
1475 rc_config = SettingsModel().get_all_settings()
1461 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1476 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1462 status_change_label = ChangesetStatus.get_status_lbl(status)
1477 status_change_label = ChangesetStatus.get_status_lbl(status)
1463 comm = CommentsModel().create(
1478 comment = CommentsModel().create(
1464 message, repo, user, commit_id=commit_id,
1479 message, repo, user, commit_id=commit_id,
1465 status_change=status_change_label,
1480 status_change=status_change_label,
1466 status_change_type=status,
1481 status_change_type=status,
@@ -1472,7 +1487,7 b' def comment_commit('
1472 # also do a status change
1487 # also do a status change
1473 try:
1488 try:
1474 ChangesetStatusModel().set_status(
1489 ChangesetStatusModel().set_status(
1475 repo, status, user, comm, revision=commit_id,
1490 repo, status, user, comment, revision=commit_id,
1476 dont_allow_on_closed_pull_request=True
1491 dont_allow_on_closed_pull_request=True
1477 )
1492 )
1478 except StatusChangeOnClosedPullRequestError:
1493 except StatusChangeOnClosedPullRequestError:
@@ -1486,7 +1501,7 b' def comment_commit('
1486 return {
1501 return {
1487 'msg': (
1502 'msg': (
1488 'Commented on commit `%s` for repository `%s`' % (
1503 'Commented on commit `%s` for repository `%s`' % (
1489 comm.revision, repo.repo_name)),
1504 comment.revision, repo.repo_name)),
1490 'status_change': status,
1505 'status_change': status,
1491 'success': True,
1506 'success': True,
1492 }
1507 }
@@ -1867,6 +1882,11 b' def strip(request, apiuser, repoid, revi'
1867
1882
1868 try:
1883 try:
1869 ScmModel().strip(repo, revision, branch)
1884 ScmModel().strip(repo, revision, branch)
1885 audit_logger.store_api(
1886 'repo.commit.strip', action_data={'commit_id': revision},
1887 repo=repo,
1888 user=apiuser, commit=True)
1889
1870 return {
1890 return {
1871 'msg': 'Stripped commit %s from repo `%s`' % (
1891 'msg': 'Stripped commit %s from repo `%s`' % (
1872 revision, repo.repo_name),
1892 revision, repo.repo_name),
@@ -1902,6 +1922,7 b' def get_repo_settings(request, apiuser, '
1902 "id": 237,
1922 "id": 237,
1903 "result": {
1923 "result": {
1904 "extensions_largefiles": true,
1924 "extensions_largefiles": true,
1925 "extensions_evolve": true,
1905 "hooks_changegroup_push_logger": true,
1926 "hooks_changegroup_push_logger": true,
1906 "hooks_changegroup_repo_size": false,
1927 "hooks_changegroup_repo_size": false,
1907 "hooks_outgoing_pull_logger": true,
1928 "hooks_outgoing_pull_logger": true,
@@ -1985,3 +2006,65 b' def set_repo_settings(request, apiuser, '
1985
2006
1986 # Indicate success.
2007 # Indicate success.
1987 return True
2008 return True
2009
2010
2011 @jsonrpc_method()
2012 def maintenance(request, apiuser, repoid):
2013 """
2014 Triggers a maintenance on the given repository.
2015
2016 This command can only be run using an |authtoken| with admin
2017 rights to the specified repository. For more information,
2018 see :ref:`config-token-ref`.
2019
2020 This command takes the following options:
2021
2022 :param apiuser: This is filled automatically from the |authtoken|.
2023 :type apiuser: AuthUser
2024 :param repoid: The repository name or repository ID.
2025 :type repoid: str or int
2026
2027 Example output:
2028
2029 .. code-block:: bash
2030
2031 id : <id_given_in_input>
2032 result : {
2033 "msg": "executed maintenance command",
2034 "executed_actions": [
2035 <action_message>, <action_message2>...
2036 ],
2037 "repository": "<repository name>"
2038 }
2039 error : null
2040
2041 Example error output:
2042
2043 .. code-block:: bash
2044
2045 id : <id_given_in_input>
2046 result : null
2047 error : {
2048 "Unable to execute maintenance on `<reponame>`"
2049 }
2050
2051 """
2052
2053 repo = get_repo_or_error(repoid)
2054 if not has_superadmin_permission(apiuser):
2055 _perms = ('repository.admin',)
2056 validate_repo_permissions(apiuser, repoid, repo, _perms)
2057
2058 try:
2059 maintenance = repo_maintenance.RepoMaintenance()
2060 executed_actions = maintenance.execute(repo)
2061
2062 return {
2063 'msg': 'executed maintenance command',
2064 'executed_actions': executed_actions,
2065 'repository': repo.repo_name
2066 }
2067 except Exception:
2068 log.exception("Exception occurred while trying to run maintenance")
2069 raise JSONRPCError(
2070 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -27,6 +27,7 b' from rhodecode.api.utils import ('
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
31 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 from rhodecode.model.db import Session
33 from rhodecode.model.db import Session
@@ -222,6 +223,13 b' def create_repo_group('
222 group_name=validated_group_name,
223 group_name=validated_group_name,
223 group_description=schema_data['repo_group_name'],
224 group_description=schema_data['repo_group_name'],
224 copy_permissions=schema_data['repo_group_copy_permissions'])
225 copy_permissions=schema_data['repo_group_copy_permissions'])
226 Session().flush()
227
228 repo_group_data = repo_group.get_api_data()
229 audit_logger.store_api(
230 'repo_group.create', action_data={'data': repo_group_data},
231 user=apiuser)
232
225 Session().commit()
233 Session().commit()
226 return {
234 return {
227 'msg': 'Created new repo group `%s`' % validated_group_name,
235 'msg': 'Created new repo group `%s`' % validated_group_name,
@@ -310,8 +318,13 b' def update_repo_group('
310 enable_locking=schema_data['repo_group_enable_locking'],
318 enable_locking=schema_data['repo_group_enable_locking'],
311 )
319 )
312
320
321 old_data = repo_group.get_api_data()
313 try:
322 try:
314 RepoGroupModel().update(repo_group, validated_updates)
323 RepoGroupModel().update(repo_group, validated_updates)
324 audit_logger.store_api(
325 'repo_group.edit', action_data={'old_data': old_data},
326 user=apiuser)
327
315 Session().commit()
328 Session().commit()
316 return {
329 return {
317 'msg': 'updated repository group ID:%s %s' % (
330 'msg': 'updated repository group ID:%s %s' % (
@@ -365,8 +378,12 b' def delete_repo_group(request, apiuser, '
365 validate_repo_group_permissions(
378 validate_repo_group_permissions(
366 apiuser, repogroupid, repo_group, ('group.admin',))
379 apiuser, repogroupid, repo_group, ('group.admin',))
367
380
381 old_data = repo_group.get_api_data()
368 try:
382 try:
369 RepoGroupModel().delete(repo_group)
383 RepoGroupModel().delete(repo_group)
384 audit_logger.store_api(
385 'repo_group.delete', action_data={'old_data': old_data},
386 user=apiuser)
370 Session().commit()
387 Session().commit()
371 return {
388 return {
372 'msg': 'deleted repo group ID:%s %s' %
389 'msg': 'deleted repo group ID:%s %s' %
@@ -20,14 +20,18 b''
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
24 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
26 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
27 from rhodecode.lib import audit_logger
26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
28 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 from rhodecode.lib.exceptions import DefaultUserException
29 from rhodecode.lib.exceptions import DefaultUserException
28 from rhodecode.lib.utils2 import safe_int, str2bool
30 from rhodecode.lib.utils2 import safe_int, str2bool
29 from rhodecode.model.db import Session, User, Repository
31 from rhodecode.model.db import Session, User, Repository
30 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.model import validation_schema
34 from rhodecode.model.validation_schema.schemas import user_schema
31
35
32 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
33
37
@@ -237,20 +241,54 b' def create_user(request, apiuser, userna'
237 if isinstance(create_repo_group, basestring):
241 if isinstance(create_repo_group, basestring):
238 create_repo_group = str2bool(create_repo_group)
242 create_repo_group = str2bool(create_repo_group)
239
243
244 username = Optional.extract(username)
245 password = Optional.extract(password)
246 email = Optional.extract(email)
247 first_name = Optional.extract(firstname)
248 last_name = Optional.extract(lastname)
249 active = Optional.extract(active)
250 admin = Optional.extract(admin)
251 extern_type = Optional.extract(extern_type)
252 extern_name = Optional.extract(extern_name)
253
254 schema = user_schema.UserSchema().bind(
255 # user caller
256 user=apiuser)
257 try:
258 schema_data = schema.deserialize(dict(
259 username=username,
260 email=email,
261 password=password,
262 first_name=first_name,
263 last_name=last_name,
264 active=active,
265 admin=admin,
266 extern_type=extern_type,
267 extern_name=extern_name,
268 ))
269 except validation_schema.Invalid as err:
270 raise JSONRPCValidationError(colander_exc=err)
271
240 try:
272 try:
241 user = UserModel().create_or_update(
273 user = UserModel().create_or_update(
242 username=Optional.extract(username),
274 username=schema_data['username'],
243 password=Optional.extract(password),
275 password=schema_data['password'],
244 email=Optional.extract(email),
276 email=schema_data['email'],
245 firstname=Optional.extract(firstname),
277 firstname=schema_data['first_name'],
246 lastname=Optional.extract(lastname),
278 lastname=schema_data['last_name'],
247 active=Optional.extract(active),
279 active=schema_data['active'],
248 admin=Optional.extract(admin),
280 admin=schema_data['admin'],
249 extern_type=Optional.extract(extern_type),
281 extern_type=schema_data['extern_type'],
250 extern_name=Optional.extract(extern_name),
282 extern_name=schema_data['extern_name'],
251 force_password_change=Optional.extract(force_password_change),
283 force_password_change=Optional.extract(force_password_change),
252 create_repo_group=create_repo_group
284 create_repo_group=create_repo_group
253 )
285 )
286 Session().flush()
287 creation_data = user.get_api_data()
288 audit_logger.store_api(
289 'user.create', action_data={'data': creation_data},
290 user=apiuser)
291
254 Session().commit()
292 Session().commit()
255 return {
293 return {
256 'msg': 'created new user `%s`' % username,
294 'msg': 'created new user `%s`' % username,
@@ -326,7 +364,7 b' def update_user(request, apiuser, userid'
326 raise JSONRPCForbidden()
364 raise JSONRPCForbidden()
327
365
328 user = get_user_or_error(userid)
366 user = get_user_or_error(userid)
329
367 old_data = user.get_api_data()
330 # only non optional arguments will be stored in updates
368 # only non optional arguments will be stored in updates
331 updates = {}
369 updates = {}
332
370
@@ -343,6 +381,9 b' def update_user(request, apiuser, userid'
343 store_update(updates, extern_type, 'extern_type')
381 store_update(updates, extern_type, 'extern_type')
344
382
345 user = UserModel().update_user(user, **updates)
383 user = UserModel().update_user(user, **updates)
384 audit_logger.store_api(
385 'user.edit', action_data={'old_data': old_data},
386 user=apiuser)
346 Session().commit()
387 Session().commit()
347 return {
388 return {
348 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
389 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
@@ -405,9 +446,13 b' def delete_user(request, apiuser, userid'
405 raise JSONRPCForbidden()
446 raise JSONRPCForbidden()
406
447
407 user = get_user_or_error(userid)
448 user = get_user_or_error(userid)
408
449 old_data = user.get_api_data()
409 try:
450 try:
410 UserModel().delete(userid)
451 UserModel().delete(userid)
452 audit_logger.store_api(
453 'user.delete', action_data={'old_data': old_data},
454 user=apiuser)
455
411 Session().commit()
456 Session().commit()
412 return {
457 return {
413 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
458 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
@@ -20,15 +20,19 b''
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
24 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
25 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 get_user_or_error, get_user_group_or_error, get_perm_or_error)
27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
28 from rhodecode.lib import audit_logger
27 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
28 from rhodecode.lib.exceptions import UserGroupAssignedException
30 from rhodecode.lib.exceptions import UserGroupAssignedException
29 from rhodecode.model.db import Session
31 from rhodecode.model.db import Session
30 from rhodecode.model.scm import UserGroupList
32 from rhodecode.model.scm import UserGroupList
31 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.user_group import UserGroupModel
34 from rhodecode.model import validation_schema
35 from rhodecode.model.validation_schema.schemas import user_group_schema
32
36
33 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
34
38
@@ -210,20 +214,41 b' def create_user_group('
210 if UserGroupModel().get_by_name(group_name):
214 if UserGroupModel().get_by_name(group_name):
211 raise JSONRPCError("user group `%s` already exist" % (group_name,))
215 raise JSONRPCError("user group `%s` already exist" % (group_name,))
212
216
213 try:
217 if isinstance(owner, Optional):
214 if isinstance(owner, Optional):
218 owner = apiuser.user_id
215 owner = apiuser.user_id
219
220 owner = get_user_or_error(owner)
221 active = Optional.extract(active)
222 description = Optional.extract(description)
216
223
217 owner = get_user_or_error(owner)
224 schema = user_group_schema.UserGroupSchema().bind(
218 active = Optional.extract(active)
225 # user caller
219 description = Optional.extract(description)
226 user=apiuser)
220 ug = UserGroupModel().create(
227 try:
221 name=group_name, description=description, owner=owner,
228 schema_data = schema.deserialize(dict(
222 active=active)
229 user_group_name=group_name,
230 user_group_description=description,
231 user_group_owner=owner.username,
232 user_group_active=active,
233 ))
234 except validation_schema.Invalid as err:
235 raise JSONRPCValidationError(colander_exc=err)
236
237 try:
238 user_group = UserGroupModel().create(
239 name=schema_data['user_group_name'],
240 description=schema_data['user_group_description'],
241 owner=owner,
242 active=schema_data['user_group_active'])
243 Session().flush()
244 creation_data = user_group.get_api_data()
245 audit_logger.store_api(
246 'user_group.create', action_data={'data': creation_data},
247 user=apiuser)
223 Session().commit()
248 Session().commit()
224 return {
249 return {
225 'msg': 'created new user group `%s`' % group_name,
250 'msg': 'created new user group `%s`' % group_name,
226 'user_group': ug.get_api_data()
251 'user_group': creation_data
227 }
252 }
228 except Exception:
253 except Exception:
229 log.exception("Error occurred during creation of user group")
254 log.exception("Error occurred during creation of user group")
@@ -291,6 +316,7 b' def update_user_group(request, apiuser, '
291 if not isinstance(owner, Optional):
316 if not isinstance(owner, Optional):
292 owner = get_user_or_error(owner)
317 owner = get_user_or_error(owner)
293
318
319 old_data = user_group.get_api_data()
294 updates = {}
320 updates = {}
295 store_update(updates, group_name, 'users_group_name')
321 store_update(updates, group_name, 'users_group_name')
296 store_update(updates, description, 'user_group_description')
322 store_update(updates, description, 'user_group_description')
@@ -298,6 +324,9 b' def update_user_group(request, apiuser, '
298 store_update(updates, active, 'users_group_active')
324 store_update(updates, active, 'users_group_active')
299 try:
325 try:
300 UserGroupModel().update(user_group, updates)
326 UserGroupModel().update(user_group, updates)
327 audit_logger.store_api(
328 'user_group.edit', action_data={'old_data': old_data},
329 user=apiuser)
301 Session().commit()
330 Session().commit()
302 return {
331 return {
303 'msg': 'updated user group ID:%s %s' % (
332 'msg': 'updated user group ID:%s %s' % (
@@ -359,8 +388,12 b' def delete_user_group(request, apiuser, '
359 raise JSONRPCError(
388 raise JSONRPCError(
360 'user group `%s` does not exist' % (usergroupid,))
389 'user group `%s` does not exist' % (usergroupid,))
361
390
391 old_data = user_group.get_api_data()
362 try:
392 try:
363 UserGroupModel().delete(user_group)
393 UserGroupModel().delete(user_group)
394 audit_logger.store_api(
395 'user_group.delete', action_data={'old_data': old_data},
396 user=apiuser)
364 Session().commit()
397 Session().commit()
365 return {
398 return {
366 'msg': 'deleted user group ID:%s %s' % (
399 'msg': 'deleted user group ID:%s %s' % (
@@ -438,6 +471,12 b' def add_user_to_user_group(request, apiu'
438 user.username, user_group.users_group_name
471 user.username, user_group.users_group_name
439 )
472 )
440 msg = msg if success else 'User is already in that group'
473 msg = msg if success else 'User is already in that group'
474 if success:
475 user_data = user.get_api_data()
476 audit_logger.store_api(
477 'user_group.edit.member.add', action_data={'user': user_data},
478 user=apiuser)
479
441 Session().commit()
480 Session().commit()
442
481
443 return {
482 return {
@@ -501,6 +540,12 b' def remove_user_from_user_group(request,'
501 user.username, user_group.users_group_name
540 user.username, user_group.users_group_name
502 )
541 )
503 msg = msg if success else "User wasn't in group"
542 msg = msg if success else "User wasn't in group"
543 if success:
544 user_data = user.get_api_data()
545 audit_logger.store_api(
546 'user_group.edit.member.delete', action_data={'user': user_data},
547 user=apiuser)
548
504 Session().commit()
549 Session().commit()
505 return {'success': success, 'msg': msg}
550 return {'success': success, 'msg': msg}
506 except Exception:
551 except Exception:
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -24,8 +24,12 b' from pylons import tmpl_context as c'
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int
27 from rhodecode.lib.utils import PartialRenderer
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.ext_json import json
28 from rhodecode.model import repo
31 from rhodecode.model import repo
32 from rhodecode.model import repo_group
29 from rhodecode.model.db import User
33 from rhodecode.model.db import User
30 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
31
35
@@ -36,6 +40,30 b" ADMIN_PREFIX = '/_admin'"
36 STATIC_FILE_PREFIX = '/_static'
40 STATIC_FILE_PREFIX = '/_static'
37
41
38
42
43 def add_route_with_slash(config,name, pattern, **kw):
44 config.add_route(name, pattern, **kw)
45 if not pattern.endswith('/'):
46 config.add_route(name + '_slash', pattern + '/', **kw)
47
48
49 def get_format_ref_id(repo):
50 """Returns a `repo` specific reference formatter function"""
51 if h.is_svn(repo):
52 return _format_ref_id_svn
53 else:
54 return _format_ref_id
55
56
57 def _format_ref_id(name, raw_id):
58 """Default formatting of a given reference `name`"""
59 return name
60
61
62 def _format_ref_id_svn(name, raw_id):
63 """Special way of formatting a reference for Subversion including path"""
64 return '%s@%s' % (name, raw_id)
65
66
39 class TemplateArgs(StrictAttributeDict):
67 class TemplateArgs(StrictAttributeDict):
40 pass
68 pass
41
69
@@ -77,9 +105,14 b' class BaseAppView(object):'
77 raise HTTPFound(
105 raise HTTPFound(
78 self.request.route_path('my_account_password'))
106 self.request.route_path('my_account_password'))
79
107
80 def _get_local_tmpl_context(self):
108 def _get_local_tmpl_context(self, include_app_defaults=False):
81 c = TemplateArgs()
109 c = TemplateArgs()
82 c.auth_user = self.request.user
110 c.auth_user = self.request.user
111 if include_app_defaults:
112 # NOTE(marcink): after full pyramid migration include_app_defaults
113 # should be turned on by default
114 from rhodecode.lib.base import attach_context_attributes
115 attach_context_attributes(c, self.request, self.request.user.user_id)
83 return c
116 return c
84
117
85 def _register_global_c(self, tmpl_args):
118 def _register_global_c(self, tmpl_args):
@@ -121,15 +154,106 b' class RepoAppView(BaseAppView):'
121 self.db_repo_name = self.db_repo.repo_name
154 self.db_repo_name = self.db_repo.repo_name
122 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
155 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
123
156
124 def _get_local_tmpl_context(self):
157 def _handle_missing_requirements(self, error):
125 c = super(RepoAppView, self)._get_local_tmpl_context()
158 log.error(
159 'Requirements are missing for repository %s: %s',
160 self.db_repo_name, error.message)
161
162 def _get_local_tmpl_context(self, include_app_defaults=False):
163 c = super(RepoAppView, self)._get_local_tmpl_context(
164 include_app_defaults=include_app_defaults)
165
126 # register common vars for this type of view
166 # register common vars for this type of view
127 c.rhodecode_db_repo = self.db_repo
167 c.rhodecode_db_repo = self.db_repo
128 c.repo_name = self.db_repo_name
168 c.repo_name = self.db_repo_name
129 c.repository_pull_requests = self.db_repo_pull_requests
169 c.repository_pull_requests = self.db_repo_pull_requests
170
171 c.repository_requirements_missing = False
172 try:
173 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
174 except RepositoryRequirementError as e:
175 c.repository_requirements_missing = True
176 self._handle_missing_requirements(e)
177
130 return c
178 return c
131
179
132
180
181 class DataGridAppView(object):
182 """
183 Common class to have re-usable grid rendering components
184 """
185
186 def _extract_ordering(self, request, column_map=None):
187 column_map = column_map or {}
188 column_index = safe_int(request.GET.get('order[0][column]'))
189 order_dir = request.GET.get(
190 'order[0][dir]', 'desc')
191 order_by = request.GET.get(
192 'columns[%s][data][sort]' % column_index, 'name_raw')
193
194 # translate datatable to DB columns
195 order_by = column_map.get(order_by) or order_by
196
197 search_q = request.GET.get('search[value]')
198 return search_q, order_by, order_dir
199
200 def _extract_chunk(self, request):
201 start = safe_int(request.GET.get('start'), 0)
202 length = safe_int(request.GET.get('length'), 25)
203 draw = safe_int(request.GET.get('draw'))
204 return draw, start, length
205
206
207 class BaseReferencesView(RepoAppView):
208 """
209 Base for reference view for branches, tags and bookmarks.
210 """
211 def load_default_context(self):
212 c = self._get_local_tmpl_context()
213
214 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
215 c.repo_info = self.db_repo
216
217 self._register_global_c(c)
218 return c
219
220 def load_refs_context(self, ref_items, partials_template):
221 _render = PartialRenderer(partials_template)
222 _data = []
223 pre_load = ["author", "date", "message"]
224
225 is_svn = h.is_svn(self.rhodecode_vcs_repo)
226 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
227
228 for ref_name, commit_id in ref_items:
229 commit = self.rhodecode_vcs_repo.get_commit(
230 commit_id=commit_id, pre_load=pre_load)
231
232 # TODO: johbo: Unify generation of reference links
233 use_commit_id = '/' in ref_name or is_svn
234 files_url = h.url(
235 'files_home',
236 repo_name=c.repo_name,
237 f_path=ref_name if is_svn else '',
238 revision=commit_id if use_commit_id else ref_name,
239 at=ref_name)
240
241 _data.append({
242 "name": _render('name', ref_name, files_url),
243 "name_raw": ref_name,
244 "date": _render('date', commit.date),
245 "date_raw": datetime_to_time(commit.date),
246 "author": _render('author', commit.author),
247 "commit": _render(
248 'commit', commit.message, commit.raw_id, commit.idx),
249 "commit_raw": commit.idx,
250 "compare": _render(
251 'compare', format_ref_id(ref_name, commit.raw_id)),
252 })
253 c.has_references = bool(_data)
254 c.data = json.dumps(_data)
255
256
133 class RepoRoutePredicate(object):
257 class RepoRoutePredicate(object):
134 def __init__(self, val, config):
258 def __init__(self, val, config):
135 self.val = val
259 self.val = val
@@ -140,11 +264,15 b' class RepoRoutePredicate(object):'
140 phash = text
264 phash = text
141
265
142 def __call__(self, info, request):
266 def __call__(self, info, request):
267
268 if hasattr(request, 'vcs_call'):
269 # skip vcs calls
270 return
271
143 repo_name = info['match']['repo_name']
272 repo_name = info['match']['repo_name']
144 repo_model = repo.RepoModel()
273 repo_model = repo.RepoModel()
145 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
274 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
146 # if we match quickly from database, short circuit the operation,
275
147 # and validate repo based on the type.
148 if by_name_match:
276 if by_name_match:
149 # register this as request object we can re-use later
277 # register this as request object we can re-use later
150 request.db_repo = by_name_match
278 request.db_repo = by_name_match
@@ -158,6 +286,72 b' class RepoRoutePredicate(object):'
158 return False
286 return False
159
287
160
288
289 class RepoTypeRoutePredicate(object):
290 def __init__(self, val, config):
291 self.val = val or ['hg', 'git', 'svn']
292
293 def text(self):
294 return 'repo_accepted_type = %s' % self.val
295
296 phash = text
297
298 def __call__(self, info, request):
299 if hasattr(request, 'vcs_call'):
300 # skip vcs calls
301 return
302
303 rhodecode_db_repo = request.db_repo
304
305 log.debug(
306 '%s checking repo type for %s in %s',
307 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
308
309 if rhodecode_db_repo.repo_type in self.val:
310 return True
311 else:
312 log.warning('Current view is not supported for repo type:%s',
313 rhodecode_db_repo.repo_type)
314 #
315 # h.flash(h.literal(
316 # _('Action not supported for %s.' % rhodecode_repo.alias)),
317 # category='warning')
318 # return redirect(
319 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
320
321 return False
322
323
324 class RepoGroupRoutePredicate(object):
325 def __init__(self, val, config):
326 self.val = val
327
328 def text(self):
329 return 'repo_group_route = %s' % self.val
330
331 phash = text
332
333 def __call__(self, info, request):
334 if hasattr(request, 'vcs_call'):
335 # skip vcs calls
336 return
337
338 repo_group_name = info['match']['repo_group_name']
339 repo_group_model = repo_group.RepoGroupModel()
340 by_name_match = repo_group_model.get_by_group_name(
341 repo_group_name, cache=True)
342
343 if by_name_match:
344 # register this as request object we can re-use later
345 request.db_repo_group = by_name_match
346 return True
347
348 return False
349
350
161 def includeme(config):
351 def includeme(config):
162 config.add_route_predicate(
352 config.add_route_predicate(
163 'repo_route', RepoRoutePredicate)
353 'repo_route', RepoRoutePredicate)
354 config.add_route_predicate(
355 'repo_accepted_types', RepoTypeRoutePredicate)
356 config.add_route_predicate(
357 'repo_group_route', RepoGroupRoutePredicate)
@@ -30,6 +30,20 b' def admin_routes(config):'
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
34 pattern='/audit_logs')
35
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:[0-9]+}')
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:[0-9]+}')
42 config.add_route(
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:[0-9]+}')
45
46 config.add_route(
33 name='admin_settings_open_source',
47 name='admin_settings_open_source',
34 pattern='/settings/open_source')
48 pattern='/settings/open_source')
35 config.add_route(
49 config.add_route(
@@ -50,6 +64,11 b' def admin_routes(config):'
50 name='admin_settings_sessions_cleanup',
64 name='admin_settings_sessions_cleanup',
51 pattern='/settings/sessions/cleanup')
65 pattern='/settings/sessions/cleanup')
52
66
67 # global permissions
68 config.add_route(
69 name='admin_permissions_ips',
70 pattern='/permissions/ips')
71
53 # users admin
72 # users admin
54 config.add_route(
73 config.add_route(
55 name='users',
74 name='users',
@@ -70,6 +89,28 b' def admin_routes(config):'
70 name='edit_user_auth_tokens_delete',
89 name='edit_user_auth_tokens_delete',
71 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
90 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
72
91
92 # user emails
93 config.add_route(
94 name='edit_user_emails',
95 pattern='/users/{user_id:\d+}/edit/emails')
96 config.add_route(
97 name='edit_user_emails_add',
98 pattern='/users/{user_id:\d+}/edit/emails/new')
99 config.add_route(
100 name='edit_user_emails_delete',
101 pattern='/users/{user_id:\d+}/edit/emails/delete')
102
103 # user IPs
104 config.add_route(
105 name='edit_user_ips',
106 pattern='/users/{user_id:\d+}/edit/ips')
107 config.add_route(
108 name='edit_user_ips_add',
109 pattern='/users/{user_id:\d+}/edit/ips/new')
110 config.add_route(
111 name='edit_user_ips_delete',
112 pattern='/users/{user_id:\d+}/edit/ips/delete')
113
73 # user groups management
114 # user groups management
74 config.add_route(
115 config.add_route(
75 name='edit_user_groups_management',
116 name='edit_user_groups_management',
@@ -93,6 +134,8 b' def includeme(config):'
93 navigation_registry = NavigationRegistry(labs_active=labs_active)
134 navigation_registry = NavigationRegistry(labs_active=labs_active)
94 config.registry.registerUtility(navigation_registry)
135 config.registry.registerUtility(navigation_registry)
95
136
137 # main admin routes
138 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
96 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
139 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
97
140
98 # Scan module for configuration decorators.
141 # Scan module for configuration decorators.
@@ -20,7 +20,9 b''
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User, UserApiKeys
23 from rhodecode.model.db import User, UserApiKeys, UserEmailMap
24 from rhodecode.model.meta import Session
25 from rhodecode.model.user import UserModel
24
26
25 from rhodecode.tests import (
27 from rhodecode.tests import (
26 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
28 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
@@ -44,6 +46,20 b' def route_path(name, params=None, **kwar'
44 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
45 'edit_user_auth_tokens_delete':
47 'edit_user_auth_tokens_delete':
46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
48 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
49
50 'edit_user_emails':
51 ADMIN_PREFIX + '/users/{user_id}/edit/emails',
52 'edit_user_emails_add':
53 ADMIN_PREFIX + '/users/{user_id}/edit/emails/new',
54 'edit_user_emails_delete':
55 ADMIN_PREFIX + '/users/{user_id}/edit/emails/delete',
56
57 'edit_user_ips':
58 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
59 'edit_user_ips_add':
60 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
61 'edit_user_ips_delete':
62 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
47 }[name].format(**kwargs)
63 }[name].format(**kwargs)
48
64
49 if params:
65 if params:
@@ -135,8 +151,131 b' class TestAdminUsersView(TestController)'
135
151
136 response = self.app.post(
152 response = self.app.post(
137 route_path('edit_user_auth_tokens_delete', user_id=user_id),
153 route_path('edit_user_auth_tokens_delete', user_id=user_id),
138 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
154 {'del_auth_token': keys[0].user_api_key_id,
155 'csrf_token': self.csrf_token})
139
156
140 assert_session_flash(response, 'Auth token successfully deleted')
157 assert_session_flash(response, 'Auth token successfully deleted')
141 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
158 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
142 assert 2 == len(keys)
159 assert 2 == len(keys)
160
161 def test_ips(self):
162 self.log_user()
163 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
164 response = self.app.get(route_path('edit_user_ips', user_id=user.user_id))
165 response.mustcontain('All IP addresses are allowed')
166
167 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
168 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
169 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
170 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
171 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
172 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
173 ('127_bad_ip', 'foobar', 'foobar', True),
174 ])
175 def test_ips_add(self, user_util, test_name, ip, ip_range, failure):
176 self.log_user()
177 user = user_util.create_user(username=test_name)
178 user_id = user.user_id
179
180 response = self.app.post(
181 route_path('edit_user_ips_add', user_id=user_id),
182 params={'new_ip': ip, 'csrf_token': self.csrf_token})
183
184 if failure:
185 assert_session_flash(
186 response, 'Please enter a valid IPv4 or IpV6 address')
187 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
188
189 response.mustcontain(no=[ip])
190 response.mustcontain(no=[ip_range])
191
192 else:
193 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
194 response.mustcontain(ip)
195 response.mustcontain(ip_range)
196
197 def test_ips_delete(self, user_util):
198 self.log_user()
199 user = user_util.create_user()
200 user_id = user.user_id
201 ip = '127.0.0.1/32'
202 ip_range = '127.0.0.1 - 127.0.0.1'
203 new_ip = UserModel().add_extra_ip(user_id, ip)
204 Session().commit()
205 new_ip_id = new_ip.ip_id
206
207 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
208 response.mustcontain(ip)
209 response.mustcontain(ip_range)
210
211 self.app.post(
212 route_path('edit_user_ips_delete', user_id=user_id),
213 params={'del_ip_id': new_ip_id, 'csrf_token': self.csrf_token})
214
215 response = self.app.get(route_path('edit_user_ips', user_id=user_id))
216 response.mustcontain('All IP addresses are allowed')
217 response.mustcontain(no=[ip])
218 response.mustcontain(no=[ip_range])
219
220 def test_emails(self):
221 self.log_user()
222 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
223 response = self.app.get(route_path('edit_user_emails', user_id=user.user_id))
224 response.mustcontain('No additional emails specified')
225
226 def test_emails_add(self, user_util):
227 self.log_user()
228 user = user_util.create_user()
229 user_id = user.user_id
230
231 self.app.post(
232 route_path('edit_user_emails_add', user_id=user_id),
233 params={'new_email': 'example@rhodecode.com',
234 'csrf_token': self.csrf_token})
235
236 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
237 response.mustcontain('example@rhodecode.com')
238
239 def test_emails_add_existing_email(self, user_util, user_regular):
240 existing_email = user_regular.email
241
242 self.log_user()
243 user = user_util.create_user()
244 user_id = user.user_id
245
246 response = self.app.post(
247 route_path('edit_user_emails_add', user_id=user_id),
248 params={'new_email': existing_email,
249 'csrf_token': self.csrf_token})
250 assert_session_flash(
251 response, 'This e-mail address is already taken')
252
253 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
254 response.mustcontain(no=[existing_email])
255
256 def test_emails_delete(self, user_util):
257 self.log_user()
258 user = user_util.create_user()
259 user_id = user.user_id
260
261 self.app.post(
262 route_path('edit_user_emails_add', user_id=user_id),
263 params={'new_email': 'example@rhodecode.com',
264 'csrf_token': self.csrf_token})
265
266 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
267 response.mustcontain('example@rhodecode.com')
268
269 user_email = UserEmailMap.query()\
270 .filter(UserEmailMap.email == 'example@rhodecode.com') \
271 .filter(UserEmailMap.user_id == user_id)\
272 .one()
273
274 del_email_id = user_email.email_id
275 self.app.post(
276 route_path('edit_user_emails_delete', user_id=user_id),
277 params={'del_email_id': del_email_id,
278 'csrf_token': self.csrf_token})
279
280 response = self.app.get(route_path('edit_user_emails', user_id=user_id))
281 response.mustcontain(no=['example@rhodecode.com']) No newline at end of file
@@ -21,7 +21,7 b''
21 import collections
21 import collections
22 import logging
22 import logging
23
23
24 from pylons import tmpl_context as c
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
@@ -34,15 +34,21 b' log = logging.getLogger(__name__)'
34
34
35 class OpenSourceLicensesAdminSettingsView(BaseAppView):
35 class OpenSourceLicensesAdminSettingsView(BaseAppView):
36
36
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
39 self._register_global_c(c)
40 return c
41
37 @LoginRequired()
42 @LoginRequired()
38 @HasPermissionAllDecorator('hg.admin')
43 @HasPermissionAllDecorator('hg.admin')
39 @view_config(
44 @view_config(
40 route_name='admin_settings_open_source', request_method='GET',
45 route_name='admin_settings_open_source', request_method='GET',
41 renderer='rhodecode:templates/admin/settings/settings.mako')
46 renderer='rhodecode:templates/admin/settings/settings.mako')
42 def open_source_licenses(self):
47 def open_source_licenses(self):
48 c = self.load_default_context()
43 c.active = 'open_source'
49 c.active = 'open_source'
44 c.navlist = navigation_list(self.request)
50 c.navlist = navigation_list(self.request)
45 c.opensource_licenses = collections.OrderedDict(
51 c.opensource_licenses = collections.OrderedDict(
46 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
52 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
47
53
48 return {}
54 return self._get_template_context(c)
@@ -20,7 +20,6 b''
20
20
21 import logging
21 import logging
22
22
23 from pylons import tmpl_context as c
24 from pyramid.view import view_config
23 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
26
25
@@ -37,6 +36,11 b' log = logging.getLogger(__name__)'
37
36
38
37
39 class AdminSessionSettingsView(BaseAppView):
38 class AdminSessionSettingsView(BaseAppView):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
41
42 self._register_global_c(c)
43 return c
40
44
41 @LoginRequired()
45 @LoginRequired()
42 @HasPermissionAllDecorator('hg.admin')
46 @HasPermissionAllDecorator('hg.admin')
@@ -44,6 +48,8 b' class AdminSessionSettingsView(BaseAppVi'
44 route_name='admin_settings_sessions', request_method='GET',
48 route_name='admin_settings_sessions', request_method='GET',
45 renderer='rhodecode:templates/admin/settings/settings.mako')
49 renderer='rhodecode:templates/admin/settings/settings.mako')
46 def settings_sessions(self):
50 def settings_sessions(self):
51 c = self.load_default_context()
52
47 c.active = 'sessions'
53 c.active = 'sessions'
48 c.navlist = navigation_list(self.request)
54 c.navlist = navigation_list(self.request)
49
55
@@ -59,11 +65,11 b' class AdminSessionSettingsView(BaseAppVi'
59 c.session_expired_count = c.session_model.get_expired_count(
65 c.session_expired_count = c.session_model.get_expired_count(
60 older_than_seconds)
66 older_than_seconds)
61
67
62 return {}
68 return self._get_template_context(c)
63
69
64 @LoginRequired()
70 @LoginRequired()
71 @HasPermissionAllDecorator('hg.admin')
65 @CSRFRequired()
72 @CSRFRequired()
66 @HasPermissionAllDecorator('hg.admin')
67 @view_config(
73 @view_config(
68 route_name='admin_settings_sessions_cleanup', request_method='POST')
74 route_name='admin_settings_sessions_cleanup', request_method='POST')
69 def settings_sessions_cleanup(self):
75 def settings_sessions_cleanup(self):
@@ -33,8 +33,8 b' log = logging.getLogger(__name__)'
33 class SvnConfigAdminSettingsView(BaseAppView):
33 class SvnConfigAdminSettingsView(BaseAppView):
34
34
35 @LoginRequired()
35 @LoginRequired()
36 @HasPermissionAllDecorator('hg.admin')
36 @CSRFRequired()
37 @CSRFRequired()
37 @HasPermissionAllDecorator('hg.admin')
38 @view_config(
38 @view_config(
39 route_name='admin_settings_vcs_svn_generate_cfg',
39 route_name='admin_settings_vcs_svn_generate_cfg',
40 request_method='POST', renderer='json')
40 request_method='POST', renderer='json')
@@ -22,7 +22,6 b' import logging'
22 import urllib2
22 import urllib2
23 import packaging.version
23 import packaging.version
24
24
25 from pylons import tmpl_context as c
26 from pyramid.view import view_config
25 from pyramid.view import view_config
27
26
28 import rhodecode
27 import rhodecode
@@ -39,6 +38,10 b' log = logging.getLogger(__name__)'
39
38
40
39
41 class AdminSystemInfoSettingsView(BaseAppView):
40 class AdminSystemInfoSettingsView(BaseAppView):
41 def load_default_context(self):
42 c = self._get_local_tmpl_context()
43 self._register_global_c(c)
44 return c
42
45
43 @staticmethod
46 @staticmethod
44 def get_update_data(update_url):
47 def get_update_data(update_url):
@@ -64,6 +67,7 b' class AdminSystemInfoSettingsView(BaseAp'
64 renderer='rhodecode:templates/admin/settings/settings.mako')
67 renderer='rhodecode:templates/admin/settings/settings.mako')
65 def settings_system_info(self):
68 def settings_system_info(self):
66 _ = self.request.translate
69 _ = self.request.translate
70 c = self.load_default_context()
67
71
68 c.active = 'system'
72 c.active = 'system'
69 c.navlist = navigation_list(self.request)
73 c.navlist = navigation_list(self.request)
@@ -106,6 +110,7 b' class AdminSystemInfoSettingsView(BaseAp'
106 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
110 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
107 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
111 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
108 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
112 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
113 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
109 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
114 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
110 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
115 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
111 ('', '', ''), # spacer
116 ('', '', ''), # spacer
@@ -163,7 +168,7 b' class AdminSystemInfoSettingsView(BaseAp'
163 else:
168 else:
164 self.request.session.flash(
169 self.request.session.flash(
165 'You are not allowed to do this', queue='warning')
170 'You are not allowed to do this', queue='warning')
166 return {}
171 return self._get_template_context(c)
167
172
168 @LoginRequired()
173 @LoginRequired()
169 @HasPermissionAllDecorator('hg.admin')
174 @HasPermissionAllDecorator('hg.admin')
@@ -172,6 +177,7 b' class AdminSystemInfoSettingsView(BaseAp'
172 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
177 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
173 def settings_system_info_check_update(self):
178 def settings_system_info_check_update(self):
174 _ = self.request.translate
179 _ = self.request.translate
180 c = self.load_default_context()
175
181
176 update_url = self.get_update_url()
182 update_url = self.get_update_url()
177
183
@@ -200,4 +206,4 b' class AdminSystemInfoSettingsView(BaseAp'
200 c.should_upgrade = True
206 c.should_upgrade = True
201 c.important_notices = latest['general']
207 c.important_notices = latest['general']
202
208
203 return {}
209 return self._get_template_context(c)
@@ -20,15 +20,16 b''
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23
24
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26 from pyramid.view import view_config
26 from sqlalchemy.sql.functions import coalesce
27 from sqlalchemy.sql.functions import coalesce
27
28
28 from rhodecode.lib.helpers import Page
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 from rhodecode_tools.lib.ext_json import json
30
30
31 from rhodecode.apps._base import BaseAppView
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
@@ -37,13 +38,13 b' from rhodecode.lib.utils2 import safe_in'
37 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.db import User, or_
41 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
41 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
42
43
43 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
44
45
45
46
46 class AdminUsersView(BaseAppView):
47 class AdminUsersView(BaseAppView, DataGridAppView):
47 ALLOW_SCOPED_TOKENS = False
48 ALLOW_SCOPED_TOKENS = False
48 """
49 """
49 This view has alternative version inside EE, if modified please take a look
50 This view has alternative version inside EE, if modified please take a look
@@ -64,28 +65,6 b' class AdminUsersView(BaseAppView):'
64 # is a pyramid view
65 # is a pyramid view
65 raise HTTPFound('/')
66 raise HTTPFound('/')
66
67
67 def _extract_ordering(self, request):
68 column_index = safe_int(request.GET.get('order[0][column]'))
69 order_dir = request.GET.get(
70 'order[0][dir]', 'desc')
71 order_by = request.GET.get(
72 'columns[%s][data][sort]' % column_index, 'name_raw')
73
74 # translate datatable to DB columns
75 order_by = {
76 'first_name': 'name',
77 'last_name': 'lastname',
78 }.get(order_by) or order_by
79
80 search_q = request.GET.get('search[value]')
81 return search_q, order_by, order_dir
82
83 def _extract_chunk(self, request):
84 start = safe_int(request.GET.get('start'), 0)
85 length = safe_int(request.GET.get('length'), 25)
86 draw = safe_int(request.GET.get('draw'))
87 return draw, start, length
88
89 @HasPermissionAllDecorator('hg.admin')
68 @HasPermissionAllDecorator('hg.admin')
90 @view_config(
69 @view_config(
91 route_name='users', request_method='GET',
70 route_name='users', request_method='GET',
@@ -97,8 +76,8 b' class AdminUsersView(BaseAppView):'
97 @HasPermissionAllDecorator('hg.admin')
76 @HasPermissionAllDecorator('hg.admin')
98 @view_config(
77 @view_config(
99 # renderer defined below
78 # renderer defined below
100 route_name='users_data', request_method='GET', renderer='json',
79 route_name='users_data', request_method='GET',
101 xhr=True)
80 renderer='json_ext', xhr=True)
102 def users_list_data(self):
81 def users_list_data(self):
103 draw, start, limit = self._extract_chunk(self.request)
82 draw, start, limit = self._extract_chunk(self.request)
104 search_q, order_by, order_dir = self._extract_ordering(self.request)
83 search_q, order_by, order_dir = self._extract_ordering(self.request)
@@ -149,8 +128,8 b' class AdminUsersView(BaseAppView):'
149 users_data.append({
128 users_data.append({
150 "username": h.gravatar_with_user(user.username),
129 "username": h.gravatar_with_user(user.username),
151 "email": user.email,
130 "email": user.email,
152 "first_name": h.escape(user.name),
131 "first_name": user.first_name,
153 "last_name": h.escape(user.lastname),
132 "last_name": user.last_name,
154 "last_login": h.format_date(user.last_login),
133 "last_login": h.format_date(user.last_login),
155 "last_activity": h.format_date(user.last_activity),
134 "last_activity": h.format_date(user.last_activity),
156 "active": h.bool2icon(user.active),
135 "active": h.bool2icon(user.active),
@@ -216,15 +195,23 b' class AdminUsersView(BaseAppView):'
216
195
217 user_id = self.request.matchdict.get('user_id')
196 user_id = self.request.matchdict.get('user_id')
218 c.user = User.get_or_404(user_id, pyramid_exc=True)
197 c.user = User.get_or_404(user_id, pyramid_exc=True)
198
219 self._redirect_for_default_user(c.user.username)
199 self._redirect_for_default_user(c.user.username)
220
200
201 user_data = c.user.get_api_data()
221 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
222 description = self.request.POST.get('description')
203 description = self.request.POST.get('description')
223 role = self.request.POST.get('role')
204 role = self.request.POST.get('role')
224
205
225 token = AuthTokenModel().create(
206 token = AuthTokenModel().create(
226 c.user.user_id, description, lifetime, role)
207 c.user.user_id, description, lifetime, role)
208 token_data = token.get_api_data()
209
227 self.maybe_attach_token_scope(token)
210 self.maybe_attach_token_scope(token)
211 audit_logger.store_web(
212 'user.edit.token.add', action_data={
213 'data': {'token': token_data, 'user': user_data}},
214 user=self._rhodecode_user, )
228 Session().commit()
215 Session().commit()
229
216
230 h.flash(_("Auth token successfully created"), category='success')
217 h.flash(_("Auth token successfully created"), category='success')
@@ -242,11 +229,19 b' class AdminUsersView(BaseAppView):'
242 user_id = self.request.matchdict.get('user_id')
229 user_id = self.request.matchdict.get('user_id')
243 c.user = User.get_or_404(user_id, pyramid_exc=True)
230 c.user = User.get_or_404(user_id, pyramid_exc=True)
244 self._redirect_for_default_user(c.user.username)
231 self._redirect_for_default_user(c.user.username)
232 user_data = c.user.get_api_data()
245
233
246 del_auth_token = self.request.POST.get('del_auth_token')
234 del_auth_token = self.request.POST.get('del_auth_token')
247
235
248 if del_auth_token:
236 if del_auth_token:
237 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
238 token_data = token.get_api_data()
239
249 AuthTokenModel().delete(del_auth_token, c.user.user_id)
240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
241 audit_logger.store_web(
242 'user.edit.token.delete', action_data={
243 'data': {'token': token_data, 'user': user_data}},
244 user=self._rhodecode_user,)
250 Session().commit()
245 Session().commit()
251 h.flash(_("Auth token successfully deleted"), category='success')
246 h.flash(_("Auth token successfully deleted"), category='success')
252
247
@@ -255,6 +250,186 b' class AdminUsersView(BaseAppView):'
255 @LoginRequired()
250 @LoginRequired()
256 @HasPermissionAllDecorator('hg.admin')
251 @HasPermissionAllDecorator('hg.admin')
257 @view_config(
252 @view_config(
253 route_name='edit_user_emails', request_method='GET',
254 renderer='rhodecode:templates/admin/users/user_edit.mako')
255 def emails(self):
256 _ = self.request.translate
257 c = self.load_default_context()
258
259 user_id = self.request.matchdict.get('user_id')
260 c.user = User.get_or_404(user_id, pyramid_exc=True)
261 self._redirect_for_default_user(c.user.username)
262
263 c.active = 'emails'
264 c.user_email_map = UserEmailMap.query() \
265 .filter(UserEmailMap.user == c.user).all()
266
267 return self._get_template_context(c)
268
269 @LoginRequired()
270 @HasPermissionAllDecorator('hg.admin')
271 @CSRFRequired()
272 @view_config(
273 route_name='edit_user_emails_add', request_method='POST')
274 def emails_add(self):
275 _ = self.request.translate
276 c = self.load_default_context()
277
278 user_id = self.request.matchdict.get('user_id')
279 c.user = User.get_or_404(user_id, pyramid_exc=True)
280 self._redirect_for_default_user(c.user.username)
281
282 email = self.request.POST.get('new_email')
283 user_data = c.user.get_api_data()
284 try:
285 UserModel().add_extra_email(c.user.user_id, email)
286 audit_logger.store_web(
287 'user.edit.email.add', action_data={'email': email, 'user': user_data},
288 user=self._rhodecode_user)
289 Session().commit()
290 h.flash(_("Added new email address `%s` for user account") % email,
291 category='success')
292 except formencode.Invalid as error:
293 h.flash(h.escape(error.error_dict['email']), category='error')
294 except Exception:
295 log.exception("Exception during email saving")
296 h.flash(_('An error occurred during email saving'),
297 category='error')
298 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
299
300 @LoginRequired()
301 @HasPermissionAllDecorator('hg.admin')
302 @CSRFRequired()
303 @view_config(
304 route_name='edit_user_emails_delete', request_method='POST')
305 def emails_delete(self):
306 _ = self.request.translate
307 c = self.load_default_context()
308
309 user_id = self.request.matchdict.get('user_id')
310 c.user = User.get_or_404(user_id, pyramid_exc=True)
311 self._redirect_for_default_user(c.user.username)
312
313 email_id = self.request.POST.get('del_email_id')
314 user_model = UserModel()
315
316 email = UserEmailMap.query().get(email_id).email
317 user_data = c.user.get_api_data()
318 user_model.delete_extra_email(c.user.user_id, email_id)
319 audit_logger.store_web(
320 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
321 user=self._rhodecode_user)
322 Session().commit()
323 h.flash(_("Removed email address from user account"),
324 category='success')
325 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
326
327 @LoginRequired()
328 @HasPermissionAllDecorator('hg.admin')
329 @view_config(
330 route_name='edit_user_ips', request_method='GET',
331 renderer='rhodecode:templates/admin/users/user_edit.mako')
332 def ips(self):
333 _ = self.request.translate
334 c = self.load_default_context()
335
336 user_id = self.request.matchdict.get('user_id')
337 c.user = User.get_or_404(user_id, pyramid_exc=True)
338 self._redirect_for_default_user(c.user.username)
339
340 c.active = 'ips'
341 c.user_ip_map = UserIpMap.query() \
342 .filter(UserIpMap.user == c.user).all()
343
344 c.inherit_default_ips = c.user.inherit_default_permissions
345 c.default_user_ip_map = UserIpMap.query() \
346 .filter(UserIpMap.user == User.get_default_user()).all()
347
348 return self._get_template_context(c)
349
350 @LoginRequired()
351 @HasPermissionAllDecorator('hg.admin')
352 @CSRFRequired()
353 @view_config(
354 route_name='edit_user_ips_add', request_method='POST')
355 def ips_add(self):
356 _ = self.request.translate
357 c = self.load_default_context()
358
359 user_id = self.request.matchdict.get('user_id')
360 c.user = User.get_or_404(user_id, pyramid_exc=True)
361 # NOTE(marcink): this view is allowed for default users, as we can
362 # edit their IP white list
363
364 user_model = UserModel()
365 desc = self.request.POST.get('description')
366 try:
367 ip_list = user_model.parse_ip_range(
368 self.request.POST.get('new_ip'))
369 except Exception as e:
370 ip_list = []
371 log.exception("Exception during ip saving")
372 h.flash(_('An error occurred during ip saving:%s' % (e,)),
373 category='error')
374 added = []
375 user_data = c.user.get_api_data()
376 for ip in ip_list:
377 try:
378 user_model.add_extra_ip(c.user.user_id, ip, desc)
379 audit_logger.store_web(
380 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
381 user=self._rhodecode_user)
382 Session().commit()
383 added.append(ip)
384 except formencode.Invalid as error:
385 msg = error.error_dict['ip']
386 h.flash(msg, category='error')
387 except Exception:
388 log.exception("Exception during ip saving")
389 h.flash(_('An error occurred during ip saving'),
390 category='error')
391 if added:
392 h.flash(
393 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
394 category='success')
395 if 'default_user' in self.request.POST:
396 # case for editing global IP list we do it for 'DEFAULT' user
397 raise HTTPFound(h.route_path('admin_permissions_ips'))
398 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
399
400 @LoginRequired()
401 @HasPermissionAllDecorator('hg.admin')
402 @CSRFRequired()
403 @view_config(
404 route_name='edit_user_ips_delete', request_method='POST')
405 def ips_delete(self):
406 _ = self.request.translate
407 c = self.load_default_context()
408
409 user_id = self.request.matchdict.get('user_id')
410 c.user = User.get_or_404(user_id, pyramid_exc=True)
411 # NOTE(marcink): this view is allowed for default users, as we can
412 # edit their IP white list
413
414 ip_id = self.request.POST.get('del_ip_id')
415 user_model = UserModel()
416 user_data = c.user.get_api_data()
417 ip = UserIpMap.query().get(ip_id).ip_addr
418 user_model.delete_extra_ip(c.user.user_id, ip_id)
419 audit_logger.store_web(
420 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
421 user=self._rhodecode_user)
422 Session().commit()
423 h.flash(_("Removed ip address from user whitelist"), category='success')
424
425 if 'default_user' in self.request.POST:
426 # case for editing global IP list we do it for 'DEFAULT' user
427 raise HTTPFound(h.route_path('admin_permissions_ips'))
428 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
429
430 @LoginRequired()
431 @HasPermissionAllDecorator('hg.admin')
432 @view_config(
258 route_name='edit_user_groups_management', request_method='GET',
433 route_name='edit_user_groups_management', request_method='GET',
259 renderer='rhodecode:templates/admin/users/user_edit.mako')
434 renderer='rhodecode:templates/admin/users/user_edit.mako')
260 def groups_management(self):
435 def groups_management(self):
@@ -264,7 +439,8 b' class AdminUsersView(BaseAppView):'
264 c.user = User.get_or_404(user_id, pyramid_exc=True)
439 c.user = User.get_or_404(user_id, pyramid_exc=True)
265 c.data = c.user.group_member
440 c.data = c.user.group_member
266 self._redirect_for_default_user(c.user.username)
441 self._redirect_for_default_user(c.user.username)
267 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group) for group in c.user.group_member]
442 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
443 for group in c.user.group_member]
268 c.groups = json.dumps(groups)
444 c.groups = json.dumps(groups)
269 c.active = 'groups'
445 c.active = 'groups'
270
446
@@ -272,6 +448,7 b' class AdminUsersView(BaseAppView):'
272
448
273 @LoginRequired()
449 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
450 @HasPermissionAllDecorator('hg.admin')
451 @CSRFRequired()
275 @view_config(
452 @view_config(
276 route_name='edit_user_groups_management_updates', request_method='POST')
453 route_name='edit_user_groups_management_updates', request_method='POST')
277 def groups_management_updates(self):
454 def groups_management_updates(self):
@@ -314,15 +491,15 b' class AdminUsersView(BaseAppView):'
314 p = safe_int(self.request.GET.get('page', 1), 1)
491 p = safe_int(self.request.GET.get('page', 1), 1)
315
492
316 filter_term = self.request.GET.get('filter')
493 filter_term = self.request.GET.get('filter')
317 c.user_log = UserModel().get_user_log(c.user, filter_term)
494 user_log = UserModel().get_user_log(c.user, filter_term)
318
495
319 def url_generator(**kw):
496 def url_generator(**kw):
320 if filter_term:
497 if filter_term:
321 kw['filter'] = filter_term
498 kw['filter'] = filter_term
322 return self.request.current_route_path(_query=kw)
499 return self.request.current_route_path(_query=kw)
323
500
324 c.user_log = Page(c.user_log, page=p, items_per_page=10,
501 c.audit_logs = h.Page(
325 url=url_generator)
502 user_log, page=p, items_per_page=10, url=url_generator)
326 c.filter_term = filter_term
503 c.filter_term = filter_term
327 return self._get_template_context(c)
504 return self._get_template_context(c)
328
505
@@ -30,8 +30,6 b' Channel Stream controller for rhodecode'
30 import logging
30 import logging
31 import uuid
31 import uuid
32
32
33 from pylons import tmpl_context as c
34 from pyramid.settings import asbool
35 from pyramid.view import view_config
33 from pyramid.view import view_config
36 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
34 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
37
35
@@ -46,7 +44,6 b' from rhodecode.lib.channelstream import '
46 update_history_from_logs,
44 update_history_from_logs,
47 STATE_PUBLIC_KEYS)
45 STATE_PUBLIC_KEYS)
48 from rhodecode.lib.auth import NotAnonymous
46 from rhodecode.lib.auth import NotAnonymous
49 from rhodecode.lib.utils2 import str2bool
50
47
51 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
52
49
@@ -82,7 +79,7 b' class ChannelstreamView(object):'
82 log.error('Incorrect permissions for requested channels')
79 log.error('Incorrect permissions for requested channels')
83 raise HTTPForbidden()
80 raise HTTPForbidden()
84
81
85 user = c.rhodecode_user
82 user = self._rhodecode_user
86 if user.user_id:
83 if user.user_id:
87 user_data = get_user_data(user.user_id)
84 user_data = get_user_data(user.user_id)
88 else:
85 else:
@@ -95,7 +92,7 b' class ChannelstreamView(object):'
95 'display_name': None,
92 'display_name': None,
96 'display_link': None,
93 'display_link': None,
97 }
94 }
98 user_data['permissions'] = c.rhodecode_user.permissions
95 user_data['permissions'] = self._rhodecode_user.permissions
99 payload = {
96 payload = {
100 'username': user.username,
97 'username': user.username,
101 'user_state': user_data,
98 'user_state': user_data,
@@ -18,31 +18,34 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
22
21
23 from mock import patch
24 import pytest
22 import pytest
25 from pylons import tmpl_context as c
26
23
27 import rhodecode
24 import rhodecode
28 from rhodecode.lib.utils import map_groups
25 from rhodecode.model.db import Repository
29 from rhodecode.model.db import Repository, User, RepoGroup
30 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
31 from rhodecode.model.repo import RepoModel
27 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.repo_group import RepoGroupModel
28 from rhodecode.model.repo_group import RepoGroupModel
33 from rhodecode.model.settings import SettingsModel
29 from rhodecode.model.settings import SettingsModel
34 from rhodecode.tests import TestController, url, TEST_USER_ADMIN_LOGIN
30 from rhodecode.tests import TestController
35 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.fixture import Fixture
32 from rhodecode.lib import helpers as h
33
34 fixture = Fixture()
36
35
37
36
38 fixture = Fixture()
37 def route_path(name, **kwargs):
38 return {
39 'home': '/',
40 'repo_group_home': '/{repo_group_name}'
41 }[name].format(**kwargs)
39
42
40
43
41 class TestHomeController(TestController):
44 class TestHomeController(TestController):
42
45
43 def test_index(self):
46 def test_index(self):
44 self.log_user()
47 self.log_user()
45 response = self.app.get(url(controller='home', action='index'))
48 response = self.app.get(route_path('home'))
46 # if global permission is set
49 # if global permission is set
47 response.mustcontain('Add Repository')
50 response.mustcontain('Add Repository')
48
51
@@ -51,8 +54,10 b' class TestHomeController(TestController)'
51 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
52
55
53 def test_index_contains_statics_with_ver(self):
56 def test_index_contains_statics_with_ver(self):
57 from pylons import tmpl_context as c
58
54 self.log_user()
59 self.log_user()
55 response = self.app.get(url(controller='home', action='index'))
60 response = self.app.get(route_path('home'))
56
61
57 rhodecode_version_hash = c.rhodecode_version_hash
62 rhodecode_version_hash = c.rhodecode_version_hash
58 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
63 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
@@ -60,7 +65,7 b' class TestHomeController(TestController)'
60
65
61 def test_index_contains_backend_specific_details(self, backend):
66 def test_index_contains_backend_specific_details(self, backend):
62 self.log_user()
67 self.log_user()
63 response = self.app.get(url(controller='home', action='index'))
68 response = self.app.get(route_path('home'))
64 tip = backend.repo.get_commit().raw_id
69 tip = backend.repo.get_commit().raw_id
65
70
66 # html in javascript variable:
71 # html in javascript variable:
@@ -72,17 +77,16 b' class TestHomeController(TestController)'
72
77
73 def test_index_with_anonymous_access_disabled(self):
78 def test_index_with_anonymous_access_disabled(self):
74 with fixture.anon_access(False):
79 with fixture.anon_access(False):
75 response = self.app.get(url(controller='home', action='index'),
80 response = self.app.get(route_path('home'), status=302)
76 status=302)
77 assert 'login' in response.location
81 assert 'login' in response.location
78
82
79 def test_index_page_on_groups(self, autologin_user, repo_group):
83 def test_index_page_on_groups(self, autologin_user, repo_group):
80 response = self.app.get(url('repo_group_home', group_name='gr1'))
84 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1'))
81 response.mustcontain("gr1/repo_in_group")
85 response.mustcontain("gr1/repo_in_group")
82
86
83 def test_index_page_on_group_with_trailing_slash(
87 def test_index_page_on_group_with_trailing_slash(
84 self, autologin_user, repo_group):
88 self, autologin_user, repo_group):
85 response = self.app.get(url('repo_group_home', group_name='gr1') + '/')
89 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1') + '/')
86 response.mustcontain("gr1/repo_in_group")
90 response.mustcontain("gr1/repo_in_group")
87
91
88 @pytest.fixture(scope='class')
92 @pytest.fixture(scope='class')
@@ -96,21 +100,19 b' class TestHomeController(TestController)'
96 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
100 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
97 Session().commit()
101 Session().commit()
98
102
99 def test_index_with_name_with_tags(self, autologin_user):
103 def test_index_with_name_with_tags(self, user_util, autologin_user):
100 user = User.get_by_username('test_admin')
104 user = user_util.create_user()
105 username = user.username
101 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
106 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
102 user.lastname = (
107 user.lastname = '#"><img src=x onerror=prompt(document.cookie);>'
103 '<img src="/image2" onload="alert(\'Hello, World!\');">')
108
104 Session().add(user)
109 Session().add(user)
105 Session().commit()
110 Session().commit()
111 user_util.create_repo(owner=username)
106
112
107 response = self.app.get(url(controller='home', action='index'))
113 response = self.app.get(route_path('home'))
108 response.mustcontain(
114 response.mustcontain(h.html_escape(user.first_name))
109 '&lt;img src=&#34;/image1&#34; onload=&#34;'
115 response.mustcontain(h.html_escape(user.last_name))
110 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
111 response.mustcontain(
112 '&lt;img src=&#34;/image2&#34; onload=&#34;'
113 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
114
116
115 @pytest.mark.parametrize("name, state", [
117 @pytest.mark.parametrize("name, state", [
116 ('Disabled', False),
118 ('Disabled', False),
@@ -125,266 +127,8 b' class TestHomeController(TestController)'
125 Session().commit()
127 Session().commit()
126 SettingsModel().invalidate_settings_cache()
128 SettingsModel().invalidate_settings_cache()
127
129
128 response = self.app.get(url(controller='home', action='index'))
130 response = self.app.get(route_path('home'))
129 if state is True:
131 if state is True:
130 response.mustcontain(version_string)
132 response.mustcontain(version_string)
131 if state is False:
133 if state is False:
132 response.mustcontain(no=[version_string])
134 response.mustcontain(no=[version_string])
133
134
135 class TestUserAutocompleteData(TestController):
136 def test_returns_list_of_users(self, user_util):
137 self.log_user()
138 user = user_util.create_user(is_active=True)
139 user_name = user.username
140 response = self.app.get(
141 url(controller='home', action='user_autocomplete_data'),
142 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
143 result = json.loads(response.body)
144 values = [suggestion['value'] for suggestion in result['suggestions']]
145 assert user_name in values
146
147 def test_returns_inactive_users_when_active_flag_sent(self, user_util):
148 self.log_user()
149 user = user_util.create_user(is_active=False)
150 user_name = user.username
151 response = self.app.get(
152 url(controller='home', action='user_autocomplete_data',
153 user_groups='true', active='0'),
154 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
155 result = json.loads(response.body)
156 values = [suggestion['value'] for suggestion in result['suggestions']]
157 assert user_name in values
158
159 def test_returns_groups_when_user_groups_sent(self, user_util):
160 self.log_user()
161 group = user_util.create_user_group(user_groups_active=True)
162 group_name = group.users_group_name
163 response = self.app.get(
164 url(controller='home', action='user_autocomplete_data',
165 user_groups='true'),
166 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
167 result = json.loads(response.body)
168 values = [suggestion['value'] for suggestion in result['suggestions']]
169 assert group_name in values
170
171 def test_result_is_limited_when_query_is_sent(self):
172 self.log_user()
173 fake_result = [
174 {
175 'first_name': 'John',
176 'value_display': 'hello{} (John Smith)'.format(i),
177 'icon_link': '/images/user14.png',
178 'value': 'hello{}'.format(i),
179 'last_name': 'Smith',
180 'username': 'hello{}'.format(i),
181 'id': i,
182 'value_type': u'user'
183 }
184 for i in range(10)
185 ]
186 users_patcher = patch.object(
187 RepoModel, 'get_users', return_value=fake_result)
188 groups_patcher = patch.object(
189 RepoModel, 'get_user_groups', return_value=fake_result)
190
191 query = 'hello'
192 with users_patcher as users_mock, groups_patcher as groups_mock:
193 response = self.app.get(
194 url(controller='home', action='user_autocomplete_data',
195 user_groups='true', query=query),
196 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
197
198 result = json.loads(response.body)
199 users_mock.assert_called_once_with(
200 name_contains=query, only_active=True)
201 groups_mock.assert_called_once_with(
202 name_contains=query, only_active=True)
203 assert len(result['suggestions']) == 20
204
205
206 def assert_and_get_content(result):
207 repos = []
208 groups = []
209 commits = []
210 for data in result:
211 for data_item in data['children']:
212 assert data_item['id']
213 assert data_item['text']
214 assert data_item['url']
215 if data_item['type'] == 'repo':
216 repos.append(data_item)
217 elif data_item['type'] == 'group':
218 groups.append(data_item)
219 elif data_item['type'] == 'commit':
220 commits.append(data_item)
221 else:
222 raise Exception('invalid type %s' % data_item['type'])
223
224 return repos, groups, commits
225
226
227 class TestGotoSwitcherData(TestController):
228 required_repos_with_groups = [
229 'abc',
230 'abc-fork',
231 'forks/abcd',
232 'abcd',
233 'abcde',
234 'a/abc',
235 'aa/abc',
236 'aaa/abc',
237 'aaaa/abc',
238 'repos_abc/aaa/abc',
239 'abc_repos/abc',
240 'abc_repos/abcd',
241 'xxx/xyz',
242 'forked-abc/a/abc'
243 ]
244
245 @pytest.fixture(autouse=True, scope='class')
246 def prepare(self, request, pylonsapp):
247 for repo_and_group in self.required_repos_with_groups:
248 # create structure of groups and return the last group
249
250 repo_group = map_groups(repo_and_group)
251
252 RepoModel()._create_repo(
253 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
254 repo_group=getattr(repo_group, 'group_id', None))
255
256 Session().commit()
257
258 request.addfinalizer(self.cleanup)
259
260 def cleanup(self):
261 # first delete all repos
262 for repo_and_groups in self.required_repos_with_groups:
263 repo = Repository.get_by_repo_name(repo_and_groups)
264 if repo:
265 RepoModel().delete(repo)
266 Session().commit()
267
268 # then delete all empty groups
269 for repo_and_groups in self.required_repos_with_groups:
270 if '/' in repo_and_groups:
271 r_group = repo_and_groups.rsplit('/', 1)[0]
272 repo_group = RepoGroup.get_by_group_name(r_group)
273 if not repo_group:
274 continue
275 parents = repo_group.parents
276 RepoGroupModel().delete(repo_group, force_delete=True)
277 Session().commit()
278
279 for el in reversed(parents):
280 RepoGroupModel().delete(el, force_delete=True)
281 Session().commit()
282
283 def test_returns_list_of_repos_and_groups(self):
284 self.log_user()
285
286 response = self.app.get(
287 url(controller='home', action='goto_switcher_data'),
288 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
289 result = json.loads(response.body)['results']
290
291 repos, groups, commits = assert_and_get_content(result)
292
293 assert len(repos) == len(Repository.get_all())
294 assert len(groups) == len(RepoGroup.get_all())
295 assert len(commits) == 0
296
297 def test_returns_list_of_repos_and_groups_filtered(self):
298 self.log_user()
299
300 response = self.app.get(
301 url(controller='home', action='goto_switcher_data'),
302 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
303 params={'query': 'abc'}, status=200)
304 result = json.loads(response.body)['results']
305
306 repos, groups, commits = assert_and_get_content(result)
307
308 assert len(repos) == 13
309 assert len(groups) == 5
310 assert len(commits) == 0
311
312 def test_returns_list_of_properly_sorted_and_filtered(self):
313 self.log_user()
314
315 response = self.app.get(
316 url(controller='home', action='goto_switcher_data'),
317 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
318 params={'query': 'abc'}, status=200)
319 result = json.loads(response.body)['results']
320
321 repos, groups, commits = assert_and_get_content(result)
322
323 test_repos = [x['text'] for x in repos[:4]]
324 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
325
326 test_groups = [x['text'] for x in groups[:4]]
327 assert ['abc_repos', 'repos_abc',
328 'forked-abc', 'forked-abc/a'] == test_groups
329
330
331 class TestRepoListData(TestController):
332 def test_returns_list_of_repos_and_groups(self, user_util):
333 self.log_user()
334
335 response = self.app.get(
336 url(controller='home', action='repo_list_data'),
337 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
338 result = json.loads(response.body)['results']
339
340 repos, groups, commits = assert_and_get_content(result)
341
342 assert len(repos) == len(Repository.get_all())
343 assert len(groups) == 0
344 assert len(commits) == 0
345
346 def test_returns_list_of_repos_and_groups_filtered(self):
347 self.log_user()
348
349 response = self.app.get(
350 url(controller='home', action='repo_list_data'),
351 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
352 params={'query': 'vcs_test_git'}, status=200)
353 result = json.loads(response.body)['results']
354
355 repos, groups, commits = assert_and_get_content(result)
356
357 assert len(repos) == len(Repository.query().filter(
358 Repository.repo_name.ilike('%vcs_test_git%')).all())
359 assert len(groups) == 0
360 assert len(commits) == 0
361
362 def test_returns_list_of_repos_and_groups_filtered_with_type(self):
363 self.log_user()
364
365 response = self.app.get(
366 url(controller='home', action='repo_list_data'),
367 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
368 params={'query': 'vcs_test_git', 'repo_type': 'git'}, status=200)
369 result = json.loads(response.body)['results']
370
371 repos, groups, commits = assert_and_get_content(result)
372
373 assert len(repos) == len(Repository.query().filter(
374 Repository.repo_name.ilike('%vcs_test_git%')).all())
375 assert len(groups) == 0
376 assert len(commits) == 0
377
378 def test_returns_list_of_repos_non_ascii_query(self):
379 self.log_user()
380 response = self.app.get(
381 url(controller='home', action='repo_list_data'),
382 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
383 params={'query': 'ć_vcs_test_ą', 'repo_type': 'git'}, status=200)
384 result = json.loads(response.body)['results']
385
386 repos, groups, commits = assert_and_get_content(result)
387
388 assert len(repos) == 0
389 assert len(groups) == 0
390 assert len(commits) == 0
@@ -25,7 +25,6 b' import formencode'
25 import logging
25 import logging
26 import urlparse
26 import urlparse
27
27
28 from pylons import url
29 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound
30 from pyramid.view import view_config
29 from pyramid.view import view_config
31 from recaptcha.client.captcha import submit
30 from recaptcha.client.captcha import submit
@@ -34,6 +33,7 b' from rhodecode.apps._base import BaseApp'
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
33 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 from rhodecode.events import UserRegistered
34 from rhodecode.events import UserRegistered
36 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import audit_logger
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
38 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
39 from rhodecode.lib.base import get_ip_addr
39 from rhodecode.lib.base import get_ip_addr
@@ -90,20 +90,21 b' def get_came_from(request):'
90 came_from = safe_str(request.GET.get('came_from', ''))
90 came_from = safe_str(request.GET.get('came_from', ''))
91 parsed = urlparse.urlparse(came_from)
91 parsed = urlparse.urlparse(came_from)
92 allowed_schemes = ['http', 'https']
92 allowed_schemes = ['http', 'https']
93 default_came_from = h.route_path('home')
93 if parsed.scheme and parsed.scheme not in allowed_schemes:
94 if parsed.scheme and parsed.scheme not in allowed_schemes:
94 log.error('Suspicious URL scheme detected %s for url %s' %
95 log.error('Suspicious URL scheme detected %s for url %s' %
95 (parsed.scheme, parsed))
96 (parsed.scheme, parsed))
96 came_from = url('home')
97 came_from = default_came_from
97 elif parsed.netloc and request.host != parsed.netloc:
98 elif parsed.netloc and request.host != parsed.netloc:
98 log.error('Suspicious NETLOC detected %s for url %s server url '
99 log.error('Suspicious NETLOC detected %s for url %s server url '
99 'is: %s' % (parsed.netloc, parsed, request.host))
100 'is: %s' % (parsed.netloc, parsed, request.host))
100 came_from = url('home')
101 came_from = default_came_from
101 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
102 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
102 log.error('Header injection detected `%s` for url %s server url ' %
103 log.error('Header injection detected `%s` for url %s server url ' %
103 (parsed.path, parsed))
104 (parsed.path, parsed))
104 came_from = url('home')
105 came_from = default_came_from
105
106
106 return came_from or url('home')
107 return came_from or default_came_from
107
108
108
109
109 class LoginView(BaseAppView):
110 class LoginView(BaseAppView):
@@ -166,6 +167,15 b' class LoginView(BaseAppView):'
166 username=form_result['username'],
167 username=form_result['username'],
167 remember=form_result['remember'])
168 remember=form_result['remember'])
168 log.debug('Redirecting to "%s" after login.', c.came_from)
169 log.debug('Redirecting to "%s" after login.', c.came_from)
170
171 audit_user = audit_logger.UserWrap(
172 username=self.request.params.get('username'),
173 ip_addr=self.request.remote_addr)
174 action_data = {'user_agent': self.request.user_agent}
175 audit_logger.store_web(
176 'user.login.success', action_data=action_data,
177 user=audit_user, commit=True)
178
169 raise HTTPFound(c.came_from, headers=headers)
179 raise HTTPFound(c.came_from, headers=headers)
170 except formencode.Invalid as errors:
180 except formencode.Invalid as errors:
171 defaults = errors.value
181 defaults = errors.value
@@ -176,6 +186,14 b' class LoginView(BaseAppView):'
176 'errors': errors.error_dict,
186 'errors': errors.error_dict,
177 'defaults': defaults,
187 'defaults': defaults,
178 })
188 })
189
190 audit_user = audit_logger.UserWrap(
191 username=self.request.params.get('username'),
192 ip_addr=self.request.remote_addr)
193 action_data = {'user_agent': self.request.user_agent}
194 audit_logger.store_web(
195 'user.login.failure', action_data=action_data,
196 user=audit_user, commit=True)
179 return render_ctx
197 return render_ctx
180
198
181 except UserCreationError as e:
199 except UserCreationError as e:
@@ -191,8 +209,13 b' class LoginView(BaseAppView):'
191 def logout(self):
209 def logout(self):
192 auth_user = self._rhodecode_user
210 auth_user = self._rhodecode_user
193 log.info('Deleting session for user: `%s`', auth_user)
211 log.info('Deleting session for user: `%s`', auth_user)
212
213 action_data = {'user_agent': self.request.user_agent}
214 audit_logger.store_web(
215 'user.logout', action_data=action_data,
216 user=auth_user, commit=True)
194 self.session.delete()
217 self.session.delete()
195 return HTTPFound(url('home'))
218 return HTTPFound(h.route_path('home'))
196
219
197 @HasPermissionAnyDecorator(
220 @HasPermissionAnyDecorator(
198 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
221 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
@@ -338,6 +361,12 b' class LoginView(BaseAppView):'
338 form_result, password_reset_url)
361 form_result, password_reset_url)
339 # Display success message and redirect.
362 # Display success message and redirect.
340 self.session.flash(msg, queue='success')
363 self.session.flash(msg, queue='success')
364
365 action_data = {'email': user_email,
366 'user_agent': self.request.user_agent}
367 audit_logger.store_web(
368 'user.password.reset_request', action_data=action_data,
369 user=self._rhodecode_user, commit=True)
341 return HTTPFound(self.request.route_path('reset_password'))
370 return HTTPFound(self.request.route_path('reset_password'))
342
371
343 except formencode.Invalid as errors:
372 except formencode.Invalid as errors:
@@ -41,12 +41,45 b' def includeme(config):'
41 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
41 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
42 config.add_route(
42 config.add_route(
43 name='my_account_auth_tokens_add',
43 name='my_account_auth_tokens_add',
44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new',
44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
45 )
46 config.add_route(
45 config.add_route(
47 name='my_account_auth_tokens_delete',
46 name='my_account_auth_tokens_delete',
48 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete',
47 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
49 )
48
49 config.add_route(
50 name='my_account_emails',
51 pattern=ADMIN_PREFIX + '/my_account/emails')
52 config.add_route(
53 name='my_account_emails_add',
54 pattern=ADMIN_PREFIX + '/my_account/emails/new')
55 config.add_route(
56 name='my_account_emails_delete',
57 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
58
59 config.add_route(
60 name='my_account_repos',
61 pattern=ADMIN_PREFIX + '/my_account/repos')
62
63 config.add_route(
64 name='my_account_watched',
65 pattern=ADMIN_PREFIX + '/my_account/watched')
66
67 config.add_route(
68 name='my_account_perms',
69 pattern=ADMIN_PREFIX + '/my_account/perms')
70
71 config.add_route(
72 name='my_account_notifications',
73 pattern=ADMIN_PREFIX + '/my_account/notifications')
74
75 config.add_route(
76 name='my_account_notifications_toggle_visibility',
77 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
78
79 # channelstream test
80 config.add_route(
81 name='my_account_notifications_test_channelstream',
82 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
50
83
51 # Scan module for configuration decorators.
84 # Scan module for configuration decorators.
52 config.scan()
85 config.scan()
@@ -103,7 +103,7 b' class TestMyAccountAuthTokens(TestContro'
103
103
104 response = self.app.post(
104 response = self.app.post(
105 route_path('my_account_auth_tokens_delete'),
105 route_path('my_account_auth_tokens_delete'),
106 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
106 {'del_auth_token': keys[0].user_api_key_id, 'csrf_token': self.csrf_token})
107 assert_session_flash(response, 'Auth token successfully deleted')
107 assert_session_flash(response, 'Auth token successfully deleted')
108
108
109 user = User.get(user_id)
109 user = User.get(user_id)
@@ -132,6 +132,7 b' class TestMyAccountPassword(TestControll'
132 self.app.post(route_path('my_account_password'), form_data)
132 self.app.post(route_path('my_account_password'), form_data)
133
133
134 response = self.app.get(route_path('home'))
134 response = self.app.get(route_path('home'))
135 new_password_hash = response.session['rhodecode_user']['password']
135 session = response.get_session_from_response()
136 new_password_hash = session['rhodecode_user']['password']
136
137
137 assert old_password_hash != new_password_hash No newline at end of file
138 assert old_password_hash != new_password_hash
@@ -19,18 +19,28 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22
23
24 import formencode
23 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
26 from pyramid.view import view_config
25
27
26 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
27 from rhodecode import forms
29 from rhodecode import forms
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
33 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29 from rhodecode.lib import helpers as h
34 from rhodecode.lib.channelstream import channelstream_request, \
35 ChannelstreamException
30 from rhodecode.lib.utils2 import safe_int, md5
36 from rhodecode.lib.utils2 import safe_int, md5
31 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.db import (
39 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload)
32 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.scm import RepoList
33 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.model.repo import RepoModel
34 from rhodecode.model.validation_schema.schemas import user_schema
44 from rhodecode.model.validation_schema.schemas import user_schema
35
45
36 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
@@ -158,7 +168,7 b' class MyAccountView(BaseAppView):'
158 @NotAnonymous()
168 @NotAnonymous()
159 @CSRFRequired()
169 @CSRFRequired()
160 @view_config(
170 @view_config(
161 route_name='my_account_auth_tokens_add', request_method='POST')
171 route_name='my_account_auth_tokens_add', request_method='POST',)
162 def my_account_auth_tokens_add(self):
172 def my_account_auth_tokens_add(self):
163 _ = self.request.translate
173 _ = self.request.translate
164 c = self.load_default_context()
174 c = self.load_default_context()
@@ -169,7 +179,13 b' class MyAccountView(BaseAppView):'
169
179
170 token = AuthTokenModel().create(
180 token = AuthTokenModel().create(
171 c.user.user_id, description, lifetime, role)
181 c.user.user_id, description, lifetime, role)
182 token_data = token.get_api_data()
183
172 self.maybe_attach_token_scope(token)
184 self.maybe_attach_token_scope(token)
185 audit_logger.store_web(
186 'user.edit.token.add', action_data={
187 'data': {'token': token_data, 'user': 'self'}},
188 user=self._rhodecode_user, )
173 Session().commit()
189 Session().commit()
174
190
175 h.flash(_("Auth token successfully created"), category='success')
191 h.flash(_("Auth token successfully created"), category='success')
@@ -187,8 +203,197 b' class MyAccountView(BaseAppView):'
187 del_auth_token = self.request.POST.get('del_auth_token')
203 del_auth_token = self.request.POST.get('del_auth_token')
188
204
189 if del_auth_token:
205 if del_auth_token:
206 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
207 token_data = token.get_api_data()
208
190 AuthTokenModel().delete(del_auth_token, c.user.user_id)
209 AuthTokenModel().delete(del_auth_token, c.user.user_id)
210 audit_logger.store_web(
211 'user.edit.token.delete', action_data={
212 'data': {'token': token_data, 'user': 'self'}},
213 user=self._rhodecode_user,)
191 Session().commit()
214 Session().commit()
192 h.flash(_("Auth token successfully deleted"), category='success')
215 h.flash(_("Auth token successfully deleted"), category='success')
193
216
194 return HTTPFound(h.route_path('my_account_auth_tokens'))
217 return HTTPFound(h.route_path('my_account_auth_tokens'))
218
219 @LoginRequired()
220 @NotAnonymous()
221 @view_config(
222 route_name='my_account_emails', request_method='GET',
223 renderer='rhodecode:templates/admin/my_account/my_account.mako')
224 def my_account_emails(self):
225 _ = self.request.translate
226
227 c = self.load_default_context()
228 c.active = 'emails'
229
230 c.user_email_map = UserEmailMap.query()\
231 .filter(UserEmailMap.user == c.user).all()
232 return self._get_template_context(c)
233
234 @LoginRequired()
235 @NotAnonymous()
236 @CSRFRequired()
237 @view_config(
238 route_name='my_account_emails_add', request_method='POST')
239 def my_account_emails_add(self):
240 _ = self.request.translate
241 c = self.load_default_context()
242
243 email = self.request.POST.get('new_email')
244
245 try:
246 UserModel().add_extra_email(c.user.user_id, email)
247 audit_logger.store_web(
248 'user.edit.email.add', action_data={
249 'data': {'email': email, 'user': 'self'}},
250 user=self._rhodecode_user,)
251
252 Session().commit()
253 h.flash(_("Added new email address `%s` for user account") % email,
254 category='success')
255 except formencode.Invalid as error:
256 h.flash(h.escape(error.error_dict['email']), category='error')
257 except Exception:
258 log.exception("Exception in my_account_emails")
259 h.flash(_('An error occurred during email saving'),
260 category='error')
261 return HTTPFound(h.route_path('my_account_emails'))
262
263 @LoginRequired()
264 @NotAnonymous()
265 @CSRFRequired()
266 @view_config(
267 route_name='my_account_emails_delete', request_method='POST')
268 def my_account_emails_delete(self):
269 _ = self.request.translate
270 c = self.load_default_context()
271
272 del_email_id = self.request.POST.get('del_email_id')
273 if del_email_id:
274 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
275 UserModel().delete_extra_email(c.user.user_id, del_email_id)
276 audit_logger.store_web(
277 'user.edit.email.delete', action_data={
278 'data': {'email': email, 'user': 'self'}},
279 user=self._rhodecode_user,)
280 Session().commit()
281 h.flash(_("Email successfully deleted"),
282 category='success')
283 return HTTPFound(h.route_path('my_account_emails'))
284
285 @LoginRequired()
286 @NotAnonymous()
287 @CSRFRequired()
288 @view_config(
289 route_name='my_account_notifications_test_channelstream',
290 request_method='POST', renderer='json_ext')
291 def my_account_notifications_test_channelstream(self):
292 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
293 self._rhodecode_user.username, datetime.datetime.now())
294 payload = {
295 # 'channel': 'broadcast',
296 'type': 'message',
297 'timestamp': datetime.datetime.utcnow(),
298 'user': 'system',
299 'pm_users': [self._rhodecode_user.username],
300 'message': {
301 'message': message,
302 'level': 'info',
303 'topic': '/notifications'
304 }
305 }
306
307 registry = self.request.registry
308 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
309 channelstream_config = rhodecode_plugins.get('channelstream', {})
310
311 try:
312 channelstream_request(channelstream_config, [payload], '/message')
313 except ChannelstreamException as e:
314 log.exception('Failed to send channelstream data')
315 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
316 return {"response": 'Channelstream data sent. '
317 'You should see a new live message now.'}
318
319 def _load_my_repos_data(self, watched=False):
320 if watched:
321 admin = False
322 follows_repos = Session().query(UserFollowing)\
323 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
324 .options(joinedload(UserFollowing.follows_repository))\
325 .all()
326 repo_list = [x.follows_repository for x in follows_repos]
327 else:
328 admin = True
329 repo_list = Repository.get_all_repos(
330 user_id=self._rhodecode_user.user_id)
331 repo_list = RepoList(repo_list, perm_set=[
332 'repository.read', 'repository.write', 'repository.admin'])
333
334 repos_data = RepoModel().get_repos_as_dict(
335 repo_list=repo_list, admin=admin)
336 # json used to render the grid
337 return json.dumps(repos_data)
338
339 @LoginRequired()
340 @NotAnonymous()
341 @view_config(
342 route_name='my_account_repos', request_method='GET',
343 renderer='rhodecode:templates/admin/my_account/my_account.mako')
344 def my_account_repos(self):
345 c = self.load_default_context()
346 c.active = 'repos'
347
348 # json used to render the grid
349 c.data = self._load_my_repos_data()
350 return self._get_template_context(c)
351
352 @LoginRequired()
353 @NotAnonymous()
354 @view_config(
355 route_name='my_account_watched', request_method='GET',
356 renderer='rhodecode:templates/admin/my_account/my_account.mako')
357 def my_account_watched(self):
358 c = self.load_default_context()
359 c.active = 'watched'
360
361 # json used to render the grid
362 c.data = self._load_my_repos_data(watched=True)
363 return self._get_template_context(c)
364
365 @LoginRequired()
366 @NotAnonymous()
367 @view_config(
368 route_name='my_account_perms', request_method='GET',
369 renderer='rhodecode:templates/admin/my_account/my_account.mako')
370 def my_account_perms(self):
371 c = self.load_default_context()
372 c.active = 'perms'
373
374 c.perm_user = c.auth_user
375 return self._get_template_context(c)
376
377 @LoginRequired()
378 @NotAnonymous()
379 @view_config(
380 route_name='my_account_notifications', request_method='GET',
381 renderer='rhodecode:templates/admin/my_account/my_account.mako')
382 def my_notifications(self):
383 c = self.load_default_context()
384 c.active = 'notifications'
385
386 return self._get_template_context(c)
387
388 @LoginRequired()
389 @NotAnonymous()
390 @CSRFRequired()
391 @view_config(
392 route_name='my_account_notifications_toggle_visibility',
393 request_method='POST', renderer='json_ext')
394 def my_notifications_toggle_visibility(self):
395 user = self._rhodecode_db_user
396 new_status = not user.user_data.get('notification_status', True)
397 user.update_userdata(notification_status=new_status)
398 Session().commit()
399 return user.user_data['notification_status'] No newline at end of file
@@ -17,30 +17,137 b''
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
20
21
21
22
22 def includeme(config):
23 def includeme(config):
23
24
25 # Summary
26 # NOTE(marcink): one additional route is defined in very bottom, catch
27 # all pattern
28 config.add_route(
29 name='repo_summary_explicit',
30 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
31 config.add_route(
32 name='repo_summary_commits',
33 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
34
35 # repo commits
36 config.add_route(
37 name='repo_commit',
38 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
39
40 # refs data
41 config.add_route(
42 name='repo_refs_data',
43 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
44
45 config.add_route(
46 name='repo_refs_changelog_data',
47 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
48
49 config.add_route(
50 name='repo_stats',
51 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
52
53 # Tags
54 config.add_route(
55 name='tags_home',
56 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
57
58 # Branches
59 config.add_route(
60 name='branches_home',
61 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
62
63 config.add_route(
64 name='bookmarks_home',
65 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
66
67 # Pull Requests
68 config.add_route(
69 name='pullrequest_show',
70 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}',
71 repo_route=True)
72
73 config.add_route(
74 name='pullrequest_show_all',
75 pattern='/{repo_name:.*?[^/]}/pull-request',
76 repo_route=True, repo_accepted_types=['hg', 'git'])
77
78 config.add_route(
79 name='pullrequest_show_all_data',
80 pattern='/{repo_name:.*?[^/]}/pull-request-data',
81 repo_route=True, repo_accepted_types=['hg', 'git'])
82
83 # Settings
84 config.add_route(
85 name='edit_repo',
86 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
87
88 # Settings advanced
89 config.add_route(
90 name='edit_repo_advanced',
91 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
92 config.add_route(
93 name='edit_repo_advanced_delete',
94 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
95 config.add_route(
96 name='edit_repo_advanced_locking',
97 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
98 config.add_route(
99 name='edit_repo_advanced_journal',
100 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
101 config.add_route(
102 name='edit_repo_advanced_fork',
103 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
104
105 # Caches
106 config.add_route(
107 name='edit_repo_caches',
108 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
109
110 # Permissions
111 config.add_route(
112 name='edit_repo_perms',
113 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
114
115 # Repo Review Rules
116 config.add_route(
117 name='repo_reviewers',
118 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
119
120 config.add_route(
121 name='repo_default_reviewers_data',
122 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
123
124 # Maintenance
24 config.add_route(
125 config.add_route(
25 name='repo_maintenance',
126 name='repo_maintenance',
26 pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
127 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
27
128
28 config.add_route(
129 config.add_route(
29 name='repo_maintenance_execute',
130 name='repo_maintenance_execute',
30 pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
131 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
31
32
132
33 # Strip
133 # Strip
34 config.add_route(
134 config.add_route(
35 name='strip',
135 name='strip',
36 pattern='/{repo_name:.*?[^/]}/strip', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
37
137
38 config.add_route(
138 config.add_route(
39 name='strip_check',
139 name='strip_check',
40 pattern='/{repo_name:.*?[^/]}/strip_check', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
41
141
42 config.add_route(
142 config.add_route(
43 name='strip_execute',
143 name='strip_execute',
44 pattern='/{repo_name:.*?[^/]}/strip_execute', repo_route=True)
144 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
145
146 # NOTE(marcink): needs to be at the end for catch-all
147 add_route_with_slash(
148 config,
149 name='repo_summary',
150 pattern='/{repo_name:.*?[^/]}', repo_route=True)
151
45 # Scan module for configuration decorators.
152 # Scan module for configuration decorators.
46 config.scan()
153 config.scan()
@@ -18,24 +18,35 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 from rhodecode.model.db import Repository
22 from rhodecode.model.db import Repository
22 from rhodecode.tests import *
23
23
24
24
25 class TestBookmarksController(TestController):
25 def route_path(name, params=None, **kwargs):
26 import urllib
27
28 base_url = {
29 'bookmarks_home': '/{repo_name}/bookmarks',
30 }[name].format(**kwargs)
31
32 if params:
33 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
34 return base_url
35
36
37 @pytest.mark.usefixtures('autologin_user', 'app')
38 class TestBookmarks(object):
26
39
27 def test_index(self, backend):
40 def test_index(self, backend):
28 self.log_user()
29 if backend.alias == 'hg':
41 if backend.alias == 'hg':
30 response = self.app.get(url(controller='bookmarks',
42 response = self.app.get(
31 action='index',
43 route_path('bookmarks_home', repo_name=backend.repo_name))
32 repo_name=backend.repo_name))
33
44
34 repo = Repository.get_by_repo_name(backend.repo_name)
45 repo = Repository.get_by_repo_name(backend.repo_name)
35 for commit_id, obj_name in repo.scm_instance().bookmarks.items():
46 for commit_id, obj_name in repo.scm_instance().bookmarks.items():
36 assert commit_id in response
47 assert commit_id in response
37 assert obj_name in response
48 assert obj_name in response
38 else:
49 else:
39 self.app.get(url(controller='bookmarks',
50 self.app.get(
40 action='index',
51 route_path('bookmarks_home', repo_name=backend.repo_name),
41 repo_name=backend.repo_name), status=404) No newline at end of file
52 status=404)
@@ -18,17 +18,28 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 from rhodecode.model.db import Repository
22 from rhodecode.model.db import Repository
22 from rhodecode.tests import *
23
23
24
24
25 class TestBranchesController(TestController):
25 def route_path(name, params=None, **kwargs):
26 import urllib
27
28 base_url = {
29 'branches_home': '/{repo_name}/branches',
30 }[name].format(**kwargs)
31
32 if params:
33 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
34 return base_url
35
36
37 @pytest.mark.usefixtures('autologin_user', 'app')
38 class TestBranchesController(object):
26
39
27 def test_index(self, backend):
40 def test_index(self, backend):
28 self.log_user()
41 response = self.app.get(
29 response = self.app.get(url(controller='branches',
42 route_path('branches_home', repo_name=backend.repo_name))
30 action='index',
31 repo_name=backend.repo_name))
32
43
33 repo = Repository.get_by_repo_name(backend.repo_name)
44 repo = Repository.get_by_repo_name(backend.repo_name)
34
45
@@ -23,16 +23,16 b' import re'
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.controllers import summary
26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.compat import OrderedDict
28 from rhodecode.lib.compat import OrderedDict
29 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model.db import Repository
31 from rhodecode.model.db import Repository
31 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
32 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
34 from rhodecode.tests import (
35 from rhodecode.tests import assert_session_flash
35 TestController, url, HG_REPO, assert_session_flash)
36 from rhodecode.tests.fixture import Fixture
36 from rhodecode.tests.fixture import Fixture
37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
38
38
@@ -40,14 +40,31 b' from rhodecode.tests.utils import Assert'
40 fixture = Fixture()
40 fixture = Fixture()
41
41
42
42
43 class TestSummaryController(TestController):
43 def route_path(name, params=None, **kwargs):
44 def test_index(self, backend):
44 import urllib
45 self.log_user()
45
46 base_url = {
47 'repo_summary': '/{repo_name}',
48 'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
49 'repo_refs_data': '/{repo_name}/refs-data',
50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog'
51
52 }[name].format(**kwargs)
53
54 if params:
55 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
56 return base_url
57
58
59 @pytest.mark.usefixtures('app')
60 class TestSummaryView(object):
61 def test_index(self, autologin_user, backend, http_host_only_stub):
46 repo_id = backend.repo.repo_id
62 repo_id = backend.repo.repo_id
47 repo_name = backend.repo_name
63 repo_name = backend.repo_name
48 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
64 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
49 return_value=False):
65 return_value=False):
50 response = self.app.get(url('summary_home', repo_name=repo_name))
66 response = self.app.get(
67 route_path('repo_summary', repo_name=repo_name))
51
68
52 # repo type
69 # repo type
53 response.mustcontain(
70 response.mustcontain(
@@ -61,46 +78,47 b' class TestSummaryController(TestControll'
61 # clone url...
78 # clone url...
62 response.mustcontain(
79 response.mustcontain(
63 'id="clone_url" readonly="readonly"'
80 'id="clone_url" readonly="readonly"'
64 ' value="http://test_admin@test.example.com:80/%s"' % (repo_name, ))
81 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
65 response.mustcontain(
82 response.mustcontain(
66 'id="clone_url_id" readonly="readonly"'
83 'id="clone_url_id" readonly="readonly"'
67 ' value="http://test_admin@test.example.com:80/_%s"' % (repo_id, ))
84 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
68
85
69 def test_index_svn_without_proxy(self, backend_svn):
86 def test_index_svn_without_proxy(
70 self.log_user()
87 self, autologin_user, backend_svn, http_host_only_stub):
71 repo_id = backend_svn.repo.repo_id
88 repo_id = backend_svn.repo.repo_id
72 repo_name = backend_svn.repo_name
89 repo_name = backend_svn.repo_name
73 response = self.app.get(url('summary_home', repo_name=repo_name))
90 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
74 # clone url...
91 # clone url...
75 response.mustcontain(
92 response.mustcontain(
76 'id="clone_url" disabled'
93 'id="clone_url" disabled'
77 ' value="http://test_admin@test.example.com:80/%s"' % (repo_name, ))
94 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
78 response.mustcontain(
95 response.mustcontain(
79 'id="clone_url_id" disabled'
96 'id="clone_url_id" disabled'
80 ' value="http://test_admin@test.example.com:80/_%s"' % (repo_id, ))
97 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
81
98
82 def test_index_with_trailing_slash(self, autologin_user, backend):
99 def test_index_with_trailing_slash(
100 self, autologin_user, backend, http_host_only_stub):
101
83 repo_id = backend.repo.repo_id
102 repo_id = backend.repo.repo_id
84 repo_name = backend.repo_name
103 repo_name = backend.repo_name
85 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
104 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
86 return_value=False):
105 return_value=False):
87 response = self.app.get(
106 response = self.app.get(
88 url('summary_home', repo_name=repo_name) + '/',
107 route_path('repo_summary', repo_name=repo_name) + '/',
89 status=200)
108 status=200)
90
109
91 # clone url...
110 # clone url...
92 response.mustcontain(
111 response.mustcontain(
93 'id="clone_url" readonly="readonly"'
112 'id="clone_url" readonly="readonly"'
94 ' value="http://test_admin@test.example.com:80/%s"' % (repo_name, ))
113 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
95 response.mustcontain(
114 response.mustcontain(
96 'id="clone_url_id" readonly="readonly"'
115 'id="clone_url_id" readonly="readonly"'
97 ' value="http://test_admin@test.example.com:80/_%s"' % (repo_id, ))
116 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
98
117
99 def test_index_by_id(self, backend):
118 def test_index_by_id(self, autologin_user, backend):
100 self.log_user()
101 repo_id = backend.repo.repo_id
119 repo_id = backend.repo.repo_id
102 response = self.app.get(url(
120 response = self.app.get(
103 'summary_home', repo_name='_%s' % (repo_id,)))
121 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
104
122
105 # repo type
123 # repo type
106 response.mustcontain(
124 response.mustcontain(
@@ -111,10 +129,9 b' class TestSummaryController(TestControll'
111 """<i class="icon-unlock-alt">"""
129 """<i class="icon-unlock-alt">"""
112 )
130 )
113
131
114 def test_index_by_repo_having_id_path_in_name_hg(self):
132 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
115 self.log_user()
116 fixture.create_repo(name='repo_1')
133 fixture.create_repo(name='repo_1')
117 response = self.app.get(url('summary_home', repo_name='repo_1'))
134 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
118
135
119 try:
136 try:
120 response.mustcontain("repo_1")
137 response.mustcontain("repo_1")
@@ -122,11 +139,11 b' class TestSummaryController(TestControll'
122 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
139 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
123 Session().commit()
140 Session().commit()
124
141
125 def test_index_with_anonymous_access_disabled(self):
142 def test_index_with_anonymous_access_disabled(
126 with fixture.anon_access(False):
143 self, backend, disable_anonymous_user):
127 response = self.app.get(url('summary_home', repo_name=HG_REPO),
144 response = self.app.get(
128 status=302)
145 route_path('repo_summary', repo_name=backend.repo_name), status=302)
129 assert 'login' in response.location
146 assert 'login' in response.location
130
147
131 def _enable_stats(self, repo):
148 def _enable_stats(self, repo):
132 r = Repository.get_by_repo_name(repo)
149 r = Repository.get_by_repo_name(repo)
@@ -173,17 +190,15 b' class TestSummaryController(TestControll'
173 },
190 },
174 }
191 }
175
192
176 def test_repo_stats(self, backend, xhr_header):
193 def test_repo_stats(self, autologin_user, backend, xhr_header):
177 self.log_user()
178 response = self.app.get(
194 response = self.app.get(
179 url('repo_stats',
195 route_path(
180 repo_name=backend.repo_name, commit_id='tip'),
196 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
181 extra_environ=xhr_header,
197 extra_environ=xhr_header,
182 status=200)
198 status=200)
183 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
199 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
184
200
185 def test_repo_stats_code_stats_enabled(self, backend, xhr_header):
201 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
186 self.log_user()
187 repo_name = backend.repo_name
202 repo_name = backend.repo_name
188
203
189 # codes stats
204 # codes stats
@@ -191,8 +206,8 b' class TestSummaryController(TestControll'
191 ScmModel().mark_for_invalidation(repo_name)
206 ScmModel().mark_for_invalidation(repo_name)
192
207
193 response = self.app.get(
208 response = self.app.get(
194 url('repo_stats',
209 route_path(
195 repo_name=backend.repo_name, commit_id='tip'),
210 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
196 extra_environ=xhr_header,
211 extra_environ=xhr_header,
197 status=200)
212 status=200)
198
213
@@ -203,7 +218,7 b' class TestSummaryController(TestControll'
203
218
204 def test_repo_refs_data(self, backend):
219 def test_repo_refs_data(self, backend):
205 response = self.app.get(
220 response = self.app.get(
206 url('repo_refs_data', repo_name=backend.repo_name),
221 route_path('repo_refs_data', repo_name=backend.repo_name),
207 status=200)
222 status=200)
208
223
209 # Ensure that there is the correct amount of items in the result
224 # Ensure that there is the correct amount of items in the result
@@ -220,72 +235,68 b' class TestSummaryController(TestControll'
220 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
235 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
221
236
222 with scm_patcher:
237 with scm_patcher:
223 response = self.app.get(url('summary_home', repo_name=repo_name))
238 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
224 assert_response = AssertResponse(response)
239 assert_response = AssertResponse(response)
225 assert_response.element_contains(
240 assert_response.element_contains(
226 '.main .alert-warning strong', 'Missing requirements')
241 '.main .alert-warning strong', 'Missing requirements')
227 assert_response.element_contains(
242 assert_response.element_contains(
228 '.main .alert-warning',
243 '.main .alert-warning',
229 'These commits cannot be displayed, because this repository'
244 'Commits cannot be displayed, because this repository '
230 ' uses the Mercurial largefiles extension, which was not enabled.')
245 'uses one or more extensions, which was not enabled.')
231
246
232 def test_missing_requirements_page_does_not_contains_switch_to(
247 def test_missing_requirements_page_does_not_contains_switch_to(
233 self, backend):
248 self, autologin_user, backend):
234 self.log_user()
235 repo_name = backend.repo_name
249 repo_name = backend.repo_name
236 scm_patcher = mock.patch.object(
250 scm_patcher = mock.patch.object(
237 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
238
252
239 with scm_patcher:
253 with scm_patcher:
240 response = self.app.get(url('summary_home', repo_name=repo_name))
254 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
241 response.mustcontain(no='Switch To')
255 response.mustcontain(no='Switch To')
242
256
243
257
244 @pytest.mark.usefixtures('pylonsapp')
258 @pytest.mark.usefixtures('app')
245 class TestSwitcherReferenceData:
259 class TestRepoLocation(object):
246
260
247 def test_creates_reference_urls_based_on_name(self):
261 @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii'])
248 references = {
262 def test_manual_delete(self, autologin_user, backend, suffix, csrf_token):
249 'name': 'commit_id',
263 repo = backend.create_repo(name_suffix=suffix)
250 }
264 repo_name = repo.repo_name
251 controller = summary.SummaryController()
265
252 is_svn = False
266 # delete from file system
253 result = controller._switcher_reference_data(
267 RepoModel()._delete_filesystem_repo(repo)
254 'repo_name', references, is_svn)
255 expected_url = h.url(
256 'files_home', repo_name='repo_name', revision='name',
257 at='name')
258 assert result[0]['files_url'] == expected_url
259
268
260 def test_urls_contain_commit_id_if_slash_in_name(self):
269 # test if the repo is still in the database
261 references = {
270 new_repo = RepoModel().get_by_repo_name(repo_name)
262 'name/with/slash': 'commit_id',
271 assert new_repo.repo_name == repo_name
263 }
264 controller = summary.SummaryController()
265 is_svn = False
266 result = controller._switcher_reference_data(
267 'repo_name', references, is_svn)
268 expected_url = h.url(
269 'files_home', repo_name='repo_name', revision='commit_id',
270 at='name/with/slash')
271 assert result[0]['files_url'] == expected_url
272
272
273 def test_adds_reference_to_path_for_svn(self):
273 # check if repo is not in the filesystem
274 references = {
274 assert not repo_on_filesystem(repo_name)
275 'name/with/slash': 'commit_id',
275 self.assert_repo_not_found_redirect(repo_name)
276 }
276
277 controller = summary.SummaryController()
277 def assert_repo_not_found_redirect(self, repo_name):
278 is_svn = True
278 # run the check page that triggers the other flash message
279 result = controller._switcher_reference_data(
279 response = self.app.get(h.url('repo_check_home', repo_name=repo_name))
280 'repo_name', references, is_svn)
280 assert_session_flash(
281 expected_url = h.url(
281 response, 'The repository at %s cannot be located.' % repo_name)
282 'files_home', repo_name='repo_name', f_path='name/with/slash',
283 revision='commit_id', at='name/with/slash')
284 assert result[0]['files_url'] == expected_url
285
282
286
283
287 @pytest.mark.usefixtures('pylonsapp')
284 @pytest.fixture()
288 class TestCreateReferenceData:
285 def summary_view(context_stub, request_stub, user_util):
286 """
287 Bootstrap view to test the view functions
288 """
289 request_stub.matched_route = AttributeDict(name='test_view')
290
291 request_stub.user = user_util.create_user().AuthUser
292 request_stub.db_repo = user_util.create_repo()
293
294 view = RepoSummaryView(context=context_stub, request=request_stub)
295 return view
296
297
298 @pytest.mark.usefixtures('app')
299 class TestCreateReferenceData(object):
289
300
290 @pytest.fixture
301 @pytest.fixture
291 def example_refs(self):
302 def example_refs(self):
@@ -296,14 +307,13 b' class TestCreateReferenceData:'
296 ]
307 ]
297 return example_refs
308 return example_refs
298
309
299 def test_generates_refs_based_on_commit_ids(self, example_refs):
310 def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view):
300 repo = mock.Mock()
311 repo = mock.Mock()
301 repo.name = 'test-repo'
312 repo.name = 'test-repo'
302 repo.alias = 'git'
313 repo.alias = 'git'
303 full_repo_name = 'pytest-repo-group/' + repo.name
314 full_repo_name = 'pytest-repo-group/' + repo.name
304 controller = summary.SummaryController()
305
315
306 result = controller._create_reference_data(
316 result = summary_view._create_reference_data(
307 repo, full_repo_name, example_refs)
317 repo, full_repo_name, example_refs)
308
318
309 expected_files_url = '/{}/files/'.format(full_repo_name)
319 expected_files_url = '/{}/files/'.format(full_repo_name)
@@ -332,13 +342,13 b' class TestCreateReferenceData:'
332 }]
342 }]
333 assert result == expected_result
343 assert result == expected_result
334
344
335 def test_generates_refs_with_path_for_svn(self, example_refs):
345 def test_generates_refs_with_path_for_svn(self, example_refs, summary_view):
336 repo = mock.Mock()
346 repo = mock.Mock()
337 repo.name = 'test-repo'
347 repo.name = 'test-repo'
338 repo.alias = 'svn'
348 repo.alias = 'svn'
339 full_repo_name = 'pytest-repo-group/' + repo.name
349 full_repo_name = 'pytest-repo-group/' + repo.name
340 controller = summary.SummaryController()
350
341 result = controller._create_reference_data(
351 result = summary_view._create_reference_data(
342 repo, full_repo_name, example_refs)
352 repo, full_repo_name, example_refs)
343
353
344 expected_files_url = '/{}/files/'.format(full_repo_name)
354 expected_files_url = '/{}/files/'.format(full_repo_name)
@@ -372,35 +382,9 b' class TestCreateReferenceData:'
372 assert result == expected_result
382 assert result == expected_result
373
383
374
384
375 @pytest.mark.usefixtures("app")
385 class TestCreateFilesUrl(object):
376 class TestRepoLocation:
377
378 @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii'])
379 def test_manual_delete(self, autologin_user, backend, suffix, csrf_token):
380 repo = backend.create_repo(name_suffix=suffix)
381 repo_name = repo.repo_name
382
383 # delete from file system
384 RepoModel()._delete_filesystem_repo(repo)
385
386 # test if the repo is still in the database
387 new_repo = RepoModel().get_by_repo_name(repo_name)
388 assert new_repo.repo_name == repo_name
389
386
390 # check if repo is not in the filesystem
387 def test_creates_non_svn_url(self, summary_view):
391 assert not repo_on_filesystem(repo_name)
392 self.assert_repo_not_found_redirect(repo_name)
393
394 def assert_repo_not_found_redirect(self, repo_name):
395 # run the check page that triggers the other flash message
396 response = self.app.get(url('repo_check_home', repo_name=repo_name))
397 assert_session_flash(
398 response, 'The repository at %s cannot be located.' % repo_name)
399
400
401 class TestCreateFilesUrl(object):
402 def test_creates_non_svn_url(self):
403 controller = summary.SummaryController()
404 repo = mock.Mock()
388 repo = mock.Mock()
405 repo.name = 'abcde'
389 repo.name = 'abcde'
406 full_repo_name = 'test-repo-group/' + repo.name
390 full_repo_name = 'test-repo-group/' + repo.name
@@ -408,16 +392,15 b' class TestCreateFilesUrl(object):'
408 raw_id = 'deadbeef0123456789'
392 raw_id = 'deadbeef0123456789'
409 is_svn = False
393 is_svn = False
410
394
411 with mock.patch.object(summary.h, 'url') as url_mock:
395 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
412 result = controller._create_files_url(
396 result = summary_view._create_files_url(
413 repo, full_repo_name, ref_name, raw_id, is_svn)
397 repo, full_repo_name, ref_name, raw_id, is_svn)
414 url_mock.assert_called_once_with(
398 url_mock.assert_called_once_with(
415 'files_home', repo_name=full_repo_name, f_path='',
399 'files_home', repo_name=full_repo_name, f_path='',
416 revision=ref_name, at=ref_name)
400 revision=ref_name, at=ref_name)
417 assert result == url_mock.return_value
401 assert result == url_mock.return_value
418
402
419 def test_creates_svn_url(self):
403 def test_creates_svn_url(self, summary_view):
420 controller = summary.SummaryController()
421 repo = mock.Mock()
404 repo = mock.Mock()
422 repo.name = 'abcde'
405 repo.name = 'abcde'
423 full_repo_name = 'test-repo-group/' + repo.name
406 full_repo_name = 'test-repo-group/' + repo.name
@@ -425,16 +408,15 b' class TestCreateFilesUrl(object):'
425 raw_id = 'deadbeef0123456789'
408 raw_id = 'deadbeef0123456789'
426 is_svn = True
409 is_svn = True
427
410
428 with mock.patch.object(summary.h, 'url') as url_mock:
411 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
429 result = controller._create_files_url(
412 result = summary_view._create_files_url(
430 repo, full_repo_name, ref_name, raw_id, is_svn)
413 repo, full_repo_name, ref_name, raw_id, is_svn)
431 url_mock.assert_called_once_with(
414 url_mock.assert_called_once_with(
432 'files_home', repo_name=full_repo_name, f_path=ref_name,
415 'files_home', repo_name=full_repo_name, f_path=ref_name,
433 revision=raw_id, at=ref_name)
416 revision=raw_id, at=ref_name)
434 assert result == url_mock.return_value
417 assert result == url_mock.return_value
435
418
436 def test_name_has_slashes(self):
419 def test_name_has_slashes(self, summary_view):
437 controller = summary.SummaryController()
438 repo = mock.Mock()
420 repo = mock.Mock()
439 repo.name = 'abcde'
421 repo.name = 'abcde'
440 full_repo_name = 'test-repo-group/' + repo.name
422 full_repo_name = 'test-repo-group/' + repo.name
@@ -442,8 +424,8 b' class TestCreateFilesUrl(object):'
442 raw_id = 'deadbeef0123456789'
424 raw_id = 'deadbeef0123456789'
443 is_svn = False
425 is_svn = False
444
426
445 with mock.patch.object(summary.h, 'url') as url_mock:
427 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
446 result = controller._create_files_url(
428 result = summary_view._create_files_url(
447 repo, full_repo_name, ref_name, raw_id, is_svn)
429 repo, full_repo_name, ref_name, raw_id, is_svn)
448 url_mock.assert_called_once_with(
430 url_mock.assert_called_once_with(
449 'files_home', repo_name=full_repo_name, f_path='', revision=raw_id,
431 'files_home', repo_name=full_repo_name, f_path='', revision=raw_id,
@@ -462,42 +444,39 b' class TestReferenceItems(object):'
462 def _format_function(name, id_):
444 def _format_function(name, id_):
463 return 'format_function_{}_{}'.format(name, id_)
445 return 'format_function_{}_{}'.format(name, id_)
464
446
465 def test_creates_required_amount_of_items(self):
447 def test_creates_required_amount_of_items(self, summary_view):
466 amount = 100
448 amount = 100
467 refs = {
449 refs = {
468 'ref{}'.format(i): '{0:040d}'.format(i)
450 'ref{}'.format(i): '{0:040d}'.format(i)
469 for i in range(amount)
451 for i in range(amount)
470 }
452 }
471
453
472 controller = summary.SummaryController()
454 url_patcher = mock.patch.object(summary_view, '_create_files_url')
473
455 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
474 url_patcher = mock.patch.object(
456 return_value=False)
475 controller, '_create_files_url')
476 svn_patcher = mock.patch.object(
477 summary.h, 'is_svn', return_value=False)
478
457
479 with url_patcher as url_mock, svn_patcher:
458 with url_patcher as url_mock, svn_patcher:
480 result = controller._create_reference_items(
459 result = summary_view._create_reference_items(
481 self.repo, self.repo_full_name, refs, self.ref_type,
460 self.repo, self.repo_full_name, refs, self.ref_type,
482 self._format_function)
461 self._format_function)
483 assert len(result) == amount
462 assert len(result) == amount
484 assert url_mock.call_count == amount
463 assert url_mock.call_count == amount
485
464
486 def test_single_item_details(self):
465 def test_single_item_details(self, summary_view):
487 ref_name = 'ref1'
466 ref_name = 'ref1'
488 ref_id = 'deadbeef'
467 ref_id = 'deadbeef'
489 refs = {
468 refs = {
490 ref_name: ref_id
469 ref_name: ref_id
491 }
470 }
492
471
493 controller = summary.SummaryController()
472 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
473 return_value=False)
474
494 url_patcher = mock.patch.object(
475 url_patcher = mock.patch.object(
495 controller, '_create_files_url', return_value=self.fake_url)
476 summary_view, '_create_files_url', return_value=self.fake_url)
496 svn_patcher = mock.patch.object(
497 summary.h, 'is_svn', return_value=False)
498
477
499 with url_patcher as url_mock, svn_patcher:
478 with url_patcher as url_mock, svn_patcher:
500 result = controller._create_reference_items(
479 result = summary_view._create_reference_items(
501 self.repo, self.repo_full_name, refs, self.ref_type,
480 self.repo, self.repo_full_name, refs, self.ref_type,
502 self._format_function)
481 self._format_function)
503
482
@@ -18,16 +18,27 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 from rhodecode.model.db import Repository
22 from rhodecode.model.db import Repository
22 from rhodecode.tests import *
23
23
24
24
25 class TestTagsController(TestController):
25 def route_path(name, params=None, **kwargs):
26 import urllib
27
28 base_url = {
29 'tags_home': '/{repo_name}/tags',
30 }[name].format(**kwargs)
31
32 if params:
33 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
34 return base_url
35
36
37 @pytest.mark.usefixtures('autologin_user', 'app')
38 class TestTagsController(object):
26 def test_index(self, backend):
39 def test_index(self, backend):
27 self.log_user()
40 response = self.app.get(
28 response = self.app.get(url(controller='tags',
41 route_path('tags_home', repo_name=backend.repo_name))
29 action='index',
30 repo_name=backend.repo_name))
31
42
32 repo = Repository.get_by_repo_name(backend.repo_name)
43 repo = Repository.get_by_repo_name(backend.repo_name)
33
44
@@ -41,7 +41,6 b' class RepoMaintenanceView(RepoAppView):'
41 return c
41 return c
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @NotAnonymous()
45 @HasRepoPermissionAnyDecorator('repository.admin')
44 @HasRepoPermissionAnyDecorator('repository.admin')
46 @view_config(
45 @view_config(
47 route_name='repo_maintenance', request_method='GET',
46 route_name='repo_maintenance', request_method='GET',
@@ -54,7 +53,6 b' class RepoMaintenanceView(RepoAppView):'
54 return self._get_template_context(c)
53 return self._get_template_context(c)
55
54
56 @LoginRequired()
55 @LoginRequired()
57 @NotAnonymous()
58 @HasRepoPermissionAnyDecorator('repository.admin')
56 @HasRepoPermissionAnyDecorator('repository.admin')
59 @view_config(
57 @view_config(
60 route_name='repo_maintenance_execute', request_method='GET',
58 route_name='repo_maintenance_execute', request_method='GET',
@@ -1,6 +1,6 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
@@ -22,9 +22,11 b' import logging'
22 from pyramid.view import view_config
22 from pyramid.view import view_config
23
23
24 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib import audit_logger
26 from rhodecode.lib import helpers as h
25 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
26 NotAnonymous)
28 NotAnonymous, CSRFRequired)
27
29 from rhodecode.lib.ext_json import json
28
30
29 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
30
32
@@ -40,7 +42,6 b' class StripView(RepoAppView):'
40 return c
42 return c
41
43
42 @LoginRequired()
44 @LoginRequired()
43 @NotAnonymous()
44 @HasRepoPermissionAnyDecorator('repository.admin')
45 @HasRepoPermissionAnyDecorator('repository.admin')
45 @view_config(
46 @view_config(
46 route_name='strip', request_method='GET',
47 route_name='strip', request_method='GET',
@@ -53,41 +54,39 b' class StripView(RepoAppView):'
53 return self._get_template_context(c)
54 return self._get_template_context(c)
54
55
55 @LoginRequired()
56 @LoginRequired()
56 @NotAnonymous()
57 @HasRepoPermissionAnyDecorator('repository.admin')
57 @HasRepoPermissionAnyDecorator('repository.admin')
58 @CSRFRequired()
58 @view_config(
59 @view_config(
59 route_name='strip_check', request_method='POST',
60 route_name='strip_check', request_method='POST',
60 renderer='json', xhr=True
61 renderer='json', xhr=True)
61 )
62 def strip_check(self):
62 def strip_check(self):
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
64 data = {}
64 data = {}
65 rp = self.request.POST
65 rp = self.request.POST
66 for i in range(1, 11):
66 for i in range(1, 11):
67 chset = 'changeset_id-%d'%(i,)
67 chset = 'changeset_id-%d' % (i,)
68 check = rp.get(chset)
68 check = rp.get(chset)
69
69 if check:
70 if check:
70 data[i] = self.db_repo.get_changeset(rp[chset])
71 data[i] = self.db_repo.get_changeset(rp[chset])
71 if isinstance(data[i], EmptyCommit):
72 if isinstance(data[i], EmptyCommit):
72 data[i] = {'rev': None, 'commit': rp[chset]}
73 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
73 else:
74 else:
74 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch, 'author': data[i].author,
75 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
76 'author': data[i].author,
75 'comment': data[i].message}
77 'comment': data[i].message}
76 else:
78 else:
77 break
79 break
78 return data
80 return data
79
81
80 @LoginRequired()
82 @LoginRequired()
81 @NotAnonymous()
82 @HasRepoPermissionAnyDecorator('repository.admin')
83 @HasRepoPermissionAnyDecorator('repository.admin')
84 @CSRFRequired()
83 @view_config(
85 @view_config(
84 route_name='strip_execute', request_method='POST',
86 route_name='strip_execute', request_method='POST',
85 renderer='json', xhr=True
87 renderer='json', xhr=True)
86 )
87 def strip_execute(self):
88 def strip_execute(self):
88
89 from rhodecode.model.scm import ScmModel
89 from rhodecode.model.scm import ScmModel
90 from rhodecode.lib.ext_json import json
91
90
92 c = self.load_default_context()
91 c = self.load_default_context()
93 user = self._rhodecode_user
92 user = self._rhodecode_user
@@ -95,16 +94,23 b' class StripView(RepoAppView):'
95 data = {}
94 data = {}
96 for idx in rp:
95 for idx in rp:
97 commit = json.loads(rp[idx])
96 commit = json.loads(rp[idx])
98 #If someone put two times the same branch
97 # If someone put two times the same branch
99 if commit['branch'] in data.keys():
98 if commit['branch'] in data.keys():
100 continue
99 continue
101 try:
100 try:
102 ScmModel().strip(repo=c.repo_info,
101 ScmModel().strip(
103 commit_id=commit['rev'], branch=commit['branch'])
102 repo=c.repo_info,
104 log.info('Stripped commit %s from repo `%s` by %s' % (commit['rev'], c.repo_info.repo_name, user))
103 commit_id=commit['rev'], branch=commit['branch'])
104 log.info('Stripped commit %s from repo `%s` by %s' % (
105 commit['rev'], c.repo_info.repo_name, user))
105 data[commit['rev']] = True
106 data[commit['rev']] = True
106 except Exception, e:
107
108 audit_logger.store_web(
109 'repo.commit.strip', action_data={'commit_id': commit['rev']},
110 repo=self.db_repo, user=self._rhodecode_user, commit=True)
111
112 except Exception as e:
107 data[commit['rev']] = False
113 data[commit['rev']] = False
108 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (commit['rev'],
114 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
109 c.repo_info.repo_name, user, e.message))
115 commit['rev'], self.db_repo_name, user, e.message))
110 return data
116 return data
@@ -564,8 +564,6 b' def authenticate(username, password, env'
564 for plugin in authn_registry.get_plugins_for_authentication():
564 for plugin in authn_registry.get_plugins_for_authentication():
565 plugin.set_auth_type(auth_type)
565 plugin.set_auth_type(auth_type)
566 plugin.set_calling_scope_repo(acl_repo_name)
566 plugin.set_calling_scope_repo(acl_repo_name)
567 user = plugin.get_user(username)
568 display_user = user.username if user else username
569
567
570 if headers_only and not plugin.is_headers_auth:
568 if headers_only and not plugin.is_headers_auth:
571 log.debug('Auth type is for headers only and plugin `%s` is not '
569 log.debug('Auth type is for headers only and plugin `%s` is not '
@@ -71,15 +71,17 b' class LdapSettingsSchema(AuthnPluginSett'
71 host = colander.SchemaNode(
71 host = colander.SchemaNode(
72 colander.String(),
72 colander.String(),
73 default='',
73 default='',
74 description=_('Host of the LDAP Server \n'
74 description=_('Host[s] of the LDAP Server \n'
75 '(e.g., 192.168.2.154, or ldap-server.domain.com'),
75 '(e.g., 192.168.2.154, or ldap-server.domain.com.\n '
76 'Multiple servers can be specified using commas'),
76 preparer=strip_whitespace,
77 preparer=strip_whitespace,
77 title=_('LDAP Host'),
78 title=_('LDAP Host'),
78 widget='string')
79 widget='string')
79 port = colander.SchemaNode(
80 port = colander.SchemaNode(
80 colander.Int(),
81 colander.Int(),
81 default=389,
82 default=389,
82 description=_('Custom port that the LDAP server is listening on. Default: 389'),
83 description=_('Custom port that the LDAP server is listening on. '
84 'Default value is: 389'),
83 preparer=strip_whitespace,
85 preparer=strip_whitespace,
84 title=_('Port'),
86 title=_('Port'),
85 validator=colander.Range(min=0, max=65536),
87 validator=colander.Range(min=0, max=65536),
@@ -112,7 +114,9 b' class LdapSettingsSchema(AuthnPluginSett'
112 tls_reqcert = colander.SchemaNode(
114 tls_reqcert = colander.SchemaNode(
113 colander.String(),
115 colander.String(),
114 default=tls_reqcert_choices[0],
116 default=tls_reqcert_choices[0],
115 description=_('Require Cert over TLS?'),
117 description=_('Require Cert over TLS?. Self-signed and custom '
118 'certificates can be used when\n `RhodeCode Certificate` '
119 'found in admin > settings > system info page is extended.'),
116 title=_('Certificate Checks'),
120 title=_('Certificate Checks'),
117 validator=colander.OneOf(tls_reqcert_choices),
121 validator=colander.OneOf(tls_reqcert_choices),
118 widget='select')
122 widget='select')
@@ -1,11 +1,11 b''
1 {
1 {
2 "nodejs-4.3.1": {
2 "libnghttp2-1.7.1": {
3 "MIT License": "http://spdx.org/licenses/MIT"
3 "MIT License": "http://spdx.org/licenses/MIT"
4 },
4 },
5 "postgresql-9.5.1": {
5 "nodejs-4.3.1": {
6 "PostgreSQL License": "http://spdx.org/licenses/PostgreSQL"
6 "MIT License": "http://spdx.org/licenses/MIT"
7 },
7 },
8 "python-2.7.11": {
8 "python-2.7.12": {
9 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
9 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
10 },
10 },
11 "python2.7-Babel-1.3": {
11 "python2.7-Babel-1.3": {
@@ -14,19 +14,25 b''
14 "python2.7-Beaker-1.7.0": {
14 "python2.7-Beaker-1.7.0": {
15 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
15 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
16 },
16 },
17 "python2.7-Chameleon-2.24": {
18 "BSD-like": "http://repoze.org/license.html"
19 },
17 "python2.7-FormEncode-1.2.4": {
20 "python2.7-FormEncode-1.2.4": {
18 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
21 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
19 },
22 },
20 "python2.7-Mako-1.0.1": {
23 "python2.7-Jinja2-2.7.3": {
24 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
25 },
26 "python2.7-Mako-1.0.6": {
21 "MIT License": "http://spdx.org/licenses/MIT"
27 "MIT License": "http://spdx.org/licenses/MIT"
22 },
28 },
23 "python2.7-Markdown-2.6.2": {
29 "python2.7-Markdown-2.6.7": {
24 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
30 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
25 },
31 },
26 "python2.7-MarkupSafe-0.23": {
32 "python2.7-MarkupSafe-0.23": {
27 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
33 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
28 },
34 },
29 "python2.7-Paste-2.0.2": {
35 "python2.7-Paste-2.0.3": {
30 "MIT License": "http://spdx.org/licenses/MIT"
36 "MIT License": "http://spdx.org/licenses/MIT"
31 },
37 },
32 "python2.7-PasteDeploy-1.5.2": {
38 "python2.7-PasteDeploy-1.5.2": {
@@ -35,12 +41,12 b''
35 "python2.7-PasteScript-1.7.5": {
41 "python2.7-PasteScript-1.7.5": {
36 "MIT License": "http://spdx.org/licenses/MIT"
42 "MIT License": "http://spdx.org/licenses/MIT"
37 },
43 },
38 "python2.7-Pygments-2.0.2": {
44 "python2.7-Pygments-2.2.0": {
39 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
45 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
40 },
46 },
41 "python2.7-Pylons-1.0.1-patch1": {
47 "python2.7-Pylons-1.0.2.rhodecode-patch1": {
42 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
48 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
43 },
49 },
44 "python2.7-Routes-1.13": {
50 "python2.7-Routes-1.13": {
45 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
51 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
46 },
52 },
@@ -65,7 +71,7 b''
65 "python2.7-WebOb-1.3.1": {
71 "python2.7-WebOb-1.3.1": {
66 "MIT License": "http://spdx.org/licenses/MIT"
72 "MIT License": "http://spdx.org/licenses/MIT"
67 },
73 },
68 "python2.7-Whoosh-2.7.0": {
74 "python2.7-Whoosh-2.7.4": {
69 "BSD 2-clause \"Simplified\" License": "http://spdx.org/licenses/BSD-2-Clause",
75 "BSD 2-clause \"Simplified\" License": "http://spdx.org/licenses/BSD-2-Clause",
70 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
76 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
71 },
77 },
@@ -87,9 +93,18 b''
87 "python2.7-backport-ipaddress-0.1": {
93 "python2.7-backport-ipaddress-0.1": {
88 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
94 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
89 },
95 },
96 "python2.7-backports.shutil-get-terminal-size-1.0.0": {
97 "MIT License": "http://spdx.org/licenses/MIT"
98 },
99 "python2.7-bleach-1.5.0": {
100 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
101 },
90 "python2.7-celery-2.2.10": {
102 "python2.7-celery-2.2.10": {
91 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
103 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
92 },
104 },
105 "python2.7-channelstream-0.5.2": {
106 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
107 },
93 "python2.7-click-5.1": {
108 "python2.7-click-5.1": {
94 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
109 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
95 },
110 },
@@ -99,82 +114,169 b''
99 "python2.7-configobj-5.0.6": {
114 "python2.7-configobj-5.0.6": {
100 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
115 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
101 },
116 },
102 "python2.7-cssselect-0.9.1": {
117 "python2.7-configparser-3.5.0": {
118 "MIT License": "http://spdx.org/licenses/MIT"
119 },
120 "python2.7-cssselect-1.0.1": {
103 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
121 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
104 },
122 },
105 "python2.7-decorator-3.4.2": {
123 "python2.7-decorator-4.0.11": {
106 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
124 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
107 },
125 },
126 "python2.7-deform-2.0a2": {
127 "BSD-derived": "http://www.repoze.org/LICENSE.txt"
128 },
108 "python2.7-docutils-0.12": {
129 "python2.7-docutils-0.12": {
109 "BSD 2-clause \"Simplified\" License": "http://spdx.org/licenses/BSD-2-Clause"
130 "BSD 2-clause \"Simplified\" License": "http://spdx.org/licenses/BSD-2-Clause"
110 },
131 },
132 "python2.7-dogpile.cache-0.6.1": {
133 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
134 },
135 "python2.7-dogpile.core-0.4.1": {
136 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
137 },
111 "python2.7-elasticsearch-2.3.0": {
138 "python2.7-elasticsearch-2.3.0": {
112 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
139 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
113 },
140 },
114 "python2.7-elasticsearch-dsl-2.0.0": {
141 "python2.7-elasticsearch-dsl-2.2.0": {
115 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
142 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
116 },
143 },
144 "python2.7-entrypoints-0.2.2": {
145 "MIT License": "http://spdx.org/licenses/MIT"
146 },
147 "python2.7-enum34-1.1.6": {
148 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
149 },
150 "python2.7-functools32-3.2.3.post2": {
151 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
152 },
117 "python2.7-future-0.14.3": {
153 "python2.7-future-0.14.3": {
118 "MIT License": "http://spdx.org/licenses/MIT"
154 "MIT License": "http://spdx.org/licenses/MIT"
119 },
155 },
120 "python2.7-futures-3.0.2": {
156 "python2.7-futures-3.0.2": {
121 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
157 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
122 },
158 },
159 "python2.7-gevent-1.1.2": {
160 "MIT License": "http://spdx.org/licenses/MIT"
161 },
123 "python2.7-gnureadline-6.3.3": {
162 "python2.7-gnureadline-6.3.3": {
124 "GNU General Public License v1.0 only": "http://spdx.org/licenses/GPL-1.0"
163 "GNU General Public License v1.0 only": "http://spdx.org/licenses/GPL-1.0"
125 },
164 },
165 "python2.7-gprof2dot-2016.10.13": {
166 "GNU Lesser General Public License v3.0 or later": "http://spdx.org/licenses/LGPL-3.0+"
167 },
168 "python2.7-greenlet-0.4.10": {
169 "MIT License": "http://spdx.org/licenses/MIT"
170 },
126 "python2.7-gunicorn-19.6.0": {
171 "python2.7-gunicorn-19.6.0": {
127 "MIT License": "http://spdx.org/licenses/MIT"
172 "MIT License": "http://spdx.org/licenses/MIT"
128 },
173 },
174 "python2.7-html5lib-0.9999999": {
175 "MIT License": "http://spdx.org/licenses/MIT"
176 },
129 "python2.7-infrae.cache-1.0.1": {
177 "python2.7-infrae.cache-1.0.1": {
130 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
178 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
131 },
179 },
132 "python2.7-ipython-3.1.0": {
180 "python2.7-ipython-5.1.0": {
181 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
182 },
183 "python2.7-ipython-genutils-0.2.0": {
133 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
184 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
134 },
185 },
135 "python2.7-iso8601-0.1.11": {
186 "python2.7-iso8601-0.1.11": {
136 "MIT License": "http://spdx.org/licenses/MIT"
187 "MIT License": "http://spdx.org/licenses/MIT"
137 },
188 },
189 "python2.7-itsdangerous-0.24": {
190 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
191 },
192 "python2.7-jsonschema-2.6.0": {
193 "MIT License": "http://spdx.org/licenses/MIT"
194 },
195 "python2.7-jupyter-client-5.0.0": {
196 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
197 },
198 "python2.7-jupyter-core-4.3.0": {
199 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
200 },
138 "python2.7-kombu-1.5.1": {
201 "python2.7-kombu-1.5.1": {
139 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
202 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
140 },
203 },
141 "python2.7-msgpack-python-0.4.6": {
204 "python2.7-mistune-0.7.4": {
205 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
206 },
207 "python2.7-msgpack-python-0.4.8": {
142 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
208 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
143 },
209 },
210 "python2.7-nbconvert-5.1.1": {
211 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
212 },
213 "python2.7-nbformat-4.3.0": {
214 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
215 },
144 "python2.7-packaging-15.2": {
216 "python2.7-packaging-15.2": {
145 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
217 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
146 },
218 },
147 "python2.7-psutil-2.2.1": {
219 "python2.7-pandocfilters-1.4.1": {
148 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
220 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
149 },
221 },
150 "python2.7-psycopg2-2.6": {
222 "python2.7-pathlib2-2.1.0": {
223 "MIT License": "http://spdx.org/licenses/MIT"
224 },
225 "python2.7-peppercorn-0.5": {
226 "BSD-derived": "http://www.repoze.org/LICENSE.txt"
227 },
228 "python2.7-pexpect-4.2.1": {
229 "ISC License": "http://spdx.org/licenses/ISC"
230 },
231 "python2.7-pickleshare-0.7.4": {
232 "MIT License": "http://spdx.org/licenses/MIT"
233 },
234 "python2.7-prompt-toolkit-1.0.14": {
235 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
236 },
237 "python2.7-psutil-4.3.1": {
238 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
239 },
240 "python2.7-psycopg2-2.6.1": {
151 "GNU Lesser General Public License v3.0 or later": "http://spdx.org/licenses/LGPL-3.0+"
241 "GNU Lesser General Public License v3.0 or later": "http://spdx.org/licenses/LGPL-3.0+"
152 },
242 },
153 "python2.7-py-1.4.29": {
243 "python2.7-ptyprocess-0.5.1": {
244 "ISC License": "http://opensource.org/licenses/ISC"
245 },
246 "python2.7-py-1.4.31": {
154 "MIT License": "http://spdx.org/licenses/MIT"
247 "MIT License": "http://spdx.org/licenses/MIT"
155 },
248 },
156 "python2.7-py-bcrypt-0.4": {
249 "python2.7-py-bcrypt-0.4": {
157 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
250 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
158 },
251 },
252 "python2.7-py-gfm-0.1.3.rhodecode-upstream1": {
253 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
254 },
159 "python2.7-pycrypto-2.6.1": {
255 "python2.7-pycrypto-2.6.1": {
160 "Public Domain": null
256 "Public Domain": null
161 },
257 },
162 "python2.7-pycurl-7.19.5": {
258 "python2.7-pycurl-7.19.5": {
163 "MIT License": "http://spdx.org/licenses/MIT"
259 "MIT License": "http://spdx.org/licenses/MIT"
164 },
260 },
261 "python2.7-pygments-markdown-lexer-0.1.0.dev39": {
262 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
263 },
165 "python2.7-pyparsing-1.5.7": {
264 "python2.7-pyparsing-1.5.7": {
166 "MIT License": "http://spdx.org/licenses/MIT"
265 "MIT License": "http://spdx.org/licenses/MIT"
167 },
266 },
168 "python2.7-pyramid-1.6.1": {
267 "python2.7-pyramid-1.7.4": {
169 "Repoze License": "http://www.repoze.org/LICENSE.txt"
268 "Repoze License": "http://www.repoze.org/LICENSE.txt"
170 },
269 },
171 "python2.7-pyramid-beaker-0.8": {
270 "python2.7-pyramid-beaker-0.8": {
172 "Repoze License": "http://www.repoze.org/LICENSE.txt"
271 "Repoze License": "http://www.repoze.org/LICENSE.txt"
173 },
272 },
174 "python2.7-pyramid-debugtoolbar-2.4.2": {
273 "python2.7-pyramid-debugtoolbar-3.0.5": {
175 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause",
274 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause",
176 "Repoze License": "http://www.repoze.org/LICENSE.txt"
275 "Repoze License": "http://www.repoze.org/LICENSE.txt"
177 },
276 },
277 "python2.7-pyramid-jinja2-2.5": {
278 "BSD-derived": "http://www.repoze.org/LICENSE.txt"
279 },
178 "python2.7-pyramid-mako-1.0.2": {
280 "python2.7-pyramid-mako-1.0.2": {
179 "Repoze License": "http://www.repoze.org/LICENSE.txt"
281 "Repoze License": "http://www.repoze.org/LICENSE.txt"
180 },
282 },
@@ -182,16 +284,25 b''
182 "libpng License": "http://spdx.org/licenses/Libpng",
284 "libpng License": "http://spdx.org/licenses/Libpng",
183 "zlib License": "http://spdx.org/licenses/Zlib"
285 "zlib License": "http://spdx.org/licenses/Zlib"
184 },
286 },
185 "python2.7-pytest-2.8.5": {
287 "python2.7-pytest-3.0.5": {
288 "MIT License": "http://spdx.org/licenses/MIT"
289 },
290 "python2.7-pytest-profiling-1.2.2": {
291 "MIT License": "http://spdx.org/licenses/MIT"
292 },
293 "python2.7-pytest-runner-2.9": {
186 "MIT License": "http://spdx.org/licenses/MIT"
294 "MIT License": "http://spdx.org/licenses/MIT"
187 },
295 },
188 "python2.7-pytest-runner-2.7.1": {
296 "python2.7-pytest-sugar-0.7.1": {
297 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
298 },
299 "python2.7-pytest-timeout-1.2.0": {
189 "MIT License": "http://spdx.org/licenses/MIT"
300 "MIT License": "http://spdx.org/licenses/MIT"
190 },
301 },
191 "python2.7-python-dateutil-1.5": {
302 "python2.7-python-dateutil-2.1": {
192 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
303 "Simplified BSD": null
193 },
304 },
194 "python2.7-python-editor-1.0.1": {
305 "python2.7-python-editor-1.0.3": {
195 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
306 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
196 },
307 },
197 "python2.7-python-ldap-2.4.19": {
308 "python2.7-python-ldap-2.4.19": {
@@ -203,6 +314,9 b''
203 "python2.7-pytz-2015.4": {
314 "python2.7-pytz-2015.4": {
204 "MIT License": "http://spdx.org/licenses/MIT"
315 "MIT License": "http://spdx.org/licenses/MIT"
205 },
316 },
317 "python2.7-pyzmq-14.6.0": {
318 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
319 },
206 "python2.7-recaptcha-client-1.0.6": {
320 "python2.7-recaptcha-client-1.0.6": {
207 "MIT License": "http://spdx.org/licenses/MIT"
321 "MIT License": "http://spdx.org/licenses/MIT"
208 },
322 },
@@ -211,21 +325,35 b''
211 },
325 },
212 "python2.7-requests-2.9.1": {
326 "python2.7-requests-2.9.1": {
213 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
327 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
214 },
328 },
215 "python2.7-setuptools-19.4": {
329 "python2.7-setuptools-19.4": {
216 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0",
330 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0",
217 "Zope Public License 2.0": "http://spdx.org/licenses/ZPL-2.0"
331 "Zope Public License 2.0": "http://spdx.org/licenses/ZPL-2.0"
218 },
332 },
219 "python2.7-setuptools-scm-1.11.0": {
333 "python2.7-setuptools-scm-1.15.0": {
220 "MIT License": "http://spdx.org/licenses/MIT"
334 "MIT License": "http://spdx.org/licenses/MIT"
221 },
335 },
336 "python2.7-simplegeneric-0.8.1": {
337 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
338 },
222 "python2.7-simplejson-3.7.2": {
339 "python2.7-simplejson-3.7.2": {
223 "Academic Free License": "http://spdx.org/licenses/AFL-2.1",
224 "MIT License": "http://spdx.org/licenses/MIT"
340 "MIT License": "http://spdx.org/licenses/MIT"
225 },
341 },
226 "python2.7-six-1.9.0": {
342 "python2.7-six-1.9.0": {
227 "MIT License": "http://spdx.org/licenses/MIT"
343 "MIT License": "http://spdx.org/licenses/MIT"
228 },
344 },
345 "python2.7-subprocess32-3.2.6": {
346 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
347 },
348 "python2.7-termcolor-1.1.0": {
349 "MIT License": "http://spdx.org/licenses/MIT"
350 },
351 "python2.7-testpath-0.1": {
352 "MIT License": "http://spdx.org/licenses/MIT"
353 },
354 "python2.7-traitlets-4.3.2": {
355 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
356 },
229 "python2.7-translationstring-1.3": {
357 "python2.7-translationstring-1.3": {
230 "Repoze License": "http://www.repoze.org/LICENSE.txt"
358 "Repoze License": "http://www.repoze.org/LICENSE.txt"
231 },
359 },
@@ -235,9 +363,15 b''
235 "python2.7-venusian-1.0": {
363 "python2.7-venusian-1.0": {
236 "Repoze License": "http://www.repoze.org/LICENSE.txt"
364 "Repoze License": "http://www.repoze.org/LICENSE.txt"
237 },
365 },
238 "python2.7-waitress-0.8.9": {
366 "python2.7-waitress-1.0.1": {
239 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
367 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
240 },
368 },
369 "python2.7-wcwidth-0.1.7": {
370 "MIT License": "http://spdx.org/licenses/MIT"
371 },
372 "python2.7-ws4py-0.3.5": {
373 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
374 },
241 "python2.7-zope.cachedescriptors-4.0.0": {
375 "python2.7-zope.cachedescriptors-4.0.0": {
242 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
376 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
243 },
377 },
@@ -246,5 +380,9 b''
246 },
380 },
247 "python2.7-zope.interface-4.1.3": {
381 "python2.7-zope.interface-4.1.3": {
248 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
382 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
383 },
384 "xz-5.2.2": {
385 "GNU General Public License v2.0 or later": "http://spdx.org/licenses/GPL-2.0+",
386 "GNU Library General Public License v2.1 or later": "http://spdx.org/licenses/LGPL-2.1+"
249 }
387 }
250 } No newline at end of file
388 }
@@ -39,11 +39,15 b' from routes.middleware import RoutesMidd'
39 import routes.util
39 import routes.util
40
40
41 import rhodecode
41 import rhodecode
42
42 from rhodecode.model import meta
43 from rhodecode.model import meta
43 from rhodecode.config import patches
44 from rhodecode.config import patches
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.environment import (
46 from rhodecode.config.environment import (
46 load_environment, load_pyramid_environment)
47 load_environment, load_pyramid_environment)
48
49 from rhodecode.lib.vcs import VCSCommunicationError
50 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.middleware import csrf
51 from rhodecode.lib.middleware import csrf
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
52 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.error_handling import (
53 from rhodecode.lib.middleware.error_handling import (
@@ -51,10 +55,10 b' from rhodecode.lib.middleware.error_hand'
51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
55 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 from rhodecode.lib.middleware.vcs import VCSMiddleware
56 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
57 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
58 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
55 from rhodecode.subscribers import (
59 from rhodecode.subscribers import (
56 scan_repositories_if_enabled, write_metadata_if_needed,
60 scan_repositories_if_enabled, write_js_routes_if_enabled,
57 write_js_routes_if_enabled)
61 write_metadata_if_needed)
58
62
59
63
60 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
@@ -221,7 +225,7 b' def add_pylons_compat_data(registry, glo'
221
225
222 def error_handler(exception, request):
226 def error_handler(exception, request):
223 import rhodecode
227 import rhodecode
224 from rhodecode.lib.utils2 import AttributeDict
228 from rhodecode.lib import helpers
225
229
226 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
230 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
227
231
@@ -229,6 +233,8 b' def error_handler(exception, request):'
229 # prefer original exception for the response since it may have headers set
233 # prefer original exception for the response since it may have headers set
230 if isinstance(exception, HTTPException):
234 if isinstance(exception, HTTPException):
231 base_response = exception
235 base_response = exception
236 elif isinstance(exception, VCSCommunicationError):
237 base_response = VCSServerUnavailable()
232
238
233 def is_http_error(response):
239 def is_http_error(response):
234 # error which should have traceback
240 # error which should have traceback
@@ -255,9 +261,10 b' def error_handler(exception, request):'
255 c.causes = []
261 c.causes = []
256 if hasattr(base_response, 'causes'):
262 if hasattr(base_response, 'causes'):
257 c.causes = base_response.causes
263 c.causes = base_response.causes
264 c.messages = helpers.flash.pop_messages()
258
265
259 response = render_to_response(
266 response = render_to_response(
260 '/errors/error_document.mako', {'c': c}, request=request,
267 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
261 response=base_response)
268 response=base_response)
262
269
263 return response
270 return response
@@ -284,11 +291,15 b' def includeme(config):'
284
291
285 # apps
292 # apps
286 config.include('rhodecode.apps._base')
293 config.include('rhodecode.apps._base')
294 config.include('rhodecode.apps.ops')
287
295
288 config.include('rhodecode.apps.admin')
296 config.include('rhodecode.apps.admin')
289 config.include('rhodecode.apps.channelstream')
297 config.include('rhodecode.apps.channelstream')
290 config.include('rhodecode.apps.login')
298 config.include('rhodecode.apps.login')
299 config.include('rhodecode.apps.home')
291 config.include('rhodecode.apps.repository')
300 config.include('rhodecode.apps.repository')
301 config.include('rhodecode.apps.repo_group')
302 config.include('rhodecode.apps.search')
292 config.include('rhodecode.apps.user_profile')
303 config.include('rhodecode.apps.user_profile')
293 config.include('rhodecode.apps.my_account')
304 config.include('rhodecode.apps.my_account')
294 config.include('rhodecode.apps.svn_support')
305 config.include('rhodecode.apps.svn_support')
@@ -307,6 +318,12 b' def includeme(config):'
307 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
318 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
308 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
319 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
309
320
321 # events
322 # TODO(marcink): this should be done when pyramid migration is finished
323 # config.add_subscriber(
324 # 'rhodecode.integrations.integrations_event_handler',
325 # 'rhodecode.events.RhodecodeEvent')
326
310 # Set the authorization policy.
327 # Set the authorization policy.
311 authz_policy = ACLAuthorizationPolicy()
328 authz_policy = ACLAuthorizationPolicy()
312 config.set_authorization_policy(authz_policy)
329 config.set_authorization_policy(authz_policy)
@@ -314,6 +331,10 b' def includeme(config):'
314 # Set the default renderer for HTML templates to mako.
331 # Set the default renderer for HTML templates to mako.
315 config.add_mako_renderer('.html')
332 config.add_mako_renderer('.html')
316
333
334 config.add_renderer(
335 name='json_ext',
336 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
337
317 # include RhodeCode plugins
338 # include RhodeCode plugins
318 includes = aslist(settings.get('rhodecode.includes', []))
339 includes = aslist(settings.get('rhodecode.includes', []))
319 for inc in includes:
340 for inc in includes:
@@ -395,7 +416,6 b' def wrap_app_in_wsgi_middlewares(pyramid'
395 pool = meta.Base.metadata.bind.engine.pool
416 pool = meta.Base.metadata.bind.engine.pool
396 log.debug('sa pool status: %s', pool.status())
417 log.debug('sa pool status: %s', pool.status())
397
418
398
399 return pyramid_app_with_cleanup
419 return pyramid_app_with_cleanup
400
420
401
421
@@ -32,8 +32,6 b' import os'
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
36
37 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
@@ -119,8 +117,9 b' class JSRoutesMapper(Mapper):'
119
117
120 def make_map(config):
118 def make_map(config):
121 """Create, configure and return the routes Mapper"""
119 """Create, configure and return the routes Mapper"""
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
120 rmap = JSRoutesMapper(
123 always_scan=config['debug'])
121 directory=config['pylons.paths']['controllers'],
122 always_scan=config['debug'])
124 rmap.minimization = False
123 rmap.minimization = False
125 rmap.explicit = False
124 rmap.explicit = False
126
125
@@ -186,36 +185,7 b' def make_map(config):'
186 # CUSTOM ROUTES HERE
185 # CUSTOM ROUTES HERE
187 #==========================================================================
186 #==========================================================================
188
187
189 # MAIN PAGE
188 # ping and pylons error test
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 action='goto_switcher_data')
193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 action='repo_list_data')
195
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 action='user_autocomplete_data', jsroute=True)
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 action='user_group_autocomplete_data', jsroute=True)
200
201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
202 rmap.connect('rst_help',
203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
204 _static=True)
205 rmap.connect('markdown_help',
206 'http://daringfireball.net/projects/markdown/syntax',
207 _static=True)
208 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
209 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
210 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
211 # TODO: anderson - making this a static link since redirect won't play
212 # nice with POST requests
213 rmap.connect('enterprise_license_convert_from_old',
214 'https://rhodecode.com/u/license-upgrade',
215 _static=True)
216
217 routing_links.connect_redirection_links(rmap)
218
219 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
189 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
220 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
190 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
221
191
@@ -228,10 +198,6 b' def make_map(config):'
228 action='index', conditions={'method': ['GET']})
198 action='index', conditions={'method': ['GET']})
229 m.connect('new_repo', '/create_repository', jsroute=True,
199 m.connect('new_repo', '/create_repository', jsroute=True,
230 action='create_repository', conditions={'method': ['GET']})
200 action='create_repository', conditions={'method': ['GET']})
231 m.connect('/repos/{repo_name}',
232 action='update', conditions={'method': ['PUT'],
233 'function': check_repo},
234 requirements=URL_NAME_REQUIREMENTS)
235 m.connect('delete_repo', '/repos/{repo_name}',
201 m.connect('delete_repo', '/repos/{repo_name}',
236 action='delete', conditions={'method': ['DELETE']},
202 action='delete', conditions={'method': ['DELETE']},
237 requirements=URL_NAME_REQUIREMENTS)
203 requirements=URL_NAME_REQUIREMENTS)
@@ -321,19 +287,6 b' def make_map(config):'
321 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
287 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
322 action='edit_perms_summary', conditions={'method': ['GET']})
288 action='edit_perms_summary', conditions={'method': ['GET']})
323
289
324 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
325 action='edit_emails', conditions={'method': ['GET']})
326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
327 action='add_email', conditions={'method': ['PUT']})
328 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
329 action='delete_email', conditions={'method': ['DELETE']})
330
331 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
332 action='edit_ips', conditions={'method': ['GET']})
333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
334 action='add_ip', conditions={'method': ['PUT']})
335 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
336 action='delete_ip', conditions={'method': ['DELETE']})
337
290
338 # ADMIN USER GROUPS REST ROUTES
291 # ADMIN USER GROUPS REST ROUTES
339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
292 with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -519,37 +472,9 b' def make_map(config):'
519 m.connect('my_account_password', '/my_account/password',
472 m.connect('my_account_password', '/my_account/password',
520 action='my_account_password', conditions={'method': ['GET']})
473 action='my_account_password', conditions={'method': ['GET']})
521
474
522 m.connect('my_account_repos', '/my_account/repos',
523 action='my_account_repos', conditions={'method': ['GET']})
524
525 m.connect('my_account_watched', '/my_account/watched',
526 action='my_account_watched', conditions={'method': ['GET']})
527
528 m.connect('my_account_pullrequests', '/my_account/pull_requests',
475 m.connect('my_account_pullrequests', '/my_account/pull_requests',
529 action='my_account_pullrequests', conditions={'method': ['GET']})
476 action='my_account_pullrequests', conditions={'method': ['GET']})
530
477
531 m.connect('my_account_perms', '/my_account/perms',
532 action='my_account_perms', conditions={'method': ['GET']})
533
534 m.connect('my_account_emails', '/my_account/emails',
535 action='my_account_emails', conditions={'method': ['GET']})
536 m.connect('my_account_emails', '/my_account/emails',
537 action='my_account_emails_add', conditions={'method': ['POST']})
538 m.connect('my_account_emails', '/my_account/emails',
539 action='my_account_emails_delete', conditions={'method': ['DELETE']})
540
541 m.connect('my_account_notifications', '/my_account/notifications',
542 action='my_notifications',
543 conditions={'method': ['GET']})
544 m.connect('my_account_notifications_toggle_visibility',
545 '/my_account/toggle_visibility',
546 action='my_notifications_toggle_visibility',
547 conditions={'method': ['POST']})
548 m.connect('my_account_notifications_test_channelstream',
549 '/my_account/test_channelstream',
550 action='my_account_notifications_test_channelstream',
551 conditions={'method': ['POST']})
552
553 # NOTIFICATION REST ROUTES
478 # NOTIFICATION REST ROUTES
554 with rmap.submapper(path_prefix=ADMIN_PREFIX,
479 with rmap.submapper(path_prefix=ADMIN_PREFIX,
555 controller='admin/notifications') as m:
480 controller='admin/notifications') as m:
@@ -597,22 +522,6 b' def make_map(config):'
597 action='show', conditions={'method': ['GET']},
522 action='show', conditions={'method': ['GET']},
598 requirements=URL_NAME_REQUIREMENTS)
523 requirements=URL_NAME_REQUIREMENTS)
599
524
600 # ADMIN MAIN PAGES
601 with rmap.submapper(path_prefix=ADMIN_PREFIX,
602 controller='admin/admin') as m:
603 m.connect('admin_home', '', action='index')
604 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
605 action='add_repo')
606 m.connect(
607 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
608 action='pull_requests')
609 m.connect(
610 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
611 action='pull_requests')
612 m.connect(
613 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
614 action='pull_requests')
615
616 # USER JOURNAL
525 # USER JOURNAL
617 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
526 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
618 controller='journal', action='index')
527 controller='journal', action='index')
@@ -642,15 +551,6 b' def make_map(config):'
642 controller='journal', action='toggle_following', jsroute=True,
551 controller='journal', action='toggle_following', jsroute=True,
643 conditions={'method': ['POST']})
552 conditions={'method': ['POST']})
644
553
645 # FULL TEXT SEARCH
646 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
647 controller='search')
648 rmap.connect('search_repo_home', '/{repo_name}/search',
649 controller='search',
650 action='index',
651 conditions={'function': check_repo},
652 requirements=URL_NAME_REQUIREMENTS)
653
654 # FEEDS
554 # FEEDS
655 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
555 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
656 controller='feed', action='rss',
556 controller='feed', action='rss',
@@ -673,21 +573,6 b' def make_map(config):'
673 controller='admin/repos', action='repo_check',
573 controller='admin/repos', action='repo_check',
674 requirements=URL_NAME_REQUIREMENTS)
574 requirements=URL_NAME_REQUIREMENTS)
675
575
676 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
677 controller='summary', action='repo_stats',
678 conditions={'function': check_repo},
679 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
680
681 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
682 controller='summary', action='repo_refs_data',
683 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
684 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
685 controller='summary', action='repo_refs_changelog_data',
686 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
687 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
688 controller='summary', action='repo_default_reviewers_data',
689 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
690
691 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
576 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
692 controller='changeset', revision='tip',
577 controller='changeset', revision='tip',
693 conditions={'function': check_repo},
578 conditions={'function': check_repo},
@@ -702,21 +587,6 b' def make_map(config):'
702 requirements=URL_NAME_REQUIREMENTS)
587 requirements=URL_NAME_REQUIREMENTS)
703
588
704 # repo edit options
589 # repo edit options
705 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
706 controller='admin/repos', action='edit',
707 conditions={'method': ['GET'], 'function': check_repo},
708 requirements=URL_NAME_REQUIREMENTS)
709
710 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
711 jsroute=True,
712 controller='admin/repos', action='edit_permissions',
713 conditions={'method': ['GET'], 'function': check_repo},
714 requirements=URL_NAME_REQUIREMENTS)
715 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
716 controller='admin/repos', action='edit_permissions_update',
717 conditions={'method': ['PUT'], 'function': check_repo},
718 requirements=URL_NAME_REQUIREMENTS)
719
720 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
590 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
721 controller='admin/repos', action='edit_fields',
591 controller='admin/repos', action='edit_fields',
722 conditions={'method': ['GET'], 'function': check_repo},
592 conditions={'method': ['GET'], 'function': check_repo},
@@ -730,39 +600,11 b' def make_map(config):'
730 conditions={'method': ['DELETE'], 'function': check_repo},
600 conditions={'method': ['DELETE'], 'function': check_repo},
731 requirements=URL_NAME_REQUIREMENTS)
601 requirements=URL_NAME_REQUIREMENTS)
732
602
733 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
734 controller='admin/repos', action='edit_advanced',
735 conditions={'method': ['GET'], 'function': check_repo},
736 requirements=URL_NAME_REQUIREMENTS)
737
738 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
739 controller='admin/repos', action='edit_advanced_locking',
740 conditions={'method': ['PUT'], 'function': check_repo},
741 requirements=URL_NAME_REQUIREMENTS)
742 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
603 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
743 controller='admin/repos', action='toggle_locking',
604 controller='admin/repos', action='toggle_locking',
744 conditions={'method': ['GET'], 'function': check_repo},
605 conditions={'method': ['GET'], 'function': check_repo},
745 requirements=URL_NAME_REQUIREMENTS)
606 requirements=URL_NAME_REQUIREMENTS)
746
607
747 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
748 controller='admin/repos', action='edit_advanced_journal',
749 conditions={'method': ['PUT'], 'function': check_repo},
750 requirements=URL_NAME_REQUIREMENTS)
751
752 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
753 controller='admin/repos', action='edit_advanced_fork',
754 conditions={'method': ['PUT'], 'function': check_repo},
755 requirements=URL_NAME_REQUIREMENTS)
756
757 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
758 controller='admin/repos', action='edit_caches_form',
759 conditions={'method': ['GET'], 'function': check_repo},
760 requirements=URL_NAME_REQUIREMENTS)
761 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
762 controller='admin/repos', action='edit_caches',
763 conditions={'method': ['PUT'], 'function': check_repo},
764 requirements=URL_NAME_REQUIREMENTS)
765
766 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
608 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
767 controller='admin/repos', action='edit_remote_form',
609 controller='admin/repos', action='edit_remote_form',
768 conditions={'method': ['GET'], 'function': check_repo},
610 conditions={'method': ['GET'], 'function': check_repo},
@@ -931,13 +773,6 b' def make_map(config):'
931 'method': ['DELETE']},
773 'method': ['DELETE']},
932 requirements=URL_NAME_REQUIREMENTS)
774 requirements=URL_NAME_REQUIREMENTS)
933
775
934 rmap.connect('pullrequest_show_all',
935 '/{repo_name}/pull-request',
936 controller='pullrequests',
937 action='show_all', conditions={'function': check_repo,
938 'method': ['GET']},
939 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
940
941 rmap.connect('pullrequest_comment',
776 rmap.connect('pullrequest_comment',
942 '/{repo_name}/pull-request-comment/{pull_request_id}',
777 '/{repo_name}/pull-request-comment/{pull_request_id}',
943 controller='pullrequests',
778 controller='pullrequests',
@@ -951,31 +786,10 b' def make_map(config):'
951 conditions={'function': check_repo, 'method': ['DELETE']},
786 conditions={'function': check_repo, 'method': ['DELETE']},
952 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
787 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
953
788
954 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
955 controller='summary', conditions={'function': check_repo},
956 requirements=URL_NAME_REQUIREMENTS)
957
958 rmap.connect('branches_home', '/{repo_name}/branches',
959 controller='branches', conditions={'function': check_repo},
960 requirements=URL_NAME_REQUIREMENTS)
961
962 rmap.connect('tags_home', '/{repo_name}/tags',
963 controller='tags', conditions={'function': check_repo},
964 requirements=URL_NAME_REQUIREMENTS)
965
966 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
967 controller='bookmarks', conditions={'function': check_repo},
968 requirements=URL_NAME_REQUIREMENTS)
969
970 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
789 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
971 controller='changelog', conditions={'function': check_repo},
790 controller='changelog', conditions={'function': check_repo},
972 requirements=URL_NAME_REQUIREMENTS)
791 requirements=URL_NAME_REQUIREMENTS)
973
792
974 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
975 controller='changelog', action='changelog_summary',
976 conditions={'function': check_repo},
977 requirements=URL_NAME_REQUIREMENTS)
978
979 rmap.connect('changelog_file_home',
793 rmap.connect('changelog_file_home',
980 '/{repo_name}/changelog/{revision}/{f_path}',
794 '/{repo_name}/changelog/{revision}/{f_path}',
981 controller='changelog', f_path=None,
795 controller='changelog', f_path=None,
@@ -1128,26 +942,4 b' def make_map(config):'
1128 conditions={'function': check_repo},
942 conditions={'function': check_repo},
1129 requirements=URL_NAME_REQUIREMENTS)
943 requirements=URL_NAME_REQUIREMENTS)
1130
944
1131 # must be here for proper group/repo catching pattern
1132 _connect_with_slash(
1133 rmap, 'repo_group_home', '/{group_name}',
1134 controller='home', action='index_repo_group',
1135 conditions={'function': check_group},
1136 requirements=URL_NAME_REQUIREMENTS)
1137
1138 # catch all, at the end
1139 _connect_with_slash(
1140 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1141 controller='summary', action='index',
1142 conditions={'function': check_repo},
1143 requirements=URL_NAME_REQUIREMENTS)
1144
1145 return rmap
945 return rmap
1146
1147
1148 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1149 """
1150 Connect a route with an optional trailing slash in `path`.
1151 """
1152 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1153 mapper.connect(name, path, *args, **kwargs)
@@ -46,27 +46,56 b' you can see it working.'
46 # flake8: noqa
46 # flake8: noqa
47 from __future__ import unicode_literals
47 from __future__ import unicode_literals
48
48
49 link_config = [
50 {
51 "name": "enterprise_docs",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 },
55 {
56 "name": "enterprise_log_file_locations",
57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
59 },
60 {
61 "name": "enterprise_issue_tracker_settings",
62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
64 },
65 {
66 "name": "enterprise_svn_setup",
67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
69 },
70 {
71 "name": "rst_help",
72 "target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
73 "external_target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
74 },
75 {
76 "name": "markdown_help",
77 "target": "https://daringfireball.net/projects/markdown/syntax",
78 "external_target": "https://daringfireball.net/projects/markdown/syntax",
79 },
80 {
81 "name": "rhodecode_official",
82 "target": "https://rhodecode.com",
83 "external_target": "https://rhodecode.com/",
84 },
85 {
86 "name": "rhodecode_support",
87 "target": "https://rhodecode.com/help/",
88 "external_target": "https://rhodecode.com/support",
89 },
90 {
91 "name": "rhodecode_translations",
92 "target": "https://rhodecode.com/translate/enterprise",
93 "external_target": "https://www.transifex.com/rhodecode/RhodeCode/",
94 },
49
95
50 link_config = [
51 {"name": "enterprise_docs",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 },
55 {"name": "enterprise_log_file_locations",
56 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
57 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
58 },
59 {"name": "enterprise_issue_tracker_settings",
60 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
61 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
62 },
63 {"name": "enterprise_svn_setup",
64 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
65 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
66 },
67 ]
96 ]
68
97
69
98
70 def connect_redirection_links(rmap):
99 def connect_redirection_links(config):
71 for link in link_config:
100 for link in link_config:
72 rmap.connect(link['name'], link['target'], _static=True)
101 config.add_route(link['name'], link['target'], static=True)
@@ -24,35 +24,27 b' my account controller for RhodeCode admi'
24 """
24 """
25
25
26 import logging
26 import logging
27 import datetime
28
27
29 import formencode
28 import formencode
30 from formencode import htmlfill
29 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
32 from pyramid.httpexceptions import HTTPFound
30 from pyramid.httpexceptions import HTTPFound
33
31
34 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c
35 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
36 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
37 from sqlalchemy.orm import joinedload
38
35
39 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
40 from rhodecode.lib import auth
37 from rhodecode.lib import auth
41 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, AuthUser)
39 LoginRequired, NotAnonymous, AuthUser)
43 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils import jsonify
45 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.lib.utils2 import safe_int, str2bool
46 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.channelstream import channelstream_request, \
48 ChannelstreamException
49
43
50 from rhodecode.model.db import (
44 from rhodecode.model.db import (
51 Repository, PullRequest, UserEmailMap, User, UserFollowing)
45 Repository, PullRequest, UserEmailMap, User, UserFollowing)
52 from rhodecode.model.forms import UserForm
46 from rhodecode.model.forms import UserForm
53 from rhodecode.model.scm import RepoList
54 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
55 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
57 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
58 from rhodecode.model.comment import CommentsModel
50 from rhodecode.model.comment import CommentsModel
@@ -82,26 +74,6 b' class MyAccountController(BaseController'
82 c.auth_user = AuthUser(
74 c.auth_user = AuthUser(
83 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
75 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
84
76
85 def _load_my_repos_data(self, watched=False):
86 if watched:
87 admin = False
88 follows_repos = Session().query(UserFollowing)\
89 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
90 .options(joinedload(UserFollowing.follows_repository))\
91 .all()
92 repo_list = [x.follows_repository for x in follows_repos]
93 else:
94 admin = True
95 repo_list = Repository.get_all_repos(
96 user_id=c.rhodecode_user.user_id)
97 repo_list = RepoList(repo_list, perm_set=[
98 'repository.read', 'repository.write', 'repository.admin'])
99
100 repos_data = RepoModel().get_repos_as_dict(
101 repo_list=repo_list, admin=admin)
102 # json used to render the grid
103 return json.dumps(repos_data)
104
105 @auth.CSRFRequired()
77 @auth.CSRFRequired()
106 def my_account_update(self):
78 def my_account_update(self):
107 """
79 """
@@ -181,65 +153,6 b' class MyAccountController(BaseController'
181 force_defaults=False
153 force_defaults=False
182 )
154 )
183
155
184 def my_account_repos(self):
185 c.active = 'repos'
186 self.__load_data()
187
188 # json used to render the grid
189 c.data = self._load_my_repos_data()
190 return render('admin/my_account/my_account.mako')
191
192 def my_account_watched(self):
193 c.active = 'watched'
194 self.__load_data()
195
196 # json used to render the grid
197 c.data = self._load_my_repos_data(watched=True)
198 return render('admin/my_account/my_account.mako')
199
200 def my_account_perms(self):
201 c.active = 'perms'
202 self.__load_data()
203 c.perm_user = c.auth_user
204
205 return render('admin/my_account/my_account.mako')
206
207 def my_account_emails(self):
208 c.active = 'emails'
209 self.__load_data()
210
211 c.user_email_map = UserEmailMap.query()\
212 .filter(UserEmailMap.user == c.user).all()
213 return render('admin/my_account/my_account.mako')
214
215 @auth.CSRFRequired()
216 def my_account_emails_add(self):
217 email = request.POST.get('new_email')
218
219 try:
220 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
221 Session().commit()
222 h.flash(_("Added new email address `%s` for user account") % email,
223 category='success')
224 except formencode.Invalid as error:
225 msg = error.error_dict['email']
226 h.flash(msg, category='error')
227 except Exception:
228 log.exception("Exception in my_account_emails")
229 h.flash(_('An error occurred during email saving'),
230 category='error')
231 return redirect(url('my_account_emails'))
232
233 @auth.CSRFRequired()
234 def my_account_emails_delete(self):
235 email_id = request.POST.get('del_email_id')
236 user_model = UserModel()
237 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
238 Session().commit()
239 h.flash(_("Removed email address from user account"),
240 category='success')
241 return redirect(url('my_account_emails'))
242
243 def _extract_ordering(self, request):
156 def _extract_ordering(self, request):
244 column_index = safe_int(request.GET.get('order[0][column]'))
157 column_index = safe_int(request.GET.get('order[0][column]'))
245 order_dir = request.GET.get('order[0][dir]', 'desc')
158 order_dir = request.GET.get('order[0][dir]', 'desc')
@@ -320,45 +233,4 b' class MyAccountController(BaseController'
320 else:
233 else:
321 return json.dumps(data)
234 return json.dumps(data)
322
235
323 def my_notifications(self):
324 c.active = 'notifications'
325 return render('admin/my_account/my_account.mako')
326
236
327 @auth.CSRFRequired()
328 @jsonify
329 def my_notifications_toggle_visibility(self):
330 user = c.rhodecode_user.get_instance()
331 new_status = not user.user_data.get('notification_status', True)
332 user.update_userdata(notification_status=new_status)
333 Session().commit()
334 return user.user_data['notification_status']
335
336 @auth.CSRFRequired()
337 @jsonify
338 def my_account_notifications_test_channelstream(self):
339 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
340 c.rhodecode_user.username, datetime.datetime.now())
341 payload = {
342 'type': 'message',
343 'timestamp': datetime.datetime.utcnow(),
344 'user': 'system',
345 #'channel': 'broadcast',
346 'pm_users': [c.rhodecode_user.username],
347 'message': {
348 'message': message,
349 'level': 'info',
350 'topic': '/notifications'
351 }
352 }
353
354 registry = get_current_registry()
355 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
356 channelstream_config = rhodecode_plugins.get('channelstream', {})
357
358 try:
359 channelstream_request(channelstream_config, [payload], '/message')
360 except ChannelstreamException as e:
361 log.exception('Failed to send channelstream data')
362 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
363 return {"response": 'Channelstream data sent. '
364 'You should see a new live message now.'}
@@ -48,10 +48,6 b' log = logging.getLogger(__name__)'
48
48
49 class NotificationsController(BaseController):
49 class NotificationsController(BaseController):
50 """REST Controller styled on the Atom Publishing Protocol"""
50 """REST Controller styled on the Atom Publishing Protocol"""
51 # To properly map this controller, ensure your config/routing.py
52 # file has a resource setup:
53 # map.resource('notification', 'notifications', controller='_admin/notifications',
54 # path_prefix='/_admin', name_prefix='_admin_')
55
51
56 @LoginRequired()
52 @LoginRequired()
57 @NotAnonymous()
53 @NotAnonymous()
@@ -62,8 +58,8 b' class NotificationsController(BaseContro'
62 """GET /_admin/notifications: All items in the collection"""
58 """GET /_admin/notifications: All items in the collection"""
63 # url('notifications')
59 # url('notifications')
64 c.user = c.rhodecode_user
60 c.user = c.rhodecode_user
65 notif = NotificationModel().get_for_user(c.rhodecode_user.user_id,
61 notif = NotificationModel().get_for_user(
66 filter_=request.GET.getall('type'))
62 c.rhodecode_user.user_id, filter_=request.GET.getall('type'))
67
63
68 p = safe_int(request.GET.get('page', 1), 1)
64 p = safe_int(request.GET.get('page', 1), 1)
69 notifications_url = webhelpers.paginate.PageURL(
65 notifications_url = webhelpers.paginate.PageURL(
@@ -86,7 +82,6 b' class NotificationsController(BaseContro'
86
82
87 return render('admin/notifications/notifications.mako')
83 return render('admin/notifications/notifications.mako')
88
84
89
90 @auth.CSRFRequired()
85 @auth.CSRFRequired()
91 def mark_all_read(self):
86 def mark_all_read(self):
92 if request.is_xhr:
87 if request.is_xhr:
@@ -115,15 +110,8 b' class NotificationsController(BaseContro'
115
110
116 @auth.CSRFRequired()
111 @auth.CSRFRequired()
117 def update(self, notification_id):
112 def update(self, notification_id):
118 """PUT /_admin/notifications/id: Update an existing item"""
113 no = Notification.get_or_404(notification_id)
119 # Forms posted to this method should contain a hidden field:
120 # <input type="hidden" name="_method" value="PUT" />
121 # Or using helpers:
122 # h.form(url('notification', notification_id=ID),
123 # method='put')
124 # url('notification', notification_id=ID)
125 try:
114 try:
126 no = Notification.get(notification_id)
127 if self._has_permissions(no):
115 if self._has_permissions(no):
128 # deletes only notification2user
116 # deletes only notification2user
129 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
117 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
@@ -136,15 +124,8 b' class NotificationsController(BaseContro'
136
124
137 @auth.CSRFRequired()
125 @auth.CSRFRequired()
138 def delete(self, notification_id):
126 def delete(self, notification_id):
139 """DELETE /_admin/notifications/id: Delete an existing item"""
127 no = Notification.get_or_404(notification_id)
140 # Forms posted to this method should contain a hidden field:
141 # <input type="hidden" name="_method" value="DELETE" />
142 # Or using helpers:
143 # h.form(url('notification', notification_id=ID),
144 # method='delete')
145 # url('notification', notification_id=ID)
146 try:
128 try:
147 no = Notification.get(notification_id)
148 if self._has_permissions(no):
129 if self._has_permissions(no):
149 # deletes only notification2user
130 # deletes only notification2user
150 NotificationModel().delete(c.rhodecode_user.user_id, no)
131 NotificationModel().delete(c.rhodecode_user.user_id, no)
@@ -156,10 +137,8 b' class NotificationsController(BaseContro'
156 raise HTTPBadRequest()
137 raise HTTPBadRequest()
157
138
158 def show(self, notification_id):
139 def show(self, notification_id):
159 """GET /_admin/notifications/id: Show a specific item"""
160 # url('notification', notification_id=ID)
161 c.user = c.rhodecode_user
140 c.user = c.rhodecode_user
162 no = Notification.get(notification_id)
141 no = Notification.get_or_404(notification_id)
163
142
164 if no and self._has_permissions(no):
143 if no and self._has_permissions(no):
165 unotification = NotificationModel()\
144 unotification = NotificationModel()\
@@ -64,12 +64,7 b' class PermissionsController(BaseControll'
64 c.active = 'application'
64 c.active = 'application'
65 self.__load_data()
65 self.__load_data()
66
66
67 c.user = User.get_default_user()
67 c.user = User.get_default_user(refresh=True)
68
69 # TODO: johbo: The default user might be based on outdated state which
70 # has been loaded from the cache. A call to refresh() ensures that the
71 # latest state from the database is used.
72 Session().refresh(c.user)
73
68
74 app_settings = SettingsModel().get_all_settings()
69 app_settings = SettingsModel().get_all_settings()
75 defaults = {
70 defaults = {
@@ -34,17 +34,18 b' from pylons.i18n.translation import _, u'
34
34
35 from rhodecode.lib import auth
35 from rhodecode.lib import auth
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
39 LoginRequired, NotAnonymous, HasPermissionAll,
40 LoginRequired, NotAnonymous, HasPermissionAll,
40 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils2 import safe_int
42 from rhodecode.model.db import RepoGroup, User
44 from rhodecode.model.db import RepoGroup, User
43 from rhodecode.model.scm import RepoGroupList
45 from rhodecode.model.scm import RepoGroupList
44 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
47 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
46 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
47 from rhodecode.lib.utils2 import safe_int
48
49
49
50
50 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
@@ -153,9 +154,6 b' class RepoGroupsController(BaseControlle'
153
154
154 @NotAnonymous()
155 @NotAnonymous()
155 def index(self):
156 def index(self):
156 """GET /repo_groups: All items in the collection"""
157 # url('repo_groups')
158
159 repo_group_list = RepoGroup.get_all_repo_groups()
157 repo_group_list = RepoGroup.get_all_repo_groups()
160 _perms = ['group.admin']
158 _perms = ['group.admin']
161 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
159 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
@@ -168,8 +166,6 b' class RepoGroupsController(BaseControlle'
168 @NotAnonymous()
166 @NotAnonymous()
169 @auth.CSRFRequired()
167 @auth.CSRFRequired()
170 def create(self):
168 def create(self):
171 """POST /repo_groups: Create a new item"""
172 # url('repo_groups')
173
169
174 parent_group_id = safe_int(request.POST.get('group_parent_id'))
170 parent_group_id = safe_int(request.POST.get('group_parent_id'))
175 can_create = self._can_create_repo_group(parent_group_id)
171 can_create = self._can_create_repo_group(parent_group_id)
@@ -183,20 +179,29 b' class RepoGroupsController(BaseControlle'
183 try:
179 try:
184 owner = c.rhodecode_user
180 owner = c.rhodecode_user
185 form_result = repo_group_form.to_python(dict(request.POST))
181 form_result = repo_group_form.to_python(dict(request.POST))
186 RepoGroupModel().create(
182 repo_group = RepoGroupModel().create(
187 group_name=form_result['group_name_full'],
183 group_name=form_result['group_name_full'],
188 group_description=form_result['group_description'],
184 group_description=form_result['group_description'],
189 owner=owner.user_id,
185 owner=owner.user_id,
190 copy_permissions=form_result['group_copy_permissions']
186 copy_permissions=form_result['group_copy_permissions']
191 )
187 )
188 Session().flush()
189
190 repo_group_data = repo_group.get_api_data()
191 audit_logger.store_web(
192 'repo_group.create', action_data={'data': repo_group_data},
193 user=c.rhodecode_user)
194
192 Session().commit()
195 Session().commit()
196
193 _new_group_name = form_result['group_name_full']
197 _new_group_name = form_result['group_name_full']
198
194 repo_group_url = h.link_to(
199 repo_group_url = h.link_to(
195 _new_group_name,
200 _new_group_name,
196 h.url('repo_group_home', group_name=_new_group_name))
201 h.route_path('repo_group_home', repo_group_name=_new_group_name))
197 h.flash(h.literal(_('Created repository group %s')
202 h.flash(h.literal(_('Created repository group %s')
198 % repo_group_url), category='success')
203 % repo_group_url), category='success')
199 # TODO: in futureaction_logger(, '', '', '', self.sa)
204
200 except formencode.Invalid as errors:
205 except formencode.Invalid as errors:
201 return htmlfill.render(
206 return htmlfill.render(
202 render('admin/repo_groups/repo_group_add.mako'),
207 render('admin/repo_groups/repo_group_add.mako'),
@@ -216,8 +221,6 b' class RepoGroupsController(BaseControlle'
216 # perm checks inside
221 # perm checks inside
217 @NotAnonymous()
222 @NotAnonymous()
218 def new(self):
223 def new(self):
219 """GET /repo_groups/new: Form to create a new item"""
220 # url('new_repo_group')
221 # perm check for admin, create_group perm or admin of parent_group
224 # perm check for admin, create_group perm or admin of parent_group
222 parent_group_id = safe_int(request.GET.get('parent_group'))
225 parent_group_id = safe_int(request.GET.get('parent_group'))
223 if not self._can_create_repo_group(parent_group_id):
226 if not self._can_create_repo_group(parent_group_id):
@@ -229,12 +232,6 b' class RepoGroupsController(BaseControlle'
229 @HasRepoGroupPermissionAnyDecorator('group.admin')
232 @HasRepoGroupPermissionAnyDecorator('group.admin')
230 @auth.CSRFRequired()
233 @auth.CSRFRequired()
231 def update(self, group_name):
234 def update(self, group_name):
232 """PUT /repo_groups/group_name: Update an existing item"""
233 # Forms posted to this method should contain a hidden field:
234 # <input type="hidden" name="_method" value="PUT" />
235 # Or using helpers:
236 # h.form(url('repos_group', group_name=GROUP_NAME), method='put')
237 # url('repo_group_home', group_name=GROUP_NAME)
238
235
239 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
236 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
240 can_create_in_root = self._can_create_repo_group()
237 can_create_in_root = self._can_create_repo_group()
@@ -250,16 +247,21 b' class RepoGroupsController(BaseControlle'
250 available_groups=c.repo_groups_choices,
247 available_groups=c.repo_groups_choices,
251 can_create_in_root=can_create_in_root, allow_disabled=True)()
248 can_create_in_root=can_create_in_root, allow_disabled=True)()
252
249
250 old_values = c.repo_group.get_api_data()
253 try:
251 try:
254 form_result = repo_group_form.to_python(dict(request.POST))
252 form_result = repo_group_form.to_python(dict(request.POST))
255 gr_name = form_result['group_name']
253 gr_name = form_result['group_name']
256 new_gr = RepoGroupModel().update(group_name, form_result)
254 new_gr = RepoGroupModel().update(group_name, form_result)
255
256 audit_logger.store_web(
257 'repo_group.edit', action_data={'old_data': old_values},
258 user=c.rhodecode_user)
259
257 Session().commit()
260 Session().commit()
258 h.flash(_('Updated repository group %s') % (gr_name,),
261 h.flash(_('Updated repository group %s') % (gr_name,),
259 category='success')
262 category='success')
260 # we now have new name !
263 # we now have new name !
261 group_name = new_gr.group_name
264 group_name = new_gr.group_name
262 # TODO: in future action_logger(, '', '', '', self.sa)
263 except formencode.Invalid as errors:
265 except formencode.Invalid as errors:
264 c.active = 'settings'
266 c.active = 'settings'
265 return htmlfill.render(
267 return htmlfill.render(
@@ -279,13 +281,6 b' class RepoGroupsController(BaseControlle'
279 @HasRepoGroupPermissionAnyDecorator('group.admin')
281 @HasRepoGroupPermissionAnyDecorator('group.admin')
280 @auth.CSRFRequired()
282 @auth.CSRFRequired()
281 def delete(self, group_name):
283 def delete(self, group_name):
282 """DELETE /repo_groups/group_name: Delete an existing item"""
283 # Forms posted to this method should contain a hidden field:
284 # <input type="hidden" name="_method" value="DELETE" />
285 # Or using helpers:
286 # h.form(url('repos_group', group_name=GROUP_NAME), method='delete')
287 # url('repo_group_home', group_name=GROUP_NAME)
288
289 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
284 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
290 repos = gr.repositories.all()
285 repos = gr.repositories.all()
291 if repos:
286 if repos:
@@ -307,11 +302,16 b' class RepoGroupsController(BaseControlle'
307 return redirect(url('repo_groups'))
302 return redirect(url('repo_groups'))
308
303
309 try:
304 try:
305 old_values = gr.get_api_data()
310 RepoGroupModel().delete(group_name)
306 RepoGroupModel().delete(group_name)
307
308 audit_logger.store_web(
309 'repo_group.delete', action_data={'old_data': old_values},
310 user=c.rhodecode_user)
311
311 Session().commit()
312 Session().commit()
312 h.flash(_('Removed repository group %s') % group_name,
313 h.flash(_('Removed repository group %s') % group_name,
313 category='success')
314 category='success')
314 # TODO: in future action_logger(, '', '', '', self.sa)
315 except Exception:
315 except Exception:
316 log.exception("Exception during deletion of repository group")
316 log.exception("Exception during deletion of repository group")
317 h.flash(_('Error occurred during deletion of repository group %s')
317 h.flash(_('Error occurred during deletion of repository group %s')
@@ -321,8 +321,7 b' class RepoGroupsController(BaseControlle'
321
321
322 @HasRepoGroupPermissionAnyDecorator('group.admin')
322 @HasRepoGroupPermissionAnyDecorator('group.admin')
323 def edit(self, group_name):
323 def edit(self, group_name):
324 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
324
325 # url('edit_repo_group', group_name=GROUP_NAME)
326 c.active = 'settings'
325 c.active = 'settings'
327
326
328 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
327 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
@@ -346,8 +345,6 b' class RepoGroupsController(BaseControlle'
346
345
347 @HasRepoGroupPermissionAnyDecorator('group.admin')
346 @HasRepoGroupPermissionAnyDecorator('group.admin')
348 def edit_repo_group_advanced(self, group_name):
347 def edit_repo_group_advanced(self, group_name):
349 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
350 # url('edit_repo_group', group_name=GROUP_NAME)
351 c.active = 'advanced'
348 c.active = 'advanced'
352 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
349 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
353
350
@@ -355,8 +352,6 b' class RepoGroupsController(BaseControlle'
355
352
356 @HasRepoGroupPermissionAnyDecorator('group.admin')
353 @HasRepoGroupPermissionAnyDecorator('group.admin')
357 def edit_repo_group_perms(self, group_name):
354 def edit_repo_group_perms(self, group_name):
358 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
359 # url('edit_repo_group', group_name=GROUP_NAME)
360 c.active = 'perms'
355 c.active = 'perms'
361 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
356 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
362 self.__load_defaults()
357 self.__load_defaults()
@@ -374,8 +369,6 b' class RepoGroupsController(BaseControlle'
374 def update_perms(self, group_name):
369 def update_perms(self, group_name):
375 """
370 """
376 Update permissions for given repository group
371 Update permissions for given repository group
377
378 :param group_name:
379 """
372 """
380
373
381 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
374 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
@@ -393,14 +386,20 b' class RepoGroupsController(BaseControlle'
393 # iterate over all members(if in recursive mode) of this groups and
386 # iterate over all members(if in recursive mode) of this groups and
394 # set the permissions !
387 # set the permissions !
395 # this can be potentially heavy operation
388 # this can be potentially heavy operation
396 RepoGroupModel().update_permissions(
389 changes = RepoGroupModel().update_permissions(
397 c.repo_group,
390 c.repo_group,
398 form['perm_additions'], form['perm_updates'],
391 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
399 form['perm_deletions'], form['recursive'])
392 form['recursive'])
400
393
401 # TODO: implement this
394 action_data = {
402 # action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
395 'added': changes['added'],
403 # repo_name, self.ip_addr, self.sa)
396 'updated': changes['updated'],
397 'deleted': changes['deleted'],
398 }
399 audit_logger.store_web(
400 'repo_group.edit.permissions', action_data=action_data,
401 user=c.rhodecode_user)
402
404 Session().commit()
403 Session().commit()
405 h.flash(_('Repository Group permissions updated'), category='success')
404 h.flash(_('Repository Group permissions updated'), category='success')
406 return redirect(url('edit_repo_group_perms', group_name=group_name))
405 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -41,15 +41,11 b' from rhodecode.lib.auth import ('
41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.exceptions import AttachedForksError
44 from rhodecode.lib.utils import repo_name_slug, jsonify
45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
46 from rhodecode.lib.utils2 import safe_int, str2bool
45 from rhodecode.lib.utils2 import safe_int, str2bool
47 from rhodecode.lib.vcs import RepositoryError
46 from rhodecode.model.db import (Repository, RepoGroup, RepositoryField)
48 from rhodecode.model.db import (
49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
50 from rhodecode.model.forms import (
47 from rhodecode.model.forms import (
51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
48 RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm)
52 IssueTrackerPatternsForm)
53 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
54 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
55 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
51 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
@@ -185,7 +181,7 b' class ReposController(BaseRepoController'
185 except Exception as e:
181 except Exception as e:
186 msg = self._log_creation_exception(e, form_result.get('repo_name'))
182 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 h.flash(msg, category='error')
183 h.flash(msg, category='error')
188 return redirect(url('home'))
184 return redirect(h.route_path('home'))
189
185
190 return redirect(h.url('repo_creating_home',
186 return redirect(h.url('repo_creating_home',
191 repo_name=form_result['repo_name_full'],
187 repo_name=form_result['repo_name_full'],
@@ -265,7 +261,7 b' class ReposController(BaseRepoController'
265 if task.failed():
261 if task.failed():
266 msg = self._log_creation_exception(task.result, c.repo)
262 msg = self._log_creation_exception(task.result, c.repo)
267 h.flash(msg, category='error')
263 h.flash(msg, category='error')
268 return redirect(url('home'), code=501)
264 return redirect(h.route_path('home'), code=501)
269
265
270 repo = Repository.get_by_repo_name(repo_name)
266 repo = Repository.get_by_repo_name(repo_name)
271 if repo and repo.repo_state == Repository.STATE_CREATED:
267 if repo and repo.repo_state == Repository.STATE_CREATED:
@@ -274,9 +270,9 b' class ReposController(BaseRepoController'
274 h.flash(_('Created repository %s from %s')
270 h.flash(_('Created repository %s from %s')
275 % (repo.repo_name, clone_uri), category='success')
271 % (repo.repo_name, clone_uri), category='success')
276 else:
272 else:
277 repo_url = h.link_to(repo.repo_name,
273 repo_url = h.link_to(
278 h.url('summary_home',
274 repo.repo_name,
279 repo_name=repo.repo_name))
275 h.route_path('repo_summary',repo_name=repo.repo_name))
280 fork = repo.fork
276 fork = repo.fork
281 if fork:
277 if fork:
282 fork_name = fork.repo_name
278 fork_name = fork.repo_name
@@ -288,165 +284,14 b' class ReposController(BaseRepoController'
288 return {'result': True}
284 return {'result': True}
289 return {'result': False}
285 return {'result': False}
290
286
291 @HasRepoPermissionAllDecorator('repository.admin')
292 @auth.CSRFRequired()
293 def update(self, repo_name):
294 """
295 PUT /repos/repo_name: Update an existing item"""
296 # Forms posted to this method should contain a hidden field:
297 # <input type="hidden" name="_method" value="PUT" />
298 # Or using helpers:
299 # h.form(url('repo', repo_name=ID),
300 # method='put')
301 # url('repo', repo_name=ID)
302
303 self.__load_data(repo_name)
304 c.active = 'settings'
305 c.repo_fields = RepositoryField.query()\
306 .filter(RepositoryField.repository == c.repo_info).all()
307
308 repo_model = RepoModel()
309 changed_name = repo_name
310
311 c.personal_repo_group = c.rhodecode_user.personal_repo_group
312 # override the choices with extracted revisions !
313 repo = Repository.get_by_repo_name(repo_name)
314 old_data = {
315 'repo_name': repo_name,
316 'repo_group': repo.group.get_dict() if repo.group else {},
317 'repo_type': repo.repo_type,
318 }
319 _form = RepoForm(
320 edit=True, old_data=old_data, repo_groups=c.repo_groups_choices,
321 landing_revs=c.landing_revs_choices, allow_disabled=True)()
322
323 try:
324 form_result = _form.to_python(dict(request.POST))
325 repo = repo_model.update(repo_name, **form_result)
326 ScmModel().mark_for_invalidation(repo_name)
327 h.flash(_('Repository %s updated successfully') % repo_name,
328 category='success')
329 changed_name = repo.repo_name
330 action_logger(c.rhodecode_user, 'admin_updated_repo',
331 changed_name, self.ip_addr, self.sa)
332 Session().commit()
333 except formencode.Invalid as errors:
334 defaults = self.__load_data(repo_name)
335 defaults.update(errors.value)
336 return htmlfill.render(
337 render('admin/repos/repo_edit.mako'),
338 defaults=defaults,
339 errors=errors.error_dict or {},
340 prefix_error=False,
341 encoding="UTF-8",
342 force_defaults=False)
343
344 except Exception:
345 log.exception("Exception during update of repository")
346 h.flash(_('Error occurred during update of repository %s') \
347 % repo_name, category='error')
348 return redirect(url('edit_repo', repo_name=changed_name))
349
350 @HasRepoPermissionAllDecorator('repository.admin')
351 @auth.CSRFRequired()
352 def delete(self, repo_name):
353 """
354 DELETE /repos/repo_name: Delete an existing item"""
355 # Forms posted to this method should contain a hidden field:
356 # <input type="hidden" name="_method" value="DELETE" />
357 # Or using helpers:
358 # h.form(url('repo', repo_name=ID),
359 # method='delete')
360 # url('repo', repo_name=ID)
361
362 repo_model = RepoModel()
363 repo = repo_model.get_by_repo_name(repo_name)
364 if not repo:
365 h.not_mapped_error(repo_name)
366 return redirect(url('repos'))
367 try:
368 _forks = repo.forks.count()
369 handle_forks = None
370 if _forks and request.POST.get('forks'):
371 do = request.POST['forks']
372 if do == 'detach_forks':
373 handle_forks = 'detach'
374 h.flash(_('Detached %s forks') % _forks, category='success')
375 elif do == 'delete_forks':
376 handle_forks = 'delete'
377 h.flash(_('Deleted %s forks') % _forks, category='success')
378 repo_model.delete(repo, forks=handle_forks)
379 action_logger(c.rhodecode_user, 'admin_deleted_repo',
380 repo_name, self.ip_addr, self.sa)
381 ScmModel().mark_for_invalidation(repo_name)
382 h.flash(_('Deleted repository %s') % repo_name, category='success')
383 Session().commit()
384 except AttachedForksError:
385 h.flash(_('Cannot delete %s it still contains attached forks')
386 % repo_name, category='warning')
387
388 except Exception:
389 log.exception("Exception during deletion of repository")
390 h.flash(_('An error occurred during deletion of %s') % repo_name,
391 category='error')
392
393 return redirect(url('repos'))
394
395 @HasPermissionAllDecorator('hg.admin')
287 @HasPermissionAllDecorator('hg.admin')
396 def show(self, repo_name, format='html'):
288 def show(self, repo_name, format='html'):
397 """GET /repos/repo_name: Show a specific item"""
289 """GET /repos/repo_name: Show a specific item"""
398 # url('repo', repo_name=ID)
290 # url('repo', repo_name=ID)
399
291
400 @HasRepoPermissionAllDecorator('repository.admin')
292 @HasRepoPermissionAllDecorator('repository.admin')
401 def edit(self, repo_name):
402 """GET /repo_name/settings: Form to edit an existing item"""
403 # url('edit_repo', repo_name=ID)
404 defaults = self.__load_data(repo_name)
405 if 'clone_uri' in defaults:
406 del defaults['clone_uri']
407
408 c.repo_fields = RepositoryField.query()\
409 .filter(RepositoryField.repository == c.repo_info).all()
410 c.personal_repo_group = c.rhodecode_user.personal_repo_group
411 c.active = 'settings'
412 return htmlfill.render(
413 render('admin/repos/repo_edit.mako'),
414 defaults=defaults,
415 encoding="UTF-8",
416 force_defaults=False)
417
418 @HasRepoPermissionAllDecorator('repository.admin')
419 def edit_permissions(self, repo_name):
420 """GET /repo_name/settings: Form to edit an existing item"""
421 # url('edit_repo', repo_name=ID)
422 c.repo_info = self._load_repo(repo_name)
423 c.active = 'permissions'
424 defaults = RepoModel()._get_defaults(repo_name)
425
426 return htmlfill.render(
427 render('admin/repos/repo_edit.mako'),
428 defaults=defaults,
429 encoding="UTF-8",
430 force_defaults=False)
431
432 @HasRepoPermissionAllDecorator('repository.admin')
433 @auth.CSRFRequired()
434 def edit_permissions_update(self, repo_name):
435 form = RepoPermsForm()().to_python(request.POST)
436 RepoModel().update_permissions(repo_name,
437 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
438
439 #TODO: implement this
440 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
441 # repo_name, self.ip_addr, self.sa)
442 Session().commit()
443 h.flash(_('Repository permissions updated'), category='success')
444 return redirect(url('edit_repo_perms', repo_name=repo_name))
445
446 @HasRepoPermissionAllDecorator('repository.admin')
447 def edit_fields(self, repo_name):
293 def edit_fields(self, repo_name):
448 """GET /repo_name/settings: Form to edit an existing item"""
294 """GET /repo_name/settings: Form to edit an existing item"""
449 # url('edit_repo', repo_name=ID)
450 c.repo_info = self._load_repo(repo_name)
295 c.repo_info = self._load_repo(repo_name)
451 c.repo_fields = RepositoryField.query()\
296 c.repo_fields = RepositoryField.query()\
452 .filter(RepositoryField.repository == c.repo_info).all()
297 .filter(RepositoryField.repository == c.repo_info).all()
@@ -490,106 +335,6 b' class ReposController(BaseRepoController'
490 h.flash(msg, category='error')
335 h.flash(msg, category='error')
491 return redirect(url('edit_repo_fields', repo_name=repo_name))
336 return redirect(url('edit_repo_fields', repo_name=repo_name))
492
337
493 @HasRepoPermissionAllDecorator('repository.admin')
494 def edit_advanced(self, repo_name):
495 """GET /repo_name/settings: Form to edit an existing item"""
496 # url('edit_repo', repo_name=ID)
497 c.repo_info = self._load_repo(repo_name)
498 c.default_user_id = User.get_default_user().user_id
499 c.in_public_journal = UserFollowing.query()\
500 .filter(UserFollowing.user_id == c.default_user_id)\
501 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
502
503 c.active = 'advanced'
504 c.has_origin_repo_read_perm = False
505 if c.repo_info.fork:
506 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
507 'repository.write', 'repository.read', 'repository.admin')(
508 c.repo_info.fork.repo_name, 'repo set as fork page')
509
510 if request.POST:
511 return redirect(url('repo_edit_advanced'))
512 return render('admin/repos/repo_edit.mako')
513
514 @HasRepoPermissionAllDecorator('repository.admin')
515 @auth.CSRFRequired()
516 def edit_advanced_journal(self, repo_name):
517 """
518 Set's this repository to be visible in public journal,
519 in other words assing default user to follow this repo
520
521 :param repo_name:
522 """
523
524 try:
525 repo_id = Repository.get_by_repo_name(repo_name).repo_id
526 user_id = User.get_default_user().user_id
527 self.scm_model.toggle_following_repo(repo_id, user_id)
528 h.flash(_('Updated repository visibility in public journal'),
529 category='success')
530 Session().commit()
531 except Exception:
532 h.flash(_('An error occurred during setting this'
533 ' repository in public journal'),
534 category='error')
535
536 return redirect(url('edit_repo_advanced', repo_name=repo_name))
537
538 @HasRepoPermissionAllDecorator('repository.admin')
539 @auth.CSRFRequired()
540 def edit_advanced_fork(self, repo_name):
541 """
542 Mark given repository as a fork of another
543
544 :param repo_name:
545 """
546
547 new_fork_id = request.POST.get('id_fork_of')
548 try:
549
550 if new_fork_id and not new_fork_id.isdigit():
551 log.error('Given fork id %s is not an INT', new_fork_id)
552
553 fork_id = safe_int(new_fork_id)
554 repo = ScmModel().mark_as_fork(repo_name, fork_id,
555 c.rhodecode_user.username)
556 fork = repo.fork.repo_name if repo.fork else _('Nothing')
557 Session().commit()
558 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
559 category='success')
560 except RepositoryError as e:
561 log.exception("Repository Error occurred")
562 h.flash(str(e), category='error')
563 except Exception as e:
564 log.exception("Exception while editing fork")
565 h.flash(_('An error occurred during this operation'),
566 category='error')
567
568 return redirect(url('edit_repo_advanced', repo_name=repo_name))
569
570 @HasRepoPermissionAllDecorator('repository.admin')
571 @auth.CSRFRequired()
572 def edit_advanced_locking(self, repo_name):
573 """
574 Unlock repository when it is locked !
575
576 :param repo_name:
577 """
578 try:
579 repo = Repository.get_by_repo_name(repo_name)
580 if request.POST.get('set_lock'):
581 Repository.lock(repo, c.rhodecode_user.user_id,
582 lock_reason=Repository.LOCK_WEB)
583 h.flash(_('Locked repository'), category='success')
584 elif request.POST.get('set_unlock'):
585 Repository.unlock(repo)
586 h.flash(_('Unlocked repository'), category='success')
587 except Exception as e:
588 log.exception("Exception during unlocking")
589 h.flash(_('An error occurred during unlocking'),
590 category='error')
591 return redirect(url('edit_repo_advanced', repo_name=repo_name))
592
593 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
338 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
594 @auth.CSRFRequired()
339 @auth.CSRFRequired()
595 def toggle_locking(self, repo_name):
340 def toggle_locking(self, repo_name):
@@ -617,32 +362,7 b' class ReposController(BaseRepoController'
617 log.exception("Exception during unlocking")
362 log.exception("Exception during unlocking")
618 h.flash(_('An error occurred during unlocking'),
363 h.flash(_('An error occurred during unlocking'),
619 category='error')
364 category='error')
620 return redirect(url('summary_home', repo_name=repo_name))
365 return redirect(h.route_path('repo_summary', repo_name=repo_name))
621
622 @HasRepoPermissionAllDecorator('repository.admin')
623 @auth.CSRFRequired()
624 def edit_caches(self, repo_name):
625 """PUT /{repo_name}/settings/caches: invalidate the repo caches."""
626 try:
627 ScmModel().mark_for_invalidation(repo_name, delete=True)
628 Session().commit()
629 h.flash(_('Cache invalidation successful'),
630 category='success')
631 except Exception:
632 log.exception("Exception during cache invalidation")
633 h.flash(_('An error occurred during cache invalidation'),
634 category='error')
635
636 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
637
638 @HasRepoPermissionAllDecorator('repository.admin')
639 def edit_caches_form(self, repo_name):
640 """GET /repo_name/settings: Form to edit an existing item"""
641 # url('edit_repo', repo_name=ID)
642 c.repo_info = self._load_repo(repo_name)
643 c.active = 'caches'
644
645 return render('admin/repos/repo_edit.mako')
646
366
647 @HasRepoPermissionAllDecorator('repository.admin')
367 @HasRepoPermissionAllDecorator('repository.admin')
648 @auth.CSRFRequired()
368 @auth.CSRFRequired()
@@ -660,7 +380,6 b' class ReposController(BaseRepoController'
660 @HasRepoPermissionAllDecorator('repository.admin')
380 @HasRepoPermissionAllDecorator('repository.admin')
661 def edit_remote_form(self, repo_name):
381 def edit_remote_form(self, repo_name):
662 """GET /repo_name/settings: Form to edit an existing item"""
382 """GET /repo_name/settings: Form to edit an existing item"""
663 # url('edit_repo', repo_name=ID)
664 c.repo_info = self._load_repo(repo_name)
383 c.repo_info = self._load_repo(repo_name)
665 c.active = 'remote'
384 c.active = 'remote'
666
385
@@ -682,7 +401,6 b' class ReposController(BaseRepoController'
682 @HasRepoPermissionAllDecorator('repository.admin')
401 @HasRepoPermissionAllDecorator('repository.admin')
683 def edit_statistics_form(self, repo_name):
402 def edit_statistics_form(self, repo_name):
684 """GET /repo_name/settings: Form to edit an existing item"""
403 """GET /repo_name/settings: Form to edit an existing item"""
685 # url('edit_repo', repo_name=ID)
686 c.repo_info = self._load_repo(repo_name)
404 c.repo_info = self._load_repo(repo_name)
687 repo = c.repo_info.scm_instance()
405 repo = c.repo_info.scm_instance()
688
406
@@ -195,9 +195,12 b' class SettingsController(BaseController)'
195 pyramid_settings = get_current_registry().settings
195 pyramid_settings = get_current_registry().settings
196 c.svn_proxy_generate_config = pyramid_settings[generate_config]
196 c.svn_proxy_generate_config = pyramid_settings[generate_config]
197
197
198 defaults = self._form_defaults()
199
200 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
198 return htmlfill.render(
201 return htmlfill.render(
199 render('admin/settings/settings.mako'),
202 render('admin/settings/settings.mako'),
200 defaults=self._form_defaults(),
203 defaults=defaults,
201 encoding="UTF-8",
204 encoding="UTF-8",
202 force_defaults=False)
205 force_defaults=False)
203
206
@@ -35,9 +35,11 b' from sqlalchemy.orm import joinedload'
35
35
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import UserGroupAssignedException,\
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
39 RepoGroupAssignmentError
41 RepoGroupAssignmentError
40 from rhodecode.lib.utils import jsonify, action_logger
42 from rhodecode.lib.utils import jsonify
41 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
42 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
43 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
@@ -52,8 +54,7 b' from rhodecode.model.forms import ('
52 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
53 UserPermissionsForm)
55 UserPermissionsForm)
54 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
55 from rhodecode.lib.utils import action_logger
57
56 from rhodecode.lib.ext_json import json
57
58
58 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
59
60
@@ -105,8 +106,6 b' class UserGroupsController(BaseControlle'
105 # permission check inside
106 # permission check inside
106 @NotAnonymous()
107 @NotAnonymous()
107 def index(self):
108 def index(self):
108 """GET /users_groups: All items in the collection"""
109 # url('users_groups')
110
109
111 from rhodecode.lib.utils import PartialRenderer
110 from rhodecode.lib.utils import PartialRenderer
112 _render = PartialRenderer('data_table/_dt_elements.mako')
111 _render = PartialRenderer('data_table/_dt_elements.mako')
@@ -142,8 +141,6 b' class UserGroupsController(BaseControlle'
142 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
141 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
143 @auth.CSRFRequired()
142 @auth.CSRFRequired()
144 def create(self):
143 def create(self):
145 """POST /users_groups: Create a new item"""
146 # url('users_groups')
147
144
148 users_group_form = UserGroupForm()()
145 users_group_form = UserGroupForm()()
149 try:
146 try:
@@ -154,14 +151,16 b' class UserGroupsController(BaseControlle'
154 owner=c.rhodecode_user.user_id,
151 owner=c.rhodecode_user.user_id,
155 active=form_result['users_group_active'])
152 active=form_result['users_group_active'])
156 Session().flush()
153 Session().flush()
157
154 creation_data = user_group.get_api_data()
158 user_group_name = form_result['users_group_name']
155 user_group_name = form_result['users_group_name']
159 action_logger(c.rhodecode_user,
156
160 'admin_created_users_group:%s' % user_group_name,
157 audit_logger.store_web(
161 None, self.ip_addr, self.sa)
158 'user_group.create', action_data={'data': creation_data},
162 user_group_link = h.link_to(h.escape(user_group_name),
159 user=c.rhodecode_user)
163 url('edit_users_group',
160
164 user_group_id=user_group.users_group_id))
161 user_group_link = h.link_to(
162 h.escape(user_group_name),
163 url('edit_users_group', user_group_id=user_group.users_group_id))
165 h.flash(h.literal(_('Created user group %(user_group_link)s')
164 h.flash(h.literal(_('Created user group %(user_group_link)s')
166 % {'user_group_link': user_group_link}),
165 % {'user_group_link': user_group_link}),
167 category='success')
166 category='success')
@@ -191,13 +190,6 b' class UserGroupsController(BaseControlle'
191 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
190 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
192 @auth.CSRFRequired()
191 @auth.CSRFRequired()
193 def update(self, user_group_id):
192 def update(self, user_group_id):
194 """PUT /user_groups/user_group_id: Update an existing item"""
195 # Forms posted to this method should contain a hidden field:
196 # <input type="hidden" name="_method" value="PUT" />
197 # Or using helpers:
198 # h.form(url('users_group', user_group_id=ID),
199 # method='put')
200 # url('users_group', user_group_id=ID)
201
193
202 user_group_id = safe_int(user_group_id)
194 user_group_id = safe_int(user_group_id)
203 c.user_group = UserGroup.get_or_404(user_group_id)
195 c.user_group = UserGroup.get_or_404(user_group_id)
@@ -207,16 +199,22 b' class UserGroupsController(BaseControlle'
207 users_group_form = UserGroupForm(
199 users_group_form = UserGroupForm(
208 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
200 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
209
201
202 old_values = c.user_group.get_api_data()
210 try:
203 try:
211 form_result = users_group_form.to_python(request.POST)
204 form_result = users_group_form.to_python(request.POST)
212 pstruct = peppercorn.parse(request.POST.items())
205 pstruct = peppercorn.parse(request.POST.items())
213 form_result['users_group_members'] = pstruct['user_group_members']
206 form_result['users_group_members'] = pstruct['user_group_members']
214
207
215 UserGroupModel().update(c.user_group, form_result)
208 user_group, added_members, removed_members = \
209 UserGroupModel().update(c.user_group, form_result)
216 updated_user_group = form_result['users_group_name']
210 updated_user_group = form_result['users_group_name']
217 action_logger(c.rhodecode_user,
211
218 'admin_updated_users_group:%s' % updated_user_group,
212 audit_logger.store_web(
219 None, self.ip_addr, self.sa)
213 'user_group.edit', action_data={'old_data': old_values},
214 user=c.rhodecode_user)
215
216 # TODO(marcink): use added/removed to set user_group.edit.member.add
217
220 h.flash(_('Updated user group %s') % updated_user_group,
218 h.flash(_('Updated user group %s') % updated_user_group,
221 category='success')
219 category='success')
222 Session().commit()
220 Session().commit()
@@ -241,19 +239,16 b' class UserGroupsController(BaseControlle'
241 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
239 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
242 @auth.CSRFRequired()
240 @auth.CSRFRequired()
243 def delete(self, user_group_id):
241 def delete(self, user_group_id):
244 """DELETE /user_groups/user_group_id: Delete an existing item"""
245 # Forms posted to this method should contain a hidden field:
246 # <input type="hidden" name="_method" value="DELETE" />
247 # Or using helpers:
248 # h.form(url('users_group', user_group_id=ID),
249 # method='delete')
250 # url('users_group', user_group_id=ID)
251 user_group_id = safe_int(user_group_id)
242 user_group_id = safe_int(user_group_id)
252 c.user_group = UserGroup.get_or_404(user_group_id)
243 c.user_group = UserGroup.get_or_404(user_group_id)
253 force = str2bool(request.POST.get('force'))
244 force = str2bool(request.POST.get('force'))
254
245
246 old_values = c.user_group.get_api_data()
255 try:
247 try:
256 UserGroupModel().delete(c.user_group, force=force)
248 UserGroupModel().delete(c.user_group, force=force)
249 audit_logger.store_web(
250 'user.delete', action_data={'old_data': old_values},
251 user=c.rhodecode_user)
257 Session().commit()
252 Session().commit()
258 h.flash(_('Successfully deleted user group'), category='success')
253 h.flash(_('Successfully deleted user group'), category='success')
259 except UserGroupAssignedException as e:
254 except UserGroupAssignedException as e:
@@ -330,9 +325,9 b' class UserGroupsController(BaseControlle'
330 except RepoGroupAssignmentError:
325 except RepoGroupAssignmentError:
331 h.flash(_('Target group cannot be the same'), category='error')
326 h.flash(_('Target group cannot be the same'), category='error')
332 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
333 #TODO: implement this
328
334 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
329 # TODO(marcink): implement global permissions
335 # repo_name, self.ip_addr, self.sa)
330 # audit_log.store_web('user_group.edit.permissions')
336 Session().commit()
331 Session().commit()
337 h.flash(_('User Group permissions updated'), category='success')
332 h.flash(_('User Group permissions updated'), category='success')
338 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
333 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
@@ -389,8 +384,6 b' class UserGroupsController(BaseControlle'
389 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
384 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
390 @auth.CSRFRequired()
385 @auth.CSRFRequired()
391 def update_global_perms(self, user_group_id):
386 def update_global_perms(self, user_group_id):
392 """PUT /users_perm/user_group_id: Update an existing item"""
393 # url('users_group_perm', user_group_id=ID, method='put')
394 user_group_id = safe_int(user_group_id)
387 user_group_id = safe_int(user_group_id)
395 user_group = UserGroup.get_or_404(user_group_id)
388 user_group = UserGroup.get_or_404(user_group_id)
396 c.active = 'global_perms'
389 c.active = 'global_perms'
@@ -492,6 +485,9 b' class UserGroupsController(BaseControlle'
492 @XHRRequired()
485 @XHRRequired()
493 @jsonify
486 @jsonify
494 def user_group_members(self, user_group_id):
487 def user_group_members(self, user_group_id):
488 """
489 Return members of given user group
490 """
495 user_group_id = safe_int(user_group_id)
491 user_group_id = safe_int(user_group_id)
496 user_group = UserGroup.get_or_404(user_group_id)
492 user_group = UserGroup.get_or_404(user_group_id)
497 group_members_obj = sorted((x.user for x in user_group.members),
493 group_members_obj = sorted((x.user for x in user_group.members),
@@ -500,8 +496,8 b' class UserGroupsController(BaseControlle'
500 group_members = [
496 group_members = [
501 {
497 {
502 'id': user.user_id,
498 'id': user.user_id,
503 'first_name': user.name,
499 'first_name': user.first_name,
504 'last_name': user.lastname,
500 'last_name': user.last_name,
505 'username': user.username,
501 'username': user.username,
506 'icon_link': h.gravatar_url(user.email, 30),
502 'icon_link': h.gravatar_url(user.email, 30),
507 'value_display': h.person(user.email),
503 'value_display': h.person(user.email),
@@ -31,15 +31,17 b' from pylons.controllers.util import redi'
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import auth
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator, AuthUser)
40 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.exceptions import (
41 from rhodecode.lib.exceptions import (
35 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
42 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
36 UserOwnsUserGroupsException, UserCreationError)
43 UserOwnsUserGroupsException, UserCreationError)
37 from rhodecode.lib import helpers as h
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
38 from rhodecode.lib import auth
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.auth_token import AuthTokenModel
43
45
44 from rhodecode.model.db import (
46 from rhodecode.model.db import (
45 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
47 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
@@ -49,8 +51,6 b' from rhodecode.model.repo_group import R'
49 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
50 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
51 from rhodecode.model.permission import PermissionModel
53 from rhodecode.model.permission import PermissionModel
52 from rhodecode.lib.utils import action_logger
53 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
@@ -88,7 +88,6 b' class UsersController(BaseController):'
88 @HasPermissionAllDecorator('hg.admin')
88 @HasPermissionAllDecorator('hg.admin')
89 @auth.CSRFRequired()
89 @auth.CSRFRequired()
90 def create(self):
90 def create(self):
91 """POST /users: Create a new item"""
92 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
91 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
93 user_model = UserModel()
92 user_model = UserModel()
94 user_form = UserForm()()
93 user_form = UserForm()()
@@ -96,9 +95,12 b' class UsersController(BaseController):'
96 form_result = user_form.to_python(dict(request.POST))
95 form_result = user_form.to_python(dict(request.POST))
97 user = user_model.create(form_result)
96 user = user_model.create(form_result)
98 Session().flush()
97 Session().flush()
98 creation_data = user.get_api_data()
99 username = form_result['username']
99 username = form_result['username']
100 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
100
101 None, self.ip_addr, self.sa)
101 audit_logger.store_web(
102 'user.create', action_data={'data': creation_data},
103 user=c.rhodecode_user)
102
104
103 user_link = h.link_to(h.escape(username),
105 user_link = h.link_to(h.escape(username),
104 url('edit_user',
106 url('edit_user',
@@ -125,8 +127,6 b' class UsersController(BaseController):'
125
127
126 @HasPermissionAllDecorator('hg.admin')
128 @HasPermissionAllDecorator('hg.admin')
127 def new(self):
129 def new(self):
128 """GET /users/new: Form to create a new item"""
129 # url('new_user')
130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
131 self._get_personal_repo_group_template_vars()
131 self._get_personal_repo_group_template_vars()
132 return render('admin/users/user_add.mako')
132 return render('admin/users/user_add.mako')
@@ -134,13 +134,7 b' class UsersController(BaseController):'
134 @HasPermissionAllDecorator('hg.admin')
134 @HasPermissionAllDecorator('hg.admin')
135 @auth.CSRFRequired()
135 @auth.CSRFRequired()
136 def update(self, user_id):
136 def update(self, user_id):
137 """PUT /users/user_id: Update an existing item"""
137
138 # Forms posted to this method should contain a hidden field:
139 # <input type="hidden" name="_method" value="PUT" />
140 # Or using helpers:
141 # h.form(url('update_user', user_id=ID),
142 # method='put')
143 # url('user', user_id=ID)
144 user_id = safe_int(user_id)
138 user_id = safe_int(user_id)
145 c.user = User.get_or_404(user_id)
139 c.user = User.get_or_404(user_id)
146 c.active = 'profile'
140 c.active = 'profile'
@@ -152,6 +146,7 b' class UsersController(BaseController):'
152 old_data={'user_id': user_id,
146 old_data={'user_id': user_id,
153 'email': c.user.email})()
147 'email': c.user.email})()
154 form_result = {}
148 form_result = {}
149 old_values = c.user.get_api_data()
155 try:
150 try:
156 form_result = _form.to_python(dict(request.POST))
151 form_result = _form.to_python(dict(request.POST))
157 skip_attrs = ['extern_type', 'extern_name']
152 skip_attrs = ['extern_type', 'extern_name']
@@ -160,12 +155,15 b' class UsersController(BaseController):'
160 # forbid updating username for external accounts
155 # forbid updating username for external accounts
161 skip_attrs.append('username')
156 skip_attrs.append('username')
162
157
163 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
158 UserModel().update_user(
164 usr = form_result['username']
159 user_id, skip_attrs=skip_attrs, **form_result)
165 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
160
166 None, self.ip_addr, self.sa)
161 audit_logger.store_web(
162 'user.edit', action_data={'old_data': old_values},
163 user=c.rhodecode_user)
164
165 Session().commit()
167 h.flash(_('User updated successfully'), category='success')
166 h.flash(_('User updated successfully'), category='success')
168 Session().commit()
169 except formencode.Invalid as errors:
167 except formencode.Invalid as errors:
170 defaults = errors.value
168 defaults = errors.value
171 e = errors.error_dict or {}
169 e = errors.error_dict or {}
@@ -188,13 +186,6 b' class UsersController(BaseController):'
188 @HasPermissionAllDecorator('hg.admin')
186 @HasPermissionAllDecorator('hg.admin')
189 @auth.CSRFRequired()
187 @auth.CSRFRequired()
190 def delete(self, user_id):
188 def delete(self, user_id):
191 """DELETE /users/user_id: Delete an existing item"""
192 # Forms posted to this method should contain a hidden field:
193 # <input type="hidden" name="_method" value="DELETE" />
194 # Or using helpers:
195 # h.form(url('delete_user', user_id=ID),
196 # method='delete')
197 # url('user', user_id=ID)
198 user_id = safe_int(user_id)
189 user_id = safe_int(user_id)
199 c.user = User.get_or_404(user_id)
190 c.user = User.get_or_404(user_id)
200
191
@@ -249,10 +240,16 b' class UsersController(BaseController):'
249 _('Deleted %s user groups') % len(_user_groups),
240 _('Deleted %s user groups') % len(_user_groups),
250 category='success')
241 category='success')
251
242
243 old_values = c.user.get_api_data()
252 try:
244 try:
253 UserModel().delete(c.user, handle_repos=handle_repos,
245 UserModel().delete(c.user, handle_repos=handle_repos,
254 handle_repo_groups=handle_repo_groups,
246 handle_repo_groups=handle_repo_groups,
255 handle_user_groups=handle_user_groups)
247 handle_user_groups=handle_user_groups)
248
249 audit_logger.store_web(
250 'user.delete', action_data={'old_data': old_values},
251 user=c.rhodecode_user)
252
256 Session().commit()
253 Session().commit()
257 set_handle_flash_repos()
254 set_handle_flash_repos()
258 set_handle_flash_repo_groups()
255 set_handle_flash_repo_groups()
@@ -272,19 +269,25 b' class UsersController(BaseController):'
272 def reset_password(self, user_id):
269 def reset_password(self, user_id):
273 """
270 """
274 toggle reset password flag for this user
271 toggle reset password flag for this user
275
276 :param user_id:
277 """
272 """
278 user_id = safe_int(user_id)
273 user_id = safe_int(user_id)
279 c.user = User.get_or_404(user_id)
274 c.user = User.get_or_404(user_id)
280 try:
275 try:
281 old_value = c.user.user_data.get('force_password_change')
276 old_value = c.user.user_data.get('force_password_change')
282 c.user.update_userdata(force_password_change=not old_value)
277 c.user.update_userdata(force_password_change=not old_value)
283 Session().commit()
278
284 if old_value:
279 if old_value:
285 msg = _('Force password change disabled for user')
280 msg = _('Force password change disabled for user')
281 audit_logger.store_web(
282 'user.edit.password_reset.disabled',
283 user=c.rhodecode_user)
286 else:
284 else:
287 msg = _('Force password change enabled for user')
285 msg = _('Force password change enabled for user')
286 audit_logger.store_web(
287 'user.edit.password_reset.enabled',
288 user=c.rhodecode_user)
289
290 Session().commit()
288 h.flash(msg, category='success')
291 h.flash(msg, category='success')
289 except Exception:
292 except Exception:
290 log.exception("Exception during password reset for user")
293 log.exception("Exception during password reset for user")
@@ -298,8 +301,6 b' class UsersController(BaseController):'
298 def create_personal_repo_group(self, user_id):
301 def create_personal_repo_group(self, user_id):
299 """
302 """
300 Create personal repository group for this user
303 Create personal repository group for this user
301
302 :param user_id:
303 """
304 """
304 from rhodecode.model.repo_group import RepoGroupModel
305 from rhodecode.model.repo_group import RepoGroupModel
305
306
@@ -381,8 +382,7 b' class UsersController(BaseController):'
381 return redirect(h.route_path('users'))
382 return redirect(h.route_path('users'))
382
383
383 c.active = 'advanced'
384 c.active = 'advanced'
384 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
385 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
385 c.personal_repo_group = c.perm_user.personal_repo_group
386 c.personal_repo_group_name = RepoGroupModel()\
386 c.personal_repo_group_name = RepoGroupModel()\
387 .get_personal_group_name(user)
387 .get_personal_group_name(user)
388 c.first_admin = User.get_first_super_admin()
388 c.first_admin = User.get_first_super_admin()
@@ -429,8 +429,6 b' class UsersController(BaseController):'
429 @HasPermissionAllDecorator('hg.admin')
429 @HasPermissionAllDecorator('hg.admin')
430 @auth.CSRFRequired()
430 @auth.CSRFRequired()
431 def update_global_perms(self, user_id):
431 def update_global_perms(self, user_id):
432 """PUT /users_perm/user_id: Update an existing item"""
433 # url('user_perm', user_id=ID, method='put')
434 user_id = safe_int(user_id)
432 user_id = safe_int(user_id)
435 user = User.get_or_404(user_id)
433 user = User.get_or_404(user_id)
436 c.active = 'global_perms'
434 c.active = 'global_perms'
@@ -457,11 +455,13 b' class UsersController(BaseController):'
457
455
458 PermissionModel().update_user_permissions(form_result)
456 PermissionModel().update_user_permissions(form_result)
459
457
458 # TODO(marcink): implement global permissions
459 # audit_log.store_web('user.edit.permissions')
460
460 Session().commit()
461 Session().commit()
461 h.flash(_('User global permissions updated successfully'),
462 h.flash(_('User global permissions updated successfully'),
462 category='success')
463 category='success')
463
464
464 Session().commit()
465 except formencode.Invalid as errors:
465 except formencode.Invalid as errors:
466 defaults = errors.value
466 defaults = errors.value
467 c.user = user
467 c.user = user
@@ -491,140 +491,3 b' class UsersController(BaseController):'
491
491
492 return render('admin/users/user_edit.mako')
492 return render('admin/users/user_edit.mako')
493
493
494 @HasPermissionAllDecorator('hg.admin')
495 def edit_emails(self, user_id):
496 user_id = safe_int(user_id)
497 c.user = User.get_or_404(user_id)
498 if c.user.username == User.DEFAULT_USER:
499 h.flash(_("You can't edit this user"), category='warning')
500 return redirect(h.route_path('users'))
501
502 c.active = 'emails'
503 c.user_email_map = UserEmailMap.query() \
504 .filter(UserEmailMap.user == c.user).all()
505
506 defaults = c.user.get_dict()
507 return htmlfill.render(
508 render('admin/users/user_edit.mako'),
509 defaults=defaults,
510 encoding="UTF-8",
511 force_defaults=False)
512
513 @HasPermissionAllDecorator('hg.admin')
514 @auth.CSRFRequired()
515 def add_email(self, user_id):
516 """POST /user_emails:Add an existing item"""
517 # url('user_emails', user_id=ID, method='put')
518 user_id = safe_int(user_id)
519 c.user = User.get_or_404(user_id)
520
521 email = request.POST.get('new_email')
522 user_model = UserModel()
523
524 try:
525 user_model.add_extra_email(user_id, email)
526 Session().commit()
527 h.flash(_("Added new email address `%s` for user account") % email,
528 category='success')
529 except formencode.Invalid as error:
530 msg = error.error_dict['email']
531 h.flash(msg, category='error')
532 except Exception:
533 log.exception("Exception during email saving")
534 h.flash(_('An error occurred during email saving'),
535 category='error')
536 return redirect(url('edit_user_emails', user_id=user_id))
537
538 @HasPermissionAllDecorator('hg.admin')
539 @auth.CSRFRequired()
540 def delete_email(self, user_id):
541 """DELETE /user_emails_delete/user_id: Delete an existing item"""
542 # url('user_emails_delete', user_id=ID, method='delete')
543 user_id = safe_int(user_id)
544 c.user = User.get_or_404(user_id)
545 email_id = request.POST.get('del_email_id')
546 user_model = UserModel()
547 user_model.delete_extra_email(user_id, email_id)
548 Session().commit()
549 h.flash(_("Removed email address from user account"), category='success')
550 return redirect(url('edit_user_emails', user_id=user_id))
551
552 @HasPermissionAllDecorator('hg.admin')
553 def edit_ips(self, user_id):
554 user_id = safe_int(user_id)
555 c.user = User.get_or_404(user_id)
556 if c.user.username == User.DEFAULT_USER:
557 h.flash(_("You can't edit this user"), category='warning')
558 return redirect(h.route_path('users'))
559
560 c.active = 'ips'
561 c.user_ip_map = UserIpMap.query() \
562 .filter(UserIpMap.user == c.user).all()
563
564 c.inherit_default_ips = c.user.inherit_default_permissions
565 c.default_user_ip_map = UserIpMap.query() \
566 .filter(UserIpMap.user == User.get_default_user()).all()
567
568 defaults = c.user.get_dict()
569 return htmlfill.render(
570 render('admin/users/user_edit.mako'),
571 defaults=defaults,
572 encoding="UTF-8",
573 force_defaults=False)
574
575 @HasPermissionAllDecorator('hg.admin')
576 @auth.CSRFRequired()
577 def add_ip(self, user_id):
578 """POST /user_ips:Add an existing item"""
579 # url('user_ips', user_id=ID, method='put')
580
581 user_id = safe_int(user_id)
582 c.user = User.get_or_404(user_id)
583 user_model = UserModel()
584 try:
585 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
586 except Exception as e:
587 ip_list = []
588 log.exception("Exception during ip saving")
589 h.flash(_('An error occurred during ip saving:%s' % (e,)),
590 category='error')
591
592 desc = request.POST.get('description')
593 added = []
594 for ip in ip_list:
595 try:
596 user_model.add_extra_ip(user_id, ip, desc)
597 Session().commit()
598 added.append(ip)
599 except formencode.Invalid as error:
600 msg = error.error_dict['ip']
601 h.flash(msg, category='error')
602 except Exception:
603 log.exception("Exception during ip saving")
604 h.flash(_('An error occurred during ip saving'),
605 category='error')
606 if added:
607 h.flash(
608 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
609 category='success')
610 if 'default_user' in request.POST:
611 return redirect(url('admin_permissions_ips'))
612 return redirect(url('edit_user_ips', user_id=user_id))
613
614 @HasPermissionAllDecorator('hg.admin')
615 @auth.CSRFRequired()
616 def delete_ip(self, user_id):
617 """DELETE /user_ips_delete/user_id: Delete an existing item"""
618 # url('user_ips_delete', user_id=ID, method='delete')
619 user_id = safe_int(user_id)
620 c.user = User.get_or_404(user_id)
621
622 ip_id = request.POST.get('del_ip_id')
623 user_model = UserModel()
624 user_model.delete_extra_ip(user_id, ip_id)
625 Session().commit()
626 h.flash(_("Removed ip address from user whitelist"), category='success')
627
628 if 'default_user' in request.POST:
629 return redirect(url('admin_permissions_ips'))
630 return redirect(url('edit_user_ips', user_id=user_id))
@@ -46,27 +46,6 b' log = logging.getLogger(__name__)'
46 DEFAULT_CHANGELOG_SIZE = 20
46 DEFAULT_CHANGELOG_SIZE = 20
47
47
48
48
49 def _load_changelog_summary():
50 p = safe_int(request.GET.get('page'), 1)
51 size = safe_int(request.GET.get('size'), 10)
52
53 def url_generator(**kw):
54 return url('summary_home',
55 repo_name=c.rhodecode_db_repo.repo_name, size=size, **kw)
56
57 pre_load = ['author', 'branch', 'date', 'message']
58 try:
59 collection = c.rhodecode_repo.get_commits(pre_load=pre_load)
60 except EmptyRepositoryError:
61 collection = c.rhodecode_repo
62
63 c.repo_commits = RepoPage(
64 collection, page=p, items_per_page=size, url=url_generator)
65 page_ids = [x.raw_id for x in c.repo_commits]
66 c.comments = c.rhodecode_db_repo.get_comments(page_ids)
67 c.statuses = c.rhodecode_db_repo.statuses(page_ids)
68
69
70 class ChangelogController(BaseRepoController):
49 class ChangelogController(BaseRepoController):
71
50
72 def __before__(self):
51 def __before__(self):
@@ -88,13 +67,11 b' class ChangelogController(BaseRepoContro'
88 except EmptyRepositoryError:
67 except EmptyRepositoryError:
89 if not redirect_after:
68 if not redirect_after:
90 return None
69 return None
91 h.flash(h.literal(_('There are no commits yet')),
70 h.flash(_('There are no commits yet'), category='warning')
92 category='warning')
93 redirect(url('changelog_home', repo_name=repo.repo_name))
71 redirect(url('changelog_home', repo_name=repo.repo_name))
94 except RepositoryError as e:
72 except RepositoryError as e:
95 msg = safe_str(e)
73 log.exception(safe_str(e))
96 log.exception(msg)
74 h.flash(safe_str(h.escape(e)), category='warning')
97 h.flash(msg, category='warning')
98 if not partial:
75 if not partial:
99 redirect(h.url('changelog_home', repo_name=repo.repo_name))
76 redirect(h.url('changelog_home', repo_name=repo.repo_name))
100 raise HTTPBadRequest()
77 raise HTTPBadRequest()
@@ -134,7 +111,7 b' class ChangelogController(BaseRepoContro'
134
111
135 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
136 if branch_name not in c.rhodecode_repo.branches_all:
113 if branch_name not in c.rhodecode_repo.branches_all:
137 h.flash('Branch {} is not found.'.format(branch_name),
114 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
138 category='warning')
115 category='warning')
139 redirect(url('changelog_file_home', repo_name=repo_name,
116 redirect(url('changelog_file_home', repo_name=repo_name,
140 revision=branch_name, f_path=f_path or ''))
117 revision=branch_name, f_path=f_path or ''))
@@ -210,12 +187,11 b' class ChangelogController(BaseRepoContro'
210 collection, p, chunk_size, c.branch_name, dynamic=f_path)
187 collection, p, chunk_size, c.branch_name, dynamic=f_path)
211
188
212 except EmptyRepositoryError as e:
189 except EmptyRepositoryError as e:
213 h.flash(safe_str(e), category='warning')
190 h.flash(safe_str(h.escape(e)), category='warning')
214 return redirect(url('summary_home', repo_name=repo_name))
191 return redirect(h.route_path('repo_summary', repo_name=repo_name))
215 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
192 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
216 msg = safe_str(e)
193 log.exception(safe_str(e))
217 log.exception(msg)
194 h.flash(safe_str(h.escape(e)), category='error')
218 h.flash(msg, category='error')
219 return redirect(url('changelog_home', repo_name=repo_name))
195 return redirect(url('changelog_home', repo_name=repo_name))
220
196
221 if (request.environ.get('HTTP_X_PARTIAL_XHR')
197 if (request.environ.get('HTTP_X_PARTIAL_XHR')
@@ -279,12 +255,3 b' class ChangelogController(BaseRepoContro'
279 c.rhodecode_repo, c.pagination,
255 c.rhodecode_repo, c.pagination,
280 prev_data=prev_data, next_data=next_data)
256 prev_data=prev_data, next_data=next_data)
281 return render('changelog/changelog_elements.mako')
257 return render('changelog/changelog_elements.mako')
282
283 @LoginRequired()
284 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
285 'repository.admin')
286 def changelog_summary(self, repo_name):
287 if request.environ.get('HTTP_X_PJAX'):
288 _load_changelog_summary()
289 return render('changelog/changelog_summary_data.mako')
290 raise HTTPNotFound()
@@ -39,8 +39,8 b' from rhodecode.lib.base import BaseRepoC'
39 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 import rhodecode.lib.helpers as h
41 import rhodecode.lib.helpers as h
42 from rhodecode.lib.utils import action_logger, jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode
43 from rhodecode.lib.utils2 import safe_unicode, safe_int
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
@@ -48,7 +48,6 b' from rhodecode.model.db import Changeset'
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.comment import CommentsModel
49 from rhodecode.model.comment import CommentsModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
52
51
53
52
54 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
@@ -268,8 +267,10 b' class ChangesetController(BaseRepoContro'
268 repo_name=c.repo_name,
267 repo_name=c.repo_name,
269 source_node_getter=_node_getter(commit1),
268 source_node_getter=_node_getter(commit1),
270 target_node_getter=_node_getter(commit2),
269 target_node_getter=_node_getter(commit2),
271 comments=inline_comments
270 comments=inline_comments)
272 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
271 diffset = diffset.render_patchset(
272 _parsed, commit1.raw_id, commit2.raw_id)
273
273 c.changes[commit.raw_id] = diffset
274 c.changes[commit.raw_id] = diffset
274 else:
275 else:
275 # downloads/raw we only need RAW diff nothing else
276 # downloads/raw we only need RAW diff nothing else
@@ -368,7 +369,6 b' class ChangesetController(BaseRepoContro'
368 comment_type=comment_type,
369 comment_type=comment_type,
369 resolves_comment_id=resolves_comment_id
370 resolves_comment_id=resolves_comment_id
370 )
371 )
371 c.inline_comment = True if comment.line_no else False
372
372
373 # get status if set !
373 # get status if set !
374 if status:
374 if status:
@@ -433,20 +433,26 b' class ChangesetController(BaseRepoContro'
433 @auth.CSRFRequired()
433 @auth.CSRFRequired()
434 @jsonify
434 @jsonify
435 def delete_comment(self, repo_name, comment_id):
435 def delete_comment(self, repo_name, comment_id):
436 comment = ChangesetComment.get(comment_id)
436 comment = ChangesetComment.get_or_404(safe_int(comment_id))
437 if not comment:
437 if not comment:
438 log.debug('Comment with id:%s not found, skipping', comment_id)
438 log.debug('Comment with id:%s not found, skipping', comment_id)
439 # comment already deleted in another call probably
439 # comment already deleted in another call probably
440 return True
440 return True
441
441
442 owner = (comment.author.user_id == c.rhodecode_user.user_id)
443 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
442 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
444 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
443 super_admin = h.HasPermissionAny('hg.admin')()
445 CommentsModel().delete(comment=comment)
444 comment_owner = (comment.author.user_id == c.rhodecode_user.user_id)
445 is_repo_comment = comment.repo.repo_name == c.repo_name
446 comment_repo_admin = is_repo_admin and is_repo_comment
447
448 if super_admin or comment_owner or comment_repo_admin:
449 CommentsModel().delete(comment=comment, user=c.rhodecode_user)
446 Session().commit()
450 Session().commit()
447 return True
451 return True
448 else:
452 else:
449 raise HTTPForbidden()
453 log.warning('No permissions for user %s to delete comment_id: %s',
454 c.rhodecode_user, comment_id)
455 raise HTTPNotFound()
450
456
451 @LoginRequired()
457 @LoginRequired()
452 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
458 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
@@ -24,7 +24,7 b' Compare controller for showing differenc'
24
24
25 import logging
25 import logging
26
26
27 from webob.exc import HTTPBadRequest
27 from webob.exc import HTTPBadRequest, HTTPNotFound
28 from pylons import request, tmpl_context as c, url
28 from pylons import request, tmpl_context as c, url
29 from pylons.controllers.util import redirect
29 from pylons.controllers.util import redirect
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
@@ -63,14 +63,13 b' class CompareController(BaseRepoControll'
63 return repo.scm_instance().EMPTY_COMMIT
63 return repo.scm_instance().EMPTY_COMMIT
64 h.flash(h.literal(_('There are no commits yet')),
64 h.flash(h.literal(_('There are no commits yet')),
65 category='warning')
65 category='warning')
66 redirect(url('summary_home', repo_name=repo.repo_name))
66 redirect(h.route_path('repo_summary', repo_name=repo.repo_name))
67
67
68 except RepositoryError as e:
68 except RepositoryError as e:
69 msg = safe_str(e)
69 log.exception(safe_str(e))
70 log.exception(msg)
70 h.flash(safe_str(h.escape(e)), category='warning')
71 h.flash(msg, category='warning')
72 if not partial:
71 if not partial:
73 redirect(h.url('summary_home', repo_name=repo.repo_name))
72 redirect(h.route_path('repo_summary', repo_name=repo.repo_name))
74 raise HTTPBadRequest()
73 raise HTTPBadRequest()
75
74
76 @LoginRequired()
75 @LoginRequired()
@@ -86,6 +85,10 b' class CompareController(BaseRepoControll'
86 target_repo = request.GET.get('target_repo', source_repo)
85 target_repo = request.GET.get('target_repo', source_repo)
87 c.source_repo = Repository.get_by_repo_name(source_repo)
86 c.source_repo = Repository.get_by_repo_name(source_repo)
88 c.target_repo = Repository.get_by_repo_name(target_repo)
87 c.target_repo = Repository.get_by_repo_name(target_repo)
88
89 if c.source_repo is None or c.target_repo is None:
90 raise HTTPNotFound()
91
89 c.source_ref = c.target_ref = _('Select commit')
92 c.source_ref = c.target_ref = _('Select commit')
90 c.source_ref_type = ""
93 c.source_ref_type = ""
91 c.target_ref_type = ""
94 c.target_ref_type = ""
@@ -141,18 +144,17 b' class CompareController(BaseRepoControll'
141 target_repo = Repository.get_by_repo_name(target_repo_name)
144 target_repo = Repository.get_by_repo_name(target_repo_name)
142
145
143 if source_repo is None:
146 if source_repo is None:
144 msg = _('Could not find the original repo: %(repo)s') % {
147 log.error('Could not find the source repo: {}'
145 'repo': source_repo}
148 .format(source_repo_name))
146
149 h.flash(_('Could not find the source repo: `{}`')
147 log.error(msg)
150 .format(h.escape(source_repo_name)), category='error')
148 h.flash(msg, category='error')
149 return redirect(url('compare_home', repo_name=c.repo_name))
151 return redirect(url('compare_home', repo_name=c.repo_name))
150
152
151 if target_repo is None:
153 if target_repo is None:
152 msg = _('Could not find the other repo: %(repo)s') % {
154 log.error('Could not find the target repo: {}'
153 'repo': target_repo_name}
155 .format(source_repo_name))
154 log.error(msg)
156 h.flash(_('Could not find the target repo: `{}`')
155 h.flash(msg, category='error')
157 .format(h.escape(target_repo_name)), category='error')
156 return redirect(url('compare_home', repo_name=c.repo_name))
158 return redirect(url('compare_home', repo_name=c.repo_name))
157
159
158 source_scm = source_repo.scm_instance()
160 source_scm = source_repo.scm_instance()
@@ -269,11 +271,13 b' class CompareController(BaseRepoControll'
269 return None
271 return None
270 return get_node
272 return get_node
271
273
272 c.diffset = codeblocks.DiffSet(
274 diffset = codeblocks.DiffSet(
273 repo_name=source_repo.repo_name,
275 repo_name=source_repo.repo_name,
274 source_node_getter=_node_getter(source_commit),
276 source_node_getter=_node_getter(source_commit),
275 target_node_getter=_node_getter(target_commit),
277 target_node_getter=_node_getter(target_commit),
276 ).render_patchset(_parsed, source_ref, target_ref)
278 )
279 c.diffset = diffset.render_patchset(
280 _parsed, source_ref, target_ref)
277
281
278 c.preview_mode = merge
282 c.preview_mode = merge
279 c.source_commit = source_commit
283 c.source_commit = source_commit
@@ -113,7 +113,7 b' class FeedController(BaseRepoController)'
113 def _generate_feed(cache_key):
113 def _generate_feed(cache_key):
114 feed = Atom1Feed(
114 feed = Atom1Feed(
115 title=self.title % repo_name,
115 title=self.title % repo_name,
116 link=url('summary_home', repo_name=repo_name, qualified=True),
116 link=h.route_url('repo_summary', repo_name=repo_name),
117 description=self.description % repo_name,
117 description=self.description % repo_name,
118 language=self.language,
118 language=self.language,
119 ttl=self.ttl
119 ttl=self.ttl
@@ -150,8 +150,7 b' class FeedController(BaseRepoController)'
150 def _generate_feed(cache_key):
150 def _generate_feed(cache_key):
151 feed = Rss201rev2Feed(
151 feed = Rss201rev2Feed(
152 title=self.title % repo_name,
152 title=self.title % repo_name,
153 link=url('summary_home', repo_name=repo_name,
153 link=h.route_url('repo_summary', repo_name=repo_name),
154 qualified=True),
155 description=self.description % repo_name,
154 description=self.description % repo_name,
156 language=self.language,
155 language=self.language,
157 ttl=self.ttl
156 ttl=self.ttl
@@ -35,10 +35,10 b' from webob.exc import HTTPNotFound, HTTP'
35
35
36 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.controllers.utils import parse_path_ref
37 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import diffs, helpers as h, caches
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils import jsonify, action_logger
41 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
43 convert_line_endings, detect_mode, safe_str, str2bool)
43 convert_line_endings, detect_mode, safe_str, str2bool)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
@@ -101,13 +101,13 b' class FilesController(BaseRepoController'
101 add_new = ""
101 add_new = ""
102 h.flash(h.literal(
102 h.flash(h.literal(
103 _('There are no files yet. %s') % add_new), category='warning')
103 _('There are no files yet. %s') % add_new), category='warning')
104 redirect(h.url('summary_home', repo_name=repo_name))
104 redirect(h.route_path('repo_summary', repo_name=repo_name))
105 except (CommitDoesNotExistError, LookupError):
105 except (CommitDoesNotExistError, LookupError):
106 msg = _('No such commit exists for this repository')
106 msg = _('No such commit exists for this repository')
107 h.flash(msg, category='error')
107 h.flash(msg, category='error')
108 raise HTTPNotFound()
108 raise HTTPNotFound()
109 except RepositoryError as e:
109 except RepositoryError as e:
110 h.flash(safe_str(e), category='error')
110 h.flash(safe_str(h.escape(e)), category='error')
111 raise HTTPNotFound()
111 raise HTTPNotFound()
112
112
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
@@ -124,12 +124,11 b' class FilesController(BaseRepoController'
124 if file_node.is_dir():
124 if file_node.is_dir():
125 raise RepositoryError('The given path is a directory')
125 raise RepositoryError('The given path is a directory')
126 except CommitDoesNotExistError:
126 except CommitDoesNotExistError:
127 msg = _('No such commit exists for this repository')
127 log.exception('No such commit exists for this repository')
128 log.exception(msg)
128 h.flash(_('No such commit exists for this repository'), category='error')
129 h.flash(msg, category='error')
130 raise HTTPNotFound()
129 raise HTTPNotFound()
131 except RepositoryError as e:
130 except RepositoryError as e:
132 h.flash(safe_str(e), category='error')
131 h.flash(safe_str(h.escape(e)), category='error')
133 raise HTTPNotFound()
132 raise HTTPNotFound()
134
133
135 return file_node
134 return file_node
@@ -257,7 +256,7 b' class FilesController(BaseRepoController'
257 repo_name, c.commit.raw_id, f_path)
256 repo_name, c.commit.raw_id, f_path)
258
257
259 except RepositoryError as e:
258 except RepositoryError as e:
260 h.flash(safe_str(e), category='error')
259 h.flash(safe_str(h.escape(e)), category='error')
261 raise HTTPNotFound()
260 raise HTTPNotFound()
262
261
263 if request.environ.get('HTTP_X_PJAX'):
262 if request.environ.get('HTTP_X_PJAX'):
@@ -450,7 +449,7 b' class FilesController(BaseRepoController'
450 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
449 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
451
450
452 c.default_message = _(
451 c.default_message = _(
453 'Deleted file %s via RhodeCode Enterprise') % (f_path)
452 'Deleted file {} via RhodeCode Enterprise').format(f_path)
454 c.f_path = f_path
453 c.f_path = f_path
455 node_path = f_path
454 node_path = f_path
456 author = c.rhodecode_user.full_contact
455 author = c.rhodecode_user.full_contact
@@ -469,12 +468,12 b' class FilesController(BaseRepoController'
469 author=author,
468 author=author,
470 )
469 )
471
470
472 h.flash(_('Successfully deleted file %s') % f_path,
471 h.flash(
473 category='success')
472 _('Successfully deleted file `{}`').format(
473 h.escape(f_path)), category='success')
474 except Exception:
474 except Exception:
475 msg = _('Error occurred during commit')
475 log.exception('Error during commit operation')
476 log.exception(msg)
476 h.flash(_('Error occurred during commit'), category='error')
477 h.flash(msg, category='error')
478 return redirect(url('changeset_home',
477 return redirect(url('changeset_home',
479 repo_name=c.repo_name, revision='tip'))
478 repo_name=c.repo_name, revision='tip'))
480
479
@@ -503,7 +502,7 b' class FilesController(BaseRepoController'
503 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
502 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
504
503
505 c.default_message = _(
504 c.default_message = _(
506 'Deleted file %s via RhodeCode Enterprise') % (f_path)
505 'Deleted file {} via RhodeCode Enterprise').format(f_path)
507 c.f_path = f_path
506 c.f_path = f_path
508
507
509 return render('files/files_delete.mako')
508 return render('files/files_delete.mako')
@@ -537,7 +536,7 b' class FilesController(BaseRepoController'
537 return redirect(url('files_home', repo_name=c.repo_name,
536 return redirect(url('files_home', repo_name=c.repo_name,
538 revision=c.commit.raw_id, f_path=f_path))
537 revision=c.commit.raw_id, f_path=f_path))
539 c.default_message = _(
538 c.default_message = _(
540 'Edited file %s via RhodeCode Enterprise') % (f_path)
539 'Edited file {} via RhodeCode Enterprise').format(f_path)
541 c.f_path = f_path
540 c.f_path = f_path
542 old_content = c.file.content
541 old_content = c.file.content
543 sl = old_content.splitlines(1)
542 sl = old_content.splitlines(1)
@@ -575,12 +574,12 b' class FilesController(BaseRepoController'
575 parent_commit=c.commit,
574 parent_commit=c.commit,
576 )
575 )
577
576
578 h.flash(_('Successfully committed to %s') % f_path,
577 h.flash(
579 category='success')
578 _('Successfully committed changes to file `{}`').format(
579 h.escape(f_path)), category='success')
580 except Exception:
580 except Exception:
581 msg = _('Error occurred during commit')
581 log.exception('Error occurred during commit')
582 log.exception(msg)
582 h.flash(_('Error occurred during commit'), category='error')
583 h.flash(msg, category='error')
584 return redirect(url('changeset_home',
583 return redirect(url('changeset_home',
585 repo_name=c.repo_name, revision='tip'))
584 repo_name=c.repo_name, revision='tip'))
586
585
@@ -612,7 +611,7 b' class FilesController(BaseRepoController'
612 return redirect(url('files_home', repo_name=c.repo_name,
611 return redirect(url('files_home', repo_name=c.repo_name,
613 revision=c.commit.raw_id, f_path=f_path))
612 revision=c.commit.raw_id, f_path=f_path))
614 c.default_message = _(
613 c.default_message = _(
615 'Edited file %s via RhodeCode Enterprise') % (f_path)
614 'Edited file {} via RhodeCode Enterprise').format(f_path)
616 c.f_path = f_path
615 c.f_path = f_path
617
616
618 return render('files/files_edit.mako')
617 return render('files/files_edit.mako')
@@ -660,7 +659,7 b' class FilesController(BaseRepoController'
660 file_obj = r_post.get('upload_file', None)
659 file_obj = r_post.get('upload_file', None)
661
660
662 if file_obj is not None and hasattr(file_obj, 'filename'):
661 if file_obj is not None and hasattr(file_obj, 'filename'):
663 filename = file_obj.filename
662 filename = r_post.get('filename_upload')
664 content = file_obj.file
663 content = file_obj.file
665
664
666 if hasattr(content, 'file'):
665 if hasattr(content, 'file'):
@@ -669,14 +668,14 b' class FilesController(BaseRepoController'
669
668
670 # If there's no commit, redirect to repo summary
669 # If there's no commit, redirect to repo summary
671 if type(c.commit) is EmptyCommit:
670 if type(c.commit) is EmptyCommit:
672 redirect_url = "summary_home"
671 redirect_url = h.route_path('repo_summary', repo_name=c.repo_name)
673 else:
672 else:
674 redirect_url = "changeset_home"
673 redirect_url = url("changeset_home", repo_name=c.repo_name,
674 revision='tip')
675
675
676 if not filename:
676 if not filename:
677 h.flash(_('No filename'), category='warning')
677 h.flash(_('No filename'), category='warning')
678 return redirect(url(redirect_url, repo_name=c.repo_name,
678 return redirect(redirect_url)
679 revision='tip'))
680
679
681 # extract the location from filename,
680 # extract the location from filename,
682 # allows using foo/bar.txt syntax to create subdirectories
681 # allows using foo/bar.txt syntax to create subdirectories
@@ -704,8 +703,9 b' class FilesController(BaseRepoController'
704 author=author,
703 author=author,
705 )
704 )
706
705
707 h.flash(_('Successfully committed to %s') % node_path,
706 h.flash(
708 category='success')
707 _('Successfully committed new file `{}`').format(
708 h.escape(node_path)), category='success')
709 except NonRelativePathError as e:
709 except NonRelativePathError as e:
710 h.flash(_(
710 h.flash(_(
711 'The location specified must be a relative path and must not '
711 'The location specified must be a relative path and must not '
@@ -713,11 +713,10 b' class FilesController(BaseRepoController'
713 return redirect(url('changeset_home', repo_name=c.repo_name,
713 return redirect(url('changeset_home', repo_name=c.repo_name,
714 revision='tip'))
714 revision='tip'))
715 except (NodeError, NodeAlreadyExistsError) as e:
715 except (NodeError, NodeAlreadyExistsError) as e:
716 h.flash(_(e), category='error')
716 h.flash(_(h.escape(e)), category='error')
717 except Exception:
717 except Exception:
718 msg = _('Error occurred during commit')
718 log.exception('Error occurred during commit')
719 log.exception(msg)
719 h.flash(_('Error occurred during commit'), category='error')
720 h.flash(msg, category='error')
721 return redirect(url('changeset_home',
720 return redirect(url('changeset_home',
722 repo_name=c.repo_name, revision='tip'))
721 repo_name=c.repo_name, revision='tip'))
723
722
@@ -801,7 +800,7 b' class FilesController(BaseRepoController'
801 if not use_cached_archive:
800 if not use_cached_archive:
802 # generate new archive
801 # generate new archive
803 fd, archive = tempfile.mkstemp()
802 fd, archive = tempfile.mkstemp()
804 log.debug('Creating new temp archive in %s' % (archive,))
803 log.debug('Creating new temp archive in %s', archive)
805 try:
804 try:
806 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
805 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
807 except ImproperArchiveTypeError:
806 except ImproperArchiveTypeError:
@@ -809,10 +808,26 b' class FilesController(BaseRepoController'
809 if archive_cache_enabled:
808 if archive_cache_enabled:
810 # if we generated the archive and we have cache enabled
809 # if we generated the archive and we have cache enabled
811 # let's use this for future
810 # let's use this for future
812 log.debug('Storing new archive in %s' % (cached_archive_path,))
811 log.debug('Storing new archive in %s', cached_archive_path)
813 shutil.move(archive, cached_archive_path)
812 shutil.move(archive, cached_archive_path)
814 archive = cached_archive_path
813 archive = cached_archive_path
815
814
815 # store download action
816 audit_logger.store_web(
817 'repo.archive.download', action_data={
818 'user_agent': request.user_agent,
819 'archive_name': archive_name,
820 'archive_spec': fname,
821 'archive_cached': use_cached_archive},
822 user=c.rhodecode_user,
823 repo=dbrepo,
824 commit=True
825 )
826
827 response.content_disposition = str(
828 'attachment; filename=%s' % archive_name)
829 response.content_type = str(content_type)
830
816 def get_chunked_archive(archive):
831 def get_chunked_archive(archive):
817 with open(archive, 'rb') as stream:
832 with open(archive, 'rb') as stream:
818 while True:
833 while True:
@@ -826,14 +841,6 b' class FilesController(BaseRepoController'
826 break
841 break
827 yield data
842 yield data
828
843
829 # store download action
830 action_logger(user=c.rhodecode_user,
831 action='user_downloaded_archive:%s' % archive_name,
832 repo=repo_name, ipaddr=self.ip_addr, commit=True)
833 response.content_disposition = str(
834 'attachment; filename=%s' % archive_name)
835 response.content_type = str(content_type)
836
837 return get_chunked_archive(archive)
844 return get_chunked_archive(archive)
838
845
839 @LoginRequired()
846 @LoginRequired()
@@ -139,7 +139,7 b' class ForksController(BaseRepoController'
139 c.repo_info = Repository.get_by_repo_name(repo_name)
139 c.repo_info = Repository.get_by_repo_name(repo_name)
140 if not c.repo_info:
140 if not c.repo_info:
141 h.not_mapped_error(repo_name)
141 h.not_mapped_error(repo_name)
142 return redirect(url('home'))
142 return redirect(h.route_path('home'))
143
143
144 defaults = self.__load_data(repo_name)
144 defaults = self.__load_data(repo_name)
145
145
@@ -24,20 +24,15 b' Home controller for RhodeCode Enterprise'
24
24
25 import logging
25 import logging
26 import time
26 import time
27 import re
28
27
29 from pylons import tmpl_context as c, request, url, config
28 from pylons import tmpl_context as c
30 from pylons.i18n.translation import _
31 from sqlalchemy.sql import func
32
29
33 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
31 LoginRequired, HasPermissionAllDecorator,
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
32 HasRepoGroupPermissionAnyDecorator)
36 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.index import searcher_from_config
34
38 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.utils import jsonify
40 from rhodecode.lib.utils2 import safe_unicode, str2bool
41 from rhodecode.model.db import Repository, RepoGroup
36 from rhodecode.model.db import Repository, RepoGroup
42 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
@@ -70,221 +65,3 b' class HomeController(BaseController):'
70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
65 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
71 % (c.rhodecode_name, time.time()))
66 % (c.rhodecode_name, time.time()))
72 raise TestException(msg)
67 raise TestException(msg)
73
74 def _get_groups_and_repos(self, repo_group_id=None):
75 # repo groups groups
76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
77 _perms = ['group.read', 'group.write', 'group.admin']
78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
80 repo_group_list=repo_group_list_acl, admin=False)
81
82 # repositories
83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
84 _perms = ['repository.read', 'repository.write', 'repository.admin']
85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
86 repo_data = RepoModel().get_repos_as_dict(
87 repo_list=repo_list_acl, admin=False)
88
89 return repo_data, repo_group_data
90
91 @LoginRequired()
92 def index(self):
93 c.repo_group = None
94
95 repo_data, repo_group_data = self._get_groups_and_repos()
96 # json used to render the grids
97 c.repos_data = json.dumps(repo_data)
98 c.repo_groups_data = json.dumps(repo_group_data)
99
100 return render('/index.mako')
101
102 @LoginRequired()
103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
104 'group.admin')
105 def index_repo_group(self, group_name):
106 """GET /repo_group_name: Show a specific item"""
107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
108 repo_data, repo_group_data = self._get_groups_and_repos(
109 c.repo_group.group_id)
110
111 # json used to render the grids
112 c.repos_data = json.dumps(repo_data)
113 c.repo_groups_data = json.dumps(repo_group_data)
114
115 return render('index_repo_group.mako')
116
117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
118 query = Repository.query()\
119 .order_by(func.length(Repository.repo_name))\
120 .order_by(Repository.repo_name)
121
122 if repo_type:
123 query = query.filter(Repository.repo_type == repo_type)
124
125 if name_contains:
126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
127 query = query.filter(
128 Repository.repo_name.ilike(ilike_expression))
129 query = query.limit(limit)
130
131 all_repos = query.all()
132 repo_iter = self.scm_model.get_repos(all_repos)
133 return [
134 {
135 'id': obj['name'],
136 'text': obj['name'],
137 'type': 'repo',
138 'obj': obj['dbrepo'],
139 'url': url('summary_home', repo_name=obj['name'])
140 }
141 for obj in repo_iter]
142
143 def _get_repo_group_list(self, name_contains=None, limit=20):
144 query = RepoGroup.query()\
145 .order_by(func.length(RepoGroup.group_name))\
146 .order_by(RepoGroup.group_name)
147
148 if name_contains:
149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
150 query = query.filter(
151 RepoGroup.group_name.ilike(ilike_expression))
152 query = query.limit(limit)
153
154 all_groups = query.all()
155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
156 return [
157 {
158 'id': obj.group_name,
159 'text': obj.group_name,
160 'type': 'group',
161 'obj': {},
162 'url': url('repo_group_home', group_name=obj.group_name)
163 }
164 for obj in repo_groups_iter]
165
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
167 if not hash_starts_with or len(hash_starts_with) < 3:
168 return []
169
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
171
172 if len(commit_hashes) != 1:
173 return []
174
175 commit_hash_prefix = commit_hashes[0]
176
177 auth_user = AuthUser(
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
179 searcher = searcher_from_config(config)
180 result = searcher.search(
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
182 raise_on_exc=False)
183
184 return [
185 {
186 'id': entry['commit_id'],
187 'text': entry['commit_id'],
188 'type': 'commit',
189 'obj': {'repo': entry['repository']},
190 'url': url('changeset_home',
191 repo_name=entry['repository'],
192 revision=entry['commit_id'])
193 }
194 for entry in result['results']]
195
196 @LoginRequired()
197 @XHRRequired()
198 @jsonify
199 def goto_switcher_data(self):
200 query = request.GET.get('query')
201 log.debug('generating goto switcher list, query %s', query)
202
203 res = []
204 repo_groups = self._get_repo_group_list(query)
205 if repo_groups:
206 res.append({
207 'text': _('Groups'),
208 'children': repo_groups
209 })
210
211 repos = self._get_repo_list(query)
212 if repos:
213 res.append({
214 'text': _('Repositories'),
215 'children': repos
216 })
217
218 commits = self._get_hash_commit_list(query)
219 if commits:
220 unique_repos = {}
221 for commit in commits:
222 unique_repos.setdefault(commit['obj']['repo'], []
223 ).append(commit)
224
225 for repo in unique_repos:
226 res.append({
227 'text': _('Commits in %(repo)s') % {'repo': repo},
228 'children': unique_repos[repo]
229 })
230
231 data = {
232 'more': False,
233 'results': res
234 }
235 return data
236
237 @LoginRequired()
238 @XHRRequired()
239 @jsonify
240 def repo_list_data(self):
241 query = request.GET.get('query')
242 repo_type = request.GET.get('repo_type')
243 log.debug('generating repo list, query:%s', query)
244
245 res = []
246 repos = self._get_repo_list(query, repo_type=repo_type)
247 if repos:
248 res.append({
249 'text': _('Repositories'),
250 'children': repos
251 })
252
253 data = {
254 'more': False,
255 'results': res
256 }
257 return data
258
259 @LoginRequired()
260 @XHRRequired()
261 @jsonify
262 def user_autocomplete_data(self):
263 query = request.GET.get('query')
264 active = str2bool(request.GET.get('active') or True)
265
266 repo_model = RepoModel()
267 _users = repo_model.get_users(
268 name_contains=query, only_active=active)
269
270 if request.GET.get('user_groups'):
271 # extend with user groups
272 _user_groups = repo_model.get_user_groups(
273 name_contains=query, only_active=active)
274 _users = _users + _user_groups
275
276 return {'suggestions': _users}
277
278 @LoginRequired()
279 @XHRRequired()
280 @jsonify
281 def user_group_autocomplete_data(self):
282 query = request.GET.get('query')
283 active = str2bool(request.GET.get('active') or True)
284
285 repo_model = RepoModel()
286 _user_groups = repo_model.get_user_groups(
287 name_contains=query, only_active=active)
288 _user_groups = _user_groups
289
290 return {'suggestions': _user_groups}
@@ -34,11 +34,11 b' from webob.exc import HTTPBadRequest'
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.controllers.admin.admin import user_log_filter
38 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
37 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
39 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
40 import rhodecode.lib.helpers as h
39 import rhodecode.lib.helpers as h
41 from rhodecode.lib.helpers import Page
40 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.user_log_filter import user_log_filter
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
@@ -21,8 +21,6 b''
21 """
21 """
22 pull requests controller for rhodecode for initializing pull requests
22 pull requests controller for rhodecode for initializing pull requests
23 """
23 """
24 import types
25
26 import peppercorn
24 import peppercorn
27 import formencode
25 import formencode
28 import logging
26 import logging
@@ -33,6 +31,7 b' from pylons import request, tmpl_context'
33 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
35 from pyramid.threadlocal import get_current_registry
33 from pyramid.threadlocal import get_current_registry
34 from pyramid.httpexceptions import HTTPFound
36 from sqlalchemy.sql import func
35 from sqlalchemy.sql import func
37 from sqlalchemy.sql.expression import or_
36 from sqlalchemy.sql.expression import or_
38
37
@@ -72,124 +71,6 b' class PullrequestsController(BaseRepoCon'
72 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
71 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
73 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
72 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
74
73
75 def _extract_ordering(self, request):
76 column_index = safe_int(request.GET.get('order[0][column]'))
77 order_dir = request.GET.get('order[0][dir]', 'desc')
78 order_by = request.GET.get(
79 'columns[%s][data][sort]' % column_index, 'name_raw')
80 return order_by, order_dir
81
82 @LoginRequired()
83 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
84 'repository.admin')
85 @HasAcceptedRepoType('git', 'hg')
86 def show_all(self, repo_name):
87 # filter types
88 c.active = 'open'
89 c.source = str2bool(request.GET.get('source'))
90 c.closed = str2bool(request.GET.get('closed'))
91 c.my = str2bool(request.GET.get('my'))
92 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
93 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
94 c.repo_name = repo_name
95
96 opened_by = None
97 if c.my:
98 c.active = 'my'
99 opened_by = [c.rhodecode_user.user_id]
100
101 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
102 if c.closed:
103 c.active = 'closed'
104 statuses = [PullRequest.STATUS_CLOSED]
105
106 if c.awaiting_review and not c.source:
107 c.active = 'awaiting'
108 if c.source and not c.awaiting_review:
109 c.active = 'source'
110 if c.awaiting_my_review:
111 c.active = 'awaiting_my'
112
113 data = self._get_pull_requests_list(
114 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
115 if not request.is_xhr:
116 c.data = json.dumps(data['data'])
117 c.records_total = data['recordsTotal']
118 return render('/pullrequests/pullrequests.mako')
119 else:
120 return json.dumps(data)
121
122 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
123 # pagination
124 start = safe_int(request.GET.get('start'), 0)
125 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
126 order_by, order_dir = self._extract_ordering(request)
127
128 if c.awaiting_review:
129 pull_requests = PullRequestModel().get_awaiting_review(
130 repo_name, source=c.source, opened_by=opened_by,
131 statuses=statuses, offset=start, length=length,
132 order_by=order_by, order_dir=order_dir)
133 pull_requests_total_count = PullRequestModel(
134 ).count_awaiting_review(
135 repo_name, source=c.source, statuses=statuses,
136 opened_by=opened_by)
137 elif c.awaiting_my_review:
138 pull_requests = PullRequestModel().get_awaiting_my_review(
139 repo_name, source=c.source, opened_by=opened_by,
140 user_id=c.rhodecode_user.user_id, statuses=statuses,
141 offset=start, length=length, order_by=order_by,
142 order_dir=order_dir)
143 pull_requests_total_count = PullRequestModel(
144 ).count_awaiting_my_review(
145 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
146 statuses=statuses, opened_by=opened_by)
147 else:
148 pull_requests = PullRequestModel().get_all(
149 repo_name, source=c.source, opened_by=opened_by,
150 statuses=statuses, offset=start, length=length,
151 order_by=order_by, order_dir=order_dir)
152 pull_requests_total_count = PullRequestModel().count_all(
153 repo_name, source=c.source, statuses=statuses,
154 opened_by=opened_by)
155
156 from rhodecode.lib.utils import PartialRenderer
157 _render = PartialRenderer('data_table/_dt_elements.mako')
158 data = []
159 for pr in pull_requests:
160 comments = CommentsModel().get_all_comments(
161 c.rhodecode_db_repo.repo_id, pull_request=pr)
162
163 data.append({
164 'name': _render('pullrequest_name',
165 pr.pull_request_id, pr.target_repo.repo_name),
166 'name_raw': pr.pull_request_id,
167 'status': _render('pullrequest_status',
168 pr.calculated_review_status()),
169 'title': _render(
170 'pullrequest_title', pr.title, pr.description),
171 'description': h.escape(pr.description),
172 'updated_on': _render('pullrequest_updated_on',
173 h.datetime_to_time(pr.updated_on)),
174 'updated_on_raw': h.datetime_to_time(pr.updated_on),
175 'created_on': _render('pullrequest_updated_on',
176 h.datetime_to_time(pr.created_on)),
177 'created_on_raw': h.datetime_to_time(pr.created_on),
178 'author': _render('pullrequest_author',
179 pr.author.full_contact, ),
180 'author_raw': pr.author.full_name,
181 'comments': _render('pullrequest_comments', len(comments)),
182 'comments_raw': len(comments),
183 'closed': pr.is_closed(),
184 })
185 # json used to render the grid
186 data = ({
187 'data': data,
188 'recordsTotal': pull_requests_total_count,
189 'recordsFiltered': pull_requests_total_count,
190 })
191 return data
192
193 @LoginRequired()
74 @LoginRequired()
194 @NotAnonymous()
75 @NotAnonymous()
195 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
@@ -203,7 +84,7 b' class PullrequestsController(BaseRepoCon'
203 except EmptyRepositoryError:
84 except EmptyRepositoryError:
204 h.flash(h.literal(_('There are no commits yet')),
85 h.flash(h.literal(_('There are no commits yet')),
205 category='warning')
86 category='warning')
206 redirect(url('summary_home', repo_name=source_repo.repo_name))
87 redirect(h.route_path('repo_summary', repo_name=source_repo.repo_name))
207
88
208 commit_id = request.GET.get('commit')
89 commit_id = request.GET.get('commit')
209 branch_ref = request.GET.get('branch')
90 branch_ref = request.GET.get('branch')
@@ -346,8 +227,6 b' class PullrequestsController(BaseRepoCon'
346 target_repo = _form['target_repo']
227 target_repo = _form['target_repo']
347 target_ref = _form['target_ref']
228 target_ref = _form['target_ref']
348 commit_ids = _form['revisions'][::-1]
229 commit_ids = _form['revisions'][::-1]
349 reviewers = [
350 (r['user_id'], r['reasons']) for r in _form['review_members']]
351
230
352 # find the ancestor for this pr
231 # find the ancestor for this pr
353 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
232 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
@@ -375,23 +254,36 b' class PullrequestsController(BaseRepoCon'
375 )
254 )
376
255
377 description = _form['pullrequest_desc']
256 description = _form['pullrequest_desc']
257
258 get_default_reviewers_data, validate_default_reviewers = \
259 PullRequestModel().get_reviewer_functions()
260
261 # recalculate reviewers logic, to make sure we can validate this
262 reviewer_rules = get_default_reviewers_data(
263 c.rhodecode_user.get_instance(), source_db_repo,
264 source_commit, target_db_repo, target_commit)
265
266 given_reviewers = _form['review_members']
267 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
268
378 try:
269 try:
379 pull_request = PullRequestModel().create(
270 pull_request = PullRequestModel().create(
380 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
271 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
381 target_ref, commit_ids, reviewers, pullrequest_title,
272 target_ref, commit_ids, reviewers, pullrequest_title,
382 description
273 description, reviewer_rules
383 )
274 )
384 Session().commit()
275 Session().commit()
385 h.flash(_('Successfully opened new pull request'),
276 h.flash(_('Successfully opened new pull request'),
386 category='success')
277 category='success')
387 except Exception as e:
278 except Exception as e:
388 msg = _('Error occurred during sending pull request')
279 msg = _('Error occurred during creation of this pull request.')
389 log.exception(msg)
280 log.exception(msg)
390 h.flash(msg, category='error')
281 h.flash(msg, category='error')
391 return redirect(url('pullrequest_home', repo_name=repo_name))
282 return redirect(url('pullrequest_home', repo_name=repo_name))
392
283
393 return redirect(url('pullrequest_show', repo_name=target_repo,
284 raise HTTPFound(
394 pull_request_id=pull_request.pull_request_id))
285 h.route_path('pullrequest_show', repo_name=target_repo,
286 pull_request_id=pull_request.pull_request_id))
395
287
396 @LoginRequired()
288 @LoginRequired()
397 @NotAnonymous()
289 @NotAnonymous()
@@ -410,11 +302,10 b' class PullrequestsController(BaseRepoCon'
410
302
411 if 'review_members' in controls:
303 if 'review_members' in controls:
412 self._update_reviewers(
304 self._update_reviewers(
413 pull_request_id, controls['review_members'])
305 pull_request_id, controls['review_members'],
306 pull_request.reviewer_data)
414 elif str2bool(request.POST.get('update_commits', 'false')):
307 elif str2bool(request.POST.get('update_commits', 'false')):
415 self._update_commits(pull_request)
308 self._update_commits(pull_request)
416 elif str2bool(request.POST.get('close_pull_request', 'false')):
417 self._reject_close(pull_request)
418 elif str2bool(request.POST.get('edit_pull_request', 'false')):
309 elif str2bool(request.POST.get('edit_pull_request', 'false')):
419 self._edit_pull_request(pull_request)
310 self._edit_pull_request(pull_request)
420 else:
311 else:
@@ -426,7 +317,7 b' class PullrequestsController(BaseRepoCon'
426 try:
317 try:
427 PullRequestModel().edit(
318 PullRequestModel().edit(
428 pull_request, request.POST.get('title'),
319 pull_request, request.POST.get('title'),
429 request.POST.get('description'))
320 request.POST.get('description'), c.rhodecode_user)
430 except ValueError:
321 except ValueError:
431 msg = _(u'Cannot update closed pull requests.')
322 msg = _(u'Cannot update closed pull requests.')
432 h.flash(msg, category='error')
323 h.flash(msg, category='error')
@@ -492,7 +383,7 b' class PullrequestsController(BaseRepoCon'
492 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
383 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
493 warning_reasons = [
384 warning_reasons = [
494 UpdateFailureReason.NO_CHANGE,
385 UpdateFailureReason.NO_CHANGE,
495 UpdateFailureReason.WRONG_REF_TPYE,
386 UpdateFailureReason.WRONG_REF_TYPE,
496 ]
387 ]
497 category = 'warning' if resp.reason in warning_reasons else 'error'
388 category = 'warning' if resp.reason in warning_reasons else 'error'
498 h.flash(msg, category=category)
389 h.flash(msg, category=category)
@@ -529,10 +420,10 b' class PullrequestsController(BaseRepoCon'
529 scm=pull_request.target_repo.repo_type)
420 scm=pull_request.target_repo.repo_type)
530 self._merge_pull_request(pull_request, user, extras)
421 self._merge_pull_request(pull_request, user, extras)
531
422
532 return redirect(url(
423 raise HTTPFound(
533 'pullrequest_show',
424 h.route_path('pullrequest_show',
534 repo_name=pull_request.target_repo.repo_name,
425 repo_name=pull_request.target_repo.repo_name,
535 pull_request_id=pull_request.pull_request_id))
426 pull_request_id=pull_request.pull_request_id))
536
427
537 def _merge_pull_request(self, pull_request, user, extras):
428 def _merge_pull_request(self, pull_request, user, extras):
538 merge_resp = PullRequestModel().merge(
429 merge_resp = PullRequestModel().merge(
@@ -553,18 +444,21 b' class PullrequestsController(BaseRepoCon'
553 merge_resp.failure_reason)
444 merge_resp.failure_reason)
554 h.flash(msg, category='error')
445 h.flash(msg, category='error')
555
446
556 def _update_reviewers(self, pull_request_id, review_members):
447 def _update_reviewers(self, pull_request_id, review_members, reviewer_rules):
557 reviewers = [
448
558 (int(r['user_id']), r['reasons']) for r in review_members]
449 get_default_reviewers_data, validate_default_reviewers = \
559 PullRequestModel().update_reviewers(pull_request_id, reviewers)
450 PullRequestModel().get_reviewer_functions()
560 Session().commit()
561
451
562 def _reject_close(self, pull_request):
452 try:
563 if pull_request.is_closed():
453 reviewers = validate_default_reviewers(review_members, reviewer_rules)
564 raise HTTPForbidden()
454 except ValueError as e:
455 log.error('Reviewers Validation: {}'.format(e))
456 h.flash(e, category='error')
457 return
565
458
566 PullRequestModel().close_pull_request_with_comment(
459 PullRequestModel().update_reviewers(
567 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
460 pull_request_id, reviewers, c.rhodecode_user)
461 h.flash(_('Pull request reviewers updated.'), category='success')
568 Session().commit()
462 Session().commit()
569
463
570 @LoginRequired()
464 @LoginRequired()
@@ -583,7 +477,7 b' class PullrequestsController(BaseRepoCon'
583
477
584 # only owner can delete it !
478 # only owner can delete it !
585 if allowed_to_delete:
479 if allowed_to_delete:
586 PullRequestModel().delete(pull_request)
480 PullRequestModel().delete(pull_request, c.rhodecode_user)
587 Session().commit()
481 Session().commit()
588 h.flash(_('Successfully deleted pull request'),
482 h.flash(_('Successfully deleted pull request'),
589 category='success')
483 category='success')
@@ -608,7 +502,8 b' class PullrequestsController(BaseRepoCon'
608 _org_pull_request_obj = pull_request_ver.pull_request
502 _org_pull_request_obj = pull_request_ver.pull_request
609 at_version = pull_request_ver.pull_request_version_id
503 at_version = pull_request_ver.pull_request_version_id
610 else:
504 else:
611 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(pull_request_id)
505 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
506 pull_request_id)
612
507
613 pull_request_display_obj = PullRequest.get_pr_display_object(
508 pull_request_display_obj = PullRequest.get_pr_display_object(
614 pull_request_obj, _org_pull_request_obj)
509 pull_request_obj, _org_pull_request_obj)
@@ -715,9 +610,9 b' class PullrequestsController(BaseRepoCon'
715 c.allowed_to_comment = False
610 c.allowed_to_comment = False
716 c.allowed_to_close = False
611 c.allowed_to_close = False
717 else:
612 else:
718 c.allowed_to_change_status = PullRequestModel(). \
613 can_change_status = PullRequestModel().check_user_change_status(
719 check_user_change_status(pull_request_at_ver, c.rhodecode_user) \
614 pull_request_at_ver, c.rhodecode_user)
720 and not pr_closed
615 c.allowed_to_change_status = can_change_status and not pr_closed
721
616
722 c.allowed_to_update = PullRequestModel().check_user_update(
617 c.allowed_to_update = PullRequestModel().check_user_update(
723 pull_request_latest, c.rhodecode_user) and not pr_closed
618 pull_request_latest, c.rhodecode_user) and not pr_closed
@@ -726,7 +621,24 b' class PullrequestsController(BaseRepoCon'
726 c.allowed_to_delete = PullRequestModel().check_user_delete(
621 c.allowed_to_delete = PullRequestModel().check_user_delete(
727 pull_request_latest, c.rhodecode_user) and not pr_closed
622 pull_request_latest, c.rhodecode_user) and not pr_closed
728 c.allowed_to_comment = not pr_closed
623 c.allowed_to_comment = not pr_closed
729 c.allowed_to_close = c.allowed_to_change_status and not pr_closed
624 c.allowed_to_close = c.allowed_to_merge and not pr_closed
625
626 c.forbid_adding_reviewers = False
627 c.forbid_author_to_review = False
628 c.forbid_commit_author_to_review = False
629
630 if pull_request_latest.reviewer_data and \
631 'rules' in pull_request_latest.reviewer_data:
632 rules = pull_request_latest.reviewer_data['rules'] or {}
633 try:
634 c.forbid_adding_reviewers = rules.get(
635 'forbid_adding_reviewers')
636 c.forbid_author_to_review = rules.get(
637 'forbid_author_to_review')
638 c.forbid_commit_author_to_review = rules.get(
639 'forbid_commit_author_to_review')
640 except Exception:
641 pass
730
642
731 # check merge capabilities
643 # check merge capabilities
732 _merge_check = MergeCheck.validate(
644 _merge_check = MergeCheck.validate(
@@ -748,7 +660,7 b' class PullrequestsController(BaseRepoCon'
748 # GENERAL COMMENTS with versions #
660 # GENERAL COMMENTS with versions #
749 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
661 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
750 q = q.order_by(ChangesetComment.comment_id.asc())
662 q = q.order_by(ChangesetComment.comment_id.asc())
751 general_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
663 general_comments = q
752
664
753 # pick comments we want to render at current version
665 # pick comments we want to render at current version
754 c.comment_versions = comments_model.aggregate_comments(
666 c.comment_versions = comments_model.aggregate_comments(
@@ -758,7 +670,8 b' class PullrequestsController(BaseRepoCon'
758 # INLINE COMMENTS with versions #
670 # INLINE COMMENTS with versions #
759 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
671 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
760 q = q.order_by(ChangesetComment.comment_id.asc())
672 q = q.order_by(ChangesetComment.comment_id.asc())
761 inline_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
673 inline_comments = q
674
762 c.inline_versions = comments_model.aggregate_comments(
675 c.inline_versions = comments_model.aggregate_comments(
763 inline_comments, versions, c.at_version_num, inline=True)
676 inline_comments, versions, c.at_version_num, inline=True)
764
677
@@ -841,12 +754,18 b' class PullrequestsController(BaseRepoCon'
841 c.commit_ranges.append(comm)
754 c.commit_ranges.append(comm)
842 commit_cache[comm.raw_id] = comm
755 commit_cache[comm.raw_id] = comm
843
756
757 # Order here matters, we first need to get target, and then
758 # the source
844 target_commit = commits_source_repo.get_commit(
759 target_commit = commits_source_repo.get_commit(
845 commit_id=safe_str(target_ref_id))
760 commit_id=safe_str(target_ref_id))
761
846 source_commit = commits_source_repo.get_commit(
762 source_commit = commits_source_repo.get_commit(
847 commit_id=safe_str(source_ref_id))
763 commit_id=safe_str(source_ref_id))
764
848 except CommitDoesNotExistError:
765 except CommitDoesNotExistError:
849 pass
766 log.warning(
767 'Failed to get commit from `{}` repo'.format(
768 commits_source_repo), exc_info=True)
850 except RepositoryRequirementError:
769 except RepositoryRequirementError:
851 log.warning(
770 log.warning(
852 'Failed to get all required data from repo', exc_info=True)
771 'Failed to get all required data from repo', exc_info=True)
@@ -960,6 +879,7 b' class PullrequestsController(BaseRepoCon'
960 pull_request_id = safe_int(pull_request_id)
879 pull_request_id = safe_int(pull_request_id)
961 pull_request = PullRequest.get_or_404(pull_request_id)
880 pull_request = PullRequest.get_or_404(pull_request_id)
962 if pull_request.is_closed():
881 if pull_request.is_closed():
882 log.debug('comment: forbidden because pull request is closed')
963 raise HTTPForbidden()
883 raise HTTPForbidden()
964
884
965 status = request.POST.get('changeset_status', None)
885 status = request.POST.get('changeset_status', None)
@@ -968,95 +888,95 b' class PullrequestsController(BaseRepoCon'
968 resolves_comment_id = request.POST.get('resolves_comment_id', None)
888 resolves_comment_id = request.POST.get('resolves_comment_id', None)
969 close_pull_request = request.POST.get('close_pull_request')
889 close_pull_request = request.POST.get('close_pull_request')
970
890
971 close_pr = False
891 # the logic here should work like following, if we submit close
972 if close_pull_request:
892 # pr comment, use `close_pull_request_with_comment` function
973 close_pr = True
893 # else handle regular comment logic
974 pull_request_review_status = pull_request.calculated_review_status()
894 user = c.rhodecode_user
975 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
895 repo = c.rhodecode_db_repo
976 # approved only if we have voting consent
977 status = ChangesetStatus.STATUS_APPROVED
978 else:
979 status = ChangesetStatus.STATUS_REJECTED
980
981 allowed_to_change_status = PullRequestModel().check_user_change_status(
982 pull_request, c.rhodecode_user)
983
896
984 if status and allowed_to_change_status:
897 if close_pull_request:
985 message = (_('Status change %(transition_icon)s %(status)s')
898 # only owner or admin or person with write permissions
986 % {'transition_icon': '>',
899 allowed_to_close = PullRequestModel().check_user_update(
987 'status': ChangesetStatus.get_status_lbl(status)})
900 pull_request, c.rhodecode_user)
988 if close_pr:
901 if not allowed_to_close:
989 message = _('Closing with') + ' ' + message
902 log.debug('comment: forbidden because not allowed to close '
990 text = text or message
903 'pull request %s', pull_request_id)
991 comm = CommentsModel().create(
904 raise HTTPForbidden()
992 text=text,
905 comment, status = PullRequestModel().close_pull_request_with_comment(
993 repo=c.rhodecode_db_repo.repo_id,
906 pull_request, user, repo, message=text)
994 user=c.rhodecode_user.user_id,
907 Session().flush()
995 pull_request=pull_request_id,
908 events.trigger(
996 f_path=request.POST.get('f_path'),
909 events.PullRequestCommentEvent(pull_request, comment))
997 line_no=request.POST.get('line'),
910
998 status_change=(ChangesetStatus.get_status_lbl(status)
911 else:
999 if status and allowed_to_change_status else None),
912 # regular comment case, could be inline, or one with status.
1000 status_change_type=(status
913 # for that one we check also permissions
1001 if status and allowed_to_change_status else None),
914
1002 closing_pr=close_pr,
915 allowed_to_change_status = PullRequestModel().check_user_change_status(
1003 comment_type=comment_type,
916 pull_request, c.rhodecode_user)
1004 resolves_comment_id=resolves_comment_id
917
1005 )
918 if status and allowed_to_change_status:
919 message = (_('Status change %(transition_icon)s %(status)s')
920 % {'transition_icon': '>',
921 'status': ChangesetStatus.get_status_lbl(status)})
922 text = text or message
1006
923
1007 if allowed_to_change_status:
924 comment = CommentsModel().create(
1008 old_calculated_status = pull_request.calculated_review_status()
925 text=text,
1009 # get status if set !
926 repo=c.rhodecode_db_repo.repo_id,
1010 if status:
927 user=c.rhodecode_user.user_id,
1011 ChangesetStatusModel().set_status(
928 pull_request=pull_request_id,
1012 c.rhodecode_db_repo.repo_id,
929 f_path=request.POST.get('f_path'),
1013 status,
930 line_no=request.POST.get('line'),
1014 c.rhodecode_user.user_id,
931 status_change=(ChangesetStatus.get_status_lbl(status)
1015 comm,
932 if status and allowed_to_change_status else None),
1016 pull_request=pull_request_id
933 status_change_type=(status
1017 )
934 if status and allowed_to_change_status else None),
935 comment_type=comment_type,
936 resolves_comment_id=resolves_comment_id
937 )
938
939 if allowed_to_change_status:
940 # calculate old status before we change it
941 old_calculated_status = pull_request.calculated_review_status()
1018
942
1019 Session().flush()
943 # get status if set !
1020 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
944 if status:
1021 # we now calculate the status of pull request, and based on that
945 ChangesetStatusModel().set_status(
1022 # calculation we set the commits status
946 c.rhodecode_db_repo.repo_id,
1023 calculated_status = pull_request.calculated_review_status()
947 status,
1024 if old_calculated_status != calculated_status:
948 c.rhodecode_user.user_id,
1025 PullRequestModel()._trigger_pull_request_hook(
949 comment,
1026 pull_request, c.rhodecode_user, 'review_status_change')
950 pull_request=pull_request_id
1027
951 )
1028 calculated_status_lbl = ChangesetStatus.get_status_lbl(
1029 calculated_status)
1030
952
1031 if close_pr:
953 Session().flush()
1032 status_completed = (
954 events.trigger(
1033 calculated_status in [ChangesetStatus.STATUS_APPROVED,
955 events.PullRequestCommentEvent(pull_request, comment))
1034 ChangesetStatus.STATUS_REJECTED])
956
1035 if close_pull_request or status_completed:
957 # we now calculate the status of pull request, and based on that
1036 PullRequestModel().close_pull_request(
958 # calculation we set the commits status
1037 pull_request_id, c.rhodecode_user)
959 calculated_status = pull_request.calculated_review_status()
1038 else:
960 if old_calculated_status != calculated_status:
1039 h.flash(_('Closing pull request on other statuses than '
961 PullRequestModel()._trigger_pull_request_hook(
1040 'rejected or approved is forbidden. '
962 pull_request, c.rhodecode_user, 'review_status_change')
1041 'Calculated status from all reviewers '
1042 'is currently: %s') % calculated_status_lbl,
1043 category='warning')
1044
963
1045 Session().commit()
964 Session().commit()
1046
965
1047 if not request.is_xhr:
966 if not request.is_xhr:
1048 return redirect(h.url('pullrequest_show', repo_name=repo_name,
967 raise HTTPFound(
1049 pull_request_id=pull_request_id))
968 h.route_path('pullrequest_show',
969 repo_name=repo_name,
970 pull_request_id=pull_request_id))
1050
971
1051 data = {
972 data = {
1052 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
973 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
1053 }
974 }
1054 if comm:
975 if comment:
1055 c.co = comm
976 c.co = comment
1056 c.inline_comment = True if comm.line_no else False
977 rendered_comment = render('changeset/changeset_comment_block.mako')
1057 data.update(comm.get_dict())
978 data.update(comment.get_dict())
1058 data.update({'rendered_text':
979 data.update({'rendered_text': rendered_comment})
1059 render('changeset/changeset_comment_block.mako')})
1060
980
1061 return data
981 return data
1062
982
@@ -1067,25 +987,32 b' class PullrequestsController(BaseRepoCon'
1067 @auth.CSRFRequired()
987 @auth.CSRFRequired()
1068 @jsonify
988 @jsonify
1069 def delete_comment(self, repo_name, comment_id):
989 def delete_comment(self, repo_name, comment_id):
1070 return self._delete_comment(comment_id)
990 comment = ChangesetComment.get_or_404(safe_int(comment_id))
991 if not comment:
992 log.debug('Comment with id:%s not found, skipping', comment_id)
993 # comment already deleted in another call probably
994 return True
1071
995
1072 def _delete_comment(self, comment_id):
996 if comment.pull_request.is_closed():
1073 comment_id = safe_int(comment_id)
1074 co = ChangesetComment.get_or_404(comment_id)
1075 if co.pull_request.is_closed():
1076 # don't allow deleting comments on closed pull request
997 # don't allow deleting comments on closed pull request
1077 raise HTTPForbidden()
998 raise HTTPForbidden()
1078
999
1079 is_owner = co.author.user_id == c.rhodecode_user.user_id
1080 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1000 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1081 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1001 super_admin = h.HasPermissionAny('hg.admin')()
1082 old_calculated_status = co.pull_request.calculated_review_status()
1002 comment_owner = comment.author.user_id == c.rhodecode_user.user_id
1083 CommentsModel().delete(comment=co)
1003 is_repo_comment = comment.repo.repo_name == c.repo_name
1004 comment_repo_admin = is_repo_admin and is_repo_comment
1005
1006 if super_admin or comment_owner or comment_repo_admin:
1007 old_calculated_status = comment.pull_request.calculated_review_status()
1008 CommentsModel().delete(comment=comment, user=c.rhodecode_user)
1084 Session().commit()
1009 Session().commit()
1085 calculated_status = co.pull_request.calculated_review_status()
1010 calculated_status = comment.pull_request.calculated_review_status()
1086 if old_calculated_status != calculated_status:
1011 if old_calculated_status != calculated_status:
1087 PullRequestModel()._trigger_pull_request_hook(
1012 PullRequestModel()._trigger_pull_request_hook(
1088 co.pull_request, c.rhodecode_user, 'review_status_change')
1013 comment.pull_request, c.rhodecode_user, 'review_status_change')
1089 return True
1014 return True
1090 else:
1015 else:
1091 raise HTTPForbidden()
1016 log.warning('No permissions for user %s to delete comment_id: %s',
1017 c.rhodecode_user, comment_id)
1018 raise HTTPNotFound()
@@ -87,21 +87,3 b' def get_commit_from_ref_name(repo, ref_n'
87 '%s "%s" does not exist' % (ref_type, ref_name))
87 '%s "%s" does not exist' % (ref_type, ref_name))
88
88
89 return repo_scm.get_commit(commit_id)
89 return repo_scm.get_commit(commit_id)
90
91
92 def reviewer_as_json(user, reasons):
93 """
94 Returns json struct of a reviewer for frontend
95
96 :param user: the reviewer
97 :param reasons: list of strings of why they are reviewers
98 """
99
100 return {
101 'user_id': user.user_id,
102 'reasons': reasons,
103 'username': user.username,
104 'firstname': user.firstname,
105 'lastname': user.lastname,
106 'gravatar_link': h.gravatar_url(user.email, 14),
107 }
@@ -18,6 +18,8 b''
18
18
19 import logging
19 import logging
20 from pyramid.threadlocal import get_current_registry
20 from pyramid.threadlocal import get_current_registry
21 from rhodecode.events.base import RhodecodeEvent
22
21
23
22 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
23
25
@@ -32,20 +34,21 b' def trigger(event, registry=None):'
32 # passing the registry as an argument to get rid of it.
34 # passing the registry as an argument to get rid of it.
33 registry = registry or get_current_registry()
35 registry = registry or get_current_registry()
34 registry.notify(event)
36 registry.notify(event)
35 log.debug('event %s triggered', event)
37 log.debug('event %s triggered using registry %s', event, registry)
36
38
37 # Until we can work around the problem that VCS operations do not have a
39 # Until we can work around the problem that VCS operations do not have a
38 # pyramid context to work with, we send the events to integrations directly
40 # pyramid context to work with, we send the events to integrations directly
39
41
40 # Later it will be possible to use regular pyramid subscribers ie:
42 # Later it will be possible to use regular pyramid subscribers ie:
41 # config.add_subscriber(integrations_event_handler, RhodecodeEvent)
43 # config.add_subscriber(
44 # 'rhodecode.integrations.integrations_event_handler',
45 # 'rhodecode.events.RhodecodeEvent')
46 # trigger(event, request.registry)
47
42 from rhodecode.integrations import integrations_event_handler
48 from rhodecode.integrations import integrations_event_handler
43 if isinstance(event, RhodecodeEvent):
49 if isinstance(event, RhodecodeEvent):
44 integrations_event_handler(event)
50 integrations_event_handler(event)
45
51
46
47 from rhodecode.events.base import RhodecodeEvent
48
49 from rhodecode.events.user import ( # noqa
52 from rhodecode.events.user import ( # noqa
50 UserPreCreate,
53 UserPreCreate,
51 UserPostCreate,
54 UserPostCreate,
@@ -24,7 +24,8 b' from rhodecode.lib.utils2 import Attribu'
24
24
25 # this is a user object to be used for events caused by the system (eg. shell)
25 # this is a user object to be used for events caused by the system (eg. shell)
26 SYSTEM_USER = AttributeDict(dict(
26 SYSTEM_USER = AttributeDict(dict(
27 username='__SYSTEM__'
27 username='__SYSTEM__',
28 user_id='__SYSTEM_ID__'
28 ))
29 ))
29
30
30 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
@@ -32,12 +33,12 b' log = logging.getLogger(__name__)'
32
33
33 class RhodecodeEvent(object):
34 class RhodecodeEvent(object):
34 """
35 """
35 Base event class for all Rhodecode events
36 Base event class for all RhodeCode events
36 """
37 """
37 name = "RhodeCodeEvent"
38 name = "RhodeCodeEvent"
38
39
39 def __init__(self):
40 def __init__(self, request=None):
40 self.request = get_current_request()
41 self.request = request or get_current_request()
41 self.utc_timestamp = datetime.utcnow()
42 self.utc_timestamp = datetime.utcnow()
42
43
43 @property
44 @property
@@ -61,7 +62,8 b' class RhodecodeEvent(object):'
61 instance = auth_user.get_instance()
62 instance = auth_user.get_instance()
62 if not instance:
63 if not instance:
63 return AttributeDict(dict(
64 return AttributeDict(dict(
64 username=auth_user.username
65 username=auth_user.username,
66 user_id=auth_user.user_id,
65 ))
67 ))
66 return instance
68 return instance
67
69
@@ -78,9 +80,8 b' class RhodecodeEvent(object):'
78 def server_url(self):
80 def server_url(self):
79 default = '<no server_url available>'
81 default = '<no server_url available>'
80 if self.request:
82 if self.request:
81 from rhodecode.lib import helpers as h
82 try:
83 try:
83 return h.url('home', qualified=True)
84 return self.request.route_url('home')
84 except Exception:
85 except Exception:
85 log.exception('Failed to fetch URL for server')
86 log.exception('Failed to fetch URL for server')
86 return default
87 return default
@@ -93,7 +94,8 b' class RhodecodeEvent(object):'
93 'utc_timestamp': self.utc_timestamp,
94 'utc_timestamp': self.utc_timestamp,
94 'actor_ip': self.actor_ip,
95 'actor_ip': self.actor_ip,
95 'actor': {
96 'actor': {
96 'username': self.actor.username
97 'username': self.actor.username,
98 'user_id': self.actor.user_id
97 },
99 },
98 'server_url': self.server_url
100 'server_url': self.server_url
99 }
101 }
@@ -41,6 +41,7 b' class PullRequestEvent(RepoEvent):'
41 data = super(PullRequestEvent, self).as_dict()
41 data = super(PullRequestEvent, self).as_dict()
42
42
43 commits = _commits_as_dict(
43 commits = _commits_as_dict(
44 self,
44 commit_ids=self.pullrequest.revisions,
45 commit_ids=self.pullrequest.revisions,
45 repos=[self.pullrequest.source_repo]
46 repos=[self.pullrequest.source_repo]
46 )
47 )
@@ -52,6 +53,8 b' class PullRequestEvent(RepoEvent):'
52 'issues': issues,
53 'issues': issues,
53 'pull_request_id': self.pullrequest.pull_request_id,
54 'pull_request_id': self.pullrequest.pull_request_id,
54 'url': PullRequestModel().get_url(self.pullrequest),
55 'url': PullRequestModel().get_url(self.pullrequest),
56 'permalink_url': PullRequestModel().get_url(
57 self.pullrequest, permalink=True),
55 'status': self.pullrequest.calculated_review_status(),
58 'status': self.pullrequest.calculated_review_status(),
56 'commits': commits,
59 'commits': commits,
57 }
60 }
@@ -131,7 +134,9 b' class PullRequestCommentEvent(PullReques'
131 'type': self.comment.comment_type,
134 'type': self.comment.comment_type,
132 'file': self.comment.f_path,
135 'file': self.comment.f_path,
133 'line': self.comment.line_no,
136 'line': self.comment.line_no,
134 'url': CommentsModel().get_url(self.comment)
137 'url': CommentsModel().get_url(self.comment),
138 'permalink_url': CommentsModel().get_url(
139 self.comment, permalink=True),
135 }
140 }
136 })
141 })
137 return data
142 return data
@@ -16,6 +16,7 b''
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import collections
19 import logging
20 import logging
20
21
21 from rhodecode.translation import lazy_ugettext
22 from rhodecode.translation import lazy_ugettext
@@ -26,17 +27,18 b' from rhodecode.lib.vcs.exceptions import'
26 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
27
28
28
29
29 def _commits_as_dict(commit_ids, repos):
30 def _commits_as_dict(event, commit_ids, repos):
30 """
31 """
31 Helper function to serialize commit_ids
32 Helper function to serialize commit_ids
32
33
34 :param event: class calling this method
33 :param commit_ids: commits to get
35 :param commit_ids: commits to get
34 :param repos: list of repos to check
36 :param repos: list of repos to check
35 """
37 """
36 from rhodecode.lib.utils2 import extract_mentioned_users
38 from rhodecode.lib.utils2 import extract_mentioned_users
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.helpers import (
39 from rhodecode.lib.helpers import (
39 urlify_commit_message, process_patterns, chop_at_smart)
40 urlify_commit_message, process_patterns, chop_at_smart)
41 from rhodecode.model.repo import RepoModel
40
42
41 if not repos:
43 if not repos:
42 raise Exception('no repo defined')
44 raise Exception('no repo defined')
@@ -53,7 +55,7 b' def _commits_as_dict(commit_ids, repos):'
53 reviewers = []
55 reviewers = []
54 for repo in repos:
56 for repo in repos:
55 if not needed_commits:
57 if not needed_commits:
56 return commits # return early if we have the commits we need
58 return commits # return early if we have the commits we need
57
59
58 vcs_repo = repo.scm_instance(cache=False)
60 vcs_repo = repo.scm_instance(cache=False)
59 try:
61 try:
@@ -62,22 +64,22 b' def _commits_as_dict(commit_ids, repos):'
62 try:
64 try:
63 cs = vcs_repo.get_changeset(commit_id)
65 cs = vcs_repo.get_changeset(commit_id)
64 except CommitDoesNotExistError:
66 except CommitDoesNotExistError:
65 continue # maybe its in next repo
67 continue # maybe its in next repo
66
68
67 cs_data = cs.__json__()
69 cs_data = cs.__json__()
68 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
70 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
69 cs_data['reviewers'] = reviewers
71 cs_data['reviewers'] = reviewers
70 cs_data['url'] = h.url('changeset_home',
72 cs_data['url'] = RepoModel().get_commit_url(
71 repo_name=repo.repo_name,
73 repo, cs_data['raw_id'], request=event.request)
72 revision=cs_data['raw_id'],
74 cs_data['permalink_url'] = RepoModel().get_commit_url(
73 qualified=True
75 repo, cs_data['raw_id'], request=event.request, permalink=True)
74 )
75 urlified_message, issues_data = process_patterns(
76 urlified_message, issues_data = process_patterns(
76 cs_data['message'], repo.repo_name)
77 cs_data['message'], repo.repo_name)
77 cs_data['issues'] = issues_data
78 cs_data['issues'] = issues_data
78 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
79 cs_data['message_html'] = urlify_commit_message(
79 repo.repo_name)
80 cs_data['message'], repo.repo_name)
80 cs_data['message_html_title'] = chop_at_smart(cs_data['message'], '\n', suffix_if_chopped='...')
81 cs_data['message_html_title'] = chop_at_smart(
82 cs_data['message'], '\n', suffix_if_chopped='...')
81 commits.append(cs_data)
83 commits.append(cs_data)
82
84
83 needed_commits.remove(commit_id)
85 needed_commits.remove(commit_id)
@@ -118,12 +120,20 b' class RepoEvent(RhodecodeEvent):'
118 def as_dict(self):
120 def as_dict(self):
119 from rhodecode.model.repo import RepoModel
121 from rhodecode.model.repo import RepoModel
120 data = super(RepoEvent, self).as_dict()
122 data = super(RepoEvent, self).as_dict()
123 extra_fields = collections.OrderedDict()
124 for field in self.repo.extra_fields:
125 extra_fields[field.field_key] = field.field_value
126
121 data.update({
127 data.update({
122 'repo': {
128 'repo': {
123 'repo_id': self.repo.repo_id,
129 'repo_id': self.repo.repo_id,
124 'repo_name': self.repo.repo_name,
130 'repo_name': self.repo.repo_name,
125 'repo_type': self.repo.repo_type,
131 'repo_type': self.repo.repo_type,
126 'url': RepoModel().get_url(self.repo)
132 'url': RepoModel().get_url(
133 self.repo, request=self.request),
134 'permalink_url': RepoModel().get_url(
135 self.repo, request=self.request, permalink=True),
136 'extra_fields': extra_fields
127 }
137 }
128 })
138 })
129 return data
139 return data
@@ -235,10 +245,13 b' class RepoPushEvent(RepoVCSEvent):'
235
245
236 def as_dict(self):
246 def as_dict(self):
237 data = super(RepoPushEvent, self).as_dict()
247 data = super(RepoPushEvent, self).as_dict()
238 branch_url = repo_url = data['repo']['url']
248
249 def branch_url(branch_name):
250 return '{}/changelog?branch={}'.format(
251 data['repo']['url'], branch_name)
239
252
240 commits = _commits_as_dict(
253 commits = _commits_as_dict(
241 commit_ids=self.pushed_commit_ids, repos=[self.repo])
254 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
242
255
243 last_branch = None
256 last_branch = None
244 for commit in reversed(commits):
257 for commit in reversed(commits):
@@ -251,8 +264,7 b' class RepoPushEvent(RepoVCSEvent):'
251 branches = [
264 branches = [
252 {
265 {
253 'name': branch,
266 'name': branch,
254 'url': '{}/changelog?branch={}'.format(
267 'url': branch_url(branch)
255 data['repo']['url'], branch)
256 }
268 }
257 for branch in branches
269 for branch in branches
258 ]
270 ]
@@ -24,6 +24,9 b' deform - later can be replaced with some'
24 """
24 """
25
25
26 from rhodecode.translation import _
26 from rhodecode.translation import _
27 from rhodecode.translation import TranslationString
28
29 from mako.template import Template
27 from deform import Button, Form, widget, ValidationFailure
30 from deform import Button, Form, widget, ValidationFailure
28
31
29
32
@@ -31,3 +34,16 b' class buttons:'
31 save = Button(name='Save', type='submit')
34 save = Button(name='Save', type='submit')
32 reset = Button(name=_('Reset'), type='reset')
35 reset = Button(name=_('Reset'), type='reset')
33 delete = Button(name=_('Delete'), type='submit')
36 delete = Button(name=_('Delete'), type='submit')
37
38
39 class RcForm(Form):
40 def render_error(self, request, field):
41 html = ''
42 if field.error:
43 for err in field.error.messages():
44 if isinstance(err, TranslationString):
45 err = request.translate(err)
46 html = Template(
47 '<span class="error-message">${err}</span>').render(err=err)
48
49 return html
This diff has been collapsed as it changes many lines, (3035 lines changed) Show them Hide them
@@ -6,9 +6,9 b''
6 #, fuzzy
6 #, fuzzy
7 msgid ""
7 msgid ""
8 msgstr ""
8 msgstr ""
9 "Project-Id-Version: rhodecode-enterprise-ce 4.7.0\n"
9 "Project-Id-Version: rhodecode-enterprise-ce 4.8.0\n"
10 "Report-Msgid-Bugs-To: marcin@rhodecode.com\n"
10 "Report-Msgid-Bugs-To: marcin@rhodecode.com\n"
11 "POT-Creation-Date: 2017-04-07 13:01+0200\n"
11 "POT-Creation-Date: 2017-06-27 17:25+0200\n"
12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -24,7 +24,7 b' msgid "Global"'
24 msgstr ""
24 msgstr ""
25
25
26 #: rhodecode/apps/admin/navigation.py:84
26 #: rhodecode/apps/admin/navigation.py:84
27 #: rhodecode/templates/admin/repos/repo_edit.mako:52
27 #: rhodecode/templates/admin/repos/repo_edit.mako:55
28 msgid "VCS"
28 msgid "VCS"
29 msgstr ""
29 msgstr ""
30
30
@@ -37,7 +37,7 b' msgid "Remap and Rescan"'
37 msgstr ""
37 msgstr ""
38
38
39 #: rhodecode/apps/admin/navigation.py:87
39 #: rhodecode/apps/admin/navigation.py:87
40 #: rhodecode/templates/admin/repos/repo_edit.mako:58
40 #: rhodecode/templates/admin/repos/repo_edit.mako:61
41 msgid "Issue Tracker"
41 msgid "Issue Tracker"
42 msgstr ""
42 msgstr ""
43
43
@@ -48,7 +48,7 b' msgstr ""'
48 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:97
48 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:97
49 #: rhodecode/templates/admin/users/user_add.mako:86
49 #: rhodecode/templates/admin/users/user_add.mako:86
50 #: rhodecode/templates/admin/users/user_edit_profile.mako:65
50 #: rhodecode/templates/admin/users/user_edit_profile.mako:65
51 #: rhodecode/templates/admin/users/users.mako:64
51 #: rhodecode/templates/admin/users/users.mako:65
52 #: rhodecode/templates/email_templates/user_registration.mako:25
52 #: rhodecode/templates/email_templates/user_registration.mako:25
53 #: rhodecode/templates/users/user_profile.mako:51
53 #: rhodecode/templates/users/user_profile.mako:51
54 msgid "Email"
54 msgid "Email"
@@ -75,7 +75,7 b' msgstr ""'
75 #: rhodecode/templates/admin/integrations/new.mako:17
75 #: rhodecode/templates/admin/integrations/new.mako:17
76 #: rhodecode/templates/admin/integrations/new.mako:23
76 #: rhodecode/templates/admin/integrations/new.mako:23
77 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:51
77 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:51
78 #: rhodecode/templates/admin/repos/repo_edit.mako:72
78 #: rhodecode/templates/admin/repos/repo_edit.mako:75
79 #: rhodecode/templates/base/base.mako:82
79 #: rhodecode/templates/base/base.mako:82
80 msgid "Integrations"
80 msgid "Integrations"
81 msgstr ""
81 msgstr ""
@@ -97,11 +97,11 b' msgstr ""'
97 msgid "Labs"
97 msgid "Labs"
98 msgstr ""
98 msgstr ""
99
99
100 #: rhodecode/apps/admin/views/sessions.py:86
100 #: rhodecode/apps/admin/views/sessions.py:92
101 msgid "Cleaned up old sessions"
101 msgid "Cleaned up old sessions"
102 msgstr ""
102 msgstr ""
103
103
104 #: rhodecode/apps/admin/views/sessions.py:92
104 #: rhodecode/apps/admin/views/sessions.py:98
105 msgid "Failed to cleanup up old sessions"
105 msgid "Failed to cleanup up old sessions"
106 msgstr ""
106 msgstr ""
107
107
@@ -113,245 +113,409 b' msgstr ""'
113 msgid "Failed to generate the Apache configuration for Subversion."
113 msgid "Failed to generate the Apache configuration for Subversion."
114 msgstr ""
114 msgstr ""
115
115
116 #: rhodecode/apps/admin/views/system_info.py:95
116 #: rhodecode/apps/admin/views/system_info.py:99
117 msgid "Note: please make sure this server can access `${url}` for the update link to work"
117 msgid "Note: please make sure this server can access `${url}` for the update link to work"
118 msgstr ""
118 msgstr ""
119
119
120 #: rhodecode/apps/admin/views/system_info.py:98
120 #: rhodecode/apps/admin/views/system_info.py:102
121 msgid "Update info"
121 msgid "Update info"
122 msgstr ""
122 msgstr ""
123
123
124 #: rhodecode/apps/admin/views/system_info.py:100
124 #: rhodecode/apps/admin/views/system_info.py:104
125 msgid "Check for updates"
125 msgid "Check for updates"
126 msgstr ""
126 msgstr ""
127
127
128 #: rhodecode/apps/admin/views/system_info.py:105
129 msgid "RhodeCode Version"
130 msgstr ""
131
132 #: rhodecode/apps/admin/views/system_info.py:106
133 msgid "RhodeCode Server IP"
134 msgstr ""
135
136 #: rhodecode/apps/admin/views/system_info.py:107
137 msgid "RhodeCode Server ID"
138 msgstr ""
139
140 #: rhodecode/apps/admin/views/system_info.py:108
141 msgid "RhodeCode Configuration"
142 msgstr ""
143
144 #: rhodecode/apps/admin/views/system_info.py:109
128 #: rhodecode/apps/admin/views/system_info.py:109
145 msgid "Workers"
129 msgid "RhodeCode Version"
146 msgstr ""
130 msgstr ""
147
131
148 #: rhodecode/apps/admin/views/system_info.py:110
132 #: rhodecode/apps/admin/views/system_info.py:110
149 msgid "Worker Type"
133 msgid "RhodeCode Server IP"
134 msgstr ""
135
136 #: rhodecode/apps/admin/views/system_info.py:111
137 msgid "RhodeCode Server ID"
138 msgstr ""
139
140 #: rhodecode/apps/admin/views/system_info.py:112
141 msgid "RhodeCode Configuration"
142 msgstr ""
143
144 #: rhodecode/apps/admin/views/system_info.py:113
145 msgid "RhodeCode Certificate"
150 msgstr ""
146 msgstr ""
151
147
152 #: rhodecode/apps/admin/views/system_info.py:114
148 #: rhodecode/apps/admin/views/system_info.py:114
153 msgid "Database"
149 msgid "Workers"
154 msgstr ""
150 msgstr ""
155
151
156 #: rhodecode/apps/admin/views/system_info.py:115
152 #: rhodecode/apps/admin/views/system_info.py:115
157 msgid "Database version"
153 msgid "Worker Type"
158 msgstr ""
154 msgstr ""
159
155
160 #: rhodecode/apps/admin/views/system_info.py:119
156 #: rhodecode/apps/admin/views/system_info.py:119
161 msgid "Platform"
157 msgid "Database"
162 msgstr ""
158 msgstr ""
163
159
164 #: rhodecode/apps/admin/views/system_info.py:120
160 #: rhodecode/apps/admin/views/system_info.py:120
161 msgid "Database version"
162 msgstr ""
163
164 #: rhodecode/apps/admin/views/system_info.py:124
165 msgid "Platform"
166 msgstr ""
167
168 #: rhodecode/apps/admin/views/system_info.py:125
165 msgid "Platform UUID"
169 msgid "Platform UUID"
166 msgstr ""
170 msgstr ""
167
171
168 #: rhodecode/apps/admin/views/system_info.py:121
169 msgid "Python version"
170 msgstr ""
171
172 #: rhodecode/apps/admin/views/system_info.py:122
173 msgid "Python path"
174 msgstr ""
175
176 #: rhodecode/apps/admin/views/system_info.py:126
172 #: rhodecode/apps/admin/views/system_info.py:126
177 msgid "CPU"
173 msgid "Python version"
178 msgstr ""
174 msgstr ""
179
175
180 #: rhodecode/apps/admin/views/system_info.py:127
176 #: rhodecode/apps/admin/views/system_info.py:127
177 msgid "Python path"
178 msgstr ""
179
180 #: rhodecode/apps/admin/views/system_info.py:131
181 msgid "CPU"
182 msgstr ""
183
184 #: rhodecode/apps/admin/views/system_info.py:132
181 msgid "Load"
185 msgid "Load"
182 msgstr ""
186 msgstr ""
183
187
184 #: rhodecode/apps/admin/views/system_info.py:128
185 msgid "Memory"
186 msgstr ""
187
188 #: rhodecode/apps/admin/views/system_info.py:129
189 msgid "Uptime"
190 msgstr ""
191
192 #: rhodecode/apps/admin/views/system_info.py:133
188 #: rhodecode/apps/admin/views/system_info.py:133
193 msgid "Storage location"
189 msgid "Memory"
194 msgstr ""
190 msgstr ""
195
191
196 #: rhodecode/apps/admin/views/system_info.py:134
192 #: rhodecode/apps/admin/views/system_info.py:134
197 msgid "Storage info"
193 msgid "Uptime"
198 msgstr ""
199
200 #: rhodecode/apps/admin/views/system_info.py:135
201 msgid "Storage inodes"
202 msgstr ""
203
204 #: rhodecode/apps/admin/views/system_info.py:137
205 msgid "Gist storage location"
206 msgstr ""
194 msgstr ""
207
195
208 #: rhodecode/apps/admin/views/system_info.py:138
196 #: rhodecode/apps/admin/views/system_info.py:138
209 msgid "Gist storage info"
197 msgid "Storage location"
198 msgstr ""
199
200 #: rhodecode/apps/admin/views/system_info.py:139
201 msgid "Storage info"
210 msgstr ""
202 msgstr ""
211
203
212 #: rhodecode/apps/admin/views/system_info.py:140
204 #: rhodecode/apps/admin/views/system_info.py:140
213 msgid "Archive cache storage location"
205 msgid "Storage inodes"
214 msgstr ""
206 msgstr ""
215
207
216 #: rhodecode/apps/admin/views/system_info.py:141
208 #: rhodecode/apps/admin/views/system_info.py:142
217 msgid "Archive cache info"
209 msgid "Gist storage location"
218 msgstr ""
210 msgstr ""
219
211
220 #: rhodecode/apps/admin/views/system_info.py:143
212 #: rhodecode/apps/admin/views/system_info.py:143
221 msgid "Temp storage location"
213 msgid "Gist storage info"
222 msgstr ""
214 msgstr ""
223
215
224 #: rhodecode/apps/admin/views/system_info.py:144
216 #: rhodecode/apps/admin/views/system_info.py:145
225 msgid "Temp storage info"
217 msgid "Archive cache storage location"
226 msgstr ""
218 msgstr ""
227
219
228 #: rhodecode/apps/admin/views/system_info.py:146
220 #: rhodecode/apps/admin/views/system_info.py:146
229 msgid "Search info"
221 msgid "Archive cache info"
230 msgstr ""
222 msgstr ""
231
223
232 #: rhodecode/apps/admin/views/system_info.py:147
224 #: rhodecode/apps/admin/views/system_info.py:148
233 msgid "Search location"
225 msgid "Temp storage location"
226 msgstr ""
227
228 #: rhodecode/apps/admin/views/system_info.py:149
229 msgid "Temp storage info"
234 msgstr ""
230 msgstr ""
235
231
236 #: rhodecode/apps/admin/views/system_info.py:151
232 #: rhodecode/apps/admin/views/system_info.py:151
237 msgid "VCS Backends"
233 msgid "Search info"
238 msgstr ""
234 msgstr ""
239
235
240 #: rhodecode/apps/admin/views/system_info.py:152
236 #: rhodecode/apps/admin/views/system_info.py:152
237 msgid "Search location"
238 msgstr ""
239
240 #: rhodecode/apps/admin/views/system_info.py:156
241 msgid "VCS Backends"
242 msgstr ""
243
244 #: rhodecode/apps/admin/views/system_info.py:157
241 msgid "VCS Server"
245 msgid "VCS Server"
242 msgstr ""
246 msgstr ""
243
247
244 #: rhodecode/apps/admin/views/system_info.py:153
248 #: rhodecode/apps/admin/views/system_info.py:158
245 msgid "GIT"
249 msgid "GIT"
246 msgstr ""
250 msgstr ""
247
251
248 #: rhodecode/apps/admin/views/system_info.py:154
252 #: rhodecode/apps/admin/views/system_info.py:159
249 msgid "HG"
253 msgid "HG"
250 msgstr ""
254 msgstr ""
251
255
252 #: rhodecode/apps/admin/views/system_info.py:155
256 #: rhodecode/apps/admin/views/system_info.py:160
253 msgid "SVN"
257 msgid "SVN"
254 msgstr ""
258 msgstr ""
255
259
256 #: rhodecode/apps/admin/views/users.py:60
260 #: rhodecode/apps/admin/views/users.py:63
257 #: rhodecode/controllers/admin/users.py:359
261 #: rhodecode/controllers/admin/users.py:360
258 #: rhodecode/controllers/admin/users.py:380
262 #: rhodecode/controllers/admin/users.py:381
259 #: rhodecode/controllers/admin/users.py:412
263 #: rhodecode/controllers/admin/users.py:412
260 #: rhodecode/controllers/admin/users.py:486
264 #: rhodecode/controllers/admin/users.py:486
261 #: rhodecode/controllers/admin/users.py:499
262 #: rhodecode/controllers/admin/users.py:557
263 msgid "You can't edit this user"
265 msgid "You can't edit this user"
264 msgstr ""
266 msgstr ""
265
267
268 #: rhodecode/apps/admin/views/users.py:168
269 #: rhodecode/apps/my_account/views.py:148
270 #: rhodecode/controllers/admin/gists.py:62
271 msgid "forever"
272 msgstr ""
273
274 #: rhodecode/apps/admin/views/users.py:169
275 #: rhodecode/apps/my_account/views.py:149
276 #: rhodecode/controllers/admin/gists.py:63
277 msgid "5 minutes"
278 msgstr ""
279
280 #: rhodecode/apps/admin/views/users.py:170
281 #: rhodecode/apps/my_account/views.py:150
282 #: rhodecode/controllers/admin/gists.py:64
283 msgid "1 hour"
284 msgstr ""
285
286 #: rhodecode/apps/admin/views/users.py:171
287 #: rhodecode/apps/my_account/views.py:151
288 #: rhodecode/controllers/admin/gists.py:65
289 msgid "1 day"
290 msgstr ""
291
292 #: rhodecode/apps/admin/views/users.py:172
293 #: rhodecode/apps/my_account/views.py:152
294 #: rhodecode/controllers/admin/gists.py:66
295 msgid "1 month"
296 msgstr ""
297
298 #: rhodecode/apps/admin/views/users.py:174
299 #: rhodecode/apps/my_account/views.py:154
300 #: rhodecode/controllers/admin/gists.py:70
301 msgid "Lifetime"
302 msgstr ""
303
266 #: rhodecode/apps/admin/views/users.py:178
304 #: rhodecode/apps/admin/views/users.py:178
267 #: rhodecode/apps/my_account/views.py:138
305 #: rhodecode/apps/my_account/views.py:158
268 #: rhodecode/controllers/admin/gists.py:62
269 msgid "forever"
270 msgstr ""
271
272 #: rhodecode/apps/admin/views/users.py:179
273 #: rhodecode/apps/my_account/views.py:139
274 #: rhodecode/controllers/admin/gists.py:63
275 msgid "5 minutes"
276 msgstr ""
277
278 #: rhodecode/apps/admin/views/users.py:180
279 #: rhodecode/apps/my_account/views.py:140
280 #: rhodecode/controllers/admin/gists.py:64
281 msgid "1 hour"
282 msgstr ""
283
284 #: rhodecode/apps/admin/views/users.py:181
285 #: rhodecode/apps/my_account/views.py:141
286 #: rhodecode/controllers/admin/gists.py:65
287 msgid "1 day"
288 msgstr ""
289
290 #: rhodecode/apps/admin/views/users.py:182
291 #: rhodecode/apps/my_account/views.py:142
292 #: rhodecode/controllers/admin/gists.py:66
293 msgid "1 month"
294 msgstr ""
295
296 #: rhodecode/apps/admin/views/users.py:184
297 #: rhodecode/apps/my_account/views.py:144
298 #: rhodecode/controllers/admin/gists.py:70
299 msgid "Lifetime"
300 msgstr ""
301
302 #: rhodecode/apps/admin/views/users.py:188
303 #: rhodecode/apps/my_account/views.py:148
304 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:16
306 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:16
305 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:16
307 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:16
306 msgid "Role"
308 msgid "Role"
307 msgstr ""
309 msgstr ""
308
310
309 #: rhodecode/apps/admin/views/users.py:219
311 #: rhodecode/apps/admin/views/users.py:217
310 #: rhodecode/apps/my_account/views.py:175
312 #: rhodecode/apps/my_account/views.py:191
311 msgid "Auth token successfully created"
313 msgid "Auth token successfully created"
312 msgstr ""
314 msgstr ""
313
315
314 #: rhodecode/apps/admin/views/users.py:240
316 #: rhodecode/apps/admin/views/users.py:246
315 #: rhodecode/apps/my_account/views.py:192
317 #: rhodecode/apps/my_account/views.py:215
316 msgid "Auth token successfully deleted"
318 msgid "Auth token successfully deleted"
317 msgstr ""
319 msgstr ""
318
320
319 #: rhodecode/apps/admin/views/users.py:284
321 #: rhodecode/apps/admin/views/users.py:290
322 #: rhodecode/apps/my_account/views.py:253
323 #, python-format
324 msgid "Added new email address `%s` for user account"
325 msgstr ""
326
327 #: rhodecode/apps/admin/views/users.py:296
328 #: rhodecode/apps/my_account/views.py:259
329 msgid "An error occurred during email saving"
330 msgstr ""
331
332 #: rhodecode/apps/admin/views/users.py:323
333 msgid "Removed email address from user account"
334 msgstr ""
335
336 #: rhodecode/apps/admin/views/users.py:372
337 #, python-format
338 msgid "An error occurred during ip saving:%s"
339 msgstr ""
340
341 #: rhodecode/apps/admin/views/users.py:389
342 msgid "An error occurred during ip saving"
343 msgstr ""
344
345 #: rhodecode/apps/admin/views/users.py:393
346 #, python-format
347 msgid "Added ips %s to user whitelist"
348 msgstr ""
349
350 #: rhodecode/apps/admin/views/users.py:423
351 msgid "Removed ip address from user whitelist"
352 msgstr ""
353
354 #: rhodecode/apps/admin/views/users.py:472
320 msgid "Groups successfully changed"
355 msgid "Groups successfully changed"
321 msgstr ""
356 msgstr ""
322
357
323 #: rhodecode/apps/login/views.py:247 rhodecode/apps/login/views.py:316
358 #: rhodecode/apps/home/views.py:197 rhodecode/apps/home/views.py:230
359 #: rhodecode/controllers/pullrequests.py:191
360 #: rhodecode/templates/admin/my_account/my_account.mako:38
361 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:128
362 #: rhodecode/templates/admin/repos/repo_add.mako:15
363 #: rhodecode/templates/admin/repos/repo_add.mako:19
364 #: rhodecode/templates/admin/users/user_edit_advanced.mako:11
365 #: rhodecode/templates/base/base.mako:76 rhodecode/templates/base/base.mako:148
366 #: rhodecode/templates/base/base.mako:575
367 msgid "Repositories"
368 msgstr ""
369
370 #: rhodecode/apps/home/views.py:223
371 msgid "Groups"
372 msgstr ""
373
374 #: rhodecode/apps/home/views.py:243
375 #, python-format
376 msgid "Commits in %(repo)s"
377 msgstr ""
378
379 #: rhodecode/apps/login/views.py:270 rhodecode/apps/login/views.py:339
324 msgid "Bad captcha"
380 msgid "Bad captcha"
325 msgstr ""
381 msgstr ""
326
382
327 #: rhodecode/apps/login/views.py:256
383 #: rhodecode/apps/login/views.py:279
328 msgid "You have successfully registered with RhodeCode"
384 msgid "You have successfully registered with RhodeCode"
329 msgstr ""
385 msgstr ""
330
386
331 #: rhodecode/apps/login/views.py:292
387 #: rhodecode/apps/login/views.py:315
332 msgid "If such email exists, a password reset link was sent to it."
388 msgid "If such email exists, a password reset link was sent to it."
333 msgstr ""
389 msgstr ""
334
390
335 #: rhodecode/apps/login/views.py:298
391 #: rhodecode/apps/login/views.py:321
336 msgid "Password reset has been disabled."
392 msgid "Password reset has been disabled."
337 msgstr ""
393 msgstr ""
338
394
339 #: rhodecode/apps/login/views.py:381
395 #: rhodecode/apps/login/views.py:410
340 msgid "Given reset token is invalid"
396 msgid "Given reset token is invalid"
341 msgstr ""
397 msgstr ""
342
398
343 #: rhodecode/apps/login/views.py:389
399 #: rhodecode/apps/login/views.py:418
344 msgid "Your password reset was successful, a new password has been sent to your email"
400 msgid "Your password reset was successful, a new password has been sent to your email"
345 msgstr ""
401 msgstr ""
346
402
347 #: rhodecode/apps/my_account/views.py:115
403 #: rhodecode/apps/my_account/views.py:125
348 msgid "Error occurred during update of user password"
404 msgid "Error occurred during update of user password"
349 msgstr ""
405 msgstr ""
350
406
351 #: rhodecode/apps/my_account/views.py:122
407 #: rhodecode/apps/my_account/views.py:132
352 msgid "Successfully updated password"
408 msgid "Successfully updated password"
353 msgstr ""
409 msgstr ""
354
410
411 #: rhodecode/apps/my_account/views.py:281
412 msgid "Email successfully deleted"
413 msgstr ""
414
415 #: rhodecode/apps/repository/views/repo_caches.py:70
416 msgid "Cache invalidation successful"
417 msgstr ""
418
419 #: rhodecode/apps/repository/views/repo_caches.py:74
420 msgid "An error occurred during cache invalidation"
421 msgstr ""
422
423 #: rhodecode/apps/repository/views/repo_permissions.py:95
424 msgid "Repository permissions updated"
425 msgstr ""
426
427 #: rhodecode/apps/repository/views/repo_settings.py:171
428 msgid "Repository {} updated successfully"
429 msgstr ""
430
431 #: rhodecode/apps/repository/views/repo_settings.py:175
432 msgid "Error occurred during update of repository {}"
433 msgstr ""
434
435 #: rhodecode/apps/repository/views/repo_settings_advanced.py:93
436 #, python-format
437 msgid "Detached %s forks"
438 msgstr ""
439
440 #: rhodecode/apps/repository/views/repo_settings_advanced.py:96
441 #, python-format
442 msgid "Deleted %s forks"
443 msgstr ""
444
445 #: rhodecode/apps/repository/views/repo_settings_advanced.py:109
446 #, python-format
447 msgid "Deleted repository `%s`"
448 msgstr ""
449
450 #: rhodecode/apps/repository/views/repo_settings_advanced.py:116
451 msgid "detach or delete"
452 msgstr ""
453
454 #: rhodecode/apps/repository/views/repo_settings_advanced.py:117
455 msgid "Cannot delete `{repo}` it still contains attached forks. Try using {delete_or_detach} option."
456 msgstr ""
457
458 #: rhodecode/apps/repository/views/repo_settings_advanced.py:127
459 #, python-format
460 msgid "An error occurred during deletion of `%s`"
461 msgstr ""
462
463 #: rhodecode/apps/repository/views/repo_settings_advanced.py:152
464 msgid "Updated repository visibility in public journal"
465 msgstr ""
466
467 #: rhodecode/apps/repository/views/repo_settings_advanced.py:156
468 msgid "An error occurred during setting this repository in public journal"
469 msgstr ""
470
471 #: rhodecode/apps/repository/views/repo_settings_advanced.py:184
472 msgid "Nothing"
473 msgstr ""
474
475 #: rhodecode/apps/repository/views/repo_settings_advanced.py:186
476 #, python-format
477 msgid "Marked repo %s as fork of %s"
478 msgstr ""
479
480 #: rhodecode/apps/repository/views/repo_settings_advanced.py:193
481 msgid "An error occurred during this operation"
482 msgstr ""
483
484 #: rhodecode/apps/repository/views/repo_settings_advanced.py:217
485 msgid "Locked repository"
486 msgstr ""
487
488 #: rhodecode/apps/repository/views/repo_settings_advanced.py:220
489 msgid "Unlocked repository"
490 msgstr ""
491
492 #: rhodecode/apps/repository/views/repo_settings_advanced.py:223
493 #: rhodecode/controllers/admin/repos.py:363
494 msgid "An error occurred during unlocking"
495 msgstr ""
496
497 #: rhodecode/apps/repository/views/repo_summary.py:293
498 msgid "Branch"
499 msgstr ""
500
501 #: rhodecode/apps/repository/views/repo_summary.py:294
502 msgid "Tag"
503 msgstr ""
504
505 #: rhodecode/apps/repository/views/repo_summary.py:295
506 msgid "Bookmark"
507 msgstr ""
508
509 #: rhodecode/apps/repository/views/repo_summary.py:318
510 #: rhodecode/controllers/files.py:1021 rhodecode/model/pull_request.py:1345
511 #: rhodecode/model/scm.py:775 rhodecode/templates/base/vcs_settings.mako:255
512 msgid "Branches"
513 msgstr ""
514
515 #: rhodecode/apps/repository/views/repo_summary.py:319
516 msgid "Closed branches"
517 msgstr ""
518
355 #: rhodecode/apps/svn_support/events.py:30
519 #: rhodecode/apps/svn_support/events.py:30
356 msgid "Configuration for Apaache mad_dav_svn changed."
520 msgid "Configuration for Apaache mad_dav_svn changed."
357 msgstr ""
521 msgstr ""
@@ -408,7 +572,7 b' msgid "The Port in use by the Atlassian '
408 msgstr ""
572 msgstr ""
409
573
410 #: rhodecode/authentication/plugins/auth_crowd.py:69
574 #: rhodecode/authentication/plugins/auth_crowd.py:69
411 #: rhodecode/authentication/plugins/auth_ldap.py:84
575 #: rhodecode/authentication/plugins/auth_ldap.py:86
412 msgid "Port"
576 msgid "Port"
413 msgstr ""
577 msgstr ""
414
578
@@ -436,7 +600,7 b' msgstr ""'
436 msgid "Admin Groups"
600 msgid "Admin Groups"
437 msgstr ""
601 msgstr ""
438
602
439 #: rhodecode/authentication/plugins/auth_crowd.py:215
603 #: rhodecode/authentication/plugins/auth_crowd.py:216
440 msgid "CROWD"
604 msgid "CROWD"
441 msgstr ""
605 msgstr ""
442
606
@@ -483,126 +647,129 b' msgstr ""'
483
647
484 #: rhodecode/authentication/plugins/auth_ldap.py:74
648 #: rhodecode/authentication/plugins/auth_ldap.py:74
485 msgid ""
649 msgid ""
486 "Host of the LDAP Server \n"
650 "Host[s] of the LDAP Server \n"
487 "(e.g., 192.168.2.154, or ldap-server.domain.com"
651 "(e.g., 192.168.2.154, or ldap-server.domain.com.\n"
488 msgstr ""
652 " Multiple servers can be specified using commas"
489
653 msgstr ""
490 #: rhodecode/authentication/plugins/auth_ldap.py:77
654
655 #: rhodecode/authentication/plugins/auth_ldap.py:78
491 msgid "LDAP Host"
656 msgid "LDAP Host"
492 msgstr ""
657 msgstr ""
493
658
494 #: rhodecode/authentication/plugins/auth_ldap.py:82
659 #: rhodecode/authentication/plugins/auth_ldap.py:83
495 msgid "Custom port that the LDAP server is listening on. Default: 389"
660 msgid "Custom port that the LDAP server is listening on. Default value is: 389"
496 msgstr ""
661 msgstr ""
497
662
498 #: rhodecode/authentication/plugins/auth_ldap.py:90
663 #: rhodecode/authentication/plugins/auth_ldap.py:92
499 msgid ""
664 msgid ""
500 "Optional user DN/account to connect to LDAP if authentication is required. \n"
665 "Optional user DN/account to connect to LDAP if authentication is required. \n"
501 "e.g., cn=admin,dc=mydomain,dc=com, or uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com"
666 "e.g., cn=admin,dc=mydomain,dc=com, or uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com"
502 msgstr ""
667 msgstr ""
503
668
504 #: rhodecode/authentication/plugins/auth_ldap.py:95
669 #: rhodecode/authentication/plugins/auth_ldap.py:97
505 msgid "Account"
670 msgid "Account"
506 msgstr ""
671 msgstr ""
507
672
508 #: rhodecode/authentication/plugins/auth_ldap.py:100
673 #: rhodecode/authentication/plugins/auth_ldap.py:102
509 msgid "Password to authenticate for given user DN."
674 msgid "Password to authenticate for given user DN."
510 msgstr ""
675 msgstr ""
511
676
512 #: rhodecode/authentication/plugins/auth_ldap.py:103
677 #: rhodecode/authentication/plugins/auth_ldap.py:105
513 #: rhodecode/templates/login.mako:50 rhodecode/templates/register.mako:48
678 #: rhodecode/templates/login.mako:50 rhodecode/templates/register.mako:48
514 #: rhodecode/templates/admin/my_account/my_account.mako:30
679 #: rhodecode/templates/admin/my_account/my_account.mako:30
515 #: rhodecode/templates/admin/users/user_add.mako:44
680 #: rhodecode/templates/admin/users/user_add.mako:44
516 #: rhodecode/templates/base/base.mako:313
681 #: rhodecode/templates/base/base.mako:315
517 #: rhodecode/templates/debug_style/login.html:45
682 #: rhodecode/templates/debug_style/login.html:45
518 msgid "Password"
683 msgid "Password"
519 msgstr ""
684 msgstr ""
520
685
521 #: rhodecode/authentication/plugins/auth_ldap.py:108
686 #: rhodecode/authentication/plugins/auth_ldap.py:110
522 msgid "TLS Type"
687 msgid "TLS Type"
523 msgstr ""
688 msgstr ""
524
689
525 #: rhodecode/authentication/plugins/auth_ldap.py:109
690 #: rhodecode/authentication/plugins/auth_ldap.py:111
526 msgid "Connection Security"
691 msgid "Connection Security"
527 msgstr ""
692 msgstr ""
528
693
529 #: rhodecode/authentication/plugins/auth_ldap.py:115
694 #: rhodecode/authentication/plugins/auth_ldap.py:117
530 msgid "Require Cert over TLS?"
695 msgid ""
531 msgstr ""
696 "Require Cert over TLS?. Self-signed and custom certificates can be used when\n"
532
697 " `RhodeCode Certificate` found in admin > settings > system info page is extended."
533 #: rhodecode/authentication/plugins/auth_ldap.py:116
698 msgstr ""
699
700 #: rhodecode/authentication/plugins/auth_ldap.py:120
534 msgid "Certificate Checks"
701 msgid "Certificate Checks"
535 msgstr ""
702 msgstr ""
536
703
537 #: rhodecode/authentication/plugins/auth_ldap.py:122
704 #: rhodecode/authentication/plugins/auth_ldap.py:126
538 msgid ""
705 msgid ""
539 "Base DN to search. Dynamic bind is supported. Add `$login` marker in it to be replaced with current user credentials \n"
706 "Base DN to search. Dynamic bind is supported. Add `$login` marker in it to be replaced with current user credentials \n"
540 "(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)"
707 "(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)"
541 msgstr ""
708 msgstr ""
542
709
543 #: rhodecode/authentication/plugins/auth_ldap.py:127
710 #: rhodecode/authentication/plugins/auth_ldap.py:131
544 msgid "Base DN"
711 msgid "Base DN"
545 msgstr ""
712 msgstr ""
546
713
547 #: rhodecode/authentication/plugins/auth_ldap.py:132
714 #: rhodecode/authentication/plugins/auth_ldap.py:136
548 msgid ""
715 msgid ""
549 "Filter to narrow results \n"
716 "Filter to narrow results \n"
550 "(e.g., (&(objectCategory=Person)(objectClass=user)), or \n"
717 "(e.g., (&(objectCategory=Person)(objectClass=user)), or \n"
551 "(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))"
718 "(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))"
552 msgstr ""
719 msgstr ""
553
720
554 #: rhodecode/authentication/plugins/auth_ldap.py:137
721 #: rhodecode/authentication/plugins/auth_ldap.py:141
555 msgid "LDAP Search Filter"
722 msgid "LDAP Search Filter"
556 msgstr ""
723 msgstr ""
557
724
558 #: rhodecode/authentication/plugins/auth_ldap.py:143
725 #: rhodecode/authentication/plugins/auth_ldap.py:147
559 msgid "How deep to search LDAP. If unsure set to SUBTREE"
726 msgid "How deep to search LDAP. If unsure set to SUBTREE"
560 msgstr ""
727 msgstr ""
561
728
562 #: rhodecode/authentication/plugins/auth_ldap.py:144
729 #: rhodecode/authentication/plugins/auth_ldap.py:148
563 msgid "LDAP Search Scope"
730 msgid "LDAP Search Scope"
564 msgstr ""
731 msgstr ""
565
732
566 #: rhodecode/authentication/plugins/auth_ldap.py:150
733 #: rhodecode/authentication/plugins/auth_ldap.py:154
567 msgid "LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)"
734 msgid "LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)"
568 msgstr ""
735 msgstr ""
569
736
570 #: rhodecode/authentication/plugins/auth_ldap.py:152
737 #: rhodecode/authentication/plugins/auth_ldap.py:156
571 msgid "Login Attribute"
738 msgid "Login Attribute"
572 msgstr ""
739 msgstr ""
573
740
574 #: rhodecode/authentication/plugins/auth_ldap.py:153
741 #: rhodecode/authentication/plugins/auth_ldap.py:157
575 msgid "The LDAP Login attribute of the CN must be specified"
742 msgid "The LDAP Login attribute of the CN must be specified"
576 msgstr ""
743 msgstr ""
577
744
578 #: rhodecode/authentication/plugins/auth_ldap.py:158
745 #: rhodecode/authentication/plugins/auth_ldap.py:162
579 msgid "LDAP Attribute to map to first name (e.g., givenName)"
746 msgid "LDAP Attribute to map to first name (e.g., givenName)"
580 msgstr ""
747 msgstr ""
581
748
582 #: rhodecode/authentication/plugins/auth_ldap.py:161
749 #: rhodecode/authentication/plugins/auth_ldap.py:165
583 msgid "First Name Attribute"
750 msgid "First Name Attribute"
584 msgstr ""
751 msgstr ""
585
752
586 #: rhodecode/authentication/plugins/auth_ldap.py:166
753 #: rhodecode/authentication/plugins/auth_ldap.py:170
587 msgid "LDAP Attribute to map to last name (e.g., sn)"
754 msgid "LDAP Attribute to map to last name (e.g., sn)"
588 msgstr ""
755 msgstr ""
589
756
590 #: rhodecode/authentication/plugins/auth_ldap.py:169
757 #: rhodecode/authentication/plugins/auth_ldap.py:173
591 msgid "Last Name Attribute"
758 msgid "Last Name Attribute"
592 msgstr ""
759 msgstr ""
593
760
594 #: rhodecode/authentication/plugins/auth_ldap.py:174
761 #: rhodecode/authentication/plugins/auth_ldap.py:178
595 msgid ""
762 msgid ""
596 "LDAP Attribute to map to email address (e.g., mail).\n"
763 "LDAP Attribute to map to email address (e.g., mail).\n"
597 "Emails are a crucial part of RhodeCode. \n"
764 "Emails are a crucial part of RhodeCode. \n"
598 "If possible add a valid email attribute to ldap users."
765 "If possible add a valid email attribute to ldap users."
599 msgstr ""
766 msgstr ""
600
767
601 #: rhodecode/authentication/plugins/auth_ldap.py:179
768 #: rhodecode/authentication/plugins/auth_ldap.py:183
602 msgid "Email Attribute"
769 msgid "Email Attribute"
603 msgstr ""
770 msgstr ""
604
771
605 #: rhodecode/authentication/plugins/auth_ldap.py:360
772 #: rhodecode/authentication/plugins/auth_ldap.py:365
606 msgid "LDAP"
773 msgid "LDAP"
607 msgstr ""
774 msgstr ""
608
775
@@ -634,74 +801,71 b' msgstr ""'
634 msgid "Rhodecode Token Auth"
801 msgid "Rhodecode Token Auth"
635 msgstr ""
802 msgstr ""
636
803
637 #: rhodecode/controllers/changelog.py:91 rhodecode/controllers/compare.py:64
804 #: rhodecode/controllers/changelog.py:70 rhodecode/controllers/compare.py:64
638 #: rhodecode/controllers/pullrequests.py:204
805 #: rhodecode/controllers/pullrequests.py:85
639 msgid "There are no commits yet"
806 msgid "There are no commits yet"
640 msgstr ""
807 msgstr ""
641
808
809 #: rhodecode/controllers/changeset.py:76
810 msgid "Show whitespace"
811 msgstr ""
812
642 #: rhodecode/controllers/changeset.py:77
813 #: rhodecode/controllers/changeset.py:77
643 msgid "Show whitespace"
644 msgstr ""
645
646 #: rhodecode/controllers/changeset.py:78
647 msgid "Show whitespace for all diffs"
814 msgid "Show whitespace for all diffs"
648 msgstr ""
815 msgstr ""
649
816
817 #: rhodecode/controllers/changeset.py:83
818 msgid "Ignore whitespace"
819 msgstr ""
820
650 #: rhodecode/controllers/changeset.py:84
821 #: rhodecode/controllers/changeset.py:84
651 msgid "Ignore whitespace"
652 msgstr ""
653
654 #: rhodecode/controllers/changeset.py:85
655 msgid "Ignore whitespace for all diffs"
822 msgid "Ignore whitespace for all diffs"
656 msgstr ""
823 msgstr ""
657
824
825 #: rhodecode/controllers/changeset.py:140
826 msgid "Increase context"
827 msgstr ""
828
658 #: rhodecode/controllers/changeset.py:141
829 #: rhodecode/controllers/changeset.py:141
659 msgid "Increase context"
660 msgstr ""
661
662 #: rhodecode/controllers/changeset.py:142
663 msgid "Increase context for all diffs"
830 msgid "Increase context for all diffs"
664 msgstr ""
831 msgstr ""
665
832
666 #: rhodecode/controllers/changeset.py:190 rhodecode/controllers/files.py:106
833 #: rhodecode/controllers/changeset.py:189 rhodecode/controllers/files.py:106
667 #: rhodecode/controllers/files.py:127
834 #: rhodecode/controllers/files.py:128
668 msgid "No such commit exists for this repository"
835 msgid "No such commit exists for this repository"
669 msgstr ""
836 msgstr ""
670
837
671 #: rhodecode/controllers/changeset.py:344
838 #: rhodecode/controllers/changeset.py:343
672 #: rhodecode/controllers/pullrequests.py:985
839 #: rhodecode/controllers/pullrequests.py:919
673 #: rhodecode/model/pull_request.py:1055
674 #, python-format
840 #, python-format
675 msgid "Status change %(transition_icon)s %(status)s"
841 msgid "Status change %(transition_icon)s %(status)s"
676 msgstr ""
842 msgstr ""
677
843
678 #: rhodecode/controllers/changeset.py:389
844 #: rhodecode/controllers/changeset.py:387
679 msgid "Changing the status of a commit associated with a closed pull request is not allowed"
845 msgid "Changing the status of a commit associated with a closed pull request is not allowed"
680 msgstr ""
846 msgstr ""
681
847
682 #: rhodecode/controllers/compare.py:89
848 #: rhodecode/controllers/compare.py:92
683 msgid "Select commit"
849 msgid "Select commit"
684 msgstr ""
850 msgstr ""
685
851
686 #: rhodecode/controllers/compare.py:144
852 #: rhodecode/controllers/compare.py:149
687 #, python-format
853 msgid "Could not find the source repo: `{}`"
688 msgid "Could not find the original repo: %(repo)s"
854 msgstr ""
689 msgstr ""
855
690
856 #: rhodecode/controllers/compare.py:156
691 #: rhodecode/controllers/compare.py:152
857 msgid "Could not find the target repo: `{}`"
692 #, python-format
858 msgstr ""
693 msgid "Could not find the other repo: %(repo)s"
859
694 msgstr ""
860 #: rhodecode/controllers/compare.py:166
695
696 #: rhodecode/controllers/compare.py:164
697 msgid "The comparison of two different kinds of remote repos is not available"
861 msgid "The comparison of two different kinds of remote repos is not available"
698 msgstr ""
862 msgstr ""
699
863
700 #: rhodecode/controllers/compare.py:202
864 #: rhodecode/controllers/compare.py:204
701 msgid "Could not compare repos with different large file settings"
865 msgid "Could not compare repos with different large file settings"
702 msgstr ""
866 msgstr ""
703
867
704 #: rhodecode/controllers/compare.py:242
868 #: rhodecode/controllers/compare.py:244
705 #, python-format
869 #, python-format
706 msgid "Repositories unrelated. Cannot compare commit %(commit1)s from repository %(repo1)s with commit %(commit2)s from repository %(repo2)s."
870 msgid "Repositories unrelated. Cannot compare commit %(commit1)s from repository %(repo1)s with commit %(commit2)s from repository %(repo2)s."
707 msgstr ""
871 msgstr ""
@@ -725,51 +889,47 b' msgstr ""'
725 msgid "There are no files yet. %s"
889 msgid "There are no files yet. %s"
726 msgstr ""
890 msgstr ""
727
891
728 #: rhodecode/controllers/files.py:435 rhodecode/controllers/files.py:488
892 #: rhodecode/controllers/files.py:434 rhodecode/controllers/files.py:487
729 #: rhodecode/controllers/files.py:519 rhodecode/controllers/files.py:594
893 #: rhodecode/controllers/files.py:518 rhodecode/controllers/files.py:593
730 #: rhodecode/controllers/files.py:639 rhodecode/controllers/files.py:730
894 #: rhodecode/controllers/files.py:638 rhodecode/controllers/files.py:729
731 #, python-format
895 #, python-format
732 msgid "This repository has been locked by %s on %s"
896 msgid "This repository has been locked by %s on %s"
733 msgstr ""
897 msgstr ""
734
898
735 #: rhodecode/controllers/files.py:443 rhodecode/controllers/files.py:496
899 #: rhodecode/controllers/files.py:442 rhodecode/controllers/files.py:495
736 msgid "You can only delete files with revision being a valid branch "
900 msgid "You can only delete files with revision being a valid branch "
737 msgstr ""
901 msgstr ""
738
902
739 #: rhodecode/controllers/files.py:452 rhodecode/controllers/files.py:505
903 #: rhodecode/controllers/files.py:451 rhodecode/controllers/files.py:504
740 #, python-format
904 msgid "Deleted file {} via RhodeCode Enterprise"
741 msgid "Deleted file %s via RhodeCode Enterprise"
742 msgstr ""
905 msgstr ""
743
906
744 #: rhodecode/controllers/files.py:472
907 #: rhodecode/controllers/files.py:472
745 #, python-format
908 msgid "Successfully deleted file `{}`"
746 msgid "Successfully deleted file %s"
909 msgstr ""
747 msgstr ""
910
748
911 #: rhodecode/controllers/files.py:476 rhodecode/controllers/files.py:582
749 #: rhodecode/controllers/files.py:475 rhodecode/controllers/files.py:581
912 #: rhodecode/controllers/files.py:719
750 #: rhodecode/controllers/files.py:718
751 msgid "Error occurred during commit"
913 msgid "Error occurred during commit"
752 msgstr ""
914 msgstr ""
753
915
754 #: rhodecode/controllers/files.py:527 rhodecode/controllers/files.py:602
916 #: rhodecode/controllers/files.py:526 rhodecode/controllers/files.py:601
755 msgid "You can only edit files with revision being a valid branch "
917 msgid "You can only edit files with revision being a valid branch "
756 msgstr ""
918 msgstr ""
757
919
758 #: rhodecode/controllers/files.py:539 rhodecode/controllers/files.py:614
920 #: rhodecode/controllers/files.py:538 rhodecode/controllers/files.py:613
759 #, python-format
921 msgid "Edited file {} via RhodeCode Enterprise"
760 msgid "Edited file %s via RhodeCode Enterprise"
922 msgstr ""
761 msgstr ""
923
762
924 #: rhodecode/controllers/files.py:555
763 #: rhodecode/controllers/files.py:556
764 msgid "No changes"
925 msgid "No changes"
765 msgstr ""
926 msgstr ""
766
927
767 #: rhodecode/controllers/files.py:578 rhodecode/controllers/files.py:707
928 #: rhodecode/controllers/files.py:578
768 #, python-format
929 msgid "Successfully committed changes to file `{}`"
769 msgid "Successfully committed to %s"
930 msgstr ""
770 msgstr ""
931
771
932 #: rhodecode/controllers/files.py:651 rhodecode/controllers/files.py:740
772 #: rhodecode/controllers/files.py:652 rhodecode/controllers/files.py:741
773 msgid "Added file via RhodeCode Enterprise"
933 msgid "Added file via RhodeCode Enterprise"
774 msgstr ""
934 msgstr ""
775
935
@@ -777,39 +937,37 b' msgstr ""'
777 msgid "No filename"
937 msgid "No filename"
778 msgstr ""
938 msgstr ""
779
939
940 #: rhodecode/controllers/files.py:707
941 msgid "Successfully committed new file `{}`"
942 msgstr ""
943
780 #: rhodecode/controllers/files.py:710
944 #: rhodecode/controllers/files.py:710
781 msgid "The location specified must be a relative path and must not contain .. in the path"
945 msgid "The location specified must be a relative path and must not contain .. in the path"
782 msgstr ""
946 msgstr ""
783
947
784 #: rhodecode/controllers/files.py:764
948 #: rhodecode/controllers/files.py:763
785 msgid "Downloads disabled"
949 msgid "Downloads disabled"
786 msgstr ""
950 msgstr ""
787
951
788 #: rhodecode/controllers/files.py:770
952 #: rhodecode/controllers/files.py:769
789 #, python-format
953 #, python-format
790 msgid "Unknown revision %s"
954 msgid "Unknown revision %s"
791 msgstr ""
955 msgstr ""
792
956
793 #: rhodecode/controllers/files.py:772
957 #: rhodecode/controllers/files.py:771
794 msgid "Empty repository"
958 msgid "Empty repository"
795 msgstr ""
959 msgstr ""
796
960
797 #: rhodecode/controllers/files.py:774 rhodecode/controllers/files.py:808
961 #: rhodecode/controllers/files.py:773 rhodecode/controllers/files.py:807
798 msgid "Unknown archive type"
962 msgid "Unknown archive type"
799 msgstr ""
963 msgstr ""
800
964
801 #: rhodecode/controllers/files.py:993
965 #: rhodecode/controllers/files.py:1000
802 msgid "Changesets"
966 msgid "Changesets"
803 msgstr ""
967 msgstr ""
804
968
805 #: rhodecode/controllers/files.py:1014 rhodecode/controllers/summary.py:277
969 #: rhodecode/controllers/files.py:1025 rhodecode/model/scm.py:790
806 #: rhodecode/model/pull_request.py:1280 rhodecode/model/scm.py:782
970 #: rhodecode/templates/base/vcs_settings.mako:280
807 #: rhodecode/templates/base/vcs_settings.mako:242
808 msgid "Branches"
809 msgstr ""
810
811 #: rhodecode/controllers/files.py:1018 rhodecode/model/scm.py:797
812 #: rhodecode/templates/base/vcs_settings.mako:267
813 msgid "Tags"
971 msgid "Tags"
814 msgstr ""
972 msgstr ""
815
973
@@ -818,27 +976,6 b' msgstr ""'
818 msgid "An error occurred during repository forking %s"
976 msgid "An error occurred during repository forking %s"
819 msgstr ""
977 msgstr ""
820
978
821 #: rhodecode/controllers/home.py:207
822 msgid "Groups"
823 msgstr ""
824
825 #: rhodecode/controllers/home.py:214 rhodecode/controllers/home.py:249
826 #: rhodecode/controllers/pullrequests.py:310
827 #: rhodecode/templates/admin/my_account/my_account.mako:38
828 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:128
829 #: rhodecode/templates/admin/repos/repo_add.mako:15
830 #: rhodecode/templates/admin/repos/repo_add.mako:19
831 #: rhodecode/templates/admin/users/user_edit_advanced.mako:11
832 #: rhodecode/templates/base/base.mako:76 rhodecode/templates/base/base.mako:148
833 #: rhodecode/templates/base/base.mako:572
834 msgid "Repositories"
835 msgstr ""
836
837 #: rhodecode/controllers/home.py:227
838 #, python-format
839 msgid "Commits in %(repo)s"
840 msgstr ""
841
842 #: rhodecode/controllers/journal.py:107 rhodecode/controllers/journal.py:150
979 #: rhodecode/controllers/journal.py:107 rhodecode/controllers/journal.py:150
843 msgid "public journal"
980 msgid "public journal"
844 msgstr ""
981 msgstr ""
@@ -847,80 +984,58 b' msgstr ""'
847 msgid "journal"
984 msgid "journal"
848 msgstr ""
985 msgstr ""
849
986
850 #: rhodecode/controllers/pullrequests.py:218
987 #: rhodecode/controllers/pullrequests.py:99
851 msgid "Commit does not exist"
988 msgid "Commit does not exist"
852 msgstr ""
989 msgstr ""
853
990
854 #: rhodecode/controllers/pullrequests.py:335
991 #: rhodecode/controllers/pullrequests.py:216
855 msgid "Pull request requires a title with min. 3 chars"
992 msgid "Pull request requires a title with min. 3 chars"
856 msgstr ""
993 msgstr ""
857
994
858 #: rhodecode/controllers/pullrequests.py:337
995 #: rhodecode/controllers/pullrequests.py:218
859 msgid "Error creating pull request: {}"
996 msgid "Error creating pull request: {}"
860 msgstr ""
997 msgstr ""
861
998
862 #: rhodecode/controllers/pullrequests.py:385
999 #: rhodecode/controllers/pullrequests.py:276
863 msgid "Successfully opened new pull request"
1000 msgid "Successfully opened new pull request"
864 msgstr ""
1001 msgstr ""
865
1002
866 #: rhodecode/controllers/pullrequests.py:388
1003 #: rhodecode/controllers/pullrequests.py:279
867 msgid "Error occurred during sending pull request"
1004 msgid "Error occurred during creation of this pull request."
868 msgstr ""
1005 msgstr ""
869
1006
870 #: rhodecode/controllers/pullrequests.py:431
1007 #: rhodecode/controllers/pullrequests.py:322
871 msgid "Cannot update closed pull requests."
1008 msgid "Cannot update closed pull requests."
872 msgstr ""
1009 msgstr ""
873
1010
1011 #: rhodecode/controllers/pullrequests.py:328
1012 msgid "Pull request title & description updated."
1013 msgstr ""
1014
1015 #: rhodecode/controllers/pullrequests.py:346
1016 msgid "Pull request updated to \"{source_commit_id}\" with {count_added} added, {count_removed} removed commits. Source of changes: {change_source}"
1017 msgstr ""
1018
1019 #: rhodecode/controllers/pullrequests.py:363
1020 msgid "Reload page"
1021 msgstr ""
1022
874 #: rhodecode/controllers/pullrequests.py:437
1023 #: rhodecode/controllers/pullrequests.py:437
875 msgid "Pull request title & description updated."
876 msgstr ""
877
878 #: rhodecode/controllers/pullrequests.py:455
879 msgid "Pull request updated to \"{source_commit_id}\" with {count_added} added, {count_removed} removed commits. Source of changes: {change_source}"
880 msgstr ""
881
882 #: rhodecode/controllers/pullrequests.py:472
883 msgid "Reload page"
884 msgstr ""
885
886 #: rhodecode/controllers/pullrequests.py:546
887 msgid "Pull request was successfully merged and closed."
1024 msgid "Pull request was successfully merged and closed."
888 msgstr ""
1025 msgstr ""
889
1026
890 #: rhodecode/controllers/pullrequests.py:588
1027 #: rhodecode/controllers/pullrequests.py:461
1028 msgid "Pull request reviewers updated."
1029 msgstr ""
1030
1031 #: rhodecode/controllers/pullrequests.py:482
891 msgid "Successfully deleted pull request"
1032 msgid "Successfully deleted pull request"
892 msgstr ""
1033 msgstr ""
893
1034
894 #: rhodecode/controllers/pullrequests.py:592
1035 #: rhodecode/controllers/pullrequests.py:486
895 msgid "Your are not allowed to delete this pull request"
1036 msgid "Your are not allowed to delete this pull request"
896 msgstr ""
1037 msgstr ""
897
1038
898 #: rhodecode/controllers/pullrequests.py:989
899 #: rhodecode/model/pull_request.py:1059
900 msgid "Closing with"
901 msgstr ""
902
903 #: rhodecode/controllers/pullrequests.py:1039
904 #, python-format
905 msgid "Closing pull request on other statuses than rejected or approved is forbidden. Calculated status from all reviewers is currently: %s"
906 msgstr ""
907
908 #: rhodecode/controllers/summary.py:251
909 msgid "Branch"
910 msgstr ""
911
912 #: rhodecode/controllers/summary.py:252
913 msgid "Tag"
914 msgstr ""
915
916 #: rhodecode/controllers/summary.py:253
917 msgid "Bookmark"
918 msgstr ""
919
920 #: rhodecode/controllers/summary.py:278
921 msgid "Closed branches"
922 msgstr ""
923
924 #: rhodecode/controllers/admin/defaults.py:84
1039 #: rhodecode/controllers/admin/defaults.py:84
925 msgid "Default settings updated successfully"
1040 msgid "Default settings updated successfully"
926 msgstr ""
1041 msgstr ""
@@ -976,89 +1091,73 b' msgstr ""'
976 msgid "%(expiry)s - current value"
1091 msgid "%(expiry)s - current value"
977 msgstr ""
1092 msgstr ""
978
1093
979 #: rhodecode/controllers/admin/my_account.py:78
1094 #: rhodecode/controllers/admin/my_account.py:70
980 msgid "You can't edit this user since it's crucial for entire application"
1095 msgid "You can't edit this user since it's crucial for entire application"
981 msgstr ""
1096 msgstr ""
982
1097
983 #: rhodecode/controllers/admin/my_account.py:138
1098 #: rhodecode/controllers/admin/my_account.py:110
984 msgid "Your account was updated successfully"
1099 msgid "Your account was updated successfully"
985 msgstr ""
1100 msgstr ""
986
1101
987 #: rhodecode/controllers/admin/my_account.py:153
1102 #: rhodecode/controllers/admin/my_account.py:125
988 #: rhodecode/controllers/admin/users.py:184
1103 #: rhodecode/controllers/admin/users.py:182
989 #, python-format
1104 #, python-format
990 msgid "Error occurred during update of user %s"
1105 msgid "Error occurred during update of user %s"
991 msgstr ""
1106 msgstr ""
992
1107
993 #: rhodecode/controllers/admin/my_account.py:222
1108 #: rhodecode/controllers/admin/permissions.py:107
994 #: rhodecode/controllers/admin/users.py:527
995 #, python-format
996 msgid "Added new email address `%s` for user account"
997 msgstr ""
998
999 #: rhodecode/controllers/admin/my_account.py:229
1000 #: rhodecode/controllers/admin/users.py:534
1001 msgid "An error occurred during email saving"
1002 msgstr ""
1003
1004 #: rhodecode/controllers/admin/my_account.py:239
1005 #: rhodecode/controllers/admin/users.py:549
1006 msgid "Removed email address from user account"
1007 msgstr ""
1008
1009 #: rhodecode/controllers/admin/permissions.py:112
1010 msgid "Application permissions updated successfully"
1109 msgid "Application permissions updated successfully"
1011 msgstr ""
1110 msgstr ""
1012
1111
1013 #: rhodecode/controllers/admin/permissions.py:127
1112 #: rhodecode/controllers/admin/permissions.py:122
1014 #: rhodecode/controllers/admin/permissions.py:176
1113 #: rhodecode/controllers/admin/permissions.py:171
1015 #: rhodecode/controllers/admin/permissions.py:230
1114 #: rhodecode/controllers/admin/permissions.py:225
1016 msgid "Error occurred during update of permissions"
1115 msgid "Error occurred during update of permissions"
1017 msgstr ""
1116 msgstr ""
1018
1117
1019 #: rhodecode/controllers/admin/permissions.py:161
1118 #: rhodecode/controllers/admin/permissions.py:156
1020 msgid "Object permissions updated successfully"
1119 msgid "Object permissions updated successfully"
1021 msgstr ""
1120 msgstr ""
1022
1121
1023 #: rhodecode/controllers/admin/permissions.py:215
1122 #: rhodecode/controllers/admin/permissions.py:210
1024 msgid "Global permissions updated successfully"
1123 msgid "Global permissions updated successfully"
1025 msgstr ""
1124 msgstr ""
1026
1125
1027 #: rhodecode/controllers/admin/repo_groups.py:197
1126 #: rhodecode/controllers/admin/repo_groups.py:202
1028 #, python-format
1127 #, python-format
1029 msgid "Created repository group %s"
1128 msgid "Created repository group %s"
1030 msgstr ""
1129 msgstr ""
1031
1130
1032 #: rhodecode/controllers/admin/repo_groups.py:210
1131 #: rhodecode/controllers/admin/repo_groups.py:215
1033 #, python-format
1132 #, python-format
1034 msgid "Error occurred during creation of repository group %s"
1133 msgid "Error occurred during creation of repository group %s"
1035 msgstr ""
1134 msgstr ""
1036
1135
1037 #: rhodecode/controllers/admin/repo_groups.py:258
1136 #: rhodecode/controllers/admin/repo_groups.py:261
1038 #, python-format
1137 #, python-format
1039 msgid "Updated repository group %s"
1138 msgid "Updated repository group %s"
1040 msgstr ""
1139 msgstr ""
1041
1140
1042 #: rhodecode/controllers/admin/repo_groups.py:274
1141 #: rhodecode/controllers/admin/repo_groups.py:276
1043 #, python-format
1142 #, python-format
1044 msgid "Error occurred during update of repository group %s"
1143 msgid "Error occurred during update of repository group %s"
1045 msgstr ""
1144 msgstr ""
1046
1145
1047 #: rhodecode/controllers/admin/repo_groups.py:296
1146 #: rhodecode/controllers/admin/repo_groups.py:291
1048 #, python-format
1147 #, python-format
1049 msgid "This group contains %(num)d repository and cannot be deleted"
1148 msgid "This group contains %(num)d repository and cannot be deleted"
1050 msgid_plural "This group contains %(num)d repositories and cannot be deleted"
1149 msgid_plural "This group contains %(num)d repositories and cannot be deleted"
1051 msgstr[0] ""
1150 msgstr[0] ""
1052 msgstr[1] ""
1151 msgstr[1] ""
1053
1152
1054 #: rhodecode/controllers/admin/repo_groups.py:305
1153 #: rhodecode/controllers/admin/repo_groups.py:300
1055 #, python-format
1154 #, python-format
1056 msgid "This group contains %(num)d subgroup and cannot be deleted"
1155 msgid "This group contains %(num)d subgroup and cannot be deleted"
1057 msgid_plural "This group contains %(num)d subgroups and cannot be deleted"
1156 msgid_plural "This group contains %(num)d subgroups and cannot be deleted"
1058 msgstr[0] ""
1157 msgstr[0] ""
1059 msgstr[1] ""
1158 msgstr[1] ""
1060
1159
1061 #: rhodecode/controllers/admin/repo_groups.py:312
1160 #: rhodecode/controllers/admin/repo_groups.py:313
1062 #, python-format
1161 #, python-format
1063 msgid "Removed repository group %s"
1162 msgid "Removed repository group %s"
1064 msgstr ""
1163 msgstr ""
@@ -1068,366 +1167,285 b' msgstr ""'
1068 msgid "Error occurred during deletion of repository group %s"
1167 msgid "Error occurred during deletion of repository group %s"
1069 msgstr ""
1168 msgstr ""
1070
1169
1071 #: rhodecode/controllers/admin/repo_groups.py:388
1170 #: rhodecode/controllers/admin/repo_groups.py:381
1072 #: rhodecode/controllers/admin/user_groups.py:323
1171 #: rhodecode/controllers/admin/user_groups.py:318
1073 msgid "Cannot change permission for yourself as admin"
1172 msgid "Cannot change permission for yourself as admin"
1074 msgstr ""
1173 msgstr ""
1075
1174
1076 #: rhodecode/controllers/admin/repo_groups.py:405
1175 #: rhodecode/controllers/admin/repo_groups.py:404
1077 msgid "Repository Group permissions updated"
1176 msgid "Repository Group permissions updated"
1078 msgstr ""
1177 msgstr ""
1079
1178
1179 #: rhodecode/controllers/admin/repos.py:125
1180 #, python-format
1181 msgid "Error creating repository %s: invalid certificate"
1182 msgstr ""
1183
1080 #: rhodecode/controllers/admin/repos.py:129
1184 #: rhodecode/controllers/admin/repos.py:129
1081 #, python-format
1185 #, python-format
1082 msgid "Error creating repository %s: invalid certificate"
1083 msgstr ""
1084
1085 #: rhodecode/controllers/admin/repos.py:133
1086 #, python-format
1087 msgid "Error creating repository %s"
1186 msgid "Error creating repository %s"
1088 msgstr ""
1187 msgstr ""
1089
1188
1090 #: rhodecode/controllers/admin/repos.py:274
1189 #: rhodecode/controllers/admin/repos.py:270
1091 #, python-format
1190 #, python-format
1092 msgid "Created repository %s from %s"
1191 msgid "Created repository %s from %s"
1093 msgstr ""
1192 msgstr ""
1094
1193
1095 #: rhodecode/controllers/admin/repos.py:283
1194 #: rhodecode/controllers/admin/repos.py:279
1096 #, python-format
1195 #, python-format
1097 msgid "Forked repository %s as %s"
1196 msgid "Forked repository %s as %s"
1098 msgstr ""
1197 msgstr ""
1099
1198
1100 #: rhodecode/controllers/admin/repos.py:286
1199 #: rhodecode/controllers/admin/repos.py:282
1101 #, python-format
1200 #, python-format
1102 msgid "Created repository %s"
1201 msgid "Created repository %s"
1103 msgstr ""
1202 msgstr ""
1104
1203
1105 #: rhodecode/controllers/admin/repos.py:327
1204 #: rhodecode/controllers/admin/repos.py:319
1106 #, python-format
1205 msgid "An error occurred during creation of field"
1107 msgid "Repository %s updated successfully"
1206 msgstr ""
1108 msgstr ""
1207
1109
1208 #: rhodecode/controllers/admin/repos.py:334
1110 #: rhodecode/controllers/admin/repos.py:346
1209 msgid "An error occurred during removal of field"
1111 #, python-format
1210 msgstr ""
1112 msgid "Error occurred during update of repository %s"
1211
1113 msgstr ""
1212 #: rhodecode/controllers/admin/repos.py:353
1114
1213 msgid "Unlocked"
1115 #: rhodecode/controllers/admin/repos.py:374
1214 msgstr ""
1116 #, python-format
1215
1117 msgid "Detached %s forks"
1216 #: rhodecode/controllers/admin/repos.py:357
1118 msgstr ""
1217 msgid "Locked"
1119
1218 msgstr ""
1120 #: rhodecode/controllers/admin/repos.py:377
1219
1121 #, python-format
1220 #: rhodecode/controllers/admin/repos.py:359
1122 msgid "Deleted %s forks"
1221 #, python-format
1123 msgstr ""
1222 msgid "Repository has been %s"
1124
1223 msgstr ""
1125 #: rhodecode/controllers/admin/repos.py:382
1224
1126 #, python-format
1225 #: rhodecode/controllers/admin/repos.py:373
1127 msgid "Deleted repository %s"
1226 msgid "Pulled from remote location"
1128 msgstr ""
1227 msgstr ""
1129
1228
1130 #: rhodecode/controllers/admin/repos.py:385
1229 #: rhodecode/controllers/admin/repos.py:376
1131 #, python-format
1230 msgid "An error occurred during pull from remote location"
1132 msgid "Cannot delete %s it still contains attached forks"
1231 msgstr ""
1133 msgstr ""
1232
1134
1233 #: rhodecode/controllers/admin/repos.py:397
1135 #: rhodecode/controllers/admin/repos.py:390
1234 msgid "An error occurred during deletion of repository stats"
1136 #, python-format
1137 msgid "An error occurred during deletion of %s"
1138 msgstr ""
1235 msgstr ""
1139
1236
1140 #: rhodecode/controllers/admin/repos.py:443
1237 #: rhodecode/controllers/admin/repos.py:443
1141 msgid "Repository permissions updated"
1142 msgstr ""
1143
1144 #: rhodecode/controllers/admin/repos.py:474
1145 msgid "An error occurred during creation of field"
1146 msgstr ""
1147
1148 #: rhodecode/controllers/admin/repos.py:489
1149 msgid "An error occurred during removal of field"
1150 msgstr ""
1151
1152 #: rhodecode/controllers/admin/repos.py:528
1153 msgid "Updated repository visibility in public journal"
1154 msgstr ""
1155
1156 #: rhodecode/controllers/admin/repos.py:532
1157 msgid "An error occurred during setting this repository in public journal"
1158 msgstr ""
1159
1160 #: rhodecode/controllers/admin/repos.py:556
1161 msgid "Nothing"
1162 msgstr ""
1163
1164 #: rhodecode/controllers/admin/repos.py:558
1165 #, python-format
1166 msgid "Marked repo %s as fork of %s"
1167 msgstr ""
1168
1169 #: rhodecode/controllers/admin/repos.py:565
1170 msgid "An error occurred during this operation"
1171 msgstr ""
1172
1173 #: rhodecode/controllers/admin/repos.py:583
1174 msgid "Locked repository"
1175 msgstr ""
1176
1177 #: rhodecode/controllers/admin/repos.py:586
1178 msgid "Unlocked repository"
1179 msgstr ""
1180
1181 #: rhodecode/controllers/admin/repos.py:589
1182 #: rhodecode/controllers/admin/repos.py:618
1183 msgid "An error occurred during unlocking"
1184 msgstr ""
1185
1186 #: rhodecode/controllers/admin/repos.py:608
1187 msgid "Unlocked"
1188 msgstr ""
1189
1190 #: rhodecode/controllers/admin/repos.py:612
1191 msgid "Locked"
1192 msgstr ""
1193
1194 #: rhodecode/controllers/admin/repos.py:614
1195 #, python-format
1196 msgid "Repository has been %s"
1197 msgstr ""
1198
1199 #: rhodecode/controllers/admin/repos.py:629
1200 msgid "Cache invalidation successful"
1201 msgstr ""
1202
1203 #: rhodecode/controllers/admin/repos.py:633
1204 msgid "An error occurred during cache invalidation"
1205 msgstr ""
1206
1207 #: rhodecode/controllers/admin/repos.py:653
1208 msgid "Pulled from remote location"
1209 msgstr ""
1210
1211 #: rhodecode/controllers/admin/repos.py:656
1212 msgid "An error occurred during pull from remote location"
1213 msgstr ""
1214
1215 #: rhodecode/controllers/admin/repos.py:678
1216 msgid "An error occurred during deletion of repository stats"
1217 msgstr ""
1218
1219 #: rhodecode/controllers/admin/repos.py:725
1220 msgid "Error occurred during deleting issue tracker entry"
1238 msgid "Error occurred during deleting issue tracker entry"
1221 msgstr ""
1239 msgstr ""
1222
1240
1223 #: rhodecode/controllers/admin/repos.py:728
1241 #: rhodecode/controllers/admin/repos.py:446
1224 #: rhodecode/controllers/admin/settings.py:381
1242 #: rhodecode/controllers/admin/settings.py:384
1225 msgid "Removed issue tracker entry"
1243 msgid "Removed issue tracker entry"
1226 msgstr ""
1244 msgstr ""
1227
1245
1228 #: rhodecode/controllers/admin/repos.py:758
1246 #: rhodecode/controllers/admin/repos.py:476
1229 #: rhodecode/controllers/admin/settings.py:428
1247 #: rhodecode/controllers/admin/settings.py:431
1230 msgid "Updated issue tracker entries"
1248 msgid "Updated issue tracker entries"
1231 msgstr ""
1249 msgstr ""
1232
1250
1233 #: rhodecode/controllers/admin/repos.py:819
1251 #: rhodecode/controllers/admin/repos.py:537
1234 #: rhodecode/controllers/admin/settings.py:147
1252 #: rhodecode/controllers/admin/settings.py:147
1235 #: rhodecode/controllers/admin/settings.py:619
1253 #: rhodecode/controllers/admin/settings.py:622
1236 msgid "Some form inputs contain invalid data."
1254 msgid "Some form inputs contain invalid data."
1237 msgstr ""
1255 msgstr ""
1238
1256
1239 #: rhodecode/controllers/admin/repos.py:837
1257 #: rhodecode/controllers/admin/repos.py:555
1240 msgid "Error occurred during updating repository VCS settings"
1258 msgid "Error occurred during updating repository VCS settings"
1241 msgstr ""
1259 msgstr ""
1242
1260
1243 #: rhodecode/controllers/admin/repos.py:841
1261 #: rhodecode/controllers/admin/repos.py:559
1244 #: rhodecode/controllers/admin/settings.py:176
1262 #: rhodecode/controllers/admin/settings.py:176
1245 msgid "Updated VCS settings"
1263 msgid "Updated VCS settings"
1246 msgstr ""
1264 msgstr ""
1247
1265
1248 #: rhodecode/controllers/admin/settings.py:172
1266 #: rhodecode/controllers/admin/settings.py:172
1249 #: rhodecode/controllers/admin/settings.py:283
1267 #: rhodecode/controllers/admin/settings.py:286
1250 msgid "Error occurred during updating application settings"
1268 msgid "Error occurred during updating application settings"
1251 msgstr ""
1269 msgstr ""
1252
1270
1253 #: rhodecode/controllers/admin/settings.py:223
1271 #: rhodecode/controllers/admin/settings.py:226
1254 #, python-format
1272 #, python-format
1255 msgid "Repositories successfully rescanned added: %s ; removed: %s"
1273 msgid "Repositories successfully rescanned added: %s ; removed: %s"
1256 msgstr ""
1274 msgstr ""
1257
1275
1258 #: rhodecode/controllers/admin/settings.py:279
1276 #: rhodecode/controllers/admin/settings.py:282
1259 msgid "Updated application settings"
1277 msgid "Updated application settings"
1260 msgstr ""
1278 msgstr ""
1261
1279
1262 #: rhodecode/controllers/admin/settings.py:345
1263 msgid "Updated visualisation settings"
1264 msgstr ""
1265
1266 #: rhodecode/controllers/admin/settings.py:348
1280 #: rhodecode/controllers/admin/settings.py:348
1281 msgid "Updated visualisation settings"
1282 msgstr ""
1283
1284 #: rhodecode/controllers/admin/settings.py:351
1267 msgid "Error occurred during updating visualisation settings"
1285 msgid "Error occurred during updating visualisation settings"
1268 msgstr ""
1286 msgstr ""
1269
1287
1270 #: rhodecode/controllers/admin/settings.py:441
1288 #: rhodecode/controllers/admin/settings.py:444
1271 msgid "Please enter email address"
1289 msgid "Please enter email address"
1272 msgstr ""
1290 msgstr ""
1273
1291
1274 #: rhodecode/controllers/admin/settings.py:459
1292 #: rhodecode/controllers/admin/settings.py:462
1275 msgid "Send email task created"
1293 msgid "Send email task created"
1276 msgstr ""
1294 msgstr ""
1277
1295
1278 #: rhodecode/controllers/admin/settings.py:492
1296 #: rhodecode/controllers/admin/settings.py:495
1279 msgid "Added new hook"
1297 msgid "Added new hook"
1280 msgstr ""
1298 msgstr ""
1281
1299
1282 #: rhodecode/controllers/admin/settings.py:507
1300 #: rhodecode/controllers/admin/settings.py:510
1283 msgid "Updated hooks"
1301 msgid "Updated hooks"
1284 msgstr ""
1302 msgstr ""
1285
1303
1286 #: rhodecode/controllers/admin/settings.py:511
1304 #: rhodecode/controllers/admin/settings.py:514
1287 msgid "Error occurred during hook creation"
1305 msgid "Error occurred during hook creation"
1288 msgstr ""
1306 msgstr ""
1289
1307
1290 #: rhodecode/controllers/admin/settings.py:640
1308 #: rhodecode/controllers/admin/settings.py:643
1291 msgid "Error occurred during updating labs settings"
1309 msgid "Error occurred during updating labs settings"
1292 msgstr ""
1310 msgstr ""
1293
1311
1294 #: rhodecode/controllers/admin/settings.py:645
1312 #: rhodecode/controllers/admin/settings.py:648
1295 msgid "Updated Labs settings"
1313 msgid "Updated Labs settings"
1296 msgstr ""
1314 msgstr ""
1297
1315
1298 #: rhodecode/controllers/admin/user_groups.py:165
1316 #: rhodecode/controllers/admin/user_groups.py:164
1299 #, python-format
1317 #, python-format
1300 msgid "Created user group %(user_group_link)s"
1318 msgid "Created user group %(user_group_link)s"
1301 msgstr ""
1319 msgstr ""
1302
1320
1303 #: rhodecode/controllers/admin/user_groups.py:179
1321 #: rhodecode/controllers/admin/user_groups.py:178
1304 #, python-format
1322 #, python-format
1305 msgid "Error occurred during creation of user group %s"
1323 msgid "Error occurred during creation of user group %s"
1306 msgstr ""
1324 msgstr ""
1307
1325
1308 #: rhodecode/controllers/admin/user_groups.py:220
1326 #: rhodecode/controllers/admin/user_groups.py:218
1309 #, python-format
1327 #, python-format
1310 msgid "Updated user group %s"
1328 msgid "Updated user group %s"
1311 msgstr ""
1329 msgstr ""
1312
1330
1313 #: rhodecode/controllers/admin/user_groups.py:236
1331 #: rhodecode/controllers/admin/user_groups.py:234
1314 #, python-format
1332 #, python-format
1315 msgid "Error occurred during update of user group %s"
1333 msgid "Error occurred during update of user group %s"
1316 msgstr ""
1334 msgstr ""
1317
1335
1336 #: rhodecode/controllers/admin/user_groups.py:253
1337 msgid "Successfully deleted user group"
1338 msgstr ""
1339
1318 #: rhodecode/controllers/admin/user_groups.py:258
1340 #: rhodecode/controllers/admin/user_groups.py:258
1319 msgid "Successfully deleted user group"
1320 msgstr ""
1321
1322 #: rhodecode/controllers/admin/user_groups.py:263
1323 msgid "An error occurred during deletion of user group"
1341 msgid "An error occurred during deletion of user group"
1324 msgstr ""
1342 msgstr ""
1325
1343
1326 #: rhodecode/controllers/admin/user_groups.py:331
1344 #: rhodecode/controllers/admin/user_groups.py:326
1327 msgid "Target group cannot be the same"
1345 msgid "Target group cannot be the same"
1328 msgstr ""
1346 msgstr ""
1329
1347
1330 #: rhodecode/controllers/admin/user_groups.py:337
1348 #: rhodecode/controllers/admin/user_groups.py:332
1331 msgid "User Group permissions updated"
1349 msgid "User Group permissions updated"
1332 msgstr ""
1350 msgstr ""
1333
1351
1334 #: rhodecode/controllers/admin/user_groups.py:422
1352 #: rhodecode/controllers/admin/user_groups.py:415
1335 msgid "User Group global permissions updated successfully"
1353 msgid "User Group global permissions updated successfully"
1336 msgstr ""
1354 msgstr ""
1337
1355
1338 #: rhodecode/controllers/admin/user_groups.py:437
1356 #: rhodecode/controllers/admin/user_groups.py:430
1339 #: rhodecode/controllers/admin/users.py:477
1357 #: rhodecode/controllers/admin/users.py:477
1340 msgid "An error occurred during permissions saving"
1358 msgid "An error occurred during permissions saving"
1341 msgstr ""
1359 msgstr ""
1342
1360
1343 #: rhodecode/controllers/admin/user_groups.py:481
1361 #: rhodecode/controllers/admin/user_groups.py:474
1344 msgid "User Group synchronization updated successfully"
1362 msgid "User Group synchronization updated successfully"
1345 msgstr ""
1363 msgstr ""
1346
1364
1347 #: rhodecode/controllers/admin/user_groups.py:485
1365 #: rhodecode/controllers/admin/user_groups.py:478
1348 msgid "An error occurred during synchronization update"
1366 msgid "An error occurred during synchronization update"
1349 msgstr ""
1367 msgstr ""
1350
1368
1351 #: rhodecode/controllers/admin/users.py:106
1369 #: rhodecode/controllers/admin/users.py:108
1352 #, python-format
1370 #, python-format
1353 msgid "Created user %(user_link)s"
1371 msgid "Created user %(user_link)s"
1354 msgstr ""
1372 msgstr ""
1355
1373
1356 #: rhodecode/controllers/admin/users.py:122
1374 #: rhodecode/controllers/admin/users.py:124
1357 #, python-format
1375 #, python-format
1358 msgid "Error occurred during creation of user %s"
1376 msgid "Error occurred during creation of user %s"
1359 msgstr ""
1377 msgstr ""
1360
1378
1361 #: rhodecode/controllers/admin/users.py:167
1379 #: rhodecode/controllers/admin/users.py:166
1362 msgid "User updated successfully"
1380 msgid "User updated successfully"
1363 msgstr ""
1381 msgstr ""
1364
1382
1365 #: rhodecode/controllers/admin/users.py:218
1383 #: rhodecode/controllers/admin/users.py:209
1366 #, python-format
1384 #, python-format
1367 msgid "Detached %s repositories"
1385 msgid "Detached %s repositories"
1368 msgstr ""
1386 msgstr ""
1369
1387
1370 #: rhodecode/controllers/admin/users.py:223
1388 #: rhodecode/controllers/admin/users.py:214
1371 #, python-format
1389 #, python-format
1372 msgid "Deleted %s repositories"
1390 msgid "Deleted %s repositories"
1373 msgstr ""
1391 msgstr ""
1374
1392
1375 #: rhodecode/controllers/admin/users.py:231
1393 #: rhodecode/controllers/admin/users.py:222
1376 #, python-format
1394 #, python-format
1377 msgid "Detached %s repository groups"
1395 msgid "Detached %s repository groups"
1378 msgstr ""
1396 msgstr ""
1379
1397
1380 #: rhodecode/controllers/admin/users.py:236
1398 #: rhodecode/controllers/admin/users.py:227
1381 #, python-format
1399 #, python-format
1382 msgid "Deleted %s repository groups"
1400 msgid "Deleted %s repository groups"
1383 msgstr ""
1401 msgstr ""
1384
1402
1385 #: rhodecode/controllers/admin/users.py:244
1403 #: rhodecode/controllers/admin/users.py:235
1386 #, python-format
1404 #, python-format
1387 msgid "Detached %s user groups"
1405 msgid "Detached %s user groups"
1388 msgstr ""
1406 msgstr ""
1389
1407
1390 #: rhodecode/controllers/admin/users.py:249
1408 #: rhodecode/controllers/admin/users.py:240
1391 #, python-format
1409 #, python-format
1392 msgid "Deleted %s user groups"
1410 msgid "Deleted %s user groups"
1393 msgstr ""
1411 msgstr ""
1394
1412
1395 #: rhodecode/controllers/admin/users.py:260
1413 #: rhodecode/controllers/admin/users.py:257
1396 msgid "Successfully deleted user"
1414 msgid "Successfully deleted user"
1397 msgstr ""
1415 msgstr ""
1398
1416
1399 #: rhodecode/controllers/admin/users.py:266
1417 #: rhodecode/controllers/admin/users.py:263
1400 msgid "An error occurred during deletion of user"
1418 msgid "An error occurred during deletion of user"
1401 msgstr ""
1419 msgstr ""
1402
1420
1421 #: rhodecode/controllers/admin/users.py:280
1422 msgid "Force password change disabled for user"
1423 msgstr ""
1424
1403 #: rhodecode/controllers/admin/users.py:285
1425 #: rhodecode/controllers/admin/users.py:285
1404 msgid "Force password change disabled for user"
1405 msgstr ""
1406
1407 #: rhodecode/controllers/admin/users.py:287
1408 msgid "Force password change enabled for user"
1426 msgid "Force password change enabled for user"
1409 msgstr ""
1427 msgstr ""
1410
1428
1411 #: rhodecode/controllers/admin/users.py:291
1429 #: rhodecode/controllers/admin/users.py:294
1412 msgid "An error occurred during password reset for user"
1430 msgid "An error occurred during password reset for user"
1413 msgstr ""
1431 msgstr ""
1414
1432
1415 #: rhodecode/controllers/admin/users.py:324
1433 #: rhodecode/controllers/admin/users.py:325
1416 #, python-format
1434 #, python-format
1417 msgid "Linked repository group `%s` as personal"
1435 msgid "Linked repository group `%s` as personal"
1418 msgstr ""
1436 msgstr ""
1419
1437
1420 #: rhodecode/controllers/admin/users.py:330
1438 #: rhodecode/controllers/admin/users.py:331
1421 #, python-format
1439 #, python-format
1422 msgid "Created repository group `%s`"
1440 msgid "Created repository group `%s`"
1423 msgstr ""
1441 msgstr ""
1424
1442
1425 #: rhodecode/controllers/admin/users.py:334
1443 #: rhodecode/controllers/admin/users.py:335
1426 #, python-format
1444 #, python-format
1427 msgid "Repository group `%s` is already taken"
1445 msgid "Repository group `%s` is already taken"
1428 msgstr ""
1446 msgstr ""
1429
1447
1430 #: rhodecode/controllers/admin/users.py:339
1448 #: rhodecode/controllers/admin/users.py:340
1431 msgid "An error occurred during repository group creation for user"
1449 msgid "An error occurred during repository group creation for user"
1432 msgstr ""
1450 msgstr ""
1433
1451
@@ -1435,81 +1453,63 b' msgstr ""'
1435 msgid "The user participates as reviewer in pull requests and cannot be deleted. You can set the user to \"inactive\" instead of deleting it."
1453 msgid "The user participates as reviewer in pull requests and cannot be deleted. You can set the user to \"inactive\" instead of deleting it."
1436 msgstr ""
1454 msgstr ""
1437
1455
1438 #: rhodecode/controllers/admin/users.py:461
1456 #: rhodecode/controllers/admin/users.py:462
1439 msgid "User global permissions updated successfully"
1457 msgid "User global permissions updated successfully"
1440 msgstr ""
1458 msgstr ""
1441
1459
1442 #: rhodecode/controllers/admin/users.py:589
1460 #: rhodecode/events/pullrequest.py:71
1443 #, python-format
1444 msgid "An error occurred during ip saving:%s"
1445 msgstr ""
1446
1447 #: rhodecode/controllers/admin/users.py:604
1448 msgid "An error occurred during ip saving"
1449 msgstr ""
1450
1451 #: rhodecode/controllers/admin/users.py:608
1452 #, python-format
1453 msgid "Added ips %s to user whitelist"
1454 msgstr ""
1455
1456 #: rhodecode/controllers/admin/users.py:626
1457 msgid "Removed ip address from user whitelist"
1458 msgstr ""
1459
1460 #: rhodecode/events/pullrequest.py:68
1461 msgid "pullrequest created"
1461 msgid "pullrequest created"
1462 msgstr ""
1462 msgstr ""
1463
1463
1464 #: rhodecode/events/pullrequest.py:77
1464 #: rhodecode/events/pullrequest.py:80
1465 msgid "pullrequest closed"
1465 msgid "pullrequest closed"
1466 msgstr ""
1466 msgstr ""
1467
1467
1468 #: rhodecode/events/pullrequest.py:86
1468 #: rhodecode/events/pullrequest.py:89
1469 msgid "pullrequest commits updated"
1469 msgid "pullrequest commits updated"
1470 msgstr ""
1470 msgstr ""
1471
1471
1472 #: rhodecode/events/pullrequest.py:95
1472 #: rhodecode/events/pullrequest.py:98
1473 msgid "pullrequest review changed"
1473 msgid "pullrequest review changed"
1474 msgstr ""
1474 msgstr ""
1475
1475
1476 #: rhodecode/events/pullrequest.py:104
1476 #: rhodecode/events/pullrequest.py:107
1477 msgid "pullrequest merged"
1477 msgid "pullrequest merged"
1478 msgstr ""
1478 msgstr ""
1479
1479
1480 #: rhodecode/events/pullrequest.py:113
1480 #: rhodecode/events/pullrequest.py:116
1481 msgid "pullrequest commented"
1481 msgid "pullrequest commented"
1482 msgstr ""
1482 msgstr ""
1483
1483
1484 #: rhodecode/events/repo.py:138
1484 #: rhodecode/events/repo.py:148
1485 msgid "repository pre create"
1485 msgid "repository pre create"
1486 msgstr ""
1486 msgstr ""
1487
1487
1488 #: rhodecode/events/repo.py:147
1488 #: rhodecode/events/repo.py:157
1489 msgid "repository created"
1489 msgid "repository created"
1490 msgstr ""
1490 msgstr ""
1491
1491
1492 #: rhodecode/events/repo.py:156
1492 #: rhodecode/events/repo.py:166
1493 msgid "repository pre delete"
1493 msgid "repository pre delete"
1494 msgstr ""
1494 msgstr ""
1495
1495
1496 #: rhodecode/events/repo.py:165
1496 #: rhodecode/events/repo.py:175
1497 msgid "repository deleted"
1497 msgid "repository deleted"
1498 msgstr ""
1498 msgstr ""
1499
1499
1500 #: rhodecode/events/repo.py:201
1500 #: rhodecode/events/repo.py:211
1501 msgid "repository pre pull"
1501 msgid "repository pre pull"
1502 msgstr ""
1502 msgstr ""
1503
1503
1504 #: rhodecode/events/repo.py:210
1504 #: rhodecode/events/repo.py:220
1505 msgid "repository pull"
1505 msgid "repository pull"
1506 msgstr ""
1506 msgstr ""
1507
1507
1508 #: rhodecode/events/repo.py:219
1508 #: rhodecode/events/repo.py:229
1509 msgid "repository pre push"
1509 msgid "repository pre push"
1510 msgstr ""
1510 msgstr ""
1511
1511
1512 #: rhodecode/events/repo.py:230
1512 #: rhodecode/events/repo.py:240
1513 msgid "repository push"
1513 msgid "repository push"
1514 msgstr ""
1514 msgstr ""
1515
1515
@@ -1541,7 +1541,7 b' msgstr ""'
1541 msgid "user pre update"
1541 msgid "user pre update"
1542 msgstr ""
1542 msgstr ""
1543
1543
1544 #: rhodecode/forms/__init__.py:32 rhodecode/templates/admin/gists/new.mako:62
1544 #: rhodecode/forms/__init__.py:35 rhodecode/templates/admin/gists/new.mako:62
1545 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:87
1545 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:87
1546 #: rhodecode/templates/admin/my_account/my_account_emails.mako:65
1546 #: rhodecode/templates/admin/my_account/my_account_emails.mako:65
1547 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:107
1547 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:107
@@ -1552,8 +1552,8 b' msgstr ""'
1552 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:68
1552 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:68
1553 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:66
1553 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:66
1554 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:80
1554 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:80
1555 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:111
1555 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:110
1556 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:161
1556 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:195
1557 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:44
1557 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:44
1558 #: rhodecode/templates/admin/settings/settings_global.mako:140
1558 #: rhodecode/templates/admin/settings/settings_global.mako:140
1559 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:16
1559 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:16
@@ -1563,13 +1563,13 b' msgstr ""'
1563 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:121
1563 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:121
1564 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:83
1564 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:83
1565 #: rhodecode/templates/admin/users/user_edit_emails.mako:63
1565 #: rhodecode/templates/admin/users/user_edit_emails.mako:63
1566 #: rhodecode/templates/admin/users/user_edit_ips.mako:70
1566 #: rhodecode/templates/admin/users/user_edit_ips.mako:71
1567 #: rhodecode/templates/admin/users/user_edit_profile.mako:135
1567 #: rhodecode/templates/admin/users/user_edit_profile.mako:135
1568 #: rhodecode/templates/base/default_perms_box.mako:89
1568 #: rhodecode/templates/base/default_perms_box.mako:89
1569 msgid "Reset"
1569 msgid "Reset"
1570 msgstr ""
1570 msgstr ""
1571
1571
1572 #: rhodecode/forms/__init__.py:33 rhodecode/templates/admin/gists/show.mako:49
1572 #: rhodecode/forms/__init__.py:36 rhodecode/templates/admin/gists/show.mako:49
1573 #: rhodecode/templates/admin/integrations/list.mako:211
1573 #: rhodecode/templates/admin/integrations/list.mako:211
1574 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:49
1574 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:49
1575 #: rhodecode/templates/admin/my_account/my_account_emails.mako:32
1575 #: rhodecode/templates/admin/my_account/my_account_emails.mako:32
@@ -1578,13 +1578,13 b' msgstr ""'
1578 #: rhodecode/templates/admin/settings/settings_hooks.mako:46
1578 #: rhodecode/templates/admin/settings/settings_hooks.mako:46
1579 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:45
1579 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:45
1580 #: rhodecode/templates/admin/users/user_edit_emails.mako:31
1580 #: rhodecode/templates/admin/users/user_edit_emails.mako:31
1581 #: rhodecode/templates/admin/users/user_edit_ips.mako:34
1581 #: rhodecode/templates/admin/users/user_edit_ips.mako:35
1582 #: rhodecode/templates/base/issue_tracker_settings.mako:69
1582 #: rhodecode/templates/base/issue_tracker_settings.mako:69
1583 #: rhodecode/templates/base/vcs_settings.mako:251
1583 #: rhodecode/templates/base/vcs_settings.mako:264
1584 #: rhodecode/templates/base/vcs_settings.mako:276
1584 #: rhodecode/templates/base/vcs_settings.mako:289
1585 #: rhodecode/templates/changeset/changeset_file_comment.mako:137
1586 #: rhodecode/templates/changeset/changeset_file_comment.mako:139
1587 #: rhodecode/templates/changeset/changeset_file_comment.mako:142
1585 #: rhodecode/templates/changeset/changeset_file_comment.mako:142
1586 #: rhodecode/templates/changeset/changeset_file_comment.mako:144
1587 #: rhodecode/templates/changeset/changeset_file_comment.mako:147
1588 #: rhodecode/templates/data_table/_dt_elements.mako:123
1588 #: rhodecode/templates/data_table/_dt_elements.mako:123
1589 #: rhodecode/templates/data_table/_dt_elements.mako:184
1589 #: rhodecode/templates/data_table/_dt_elements.mako:184
1590 #: rhodecode/templates/data_table/_dt_elements.mako:198
1590 #: rhodecode/templates/data_table/_dt_elements.mako:198
@@ -1732,16 +1732,15 b' msgstr ""'
1732
1732
1733 #: rhodecode/integrations/types/slack.py:60 rhodecode/templates/login.mako:43
1733 #: rhodecode/integrations/types/slack.py:60 rhodecode/templates/login.mako:43
1734 #: rhodecode/templates/register.mako:41
1734 #: rhodecode/templates/register.mako:41
1735 #: rhodecode/templates/admin/admin_log.mako:7
1735 #: rhodecode/templates/admin/admin_log_base.mako:6
1736 #: rhodecode/templates/admin/my_account/my_account_profile.mako:24
1736 #: rhodecode/templates/admin/my_account/my_account_profile.mako:24
1737 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:24
1737 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:24
1738 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:69
1738 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:69
1739 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:70
1739 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:70
1740 #: rhodecode/templates/admin/users/user_add.mako:35
1740 #: rhodecode/templates/admin/users/user_add.mako:35
1741 #: rhodecode/templates/admin/users/user_edit_audit.mako:22
1742 #: rhodecode/templates/admin/users/user_edit_profile.mako:39
1741 #: rhodecode/templates/admin/users/user_edit_profile.mako:39
1743 #: rhodecode/templates/admin/users/users.mako:62
1742 #: rhodecode/templates/admin/users/users.mako:63
1744 #: rhodecode/templates/base/base.mako:304
1743 #: rhodecode/templates/base/base.mako:306
1745 #: rhodecode/templates/debug_style/login.html:36
1744 #: rhodecode/templates/debug_style/login.html:36
1746 #: rhodecode/templates/email_templates/user_registration.mako:23
1745 #: rhodecode/templates/email_templates/user_registration.mako:23
1747 #: rhodecode/templates/users/user_profile.mako:27
1746 #: rhodecode/templates/users/user_profile.mako:27
@@ -1776,180 +1775,180 b' msgstr ""'
1776 msgid "Send events such as repo pushes and pull requests to your slack channel."
1775 msgid "Send events such as repo pushes and pull requests to your slack channel."
1777 msgstr ""
1776 msgstr ""
1778
1777
1779 #: rhodecode/integrations/types/webhook.py:152
1778 #: rhodecode/integrations/types/webhook.py:164
1780 msgid "Webhook URL"
1779 msgid "Webhook URL"
1781 msgstr ""
1780 msgstr ""
1782
1781
1783 #: rhodecode/integrations/types/webhook.py:154
1782 #: rhodecode/integrations/types/webhook.py:166
1784 msgid "URL of the webhook to receive POST event. Following variables are allowed to be used: {vars}. Some of the variables would trigger multiple calls, like ${{branch}} or ${{commit_id}}. Webhook will be called as many times as unique objects in data in such cases."
1783 msgid "URL of the webhook to receive POST event. Following variables are allowed to be used: {vars}. Some of the variables would trigger multiple calls, like ${{branch}} or ${{commit_id}}. Webhook will be called as many times as unique objects in data in such cases."
1785 msgstr ""
1784 msgstr ""
1786
1785
1787 #: rhodecode/integrations/types/webhook.py:168
1786 #: rhodecode/integrations/types/webhook.py:180
1788 msgid "Secret Token"
1787 msgid "Secret Token"
1789 msgstr ""
1788 msgstr ""
1790
1789
1791 #: rhodecode/integrations/types/webhook.py:169
1790 #: rhodecode/integrations/types/webhook.py:181
1792 msgid "String used to validate received payloads."
1791 msgid "String used to validate received payloads."
1793 msgstr ""
1792 msgstr ""
1794
1793
1795 #: rhodecode/integrations/types/webhook.py:178
1794 #: rhodecode/integrations/types/webhook.py:190
1796 msgid "Call Method"
1795 msgid "Call Method"
1797 msgstr ""
1796 msgstr ""
1798
1797
1799 #: rhodecode/integrations/types/webhook.py:179
1798 #: rhodecode/integrations/types/webhook.py:191
1800 msgid "Select if the webhook call should be made with POST or GET."
1799 msgid "Select if the webhook call should be made with POST or GET."
1801 msgstr ""
1800 msgstr ""
1802
1801
1803 #: rhodecode/integrations/types/webhook.py:192
1802 #: rhodecode/integrations/types/webhook.py:204
1804 msgid "Webhook"
1803 msgid "Webhook"
1805 msgstr ""
1804 msgstr ""
1806
1805
1807 #: rhodecode/integrations/types/webhook.py:193
1806 #: rhodecode/integrations/types/webhook.py:205
1808 msgid "Post json events to a webhook endpoint"
1807 msgid "Post json events to a webhook endpoint"
1809 msgstr ""
1808 msgstr ""
1810
1809
1811 #: rhodecode/lib/action_parser.py:89
1810 #: rhodecode/lib/action_parser.py:94
1812 msgid "[deleted] repository"
1811 msgid "[deleted] repository"
1813 msgstr ""
1812 msgstr ""
1814
1813
1815 #: rhodecode/lib/action_parser.py:92 rhodecode/lib/action_parser.py:110
1814 #: rhodecode/lib/action_parser.py:97 rhodecode/lib/action_parser.py:115
1816 msgid "[created] repository"
1815 msgid "[created] repository"
1817 msgstr ""
1816 msgstr ""
1818
1817
1819 #: rhodecode/lib/action_parser.py:95
1818 #: rhodecode/lib/action_parser.py:100
1820 msgid "[created] repository as fork"
1819 msgid "[created] repository as fork"
1821 msgstr ""
1820 msgstr ""
1822
1821
1823 #: rhodecode/lib/action_parser.py:98 rhodecode/lib/action_parser.py:113
1822 #: rhodecode/lib/action_parser.py:103 rhodecode/lib/action_parser.py:118
1824 msgid "[forked] repository"
1823 msgid "[forked] repository"
1825 msgstr ""
1824 msgstr ""
1826
1825
1827 #: rhodecode/lib/action_parser.py:101 rhodecode/lib/action_parser.py:116
1826 #: rhodecode/lib/action_parser.py:106 rhodecode/lib/action_parser.py:121
1828 msgid "[updated] repository"
1827 msgid "[updated] repository"
1829 msgstr ""
1828 msgstr ""
1830
1829
1831 #: rhodecode/lib/action_parser.py:104
1830 #: rhodecode/lib/action_parser.py:109
1832 msgid "[downloaded] archive from repository"
1831 msgid "[downloaded] archive from repository"
1833 msgstr ""
1832 msgstr ""
1834
1833
1835 #: rhodecode/lib/action_parser.py:107
1834 #: rhodecode/lib/action_parser.py:112
1836 msgid "[delete] repository"
1835 msgid "[delete] repository"
1837 msgstr ""
1836 msgstr ""
1838
1837
1839 #: rhodecode/lib/action_parser.py:119
1838 #: rhodecode/lib/action_parser.py:124
1840 msgid "[created] user"
1839 msgid "[created] user"
1841 msgstr ""
1840 msgstr ""
1842
1841
1843 #: rhodecode/lib/action_parser.py:122
1842 #: rhodecode/lib/action_parser.py:127
1844 msgid "[updated] user"
1843 msgid "[updated] user"
1845 msgstr ""
1844 msgstr ""
1846
1845
1847 #: rhodecode/lib/action_parser.py:125
1846 #: rhodecode/lib/action_parser.py:130
1848 msgid "[created] user group"
1847 msgid "[created] user group"
1849 msgstr ""
1848 msgstr ""
1850
1849
1851 #: rhodecode/lib/action_parser.py:128
1850 #: rhodecode/lib/action_parser.py:133
1852 msgid "[updated] user group"
1851 msgid "[updated] user group"
1853 msgstr ""
1852 msgstr ""
1854
1853
1855 #: rhodecode/lib/action_parser.py:131
1854 #: rhodecode/lib/action_parser.py:136
1856 msgid "[commented] on commit in repository"
1855 msgid "[commented] on commit in repository"
1857 msgstr ""
1856 msgstr ""
1858
1857
1859 #: rhodecode/lib/action_parser.py:134
1858 #: rhodecode/lib/action_parser.py:139
1860 msgid "[commented] on pull request for"
1859 msgid "[commented] on pull request for"
1861 msgstr ""
1860 msgstr ""
1862
1861
1863 #: rhodecode/lib/action_parser.py:137
1862 #: rhodecode/lib/action_parser.py:142
1864 msgid "[closed] pull request for"
1863 msgid "[closed] pull request for"
1865 msgstr ""
1864 msgstr ""
1866
1865
1867 #: rhodecode/lib/action_parser.py:140
1866 #: rhodecode/lib/action_parser.py:145
1868 msgid "[merged] pull request for"
1867 msgid "[merged] pull request for"
1869 msgstr ""
1868 msgstr ""
1870
1869
1871 #: rhodecode/lib/action_parser.py:143
1870 #: rhodecode/lib/action_parser.py:148
1872 msgid "[pushed] into"
1871 msgid "[pushed] into"
1873 msgstr ""
1872 msgstr ""
1874
1873
1875 #: rhodecode/lib/action_parser.py:146
1874 #: rhodecode/lib/action_parser.py:151
1876 msgid "[committed via RhodeCode] into repository"
1875 msgid "[committed via RhodeCode] into repository"
1877 msgstr ""
1876 msgstr ""
1878
1877
1879 #: rhodecode/lib/action_parser.py:149
1878 #: rhodecode/lib/action_parser.py:154
1880 msgid "[pulled from remote] into repository"
1879 msgid "[pulled from remote] into repository"
1881 msgstr ""
1880 msgstr ""
1882
1881
1883 #: rhodecode/lib/action_parser.py:152
1882 #: rhodecode/lib/action_parser.py:157
1884 msgid "[pulled] from"
1883 msgid "[pulled] from"
1885 msgstr ""
1884 msgstr ""
1886
1885
1887 #: rhodecode/lib/action_parser.py:155
1886 #: rhodecode/lib/action_parser.py:160
1888 msgid "[started following] repository"
1887 msgid "[started following] repository"
1889 msgstr ""
1888 msgstr ""
1890
1889
1891 #: rhodecode/lib/action_parser.py:158
1890 #: rhodecode/lib/action_parser.py:163
1892 msgid "[stopped following] repository"
1891 msgid "[stopped following] repository"
1893 msgstr ""
1892 msgstr ""
1894
1893
1895 #: rhodecode/lib/action_parser.py:166
1894 #: rhodecode/lib/action_parser.py:172
1896 #, python-format
1895 #, python-format
1897 msgid "fork name %s"
1896 msgid "fork name %s"
1898 msgstr ""
1897 msgstr ""
1899
1898
1900 #: rhodecode/lib/action_parser.py:183
1899 #: rhodecode/lib/action_parser.py:190
1901 #: rhodecode/templates/pullrequests/pullrequest_show.mako:51
1900 #: rhodecode/templates/pullrequests/pullrequest_show.mako:51
1902 #, python-format
1901 #, python-format
1903 msgid "Pull request #%s"
1902 msgid "Pull request #%s"
1904 msgstr ""
1903 msgstr ""
1905
1904
1906 #: rhodecode/lib/action_parser.py:216
1905 #: rhodecode/lib/action_parser.py:223
1907 #, python-format
1906 #, python-format
1908 msgid "Show all combined commits %s->%s"
1907 msgid "Show all combined commits %s->%s"
1909 msgstr ""
1908 msgstr ""
1910
1909
1911 #: rhodecode/lib/action_parser.py:220
1912 msgid "compare view"
1913 msgstr ""
1914
1915 #: rhodecode/lib/action_parser.py:227
1910 #: rhodecode/lib/action_parser.py:227
1911 msgid "compare view"
1912 msgstr ""
1913
1914 #: rhodecode/lib/action_parser.py:234
1916 #, python-format
1915 #, python-format
1917 msgid " and %(num)s more commits"
1916 msgid " and %(num)s more commits"
1918 msgstr ""
1917 msgstr ""
1919
1918
1920 #: rhodecode/lib/action_parser.py:279
1919 #: rhodecode/lib/action_parser.py:286
1921 #, python-format
1920 #, python-format
1922 msgid "Deleted branch: %s"
1921 msgid "Deleted branch: %s"
1923 msgstr ""
1922 msgstr ""
1924
1923
1925 #: rhodecode/lib/action_parser.py:282
1924 #: rhodecode/lib/action_parser.py:289
1926 #, python-format
1925 #, python-format
1927 msgid "Created tag: %s"
1926 msgid "Created tag: %s"
1928 msgstr ""
1927 msgstr ""
1929
1928
1930 #: rhodecode/lib/action_parser.py:295
1929 #: rhodecode/lib/action_parser.py:302
1931 msgid "Commit not found"
1930 msgid "Commit not found"
1932 msgstr ""
1931 msgstr ""
1933
1932
1934 #: rhodecode/lib/auth.py:1197
1933 #: rhodecode/lib/auth.py:1220
1935 #, python-format
1934 #, python-format
1936 msgid "IP %s not allowed"
1935 msgid "IP %s not allowed"
1937 msgstr ""
1936 msgstr ""
1938
1937
1939 #: rhodecode/lib/auth.py:1281
1938 #: rhodecode/lib/auth.py:1309
1940 msgid "You need to be a registered user to perform this action"
1939 msgid "You need to be a registered user to perform this action"
1941 msgstr ""
1940 msgstr ""
1942
1941
1943 #: rhodecode/lib/auth.py:1329
1942 #: rhodecode/lib/auth.py:1366
1944 #, python-format
1943 #, python-format
1945 msgid "Action not supported for %s."
1944 msgid "Action not supported for %s."
1946 msgstr ""
1945 msgstr ""
1947
1946
1948 #: rhodecode/lib/auth.py:1379
1947 #: rhodecode/lib/auth.py:1412
1949 msgid "You need to be signed in to view this page"
1948 msgid "You need to be signed in to view this page"
1950 msgstr ""
1949 msgstr ""
1951
1950
1952 #: rhodecode/lib/base.py:549
1951 #: rhodecode/lib/base.py:561
1953 #, python-format
1952 #, python-format
1954 msgid "The repository at %(repo_name)s cannot be located."
1953 msgid "The repository at %(repo_name)s cannot be located."
1955 msgstr ""
1954 msgstr ""
@@ -1974,20 +1973,21 b' msgstr ""'
1974 msgid "Click to select line"
1973 msgid "Click to select line"
1975 msgstr ""
1974 msgstr ""
1976
1975
1977 #: rhodecode/lib/helpers.py:1517
1976 #: rhodecode/lib/helpers.py:1527
1978 #, python-format
1977 #, python-format
1979 msgid " and %s more"
1978 msgid " and %s more"
1980 msgstr ""
1979 msgstr ""
1981
1980
1982 #: rhodecode/lib/helpers.py:1521
1981 #: rhodecode/lib/helpers.py:1531
1983 msgid "No Files"
1982 msgid "No Files"
1984 msgstr ""
1983 msgstr ""
1985
1984
1986 #: rhodecode/lib/helpers.py:1800
1985 #: rhodecode/lib/helpers.py:1836
1987 msgid ""
1986 msgid ""
1988 "Example filter terms:\n"
1987 "Example filter terms:\n"
1989 " repository:vcs\n"
1988 " repository:vcs\n"
1990 " username:marcin\n"
1989 " username:marcin\n"
1990 " username:(NOT marcin)\n"
1991 " action:*push*\n"
1991 " action:*push*\n"
1992 " ip:127.0.0.1\n"
1992 " ip:127.0.0.1\n"
1993 " date:20120101\n"
1993 " date:20120101\n"
@@ -2002,7 +2002,21 b' msgid ""'
2002 " \"username:test AND repository:test*\"\n"
2002 " \"username:test AND repository:test*\"\n"
2003 msgstr ""
2003 msgstr ""
2004
2004
2005 #: rhodecode/lib/helpers.py:1820
2005 #: rhodecode/lib/helpers.py:1859
2006 msgid ""
2007 "Example filter terms for `{searcher}` search:\n"
2008 "{terms}\n"
2009 "Generate wildcards using '*' character:\n"
2010 " \"repo_name:vcs*\" - search everything starting with 'vcs'\n"
2011 " \"repo_name:*vcs*\" - search for repository containing 'vcs'\n"
2012 "\n"
2013 "Optional AND / OR operators in queries\n"
2014 " \"repo_name:vcs OR repo_name:test\"\n"
2015 " \"owner:test AND repo_name:test*\"\n"
2016 "More: {search_doc}"
2017 msgstr ""
2018
2019 #: rhodecode/lib/helpers.py:1875
2006 #, python-format
2020 #, python-format
2007 msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
2021 msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
2008 msgstr ""
2022 msgstr ""
@@ -2042,7 +2056,7 b' msgstr ""'
2042 #: rhodecode/lib/utils2.py:515
2056 #: rhodecode/lib/utils2.py:515
2043 #: rhodecode/public/js/rhodecode-components.js:33659
2057 #: rhodecode/public/js/rhodecode-components.js:33659
2044 #: rhodecode/public/js/scripts.js:25507
2058 #: rhodecode/public/js/scripts.js:25507
2045 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:66
2059 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:74
2046 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:174
2060 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:174
2047 msgid "just now"
2061 msgid "just now"
2048 msgstr ""
2062 msgstr ""
@@ -2076,7 +2090,7 b' msgstr ""'
2076 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2289
2090 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2289
2077 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2289
2091 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2289
2078 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2339
2092 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2339
2079 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2340 rhodecode/model/db.py:2410
2093 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2340 rhodecode/model/db.py:2482
2080 msgid "Repository no access"
2094 msgid "Repository no access"
2081 msgstr ""
2095 msgstr ""
2082
2096
@@ -2109,7 +2123,7 b' msgstr ""'
2109 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2290
2123 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2290
2110 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2290
2124 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2290
2111 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2340
2125 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2340
2112 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2341 rhodecode/model/db.py:2411
2126 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2341 rhodecode/model/db.py:2483
2113 msgid "Repository read access"
2127 msgid "Repository read access"
2114 msgstr ""
2128 msgstr ""
2115
2129
@@ -2142,7 +2156,7 b' msgstr ""'
2142 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2291
2156 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2291
2143 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2291
2157 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2291
2144 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2341
2158 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2341
2145 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2342 rhodecode/model/db.py:2412
2159 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2342 rhodecode/model/db.py:2484
2146 msgid "Repository write access"
2160 msgid "Repository write access"
2147 msgstr ""
2161 msgstr ""
2148
2162
@@ -2175,7 +2189,7 b' msgstr ""'
2175 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2292
2189 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2292
2176 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2292
2190 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2292
2177 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2342
2191 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2342
2178 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2343 rhodecode/model/db.py:2413
2192 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2343 rhodecode/model/db.py:2485
2179 msgid "Repository admin access"
2193 msgid "Repository admin access"
2180 msgstr ""
2194 msgstr ""
2181
2195
@@ -2248,7 +2262,7 b' msgstr ""'
2248 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2310
2262 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2310
2249 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2310
2263 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2310
2250 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2360
2264 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2360
2251 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2361 rhodecode/model/db.py:2431
2265 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2361 rhodecode/model/db.py:2503
2252 msgid "Repository creation disabled"
2266 msgid "Repository creation disabled"
2253 msgstr ""
2267 msgstr ""
2254
2268
@@ -2281,7 +2295,7 b' msgstr ""'
2281 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2311
2295 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2311
2282 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2311
2296 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2311
2283 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2361
2297 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2361
2284 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2362 rhodecode/model/db.py:2432
2298 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2362 rhodecode/model/db.py:2504
2285 msgid "Repository creation enabled"
2299 msgid "Repository creation enabled"
2286 msgstr ""
2300 msgstr ""
2287
2301
@@ -2314,7 +2328,7 b' msgstr ""'
2314 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2315
2328 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2315
2315 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2315
2329 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2315
2316 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2365
2330 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2365
2317 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2366 rhodecode/model/db.py:2436
2331 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2366 rhodecode/model/db.py:2508
2318 msgid "Repository forking disabled"
2332 msgid "Repository forking disabled"
2319 msgstr ""
2333 msgstr ""
2320
2334
@@ -2347,7 +2361,7 b' msgstr ""'
2347 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2316
2361 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2316
2348 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2316
2362 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2316
2349 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2366
2363 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2366
2350 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2367 rhodecode/model/db.py:2437
2364 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2367 rhodecode/model/db.py:2509
2351 msgid "Repository forking enabled"
2365 msgid "Repository forking enabled"
2352 msgstr ""
2366 msgstr ""
2353
2367
@@ -2401,7 +2415,7 b' msgstr ""'
2401 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2950
2415 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2950
2402 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2950
2416 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2950
2403 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3050
2417 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3050
2404 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3051 rhodecode/model/db.py:3121
2418 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3051 rhodecode/model/db.py:3212
2405 msgid "Not Reviewed"
2419 msgid "Not Reviewed"
2406 msgstr ""
2420 msgstr ""
2407
2421
@@ -2434,7 +2448,7 b' msgstr ""'
2434 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2951
2448 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2951
2435 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2951
2449 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2951
2436 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3051
2450 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3051
2437 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3052 rhodecode/model/db.py:3122
2451 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3052 rhodecode/model/db.py:3213
2438 msgid "Approved"
2452 msgid "Approved"
2439 msgstr ""
2453 msgstr ""
2440
2454
@@ -2467,7 +2481,7 b' msgstr ""'
2467 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2952
2481 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2952
2468 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2952
2482 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2952
2469 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3052
2483 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3052
2470 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3053 rhodecode/model/db.py:3123
2484 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3053 rhodecode/model/db.py:3214
2471 msgid "Rejected"
2485 msgid "Rejected"
2472 msgstr ""
2486 msgstr ""
2473
2487
@@ -2500,7 +2514,7 b' msgstr ""'
2500 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2953
2514 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2953
2501 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2953
2515 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2953
2502 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3053
2516 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3053
2503 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3054 rhodecode/model/db.py:3124
2517 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3054 rhodecode/model/db.py:3215
2504 msgid "Under Review"
2518 msgid "Under Review"
2505 msgstr ""
2519 msgstr ""
2506
2520
@@ -2530,7 +2544,7 b' msgstr ""'
2530 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2294
2544 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2294
2531 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2294
2545 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2294
2532 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2344
2546 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2344
2533 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2345 rhodecode/model/db.py:2415
2547 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2345 rhodecode/model/db.py:2487
2534 msgid "Repository group no access"
2548 msgid "Repository group no access"
2535 msgstr ""
2549 msgstr ""
2536
2550
@@ -2560,7 +2574,7 b' msgstr ""'
2560 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2295
2574 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2295
2561 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2295
2575 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2295
2562 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2345
2576 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2345
2563 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2346 rhodecode/model/db.py:2416
2577 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2346 rhodecode/model/db.py:2488
2564 msgid "Repository group read access"
2578 msgid "Repository group read access"
2565 msgstr ""
2579 msgstr ""
2566
2580
@@ -2590,7 +2604,7 b' msgstr ""'
2590 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2296
2604 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2296
2591 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2296
2605 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2296
2592 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2346
2606 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2346
2593 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2347 rhodecode/model/db.py:2417
2607 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2347 rhodecode/model/db.py:2489
2594 msgid "Repository group write access"
2608 msgid "Repository group write access"
2595 msgstr ""
2609 msgstr ""
2596
2610
@@ -2620,7 +2634,7 b' msgstr ""'
2620 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2297
2634 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2297
2621 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2297
2635 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2297
2622 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2347
2636 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2347
2623 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2348 rhodecode/model/db.py:2418
2637 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2348 rhodecode/model/db.py:2490
2624 msgid "Repository group admin access"
2638 msgid "Repository group admin access"
2625 msgstr ""
2639 msgstr ""
2626
2640
@@ -2649,7 +2663,7 b' msgstr ""'
2649 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2299
2663 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2299
2650 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2299
2664 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2299
2651 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2349
2665 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2349
2652 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2350 rhodecode/model/db.py:2420
2666 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2350 rhodecode/model/db.py:2492
2653 msgid "User group no access"
2667 msgid "User group no access"
2654 msgstr ""
2668 msgstr ""
2655
2669
@@ -2678,7 +2692,7 b' msgstr ""'
2678 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2300
2692 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2300
2679 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2300
2693 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2300
2680 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2350
2694 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2350
2681 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2351 rhodecode/model/db.py:2421
2695 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2351 rhodecode/model/db.py:2493
2682 msgid "User group read access"
2696 msgid "User group read access"
2683 msgstr ""
2697 msgstr ""
2684
2698
@@ -2707,7 +2721,7 b' msgstr ""'
2707 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2301
2721 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2301
2708 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2301
2722 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2301
2709 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2351
2723 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2351
2710 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2352 rhodecode/model/db.py:2422
2724 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2352 rhodecode/model/db.py:2494
2711 msgid "User group write access"
2725 msgid "User group write access"
2712 msgstr ""
2726 msgstr ""
2713
2727
@@ -2736,7 +2750,7 b' msgstr ""'
2736 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2302
2750 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2302
2737 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2302
2751 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2302
2738 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2352
2752 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2352
2739 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2353 rhodecode/model/db.py:2423
2753 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2353 rhodecode/model/db.py:2495
2740 msgid "User group admin access"
2754 msgid "User group admin access"
2741 msgstr ""
2755 msgstr ""
2742
2756
@@ -2765,7 +2779,7 b' msgstr ""'
2765 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2304
2779 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2304
2766 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2304
2780 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2304
2767 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2354
2781 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2354
2768 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2355 rhodecode/model/db.py:2425
2782 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2355 rhodecode/model/db.py:2497
2769 msgid "Repository Group creation disabled"
2783 msgid "Repository Group creation disabled"
2770 msgstr ""
2784 msgstr ""
2771
2785
@@ -2794,7 +2808,7 b' msgstr ""'
2794 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2305
2808 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2305
2795 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2305
2809 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2305
2796 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2355
2810 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2355
2797 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2356 rhodecode/model/db.py:2426
2811 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2356 rhodecode/model/db.py:2498
2798 msgid "Repository Group creation enabled"
2812 msgid "Repository Group creation enabled"
2799 msgstr ""
2813 msgstr ""
2800
2814
@@ -2823,7 +2837,7 b' msgstr ""'
2823 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2307
2837 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2307
2824 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2307
2838 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2307
2825 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2357
2839 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2357
2826 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2358 rhodecode/model/db.py:2428
2840 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2358 rhodecode/model/db.py:2500
2827 msgid "User Group creation disabled"
2841 msgid "User Group creation disabled"
2828 msgstr ""
2842 msgstr ""
2829
2843
@@ -2852,7 +2866,7 b' msgstr ""'
2852 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2308
2866 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2308
2853 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2308
2867 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2308
2854 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2358
2868 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2358
2855 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2359 rhodecode/model/db.py:2429
2869 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2359 rhodecode/model/db.py:2501
2856 msgid "User Group creation enabled"
2870 msgid "User Group creation enabled"
2857 msgstr ""
2871 msgstr ""
2858
2872
@@ -2881,7 +2895,7 b' msgstr ""'
2881 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2318
2895 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2318
2882 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2318
2896 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2318
2883 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2368
2897 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2368
2884 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2369 rhodecode/model/db.py:2439
2898 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2369 rhodecode/model/db.py:2511
2885 msgid "Registration disabled"
2899 msgid "Registration disabled"
2886 msgstr ""
2900 msgstr ""
2887
2901
@@ -2910,7 +2924,7 b' msgstr ""'
2910 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2319
2924 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2319
2911 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2319
2925 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2319
2912 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2369
2926 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2369
2913 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2370 rhodecode/model/db.py:2440
2927 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2370 rhodecode/model/db.py:2512
2914 msgid "User Registration with manual account activation"
2928 msgid "User Registration with manual account activation"
2915 msgstr ""
2929 msgstr ""
2916
2930
@@ -2939,7 +2953,7 b' msgstr ""'
2939 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2320
2953 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2320
2940 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2320
2954 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2320
2941 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2370
2955 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2370
2942 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2371 rhodecode/model/db.py:2441
2956 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2371 rhodecode/model/db.py:2513
2943 msgid "User Registration with automatic account activation"
2957 msgid "User Registration with automatic account activation"
2944 msgstr ""
2958 msgstr ""
2945
2959
@@ -2968,7 +2982,7 b' msgstr ""'
2968 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2322
2982 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2322
2969 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2322
2983 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2322
2970 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2376
2984 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2376
2971 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2377 rhodecode/model/db.py:2447
2985 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2377 rhodecode/model/db.py:2519
2972 #: rhodecode/model/permission.py:95
2986 #: rhodecode/model/permission.py:95
2973 msgid "Manual activation of external account"
2987 msgid "Manual activation of external account"
2974 msgstr ""
2988 msgstr ""
@@ -2998,7 +3012,7 b' msgstr ""'
2998 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2323
3012 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2323
2999 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2323
3013 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2323
3000 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2377
3014 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2377
3001 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2378 rhodecode/model/db.py:2448
3015 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2378 rhodecode/model/db.py:2520
3002 #: rhodecode/model/permission.py:96
3016 #: rhodecode/model/permission.py:96
3003 msgid "Automatic activation of external account"
3017 msgid "Automatic activation of external account"
3004 msgstr ""
3018 msgstr ""
@@ -3022,7 +3036,7 b' msgstr ""'
3022 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2312
3036 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2312
3023 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2312
3037 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2312
3024 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2362
3038 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2362
3025 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2363 rhodecode/model/db.py:2433
3039 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2363 rhodecode/model/db.py:2505
3026 msgid "Repository creation enabled with write permission to a repository group"
3040 msgid "Repository creation enabled with write permission to a repository group"
3027 msgstr ""
3041 msgstr ""
3028
3042
@@ -3045,7 +3059,7 b' msgstr ""'
3045 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2313
3059 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2313
3046 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2313
3060 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2313
3047 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2363
3061 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2363
3048 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2364 rhodecode/model/db.py:2434
3062 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2364 rhodecode/model/db.py:2506
3049 msgid "Repository creation disabled with write permission to a repository group"
3063 msgid "Repository creation disabled with write permission to a repository group"
3050 msgstr ""
3064 msgstr ""
3051
3065
@@ -3065,7 +3079,7 b' msgstr ""'
3065 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2287
3079 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2287
3066 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2287
3080 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2287
3067 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2337
3081 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2337
3068 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2338 rhodecode/model/db.py:2408
3082 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2338 rhodecode/model/db.py:2480
3069 msgid "RhodeCode Super Administrator"
3083 msgid "RhodeCode Super Administrator"
3070 msgstr ""
3084 msgstr ""
3071
3085
@@ -3083,7 +3097,7 b' msgstr ""'
3083 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2325
3097 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2325
3084 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2325
3098 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2325
3085 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2379
3099 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2379
3086 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2380 rhodecode/model/db.py:2450
3100 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2380 rhodecode/model/db.py:2522
3087 msgid "Inherit object permissions from default user disabled"
3101 msgid "Inherit object permissions from default user disabled"
3088 msgstr ""
3102 msgstr ""
3089
3103
@@ -3101,7 +3115,7 b' msgstr ""'
3101 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2326
3115 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2326
3102 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2326
3116 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2326
3103 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2380
3117 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2380
3104 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2381 rhodecode/model/db.py:2451
3118 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2381 rhodecode/model/db.py:2523
3105 msgid "Inherit object permissions from default user enabled"
3119 msgid "Inherit object permissions from default user enabled"
3106 msgstr ""
3120 msgstr ""
3107
3121
@@ -3111,7 +3125,7 b' msgstr ""'
3111 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:912
3125 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:912
3112 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:912
3126 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:912
3113 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:954
3127 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:954
3114 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:955 rhodecode/model/db.py:1008
3128 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:955 rhodecode/model/db.py:1043
3115 msgid "all"
3129 msgid "all"
3116 msgstr ""
3130 msgstr ""
3117
3131
@@ -3121,7 +3135,7 b' msgstr ""'
3121 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:913
3135 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:913
3122 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:913
3136 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:913
3123 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:955
3137 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:955
3124 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:956 rhodecode/model/db.py:1009
3138 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:956 rhodecode/model/db.py:1044
3125 msgid "http/web interface"
3139 msgid "http/web interface"
3126 msgstr ""
3140 msgstr ""
3127
3141
@@ -3131,7 +3145,7 b' msgstr ""'
3131 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:914
3145 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:914
3132 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:914
3146 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:914
3133 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:956
3147 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:956
3134 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:957 rhodecode/model/db.py:1010
3148 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:957 rhodecode/model/db.py:1045
3135 msgid "vcs (git/hg/svn protocol)"
3149 msgid "vcs (git/hg/svn protocol)"
3136 msgstr ""
3150 msgstr ""
3137
3151
@@ -3141,7 +3155,7 b' msgstr ""'
3141 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:915
3155 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:915
3142 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:915
3156 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:915
3143 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:957
3157 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:957
3144 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:958 rhodecode/model/db.py:1011
3158 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:958 rhodecode/model/db.py:1046
3145 msgid "api calls"
3159 msgid "api calls"
3146 msgstr ""
3160 msgstr ""
3147
3161
@@ -3151,7 +3165,7 b' msgstr ""'
3151 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:916
3165 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:916
3152 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:916
3166 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:916
3153 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:958
3167 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:958
3154 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:959 rhodecode/model/db.py:1012
3168 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:959 rhodecode/model/db.py:1047
3155 msgid "feed access"
3169 msgid "feed access"
3156 msgstr ""
3170 msgstr ""
3157
3171
@@ -3161,62 +3175,62 b' msgstr ""'
3161 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2065
3175 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2065
3162 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2065
3176 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2065
3163 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2108
3177 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2108
3164 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2109 rhodecode/model/db.py:2179
3178 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2109 rhodecode/model/db.py:2248
3165 msgid "No parent"
3179 msgid "No parent"
3166 msgstr ""
3180 msgstr ""
3167
3181
3168 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2372
3182 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2372
3169 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2373 rhodecode/model/db.py:2443
3183 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2373 rhodecode/model/db.py:2515
3170 msgid "Password reset enabled"
3184 msgid "Password reset enabled"
3171 msgstr ""
3185 msgstr ""
3172
3186
3173 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2373
3187 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2373
3174 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2374 rhodecode/model/db.py:2444
3188 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2374 rhodecode/model/db.py:2516
3175 msgid "Password reset hidden"
3189 msgid "Password reset hidden"
3176 msgstr ""
3190 msgstr ""
3177
3191
3178 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2374
3192 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2374
3179 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2375 rhodecode/model/db.py:2445
3193 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2375 rhodecode/model/db.py:2517
3180 msgid "Password reset disabled"
3194 msgid "Password reset disabled"
3181 msgstr ""
3195 msgstr ""
3182
3196
3183 #: rhodecode/lib/index/whoosh.py:149
3197 #: rhodecode/lib/index/whoosh.py:150
3184 msgid "Invalid search query. Try quoting it."
3198 msgid "Invalid search query. Try quoting it."
3185 msgstr ""
3199 msgstr ""
3186
3200
3187 #: rhodecode/lib/index/whoosh.py:151
3201 #: rhodecode/lib/index/whoosh.py:152
3188 msgid "There is no index to search in. Please run whoosh indexer"
3202 msgid "There is no index to search in. Please run whoosh indexer"
3189 msgstr ""
3203 msgstr ""
3190
3204
3191 #: rhodecode/lib/index/whoosh.py:156
3205 #: rhodecode/lib/index/whoosh.py:157
3192 msgid "An error occurred during this search operation"
3206 msgid "An error occurred during this search operation"
3193 msgstr ""
3207 msgstr ""
3194
3208
3195 #: rhodecode/lib/index/whoosh.py:164
3196 msgid "Index Type"
3197 msgstr ""
3198
3199 #: rhodecode/lib/index/whoosh.py:165
3209 #: rhodecode/lib/index/whoosh.py:165
3210 msgid "Index Type"
3211 msgstr ""
3212
3213 #: rhodecode/lib/index/whoosh.py:166
3200 msgid "File Index"
3214 msgid "File Index"
3201 msgstr ""
3215 msgstr ""
3202
3216
3203 #: rhodecode/lib/index/whoosh.py:166 rhodecode/lib/index/whoosh.py:171
3217 #: rhodecode/lib/index/whoosh.py:167 rhodecode/lib/index/whoosh.py:172
3204 msgid "Indexed documents"
3218 msgid "Indexed documents"
3205 msgstr ""
3219 msgstr ""
3206
3220
3207 #: rhodecode/lib/index/whoosh.py:168 rhodecode/lib/index/whoosh.py:173
3221 #: rhodecode/lib/index/whoosh.py:169 rhodecode/lib/index/whoosh.py:174
3208 msgid "Last update"
3222 msgid "Last update"
3209 msgstr ""
3223 msgstr ""
3210
3224
3211 #: rhodecode/lib/index/whoosh.py:170
3225 #: rhodecode/lib/index/whoosh.py:171
3212 msgid "Commit index"
3226 msgid "Commit index"
3213 msgstr ""
3227 msgstr ""
3214
3228
3215 #: rhodecode/model/comment.py:368
3229 #: rhodecode/model/comment.py:374
3216 msgid "made a comment"
3230 msgid "made a comment"
3217 msgstr ""
3231 msgstr ""
3218
3232
3219 #: rhodecode/model/comment.py:369
3233 #: rhodecode/model/comment.py:375
3220 msgid "Show it now"
3234 msgid "Show it now"
3221 msgstr ""
3235 msgstr ""
3222
3236
@@ -3302,7 +3316,7 b' msgstr ""'
3302 #: rhodecode/model/permission.py:79
3316 #: rhodecode/model/permission.py:79
3303 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:11
3317 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:11
3304 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:126
3318 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:126
3305 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:12
3319 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:11
3306 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:11
3320 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:11
3307 msgid "None"
3321 msgid "None"
3308 msgstr ""
3322 msgstr ""
@@ -3310,7 +3324,7 b' msgstr ""'
3310 #: rhodecode/model/permission.py:68 rhodecode/model/permission.py:74
3324 #: rhodecode/model/permission.py:68 rhodecode/model/permission.py:74
3311 #: rhodecode/model/permission.py:80
3325 #: rhodecode/model/permission.py:80
3312 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:12
3326 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:12
3313 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:13
3327 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:12
3314 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:12
3328 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:12
3315 msgid "Read"
3329 msgid "Read"
3316 msgstr ""
3330 msgstr ""
@@ -3318,10 +3332,10 b' msgstr ""'
3318 #: rhodecode/model/permission.py:69 rhodecode/model/permission.py:75
3332 #: rhodecode/model/permission.py:69 rhodecode/model/permission.py:75
3319 #: rhodecode/model/permission.py:81
3333 #: rhodecode/model/permission.py:81
3320 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:13
3334 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:13
3321 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:14
3335 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:13
3322 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:13
3336 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:13
3323 #: rhodecode/templates/changeset/changeset_file_comment.mako:266
3337 #: rhodecode/templates/changeset/changeset_file_comment.mako:271
3324 #: rhodecode/templates/changeset/changeset_file_comment.mako:316
3338 #: rhodecode/templates/changeset/changeset_file_comment.mako:321
3325 msgid "Write"
3339 msgid "Write"
3326 msgstr ""
3340 msgstr ""
3327
3341
@@ -3344,7 +3358,7 b' msgstr ""'
3344 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:13
3358 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:13
3345 #: rhodecode/templates/admin/repos/repo_add.mako:13
3359 #: rhodecode/templates/admin/repos/repo_add.mako:13
3346 #: rhodecode/templates/admin/repos/repo_add.mako:17
3360 #: rhodecode/templates/admin/repos/repo_add.mako:17
3347 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:15
3361 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:14
3348 #: rhodecode/templates/admin/repos/repos.mako:13
3362 #: rhodecode/templates/admin/repos/repos.mako:13
3349 #: rhodecode/templates/admin/settings/settings.mako:12
3363 #: rhodecode/templates/admin/settings/settings.mako:12
3350 #: rhodecode/templates/admin/user_groups/user_group_add.mako:11
3364 #: rhodecode/templates/admin/user_groups/user_group_add.mako:11
@@ -3354,9 +3368,9 b' msgstr ""'
3354 #: rhodecode/templates/admin/users/user_add.mako:11
3368 #: rhodecode/templates/admin/users/user_add.mako:11
3355 #: rhodecode/templates/admin/users/user_edit.mako:12
3369 #: rhodecode/templates/admin/users/user_edit.mako:12
3356 #: rhodecode/templates/admin/users/users.mako:13
3370 #: rhodecode/templates/admin/users/users.mako:13
3357 #: rhodecode/templates/admin/users/users.mako:75
3371 #: rhodecode/templates/admin/users/users.mako:76
3358 #: rhodecode/templates/base/base.mako:409
3372 #: rhodecode/templates/base/base.mako:412
3359 #: rhodecode/templates/base/base.mako:416
3373 #: rhodecode/templates/base/base.mako:419
3360 msgid "Admin"
3374 msgid "Admin"
3361 msgstr ""
3375 msgstr ""
3362
3376
@@ -3387,79 +3401,79 b' msgstr ""'
3387 msgid "Disable password recovery"
3401 msgid "Disable password recovery"
3388 msgstr ""
3402 msgstr ""
3389
3403
3390 #: rhodecode/model/pull_request.py:78
3404 #: rhodecode/model/pull_request.py:80
3391 msgid "This pull request can be automatically merged."
3405 msgid "This pull request can be automatically merged."
3392 msgstr ""
3406 msgstr ""
3393
3407
3394 #: rhodecode/model/pull_request.py:80
3408 #: rhodecode/model/pull_request.py:82
3395 msgid "This pull request cannot be merged because of an unhandled exception."
3409 msgid "This pull request cannot be merged because of an unhandled exception."
3396 msgstr ""
3410 msgstr ""
3397
3411
3398 #: rhodecode/model/pull_request.py:83
3399 msgid "This pull request cannot be merged because of merge conflicts."
3400 msgstr ""
3401
3402 #: rhodecode/model/pull_request.py:85
3412 #: rhodecode/model/pull_request.py:85
3413 msgid "This pull request cannot be merged because of merge conflicts."
3414 msgstr ""
3415
3416 #: rhodecode/model/pull_request.py:87
3403 msgid "This pull request could not be merged because push to target failed."
3417 msgid "This pull request could not be merged because push to target failed."
3404 msgstr ""
3418 msgstr ""
3405
3419
3406 #: rhodecode/model/pull_request.py:88
3420 #: rhodecode/model/pull_request.py:90
3407 msgid "This pull request cannot be merged because the target is not a head."
3421 msgid "This pull request cannot be merged because the target is not a head."
3408 msgstr ""
3422 msgstr ""
3409
3423
3410 #: rhodecode/model/pull_request.py:91
3424 #: rhodecode/model/pull_request.py:93
3411 msgid "This pull request cannot be merged because the source contains more branches than the target."
3425 msgid "This pull request cannot be merged because the source contains more branches than the target."
3412 msgstr ""
3426 msgstr ""
3413
3427
3414 #: rhodecode/model/pull_request.py:94
3428 #: rhodecode/model/pull_request.py:96
3415 msgid "This pull request cannot be merged because the target has multiple heads."
3429 msgid "This pull request cannot be merged because the target has multiple heads."
3416 msgstr ""
3430 msgstr ""
3417
3431
3418 #: rhodecode/model/pull_request.py:97
3432 #: rhodecode/model/pull_request.py:99
3419 msgid "This pull request cannot be merged because the target repository is locked."
3433 msgid "This pull request cannot be merged because the target repository is locked."
3420 msgstr ""
3434 msgstr ""
3421
3435
3422 #: rhodecode/model/pull_request.py:100
3436 #: rhodecode/model/pull_request.py:102
3423 msgid "This pull request cannot be merged because the target or the source reference is missing."
3437 msgid "This pull request cannot be merged because the target or the source reference is missing."
3424 msgstr ""
3438 msgstr ""
3425
3439
3426 #: rhodecode/model/pull_request.py:103
3440 #: rhodecode/model/pull_request.py:105
3427 msgid "This pull request cannot be merged because the target reference is missing."
3441 msgid "This pull request cannot be merged because the target reference is missing."
3428 msgstr ""
3442 msgstr ""
3429
3443
3430 #: rhodecode/model/pull_request.py:106
3444 #: rhodecode/model/pull_request.py:108
3431 msgid "This pull request cannot be merged because the source reference is missing."
3445 msgid "This pull request cannot be merged because the source reference is missing."
3432 msgstr ""
3446 msgstr ""
3433
3447
3434 #: rhodecode/model/pull_request.py:109
3448 #: rhodecode/model/pull_request.py:111
3435 msgid "This pull request cannot be merged because of conflicts related to sub repositories."
3449 msgid "This pull request cannot be merged because of conflicts related to sub repositories."
3436 msgstr ""
3450 msgstr ""
3437
3451
3438 #: rhodecode/model/pull_request.py:115
3439 msgid "Pull request update successful."
3440 msgstr ""
3441
3442 #: rhodecode/model/pull_request.py:117
3452 #: rhodecode/model/pull_request.py:117
3443 msgid "Pull request update failed because of an unknown error."
3453 msgid "Pull request update successful."
3444 msgstr ""
3454 msgstr ""
3445
3455
3446 #: rhodecode/model/pull_request.py:119
3456 #: rhodecode/model/pull_request.py:119
3447 msgid "No update needed because the source and target have not changed."
3457 msgid "Pull request update failed because of an unknown error."
3448 msgstr ""
3458 msgstr ""
3449
3459
3450 #: rhodecode/model/pull_request.py:121
3460 #: rhodecode/model/pull_request.py:121
3451 msgid "Pull request cannot be updated because the reference type is not supported for an update."
3461 msgid "No update needed because the source and target have not changed."
3452 msgstr ""
3462 msgstr ""
3453
3463
3454 #: rhodecode/model/pull_request.py:124
3464 #: rhodecode/model/pull_request.py:123
3465 msgid "Pull request cannot be updated because the reference type is not supported for an update. Only Branch, Tag or Bookmark is allowed."
3466 msgstr ""
3467
3468 #: rhodecode/model/pull_request.py:126
3455 msgid "This pull request cannot be updated because the target reference is missing."
3469 msgid "This pull request cannot be updated because the target reference is missing."
3456 msgstr ""
3470 msgstr ""
3457
3471
3458 #: rhodecode/model/pull_request.py:127
3472 #: rhodecode/model/pull_request.py:129
3459 msgid "This pull request cannot be updated because the source reference is missing."
3473 msgid "This pull request cannot be updated because the source reference is missing."
3460 msgstr ""
3474 msgstr ""
3461
3475
3462 #: rhodecode/model/pull_request.py:524
3476 #: rhodecode/model/pull_request.py:544
3463 #, python-format
3477 #, python-format
3464 msgid ""
3478 msgid ""
3465 "Merge pull request #%(pr_id)s from %(source_repo)s %(source_ref_name)s\n"
3479 "Merge pull request #%(pr_id)s from %(source_repo)s %(source_ref_name)s\n"
@@ -3467,86 +3481,90 b' msgid ""'
3467 " %(pr_title)s"
3481 " %(pr_title)s"
3468 msgstr ""
3482 msgstr ""
3469
3483
3470 #: rhodecode/model/pull_request.py:556
3484 #: rhodecode/model/pull_request.py:576
3471 msgid "Pull request merged and closed"
3485 msgid "Pull request merged and closed"
3472 msgstr ""
3486 msgstr ""
3473
3487
3474 #: rhodecode/model/pull_request.py:1087
3488 #: rhodecode/model/pull_request.py:1108
3489 msgid "Closing with status change {transition_icon} {status}."
3490 msgstr ""
3491
3492 #: rhodecode/model/pull_request.py:1152
3475 msgid "Server-side pull request merging is disabled."
3493 msgid "Server-side pull request merging is disabled."
3476 msgstr ""
3494 msgstr ""
3477
3495
3478 #: rhodecode/model/pull_request.py:1089
3496 #: rhodecode/model/pull_request.py:1154
3479 msgid "This pull request is closed."
3497 msgid "This pull request is closed."
3480 msgstr ""
3498 msgstr ""
3481
3499
3482 #: rhodecode/model/pull_request.py:1101
3500 #: rhodecode/model/pull_request.py:1166
3483 msgid "Pull request merging is not supported."
3501 msgid "Pull request merging is not supported."
3484 msgstr ""
3502 msgstr ""
3485
3503
3486 #: rhodecode/model/pull_request.py:1119
3504 #: rhodecode/model/pull_request.py:1184
3487 msgid "Target repository large files support is disabled."
3505 msgid "Target repository large files support is disabled."
3488 msgstr ""
3506 msgstr ""
3489
3507
3490 #: rhodecode/model/pull_request.py:1122
3508 #: rhodecode/model/pull_request.py:1187
3491 msgid "Source repository large files support is disabled."
3509 msgid "Source repository large files support is disabled."
3492 msgstr ""
3510 msgstr ""
3493
3511
3494 #: rhodecode/model/pull_request.py:1279 rhodecode/model/scm.py:790
3512 #: rhodecode/model/pull_request.py:1344 rhodecode/model/scm.py:783
3495 msgid "Bookmarks"
3513 msgid "Bookmarks"
3496 msgstr ""
3514 msgstr ""
3497
3515
3498 #: rhodecode/model/pull_request.py:1284
3516 #: rhodecode/model/pull_request.py:1349
3499 msgid "Commit IDs"
3517 msgid "Commit IDs"
3500 msgstr ""
3518 msgstr ""
3501
3519
3502 #: rhodecode/model/pull_request.py:1287
3520 #: rhodecode/model/pull_request.py:1352
3503 msgid "Closed Branches"
3521 msgid "Closed Branches"
3504 msgstr ""
3522 msgstr ""
3505
3523
3506 #: rhodecode/model/pull_request.py:1411
3524 #: rhodecode/model/pull_request.py:1493
3507 msgid "User `{}` not allowed to perform merge."
3525 msgid "User `{}` not allowed to perform merge."
3508 msgstr ""
3526 msgstr ""
3509
3527
3510 #: rhodecode/model/pull_request.py:1424
3528 #: rhodecode/model/pull_request.py:1506
3511 msgid "Pull request reviewer approval is pending."
3529 msgid "Pull request reviewer approval is pending."
3512 msgstr ""
3530 msgstr ""
3513
3531
3514 #: rhodecode/model/pull_request.py:1439
3532 #: rhodecode/model/pull_request.py:1521
3515 msgid "Cannot merge, {} TODO still not resolved."
3533 msgid "Cannot merge, {} TODO still not resolved."
3516 msgstr ""
3534 msgstr ""
3517
3535
3518 #: rhodecode/model/pull_request.py:1442
3536 #: rhodecode/model/pull_request.py:1524
3519 msgid "Cannot merge, {} TODOs still not resolved."
3537 msgid "Cannot merge, {} TODOs still not resolved."
3520 msgstr ""
3538 msgstr ""
3521
3539
3522 #: rhodecode/model/scm.py:768
3540 #: rhodecode/model/scm.py:761
3523 msgid "latest tip"
3541 msgid "latest tip"
3524 msgstr ""
3542 msgstr ""
3525
3543
3526 #: rhodecode/model/user.py:126
3544 #: rhodecode/model/user.py:166
3527 msgid "You can't Edit this user since it's crucial for entire application"
3545 msgid "You can't Edit this user since it's crucial for entire application"
3528 msgstr ""
3546 msgstr ""
3529
3547
3530 #: rhodecode/model/user.py:292
3548 #: rhodecode/model/user.py:332
3531 #, python-format
3549 #, python-format
3532 msgid "You can't edit this user (`%(username)s`) since it's crucial for entire application"
3550 msgid "You can't edit this user (`%(username)s`) since it's crucial for entire application"
3533 msgstr ""
3551 msgstr ""
3534
3552
3535 #: rhodecode/model/user.py:462
3553 #: rhodecode/model/user.py:502
3536 msgid "You can't remove this user since it's crucial for entire application"
3554 msgid "You can't remove this user since it's crucial for entire application"
3537 msgstr ""
3555 msgstr ""
3538
3556
3539 #: rhodecode/model/user.py:470
3557 #: rhodecode/model/user.py:510
3540 #, python-format
3558 #, python-format
3541 msgid "user \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories:%s"
3559 msgid "user \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories:%s"
3542 msgstr ""
3560 msgstr ""
3543
3561
3544 #: rhodecode/model/user.py:479
3562 #: rhodecode/model/user.py:519
3545 #, python-format
3563 #, python-format
3546 msgid "user \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups:%s"
3564 msgid "user \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups:%s"
3547 msgstr ""
3565 msgstr ""
3548
3566
3549 #: rhodecode/model/user.py:488
3567 #: rhodecode/model/user.py:528
3550 #, python-format
3568 #, python-format
3551 msgid "user \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups:%s"
3569 msgid "user \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups:%s"
3552 msgstr ""
3570 msgstr ""
@@ -3570,6 +3588,7 b' msgid "Username \\"%(username)s\\" is forb'
3570 msgstr ""
3588 msgstr ""
3571
3589
3572 #: rhodecode/model/validators.py:164
3590 #: rhodecode/model/validators.py:164
3591 #: rhodecode/model/validation_schema/schemas/user_schema.py:69
3573 msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character or underscore"
3592 msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character or underscore"
3574 msgstr ""
3593 msgstr ""
3575
3594
@@ -3675,94 +3694,107 b' msgstr ""'
3675 msgid "Repository group with name \"%(repo)s\" exists in group \"%(group)s\""
3694 msgid "Repository group with name \"%(repo)s\" exists in group \"%(group)s\""
3676 msgstr ""
3695 msgstr ""
3677
3696
3678 #: rhodecode/model/validators.py:620
3697 #: rhodecode/model/validators.py:581
3698 #: rhodecode/model/validation_schema/schemas/repo_schema.py:219
3699 msgid "Repository name cannot end with .git"
3700 msgstr ""
3701
3702 #: rhodecode/model/validators.py:640
3679 #, python-format
3703 #, python-format
3680 msgid "invalid clone url for %(rtype)s repository"
3704 msgid "invalid clone url for %(rtype)s repository"
3681 msgstr ""
3705 msgstr ""
3682
3706
3683 #: rhodecode/model/validators.py:621
3707 #: rhodecode/model/validators.py:641
3684 #, python-format
3708 #, python-format
3685 msgid "Invalid clone url, provide a valid clone url starting with one of %(allowed_prefixes)s"
3709 msgid "Invalid clone url, provide a valid clone url starting with one of %(allowed_prefixes)s"
3686 msgstr ""
3710 msgstr ""
3687
3711
3688 #: rhodecode/model/validators.py:650
3712 #: rhodecode/model/validators.py:670
3689 msgid "Fork have to be the same type as parent"
3713 msgid "Fork have to be the same type as parent"
3690 msgstr ""
3714 msgstr ""
3691
3715
3692 #: rhodecode/model/validators.py:665
3716 #: rhodecode/model/validators.py:685
3693 msgid "You do not have the permission to create repositories in this group."
3717 msgid "You do not have the permission to create repositories in this group."
3694 msgstr ""
3718 msgstr ""
3695
3719
3696 #: rhodecode/model/validators.py:668
3720 #: rhodecode/model/validators.py:688
3697 #: rhodecode/model/validation_schema/schemas/repo_schema.py:102
3721 #: rhodecode/model/validation_schema/schemas/repo_schema.py:125
3698 msgid "You do not have the permission to store repositories in the root location."
3722 msgid "You do not have the permission to store repositories in the root location."
3699 msgstr ""
3723 msgstr ""
3700
3724
3701 #: rhodecode/model/validators.py:728
3725 #: rhodecode/model/validators.py:748
3702 msgid "This username or user group name is not valid"
3726 msgid "This username or user group name is not valid"
3703 msgstr ""
3727 msgstr ""
3704
3728
3705 #: rhodecode/model/validators.py:846
3729 #: rhodecode/model/validators.py:879
3706 msgid "This is not a valid path"
3730 msgid "This is not a valid path"
3707 msgstr ""
3731 msgstr ""
3708
3732
3709 #: rhodecode/model/validators.py:861
3733 #: rhodecode/model/validators.py:894
3710 msgid "This e-mail address is already taken"
3734 msgid "This e-mail address is already taken"
3711 msgstr ""
3735 msgstr ""
3712
3736
3713 #: rhodecode/model/validators.py:881
3737 #: rhodecode/model/validators.py:914
3714 #, python-format
3738 #, python-format
3715 msgid "e-mail \"%(email)s\" does not exist."
3739 msgid "e-mail \"%(email)s\" does not exist."
3716 msgstr ""
3740 msgstr ""
3717
3741
3718 #: rhodecode/model/validators.py:902
3742 #: rhodecode/model/validators.py:935
3719 #, python-format
3743 #, python-format
3720 msgid "Revisions %(revs)s are already part of pull request or have set status"
3744 msgid "Revisions %(revs)s are already part of pull request or have set status"
3721 msgstr ""
3745 msgstr ""
3722
3746
3723 #: rhodecode/model/validators.py:933
3747 #: rhodecode/model/validators.py:966
3724 #: rhodecode/model/validation_schema/validators.py:16
3748 #: rhodecode/model/validation_schema/validators.py:40
3725 #: rhodecode/model/validation_schema/validators.py:29
3749 #: rhodecode/model/validation_schema/validators.py:53
3726 msgid "Please enter a valid IPv4 or IpV6 address"
3750 msgid "Please enter a valid IPv4 or IpV6 address"
3727 msgstr ""
3751 msgstr ""
3728
3752
3729 #: rhodecode/model/validators.py:934
3753 #: rhodecode/model/validators.py:967
3730 #, python-format
3754 #, python-format
3731 msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
3755 msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
3732 msgstr ""
3756 msgstr ""
3733
3757
3734 #: rhodecode/model/validators.py:961
3758 #: rhodecode/model/validators.py:994
3735 msgid "Key name can only consist of letters, underscore, dash or numbers"
3759 msgid "Key name can only consist of letters, underscore, dash or numbers"
3736 msgstr ""
3760 msgstr ""
3737
3761
3738 #: rhodecode/model/validators.py:976
3762 #: rhodecode/model/validators.py:1009
3739 #, python-format
3763 #, python-format
3740 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
3764 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
3741 msgstr ""
3765 msgstr ""
3742
3766
3743 #: rhodecode/model/validators.py:979
3767 #: rhodecode/model/validators.py:1012
3744 #, python-format
3768 #, python-format
3745 msgid "The plugin \"%(plugin_id)s\" is missing an includeme function."
3769 msgid "The plugin \"%(plugin_id)s\" is missing an includeme function."
3746 msgstr ""
3770 msgstr ""
3747
3771
3748 #: rhodecode/model/validators.py:982
3772 #: rhodecode/model/validators.py:1015
3749 #, python-format
3773 #, python-format
3750 msgid "Can not load plugin \"%(plugin_id)s\""
3774 msgid "Can not load plugin \"%(plugin_id)s\""
3751 msgstr ""
3775 msgstr ""
3752
3776
3753 #: rhodecode/model/validators.py:984
3777 #: rhodecode/model/validators.py:1017
3754 #, python-format
3778 #, python-format
3755 msgid "No plugin available with ID \"%(plugin_id)s\""
3779 msgid "No plugin available with ID \"%(plugin_id)s\""
3756 msgstr ""
3780 msgstr ""
3757
3781
3758 #: rhodecode/model/validation_schema/validators.py:37
3782 #: rhodecode/model/validation_schema/validators.py:61
3759 msgid "Invalid glob pattern"
3783 msgid "Invalid glob pattern"
3760 msgstr ""
3784 msgstr ""
3761
3785
3762 #: rhodecode/model/validation_schema/validators.py:46
3786 #: rhodecode/model/validation_schema/validators.py:70
3763 msgid "Name must start with a letter or number. Got `{}`"
3787 msgid "Name must start with a letter or number. Got `{}`"
3764 msgstr ""
3788 msgstr ""
3765
3789
3790 #: rhodecode/model/validation_schema/validators.py:132
3791 msgid "Invalid clone url, provide a valid clone url starting with one of {allowed_prefixes}"
3792 msgstr ""
3793
3794 #: rhodecode/model/validation_schema/validators.py:138
3795 msgid "invalid clone url for {repo_type} repository"
3796 msgstr ""
3797
3766 #: rhodecode/model/validation_schema/schemas/comment_schema.py:42
3798 #: rhodecode/model/validation_schema/schemas/comment_schema.py:42
3767 #: rhodecode/model/validation_schema/schemas/gist_schema.py:89
3799 #: rhodecode/model/validation_schema/schemas/gist_schema.py:89
3768 msgid "Gist with name {} already exists"
3800 msgid "Gist with name {} already exists"
@@ -3831,253 +3863,261 b' msgid "Repo group owner with id `{}` doe'
3831 msgstr ""
3863 msgstr ""
3832
3864
3833 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:130
3865 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:130
3834 #: rhodecode/model/validation_schema/schemas/repo_schema.py:181
3866 #: rhodecode/model/validation_schema/schemas/repo_schema.py:204
3835 msgid "Repository with name `{}` already exists"
3867 msgid "Repository with name `{}` already exists"
3836 msgstr ""
3868 msgstr ""
3837
3869
3838 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:135
3870 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:135
3839 #: rhodecode/model/validation_schema/schemas/repo_schema.py:186
3871 #: rhodecode/model/validation_schema/schemas/repo_schema.py:209
3840 msgid "Repository group with name `{}` already exists"
3872 msgid "Repository group with name `{}` already exists"
3841 msgstr ""
3873 msgstr ""
3842
3874
3843 #: rhodecode/model/validation_schema/schemas/repo_schema.py:48
3875 #: rhodecode/model/validation_schema/schemas/repo_schema.py:55
3844 msgid "Repo owner with id `{}` does not exists"
3876 msgid "Repo owner with id `{}` does not exists"
3845 msgstr ""
3877 msgstr ""
3846
3878
3847 #: rhodecode/model/validation_schema/schemas/repo_schema.py:68
3879 #: rhodecode/model/validation_schema/schemas/repo_schema.py:91
3848 msgid "Fork with id `{}` does not exists"
3880 msgid "Fork with id `{}` does not exists"
3849 msgstr ""
3881 msgstr ""
3850
3882
3851 #: rhodecode/model/validation_schema/schemas/repo_schema.py:71
3883 #: rhodecode/model/validation_schema/schemas/repo_schema.py:94
3852 msgid "Cannot set fork of parameter of this repository to itself"
3884 msgid "Cannot set fork of parameter of this repository to itself"
3853 msgstr ""
3885 msgstr ""
3854
3886
3855 #: rhodecode/model/validation_schema/schemas/repo_schema.py:96
3887 #: rhodecode/model/validation_schema/schemas/repo_schema.py:119
3856 #: rhodecode/model/validation_schema/schemas/repo_schema.py:100
3888 #: rhodecode/model/validation_schema/schemas/repo_schema.py:123
3857 msgid "Repository group `{}` does not exist"
3889 msgid "Repository group `{}` does not exist"
3858 msgstr ""
3890 msgstr ""
3859
3891
3860 #: rhodecode/model/validation_schema/schemas/user_schema.py:36
3892 #: rhodecode/model/validation_schema/schemas/user_group_schema.py:32
3893 msgid "Allowed in name are letters, numbers, and `-`, `_`, `.` Name must start with a letter or number. Got `{}`"
3894 msgstr ""
3895
3896 #: rhodecode/model/validation_schema/schemas/user_group_schema.py:48
3897 msgid "User group owner with id `{}` does not exists"
3898 msgstr ""
3899
3900 #: rhodecode/model/validation_schema/schemas/user_schema.py:38
3861 msgid "Password is incorrect"
3901 msgid "Password is incorrect"
3862 msgstr ""
3902 msgstr ""
3863
3903
3864 #: rhodecode/model/validation_schema/schemas/user_schema.py:59
3904 #: rhodecode/model/validation_schema/schemas/user_schema.py:60
3865 msgid "New password must be different to old password"
3905 msgid "New password must be different to old password"
3866 msgstr ""
3906 msgstr ""
3867
3907
3868 #: rhodecode/public/js/rhodecode-components.js:31663
3908 #: rhodecode/public/js/rhodecode-components.js:31663
3869 #: rhodecode/public/js/scripts.js:23511
3909 #: rhodecode/public/js/scripts.js:23511
3870 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:23
3910 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:29
3871 #: rhodecode/public/js/src/plugins/jquery.autocomplete.js:87
3911 #: rhodecode/public/js/src/plugins/jquery.autocomplete.js:87
3872 msgid "No results"
3912 msgid "No results"
3873 msgstr ""
3913 msgstr ""
3874
3914
3875 #: rhodecode/public/js/rhodecode-components.js:33594
3915 #: rhodecode/public/js/rhodecode-components.js:33594
3876 #: rhodecode/public/js/scripts.js:25442
3916 #: rhodecode/public/js/scripts.js:25442
3877 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:88
3917 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:97
3878 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:109
3918 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:109
3879 msgid "{0} year"
3919 msgid "{0} year"
3880 msgstr ""
3920 msgstr ""
3881
3921
3882 #: rhodecode/public/js/rhodecode-components.js:33595
3922 #: rhodecode/public/js/rhodecode-components.js:33595
3883 #: rhodecode/public/js/scripts.js:25443
3923 #: rhodecode/public/js/scripts.js:25443
3884 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:83
3924 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:92
3885 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:110
3925 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:110
3886 msgid "{0} month"
3926 msgid "{0} month"
3887 msgstr ""
3927 msgstr ""
3888
3928
3889 #: rhodecode/public/js/rhodecode-components.js:33596
3929 #: rhodecode/public/js/rhodecode-components.js:33596
3890 #: rhodecode/public/js/scripts.js:25444
3930 #: rhodecode/public/js/scripts.js:25444
3891 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:78
3931 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:87
3892 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:111
3932 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:111
3893 msgid "{0} day"
3933 msgid "{0} day"
3894 msgstr ""
3934 msgstr ""
3895
3935
3896 #: rhodecode/public/js/rhodecode-components.js:33597
3936 #: rhodecode/public/js/rhodecode-components.js:33597
3897 #: rhodecode/public/js/scripts.js:25445
3937 #: rhodecode/public/js/scripts.js:25445
3898 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:80
3938 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:89
3899 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:112
3939 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:112
3900 msgid "{0} hour"
3940 msgid "{0} hour"
3901 msgstr ""
3941 msgstr ""
3902
3942
3903 #: rhodecode/public/js/rhodecode-components.js:33598
3943 #: rhodecode/public/js/rhodecode-components.js:33598
3904 #: rhodecode/public/js/scripts.js:25446
3944 #: rhodecode/public/js/scripts.js:25446
3905 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:82
3945 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:91
3906 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:113
3946 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:113
3907 msgid "{0} min"
3947 msgid "{0} min"
3908 msgstr ""
3948 msgstr ""
3909
3949
3910 #: rhodecode/public/js/rhodecode-components.js:33599
3950 #: rhodecode/public/js/rhodecode-components.js:33599
3911 #: rhodecode/public/js/scripts.js:25447
3951 #: rhodecode/public/js/scripts.js:25447
3912 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:87
3952 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:96
3913 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:114
3953 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:114
3914 msgid "{0} sec"
3954 msgid "{0} sec"
3915 msgstr ""
3955 msgstr ""
3916
3956
3917 #: rhodecode/public/js/rhodecode-components.js:33619
3957 #: rhodecode/public/js/rhodecode-components.js:33619
3918 #: rhodecode/public/js/scripts.js:25467
3958 #: rhodecode/public/js/scripts.js:25467
3919 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:63
3959 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:71
3920 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:134
3960 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:134
3921 msgid "in {0}"
3961 msgid "in {0}"
3922 msgstr ""
3962 msgstr ""
3923
3963
3924 #: rhodecode/public/js/rhodecode-components.js:33627
3964 #: rhodecode/public/js/rhodecode-components.js:33627
3925 #: rhodecode/public/js/scripts.js:25475
3965 #: rhodecode/public/js/scripts.js:25475
3926 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:75
3966 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:84
3927 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:142
3967 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:142
3928 msgid "{0} ago"
3968 msgid "{0} ago"
3929 msgstr ""
3969 msgstr ""
3930
3970
3931 #: rhodecode/public/js/rhodecode-components.js:33639
3971 #: rhodecode/public/js/rhodecode-components.js:33639
3932 #: rhodecode/public/js/scripts.js:25487
3972 #: rhodecode/public/js/scripts.js:25487
3933 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:90
3973 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:99
3934 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:154
3974 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:154
3935 msgid "{0}, {1} ago"
3975 msgid "{0}, {1} ago"
3936 msgstr ""
3976 msgstr ""
3937
3977
3938 #: rhodecode/public/js/rhodecode-components.js:33641
3978 #: rhodecode/public/js/rhodecode-components.js:33641
3939 #: rhodecode/public/js/scripts.js:25489
3979 #: rhodecode/public/js/scripts.js:25489
3940 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:65
3980 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:73
3941 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:156
3981 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:156
3942 msgid "in {0}, {1}"
3982 msgid "in {0}, {1}"
3943 msgstr ""
3983 msgstr ""
3944
3984
3945 #: rhodecode/public/js/rhodecode-components.js:33645
3985 #: rhodecode/public/js/rhodecode-components.js:33645
3946 #: rhodecode/public/js/scripts.js:25493
3986 #: rhodecode/public/js/scripts.js:25493
3947 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:76
3987 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:85
3948 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:160
3988 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:160
3949 msgid "{0} and {1}"
3989 msgid "{0} and {1}"
3950 msgstr ""
3990 msgstr ""
3951
3991
3952 #: rhodecode/public/js/rhodecode-components.js:33647
3992 #: rhodecode/public/js/rhodecode-components.js:33647
3953 #: rhodecode/public/js/scripts.js:25495
3993 #: rhodecode/public/js/scripts.js:25495
3954 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:77
3994 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:86
3955 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:162
3995 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:162
3956 msgid "{0} and {1} ago"
3996 msgid "{0} and {1} ago"
3957 msgstr ""
3997 msgstr ""
3958
3998
3959 #: rhodecode/public/js/rhodecode-components.js:33649
3999 #: rhodecode/public/js/rhodecode-components.js:33649
3960 #: rhodecode/public/js/scripts.js:25497
4000 #: rhodecode/public/js/scripts.js:25497
3961 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:64
4001 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:72
3962 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:164
4002 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:164
3963 msgid "in {0} and {1}"
4003 msgid "in {0} and {1}"
3964 msgstr ""
4004 msgstr ""
3965
4005
3966 #: rhodecode/public/js/rhodecode-components.js:47492
4006 #: rhodecode/public/js/rhodecode-components.js:47492
3967 #: rhodecode/public/js/scripts.js:39340
4007 #: rhodecode/public/js/scripts.js:39340
3968 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:14
4008 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:20
3969 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:4
4009 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:4
3970 msgid "Loading more results..."
4010 msgid "Loading more results..."
3971 msgstr ""
4011 msgstr ""
3972
4012
3973 #: rhodecode/public/js/rhodecode-components.js:47495
4013 #: rhodecode/public/js/rhodecode-components.js:47495
3974 #: rhodecode/public/js/scripts.js:39343
4014 #: rhodecode/public/js/scripts.js:39343
3975 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:36
4015 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:43
3976 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:7
4016 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:7
3977 msgid "Searching..."
4017 msgid "Searching..."
3978 msgstr ""
4018 msgstr ""
3979
4019
3980 #: rhodecode/public/js/rhodecode-components.js:47498
4020 #: rhodecode/public/js/rhodecode-components.js:47498
3981 #: rhodecode/public/js/scripts.js:39346
4021 #: rhodecode/public/js/scripts.js:39346
3982 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:18
4022 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:24
3983 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:10
4023 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:10
3984 msgid "No matches found"
4024 msgid "No matches found"
3985 msgstr ""
4025 msgstr ""
3986
4026
3987 #: rhodecode/public/js/rhodecode-components.js:47501
4027 #: rhodecode/public/js/rhodecode-components.js:47501
3988 #: rhodecode/public/js/scripts.js:39349
4028 #: rhodecode/public/js/scripts.js:39349
3989 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:13
4029 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:19
3990 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:13
4030 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:13
3991 msgid "Loading failed"
4031 msgid "Loading failed"
3992 msgstr ""
4032 msgstr ""
3993
4033
3994 #: rhodecode/public/js/rhodecode-components.js:47505
4034 #: rhodecode/public/js/rhodecode-components.js:47505
3995 #: rhodecode/public/js/scripts.js:39353
4035 #: rhodecode/public/js/scripts.js:39353
3996 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:28
4036 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:34
3997 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:17
4037 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:17
3998 msgid "One result is available, press enter to select it."
4038 msgid "One result is available, press enter to select it."
3999 msgstr ""
4039 msgstr ""
4000
4040
4001 #: rhodecode/public/js/rhodecode-components.js:47507
4041 #: rhodecode/public/js/rhodecode-components.js:47507
4002 #: rhodecode/public/js/scripts.js:39355
4042 #: rhodecode/public/js/scripts.js:39355
4003 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:86
4043 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:95
4004 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:19
4044 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:19
4005 msgid "{0} results are available, use up and down arrow keys to navigate."
4045 msgid "{0} results are available, use up and down arrow keys to navigate."
4006 msgstr ""
4046 msgstr ""
4007
4047
4008 #: rhodecode/public/js/rhodecode-components.js:47512
4048 #: rhodecode/public/js/rhodecode-components.js:47512
4009 #: rhodecode/public/js/scripts.js:39360
4049 #: rhodecode/public/js/scripts.js:39360
4010 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:33
4050 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:39
4011 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:24
4051 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:24
4012 msgid "Please enter {0} or more character"
4052 msgid "Please enter {0} or more character"
4013 msgstr ""
4053 msgstr ""
4014
4054
4015 #: rhodecode/public/js/rhodecode-components.js:47514
4055 #: rhodecode/public/js/rhodecode-components.js:47514
4016 #: rhodecode/public/js/scripts.js:39362
4056 #: rhodecode/public/js/scripts.js:39362
4017 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:34
4057 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:40
4018 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:26
4058 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:26
4019 msgid "Please enter {0} or more characters"
4059 msgid "Please enter {0} or more characters"
4020 msgstr ""
4060 msgstr ""
4021
4061
4022 #: rhodecode/public/js/rhodecode-components.js:47519
4062 #: rhodecode/public/js/rhodecode-components.js:47519
4023 #: rhodecode/public/js/scripts.js:39367
4063 #: rhodecode/public/js/scripts.js:39367
4024 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:31
4064 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:37
4025 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:31
4065 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:31
4026 msgid "Please delete {0} character"
4066 msgid "Please delete {0} character"
4027 msgstr ""
4067 msgstr ""
4028
4068
4029 #: rhodecode/public/js/rhodecode-components.js:47521
4069 #: rhodecode/public/js/rhodecode-components.js:47521
4030 #: rhodecode/public/js/scripts.js:39369
4070 #: rhodecode/public/js/scripts.js:39369
4031 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:32
4071 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:38
4032 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:33
4072 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:33
4033 msgid "Please delete {0} characters"
4073 msgid "Please delete {0} characters"
4034 msgstr ""
4074 msgstr ""
4035
4075
4036 #: rhodecode/public/js/rhodecode-components.js:47525
4076 #: rhodecode/public/js/rhodecode-components.js:47525
4037 #: rhodecode/public/js/scripts.js:39373
4077 #: rhodecode/public/js/scripts.js:39373
4038 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:56
4078 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:64
4039 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:37
4079 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:37
4040 msgid "You can only select {0} item"
4080 msgid "You can only select {0} item"
4041 msgstr ""
4081 msgstr ""
4042
4082
4043 #: rhodecode/public/js/rhodecode-components.js:47527
4083 #: rhodecode/public/js/rhodecode-components.js:47527
4044 #: rhodecode/public/js/scripts.js:39375
4084 #: rhodecode/public/js/scripts.js:39375
4045 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:57
4085 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:65
4046 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:39
4086 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:39
4047 msgid "You can only select {0} items"
4087 msgid "You can only select {0} items"
4048 msgstr ""
4088 msgstr ""
4049
4089
4050 #: rhodecode/public/js/rhodecode-components.js:48456
4090 #: rhodecode/public/js/rhodecode-components.js:48456
4051 #: rhodecode/public/js/scripts.js:40304
4091 #: rhodecode/public/js/scripts.js:40304
4052 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:69
4092 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:78
4053 #: rhodecode/public/js/src/rhodecode/changelog.js:35
4093 #: rhodecode/public/js/src/rhodecode/changelog.js:35
4054 msgid "showing {0} out of {1} commit"
4094 msgid "showing {0} out of {1} commit"
4055 msgstr ""
4095 msgstr ""
4056
4096
4057 #: rhodecode/public/js/rhodecode-components.js:48458
4097 #: rhodecode/public/js/rhodecode-components.js:48458
4058 #: rhodecode/public/js/scripts.js:40306
4098 #: rhodecode/public/js/scripts.js:40306
4059 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:70
4099 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:79
4060 #: rhodecode/public/js/src/rhodecode/changelog.js:37
4100 #: rhodecode/public/js/src/rhodecode/changelog.js:37
4061 msgid "showing {0} out of {1} commits"
4101 msgid "showing {0} out of {1} commits"
4062 msgstr ""
4102 msgstr ""
4063
4103
4064 #: rhodecode/public/js/rhodecode-components.js:48891
4104 #: rhodecode/public/js/rhodecode-components.js:48891
4065 #: rhodecode/public/js/scripts.js:40739
4105 #: rhodecode/public/js/scripts.js:40739
4066 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:39
4106 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:46
4067 #: rhodecode/public/js/src/rhodecode/codemirror.js:296
4107 #: rhodecode/public/js/src/rhodecode/codemirror.js:296
4068 msgid "Set status to Approved"
4108 msgid "Set status to Approved"
4069 msgstr ""
4109 msgstr ""
4070
4110
4071 #: rhodecode/public/js/rhodecode-components.js:48910
4111 #: rhodecode/public/js/rhodecode-components.js:48910
4072 #: rhodecode/public/js/scripts.js:40758
4112 #: rhodecode/public/js/scripts.js:40758
4073 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:40
4113 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:47
4074 #: rhodecode/public/js/src/rhodecode/codemirror.js:315
4114 #: rhodecode/public/js/src/rhodecode/codemirror.js:315
4075 msgid "Set status to Rejected"
4115 msgid "Set status to Rejected"
4076 msgstr ""
4116 msgstr ""
4077
4117
4078 #: rhodecode/public/js/rhodecode-components.js:48929
4118 #: rhodecode/public/js/rhodecode-components.js:48929
4079 #: rhodecode/public/js/scripts.js:40777
4119 #: rhodecode/public/js/scripts.js:40777
4080 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:51
4120 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:58
4081 #: rhodecode/public/js/src/rhodecode/codemirror.js:334
4121 #: rhodecode/public/js/src/rhodecode/codemirror.js:334
4082 #: rhodecode/templates/email_templates/commit_comment.mako:99
4122 #: rhodecode/templates/email_templates/commit_comment.mako:99
4083 #: rhodecode/templates/email_templates/pull_request_comment.mako:107
4123 #: rhodecode/templates/email_templates/pull_request_comment.mako:107
@@ -4086,42 +4126,42 b' msgstr ""'
4086
4126
4087 #: rhodecode/public/js/rhodecode-components.js:48949
4127 #: rhodecode/public/js/rhodecode-components.js:48949
4088 #: rhodecode/public/js/scripts.js:40797
4128 #: rhodecode/public/js/scripts.js:40797
4089 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:27
4129 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:33
4090 #: rhodecode/public/js/src/rhodecode/codemirror.js:354
4130 #: rhodecode/public/js/src/rhodecode/codemirror.js:354
4091 msgid "Note Comment"
4131 msgid "Note Comment"
4092 msgstr ""
4132 msgstr ""
4093
4133
4094 #: rhodecode/public/js/rhodecode-components.js:49315
4134 #: rhodecode/public/js/rhodecode-components.js:49315
4095 #: rhodecode/public/js/scripts.js:41163
4135 #: rhodecode/public/js/scripts.js:41163
4096 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:68
4136 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:77
4097 #: rhodecode/public/js/src/rhodecode/comments.js:125
4137 #: rhodecode/public/js/src/rhodecode/comments.js:125
4098 msgid "resolve comment"
4138 msgid "resolve comment"
4099 msgstr ""
4139 msgstr ""
4100
4140
4101 #: rhodecode/public/js/rhodecode-components.js:49399
4141 #: rhodecode/public/js/rhodecode-components.js:49399
4102 #: rhodecode/public/js/scripts.js:41247
4142 #: rhodecode/public/js/scripts.js:41247
4103 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:46
4143 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:53
4104 #: rhodecode/public/js/src/rhodecode/comments.js:209
4144 #: rhodecode/public/js/src/rhodecode/comments.js:209
4105 msgid "Status Review"
4145 msgid "Status Review"
4106 msgstr ""
4146 msgstr ""
4107
4147
4108 #: rhodecode/public/js/rhodecode-components.js:49414
4148 #: rhodecode/public/js/rhodecode-components.js:49414
4109 #: rhodecode/public/js/scripts.js:41262
4149 #: rhodecode/public/js/scripts.js:41262
4110 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:5
4150 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:10
4111 #: rhodecode/public/js/src/rhodecode/comments.js:224
4151 #: rhodecode/public/js/src/rhodecode/comments.js:224
4112 msgid "Comment text will be set automatically based on currently selected status ({0}) ..."
4152 msgid "Comment text will be set automatically based on currently selected status ({0}) ..."
4113 msgstr ""
4153 msgstr ""
4114
4154
4115 #: rhodecode/public/js/rhodecode-components.js:49571
4155 #: rhodecode/public/js/rhodecode-components.js:49571
4116 #: rhodecode/public/js/scripts.js:41419
4156 #: rhodecode/public/js/scripts.js:41419
4117 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:48
4157 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:55
4118 #: rhodecode/public/js/src/rhodecode/comments.js:381
4158 #: rhodecode/public/js/src/rhodecode/comments.js:381
4119 msgid "Submitting..."
4159 msgid "Submitting..."
4120 msgstr ""
4160 msgstr ""
4121
4161
4122 #: rhodecode/public/js/rhodecode-components.js:49622
4162 #: rhodecode/public/js/rhodecode-components.js:49622
4123 #: rhodecode/public/js/scripts.js:41470
4163 #: rhodecode/public/js/scripts.js:41470
4124 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:12
4164 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:18
4125 #: rhodecode/public/js/src/rhodecode/comments.js:432
4165 #: rhodecode/public/js/src/rhodecode/comments.js:432
4126 #: rhodecode/templates/files/files_browser_tree.mako:51
4166 #: rhodecode/templates/files/files_browser_tree.mako:51
4127 msgid "Loading ..."
4167 msgid "Loading ..."
@@ -4129,105 +4169,170 b' msgstr ""'
4129
4169
4130 #: rhodecode/public/js/rhodecode-components.js:49727
4170 #: rhodecode/public/js/rhodecode-components.js:49727
4131 #: rhodecode/public/js/scripts.js:41575
4171 #: rhodecode/public/js/scripts.js:41575
4132 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:6
4172 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:12
4133 #: rhodecode/public/js/src/rhodecode/comments.js:537
4173 #: rhodecode/public/js/src/rhodecode/comments.js:537
4134 msgid "Delete this comment?"
4174 msgid "Delete this comment?"
4135 msgstr ""
4175 msgstr ""
4136
4176
4137 #: rhodecode/public/js/rhodecode-components.js:49798
4177 #: rhodecode/public/js/rhodecode-components.js:49798
4138 #: rhodecode/public/js/scripts.js:41646
4178 #: rhodecode/public/js/scripts.js:41646
4139 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:11
4179 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:17
4140 #: rhodecode/public/js/src/rhodecode/comments.js:608
4180 #: rhodecode/public/js/src/rhodecode/comments.js:608
4141 msgid "Leave a comment, or click resolve button to resolve TODO comment #{0}"
4181 msgid "Leave a comment, or click resolve button to resolve TODO comment #{0}"
4142 msgstr ""
4182 msgstr ""
4143
4183
4144 #: rhodecode/public/js/rhodecode-components.js:49875
4184 #: rhodecode/public/js/rhodecode-components.js:49875
4145 #: rhodecode/public/js/scripts.js:41723
4185 #: rhodecode/public/js/scripts.js:41723
4146 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:10
4186 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:16
4147 #: rhodecode/public/js/src/rhodecode/comments.js:685
4187 #: rhodecode/public/js/src/rhodecode/comments.js:685
4148 msgid "Leave a comment on line {0}."
4188 msgid "Leave a comment on line {0}."
4149 msgstr ""
4189 msgstr ""
4150
4190
4151 #: rhodecode/public/js/rhodecode-components.js:49989
4191 #: rhodecode/public/js/rhodecode-components.js:49989
4152 #: rhodecode/public/js/scripts.js:41837
4192 #: rhodecode/public/js/scripts.js:41837
4153 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:52
4193 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:59
4154 #: rhodecode/public/js/src/rhodecode/comments.js:799
4194 #: rhodecode/public/js/src/rhodecode/comments.js:799
4155 msgid "TODO from comment {0} was fixed."
4195 msgid "TODO from comment {0} was fixed."
4156 msgstr ""
4196 msgstr ""
4157
4197
4158 #: rhodecode/public/js/rhodecode-components.js:50195
4198 #: rhodecode/public/js/rhodecode-components.js:50195
4159 #: rhodecode/public/js/scripts.js:42043
4199 #: rhodecode/public/js/scripts.js:42043
4160 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:72
4200 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:81
4161 #: rhodecode/public/js/src/rhodecode/files.js:150
4201 #: rhodecode/public/js/src/rhodecode/files.js:150
4162 msgid "truncated result"
4202 msgid "truncated result"
4163 msgstr ""
4203 msgstr ""
4164
4204
4165 #: rhodecode/public/js/rhodecode-components.js:50197
4205 #: rhodecode/public/js/rhodecode-components.js:50197
4166 #: rhodecode/public/js/scripts.js:42045
4206 #: rhodecode/public/js/scripts.js:42045
4167 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:73
4207 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:82
4168 #: rhodecode/public/js/src/rhodecode/files.js:152
4208 #: rhodecode/public/js/src/rhodecode/files.js:152
4169 msgid "truncated results"
4209 msgid "truncated results"
4170 msgstr ""
4210 msgstr ""
4171
4211
4172 #: rhodecode/public/js/rhodecode-components.js:50206
4212 #: rhodecode/public/js/rhodecode-components.js:50206
4173 #: rhodecode/public/js/scripts.js:42054
4213 #: rhodecode/public/js/scripts.js:42054
4174 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:19
4214 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:25
4175 #: rhodecode/public/js/src/rhodecode/files.js:161
4215 #: rhodecode/public/js/src/rhodecode/files.js:161
4176 msgid "No matching files"
4216 msgid "No matching files"
4177 msgstr ""
4217 msgstr ""
4178
4218
4179 #: rhodecode/public/js/rhodecode-components.js:50341
4219 #: rhodecode/public/js/rhodecode-components.js:50341
4180 #: rhodecode/public/js/scripts.js:42189
4220 #: rhodecode/public/js/scripts.js:42189
4181 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:37
4221 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:44
4182 #: rhodecode/public/js/src/rhodecode/files.js:296
4222 #: rhodecode/public/js/src/rhodecode/files.js:296
4183 msgid "Selection link"
4223 msgid "Selection link"
4184 msgstr ""
4224 msgstr ""
4185
4225
4186 #: rhodecode/public/js/rhodecode-components.js:50381
4226 #: rhodecode/public/js/rhodecode-components.js:50381
4187 #: rhodecode/public/js/scripts.js:42229
4227 #: rhodecode/public/js/scripts.js:42229
4188 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:47
4228 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:54
4189 #: rhodecode/public/js/src/rhodecode/followers.js:26
4229 #: rhodecode/public/js/src/rhodecode/followers.js:26
4190 msgid "Stop following this repository"
4230 msgid "Stop following this repository"
4191 msgstr ""
4231 msgstr ""
4192
4232
4193 #: rhodecode/public/js/rhodecode-components.js:50382
4233 #: rhodecode/public/js/rhodecode-components.js:50382
4194 #: rhodecode/public/js/scripts.js:42230
4234 #: rhodecode/public/js/scripts.js:42230
4195 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:54
4235 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:61
4196 #: rhodecode/public/js/src/rhodecode/followers.js:27
4236 #: rhodecode/public/js/src/rhodecode/followers.js:27
4197 msgid "Unfollow"
4237 msgid "Unfollow"
4198 msgstr ""
4238 msgstr ""
4199
4239
4200 #: rhodecode/public/js/rhodecode-components.js:50391
4240 #: rhodecode/public/js/rhodecode-components.js:50391
4201 #: rhodecode/public/js/scripts.js:42239
4241 #: rhodecode/public/js/scripts.js:42239
4202 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:45
4242 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:52
4203 #: rhodecode/public/js/src/rhodecode/followers.js:36
4243 #: rhodecode/public/js/src/rhodecode/followers.js:36
4204 msgid "Start following this repository"
4244 msgid "Start following this repository"
4205 msgstr ""
4245 msgstr ""
4206
4246
4207 #: rhodecode/public/js/rhodecode-components.js:50392
4247 #: rhodecode/public/js/rhodecode-components.js:50392
4208 #: rhodecode/public/js/scripts.js:42240
4248 #: rhodecode/public/js/scripts.js:42240
4209 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:8
4249 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:14
4210 #: rhodecode/public/js/src/rhodecode/followers.js:37
4250 #: rhodecode/public/js/src/rhodecode/followers.js:37
4211 msgid "Follow"
4251 msgid "Follow"
4212 msgstr ""
4252 msgstr ""
4213
4253
4214 #: rhodecode/public/js/rhodecode-components.js:50849
4254 #: rhodecode/public/js/rhodecode-components.js:50770
4215 #: rhodecode/public/js/scripts.js:42697
4255 #: rhodecode/public/js/rhodecode-components.js:50779
4216 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:58
4256 #: rhodecode/public/js/scripts.js:42618 rhodecode/public/js/scripts.js:42627
4217 #: rhodecode/public/js/src/rhodecode/pullrequests.js:213
4257 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:5
4258 #: rhodecode/public/js/src/rhodecode/pullrequests.js:134
4259 #: rhodecode/public/js/src/rhodecode/pullrequests.js:143
4260 msgid "All reviewers must vote."
4261 msgstr ""
4262
4263 #: rhodecode/public/js/rhodecode-components.js:50784
4264 #: rhodecode/public/js/scripts.js:42632
4265 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:6
4266 #: rhodecode/public/js/src/rhodecode/pullrequests.js:148
4267 msgid "At least {0} reviewer must vote."
4268 msgstr ""
4269
4270 #: rhodecode/public/js/rhodecode-components.js:50790
4271 #: rhodecode/public/js/scripts.js:42638
4272 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:7
4273 #: rhodecode/public/js/src/rhodecode/pullrequests.js:154
4274 msgid "At least {0} reviewers must vote."
4275 msgstr ""
4276
4277 #: rhodecode/public/js/rhodecode-components.js:50797
4278 #: rhodecode/public/js/scripts.js:42645
4279 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:41
4280 #: rhodecode/public/js/src/rhodecode/pullrequests.js:161
4281 msgid "Reviewers picked from source code changes."
4282 msgstr ""
4283
4284 #: rhodecode/public/js/rhodecode-components.js:50804
4285 #: rhodecode/public/js/scripts.js:42652
4286 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:4
4287 #: rhodecode/public/js/src/rhodecode/pullrequests.js:168
4288 msgid "Adding new reviewers is forbidden."
4289 msgstr ""
4290
4291 #: rhodecode/public/js/rhodecode-components.js:50811
4292 #: rhodecode/public/js/scripts.js:42659
4293 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:8
4294 #: rhodecode/public/js/src/rhodecode/pullrequests.js:175
4295 msgid "Author is not allowed to be a reviewer."
4296 msgstr ""
4297
4298 #: rhodecode/public/js/rhodecode-components.js:50825
4299 #: rhodecode/public/js/scripts.js:42673
4300 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:11
4301 #: rhodecode/public/js/src/rhodecode/pullrequests.js:189
4302 msgid "Commit Authors are not allowed to be a reviewer."
4303 msgstr ""
4304
4305 #: rhodecode/public/js/rhodecode-components.js:50972
4306 #: rhodecode/public/js/scripts.js:42820
4307 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:63
4308 #: rhodecode/public/js/src/rhodecode/pullrequests.js:336
4309 msgid "User `{0}` not allowed to be a reviewer"
4310 msgstr ""
4311
4312 #: rhodecode/public/js/rhodecode-components.js:51099
4313 #: rhodecode/public/js/scripts.js:42947
4314 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:66
4315 #: rhodecode/public/js/src/rhodecode/pullrequests.js:463
4218 msgid "added manually by \"{0}\""
4316 msgid "added manually by \"{0}\""
4219 msgstr ""
4317 msgstr ""
4220
4318
4221 #: rhodecode/public/js/rhodecode-components.js:51420
4319 #: rhodecode/public/js/rhodecode-components.js:51101
4222 #: rhodecode/public/js/scripts.js:43268
4320 #: rhodecode/public/js/scripts.js:42949
4223 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:61
4321 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:76
4322 #: rhodecode/public/js/src/rhodecode/pullrequests.js:465
4323 msgid "member of \"{0}\""
4324 msgstr ""
4325
4326 #: rhodecode/public/js/rhodecode-components.js:51682
4327 #: rhodecode/public/js/scripts.js:43530
4328 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:69
4224 #: rhodecode/public/js/src/rhodecode.js:142
4329 #: rhodecode/public/js/src/rhodecode.js:142
4225 msgid "file"
4330 msgid "file"
4226 msgstr ""
4331 msgstr ""
4227
4332
4228 #: rhodecode/public/js/rhodecode-components.js:51440
4333 #: rhodecode/public/js/rhodecode-components.js:51702
4229 #: rhodecode/public/js/scripts.js:43288
4334 #: rhodecode/public/js/scripts.js:43550
4230 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:42
4335 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:49
4231 #: rhodecode/public/js/src/rhodecode.js:162
4336 #: rhodecode/public/js/src/rhodecode.js:162
4232 msgid "Show more"
4337 msgid "Show more"
4233 msgstr ""
4338 msgstr ""
@@ -4242,150 +4347,150 b' msgstr ""'
4242 msgid "Add another comment"
4347 msgid "Add another comment"
4243 msgstr ""
4348 msgstr ""
4244
4349
4245 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:4
4350 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:9
4246 #: rhodecode/public/js/src/i18n_messages.js:5
4351 #: rhodecode/public/js/src/i18n_messages.js:5
4247 #: rhodecode/templates/pullrequests/pullrequest_show.mako:325
4352 #: rhodecode/templates/pullrequests/pullrequest_show.mako:325
4248 msgid "Close"
4353 msgid "Close"
4249 msgstr ""
4354 msgstr ""
4250
4355
4251 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:7
4356 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:13
4252 msgid "Diff to Commit "
4357 msgid "Diff to Commit "
4253 msgstr ""
4358 msgstr ""
4254
4359
4255 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:9
4256 #: rhodecode/public/js/src/i18n_messages.js:4
4257 msgid "Invite reviewers to this discussion"
4258 msgstr ""
4259
4260 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:15
4360 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:15
4261 msgid "No bookmarks available yet."
4361 #: rhodecode/public/js/src/i18n_messages.js:4
4262 msgstr ""
4362 msgid "Invite reviewers to this discussion"
4263
4264 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:16
4265 msgid "No branches available yet."
4266 msgstr ""
4267
4268 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:17
4269 msgid "No gists available yet."
4270 msgstr ""
4271
4272 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:20
4273 msgid "No pull requests available yet."
4274 msgstr ""
4363 msgstr ""
4275
4364
4276 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:21
4365 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:21
4277 msgid "No repositories available yet."
4366 msgid "No bookmarks available yet."
4278 msgstr ""
4367 msgstr ""
4279
4368
4280 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:22
4369 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:22
4281 msgid "No repository groups available yet."
4370 msgid "No branches available yet."
4282 msgstr ""
4371 msgstr ""
4283
4372
4284 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:24
4373 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:23
4285 msgid "No tags available yet."
4374 msgid "No gists available yet."
4286 msgstr ""
4287
4288 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:25
4289 msgid "No user groups available yet."
4290 msgstr ""
4375 msgstr ""
4291
4376
4292 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:26
4377 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:26
4293 msgid "No users available yet."
4378 msgid "No pull requests available yet."
4294 msgstr ""
4379 msgstr ""
4295
4380
4296 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:29
4381 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:27
4297 #: rhodecode/templates/changelog/changelog.mako:61
4382 msgid "No repositories available yet."
4298 msgid "Open new pull request"
4383 msgstr ""
4384
4385 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:28
4386 msgid "No repository groups available yet."
4299 msgstr ""
4387 msgstr ""
4300
4388
4301 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:30
4389 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:30
4302 msgid "Open new pull request for selected commit"
4390 msgid "No tags available yet."
4391 msgstr ""
4392
4393 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:31
4394 msgid "No user groups available yet."
4395 msgstr ""
4396
4397 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:32
4398 msgid "No users available yet."
4303 msgstr ""
4399 msgstr ""
4304
4400
4305 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:35
4401 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:35
4402 #: rhodecode/templates/changelog/changelog.mako:61
4403 msgid "Open new pull request"
4404 msgstr ""
4405
4406 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:36
4407 msgid "Open new pull request for selected commit"
4408 msgstr ""
4409
4410 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:42
4306 msgid "Saving..."
4411 msgid "Saving..."
4307 msgstr ""
4412 msgstr ""
4308
4413
4309 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:38
4414 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:45
4310 #: rhodecode/public/js/src/i18n_messages.js:6
4415 #: rhodecode/public/js/src/i18n_messages.js:6
4311 #: rhodecode/templates/admin/settings/settings_email.mako:48
4416 #: rhodecode/templates/admin/settings/settings_email.mako:48
4312 msgid "Send"
4417 msgid "Send"
4313 msgstr ""
4418 msgstr ""
4314
4419
4315 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:41
4420 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:48
4316 msgid "Show at Commit "
4421 msgid "Show at Commit "
4317 msgstr ""
4422 msgstr ""
4318
4423
4319 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:43
4320 msgid "Show selected commit __S"
4321 msgstr ""
4322
4323 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:44
4324 msgid "Show selected commits __S ... __E"
4325 msgstr ""
4326
4327 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:49
4328 #: rhodecode/public/js/src/i18n_messages.js:7
4329 msgid "Switch to chat"
4330 msgstr ""
4331
4332 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:50
4424 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:50
4425 msgid "Show selected commit __S"
4426 msgstr ""
4427
4428 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:51
4429 msgid "Show selected commits __S ... __E"
4430 msgstr ""
4431
4432 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:56
4433 #: rhodecode/public/js/src/i18n_messages.js:7
4434 msgid "Switch to chat"
4435 msgstr ""
4436
4437 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:57
4333 #: rhodecode/public/js/src/i18n_messages.js:8
4438 #: rhodecode/public/js/src/i18n_messages.js:8
4334 msgid "Switch to comment"
4439 msgid "Switch to comment"
4335 msgstr ""
4440 msgstr ""
4336
4441
4337 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:53
4338 msgid "There are currently no open pull requests requiring your participation."
4339 msgstr ""
4340
4341 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:55
4342 msgid "Updating..."
4343 msgstr ""
4344
4345 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:59
4346 #: rhodecode/templates/admin/auth/auth_settings.mako:71
4347 msgid "disabled"
4348 msgstr ""
4349
4350 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:60
4442 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:60
4351 #: rhodecode/templates/admin/auth/auth_settings.mako:71
4443 msgid "There are currently no open pull requests requiring your participation."
4352 msgid "enabled"
4353 msgstr ""
4444 msgstr ""
4354
4445
4355 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:62
4446 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:62
4356 msgid "files"
4447 msgid "Updating..."
4357 msgstr ""
4448 msgstr ""
4358
4449
4359 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:67
4450 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:67
4360 #: rhodecode/templates/pullrequests/pullrequest.mako:108
4451 #: rhodecode/templates/admin/auth/auth_settings.mako:71
4452 msgid "disabled"
4453 msgstr ""
4454
4455 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:68
4456 #: rhodecode/templates/admin/auth/auth_settings.mako:71
4457 msgid "enabled"
4458 msgstr ""
4459
4460 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:70
4461 msgid "files"
4462 msgstr ""
4463
4464 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:75
4465 #: rhodecode/templates/pullrequests/pullrequest.mako:140
4361 msgid "loading..."
4466 msgid "loading..."
4362 msgstr ""
4467 msgstr ""
4363
4468
4364 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:71
4469 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:80
4365 msgid "specify commit"
4470 msgid "specify commit"
4366 msgstr ""
4471 msgstr ""
4367
4472
4368 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:74
4473 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:83
4369 msgid "{0} active out of {1} users"
4474 msgid "{0} active out of {1} users"
4370 msgstr ""
4475 msgstr ""
4371
4476
4372 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:79
4477 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:88
4373 msgid "{0} days"
4478 msgid "{0} days"
4374 msgstr ""
4479 msgstr ""
4375
4480
4376 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:81
4481 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:90
4377 msgid "{0} hours"
4482 msgid "{0} hours"
4378 msgstr ""
4483 msgstr ""
4379
4484
4380 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:84
4485 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:93
4381 msgid "{0} months"
4486 msgid "{0} months"
4382 msgstr ""
4487 msgstr ""
4383
4488
4384 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:85
4489 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:94
4385 msgid "{0} out of {1} users"
4490 msgid "{0} out of {1} users"
4386 msgstr ""
4491 msgstr ""
4387
4492
4388 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:89
4493 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:98
4389 msgid "{0} years"
4494 msgid "{0} years"
4390 msgstr ""
4495 msgstr ""
4391
4496
@@ -4467,7 +4572,7 b' msgstr ""'
4467 #: rhodecode/templates/admin/my_account/my_account_watched.mako:31
4572 #: rhodecode/templates/admin/my_account/my_account_watched.mako:31
4468 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:53
4573 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:53
4469 #: rhodecode/templates/admin/repos/repo_add_base.mako:9
4574 #: rhodecode/templates/admin/repos/repo_add_base.mako:9
4470 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:15
4575 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:16
4471 #: rhodecode/templates/admin/repos/repos.mako:54
4576 #: rhodecode/templates/admin/repos/repos.mako:54
4472 #: rhodecode/templates/admin/user_groups/user_groups.mako:55
4577 #: rhodecode/templates/admin/user_groups/user_groups.mako:55
4473 #: rhodecode/templates/admin/users/user_edit_groups.mako:54
4578 #: rhodecode/templates/admin/users/user_edit_groups.mako:54
@@ -4475,7 +4580,7 b' msgstr ""'
4475 #: rhodecode/templates/bookmarks/bookmarks.mako:59
4580 #: rhodecode/templates/bookmarks/bookmarks.mako:59
4476 #: rhodecode/templates/branches/branches.mako:58
4581 #: rhodecode/templates/branches/branches.mako:58
4477 #: rhodecode/templates/files/files_browser_tree.mako:5
4582 #: rhodecode/templates/files/files_browser_tree.mako:5
4478 #: rhodecode/templates/pullrequests/pullrequests.mako:100
4583 #: rhodecode/templates/pullrequests/pullrequests.mako:110
4479 #: rhodecode/templates/tags/tags.mako:59
4584 #: rhodecode/templates/tags/tags.mako:59
4480 msgid "Name"
4585 msgid "Name"
4481 msgstr ""
4586 msgstr ""
@@ -4490,7 +4595,7 b' msgstr ""'
4490 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:56
4595 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:56
4491 #: rhodecode/templates/admin/repos/repo_add_base.mako:43
4596 #: rhodecode/templates/admin/repos/repo_add_base.mako:43
4492 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:29
4597 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:29
4493 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:98
4598 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:127
4494 #: rhodecode/templates/admin/repos/repos.mako:57
4599 #: rhodecode/templates/admin/repos/repos.mako:57
4495 #: rhodecode/templates/admin/user_groups/user_group_add.mako:43
4600 #: rhodecode/templates/admin/user_groups/user_group_add.mako:43
4496 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:42
4601 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:42
@@ -4498,9 +4603,10 b' msgstr ""'
4498 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:15
4603 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:15
4499 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:67
4604 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:67
4500 #: rhodecode/templates/admin/users/user_edit_groups.mako:59
4605 #: rhodecode/templates/admin/users/user_edit_groups.mako:59
4606 #: rhodecode/templates/admin/users/user_edit_ips.mako:12
4501 #: rhodecode/templates/base/issue_tracker_settings.mako:10
4607 #: rhodecode/templates/base/issue_tracker_settings.mako:10
4502 #: rhodecode/templates/changeset/changeset.mako:53
4608 #: rhodecode/templates/changeset/changeset.mako:73
4503 #: rhodecode/templates/compare/compare_commits.mako:20
4609 #: rhodecode/templates/compare/compare_commits.mako:21
4504 #: rhodecode/templates/email_templates/commit_comment.mako:89
4610 #: rhodecode/templates/email_templates/commit_comment.mako:89
4505 #: rhodecode/templates/email_templates/pull_request_review.mako:41
4611 #: rhodecode/templates/email_templates/pull_request_review.mako:41
4506 #: rhodecode/templates/email_templates/pull_request_review.mako:75
4612 #: rhodecode/templates/email_templates/pull_request_review.mako:75
@@ -4508,9 +4614,9 b' msgstr ""'
4508 #: rhodecode/templates/files/file_tree_detail.mako:12
4614 #: rhodecode/templates/files/file_tree_detail.mako:12
4509 #: rhodecode/templates/forks/fork.mako:48
4615 #: rhodecode/templates/forks/fork.mako:48
4510 #: rhodecode/templates/forks/forks_data.mako:9
4616 #: rhodecode/templates/forks/forks_data.mako:9
4511 #: rhodecode/templates/pullrequests/pullrequest.mako:47
4617 #: rhodecode/templates/pullrequests/pullrequest.mako:54
4512 #: rhodecode/templates/pullrequests/pullrequest_show.mako:163
4618 #: rhodecode/templates/pullrequests/pullrequest_show.mako:163
4513 #: rhodecode/templates/pullrequests/pullrequest_show.mako:460
4619 #: rhodecode/templates/pullrequests/pullrequest_show.mako:487
4514 #: rhodecode/templates/summary/components.mako:73
4620 #: rhodecode/templates/summary/components.mako:73
4515 msgid "Description"
4621 msgid "Description"
4516 msgstr ""
4622 msgstr ""
@@ -4521,7 +4627,7 b' msgstr ""'
4521 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:24
4627 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:24
4522 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:60
4628 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:60
4523 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:5
4629 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:5
4524 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:80
4630 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:109
4525 #: rhodecode/templates/admin/repos/repos.mako:65
4631 #: rhodecode/templates/admin/repos/repos.mako:65
4526 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:5
4632 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:5
4527 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:24
4633 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:24
@@ -4544,13 +4650,13 b' msgstr ""'
4544 #: rhodecode/templates/bookmarks/bookmarks.mako:66
4650 #: rhodecode/templates/bookmarks/bookmarks.mako:66
4545 #: rhodecode/templates/branches/branches.mako:65
4651 #: rhodecode/templates/branches/branches.mako:65
4546 #: rhodecode/templates/changelog/changelog.mako:107
4652 #: rhodecode/templates/changelog/changelog.mako:107
4547 #: rhodecode/templates/changelog/changelog_summary_data.mako:8
4548 #: rhodecode/templates/changeset/changeset.mako:36
4653 #: rhodecode/templates/changeset/changeset.mako:36
4549 #: rhodecode/templates/compare/compare_commits.mako:18
4654 #: rhodecode/templates/compare/compare_commits.mako:19
4550 #: rhodecode/templates/email_templates/commit_comment.mako:49
4655 #: rhodecode/templates/email_templates/commit_comment.mako:49
4551 #: rhodecode/templates/email_templates/commit_comment.mako:88
4656 #: rhodecode/templates/email_templates/commit_comment.mako:88
4552 #: rhodecode/templates/pullrequests/pullrequest_show.mako:458
4657 #: rhodecode/templates/pullrequests/pullrequest_show.mako:485
4553 #: rhodecode/templates/search/search_commit.mako:6
4658 #: rhodecode/templates/search/search_commit.mako:6
4659 #: rhodecode/templates/summary/summary_commits.mako:8
4554 #: rhodecode/templates/tags/tags.mako:66
4660 #: rhodecode/templates/tags/tags.mako:66
4555 msgid "Commit"
4661 msgid "Commit"
4556 msgstr ""
4662 msgstr ""
@@ -4565,7 +4671,7 b' msgid "Home"'
4565 msgstr ""
4671 msgstr ""
4566
4672
4567 #: rhodecode/templates/login.mako:5 rhodecode/templates/login.mako:35
4673 #: rhodecode/templates/login.mako:5 rhodecode/templates/login.mako:35
4568 #: rhodecode/templates/login.mako:75 rhodecode/templates/base/base.mako:329
4674 #: rhodecode/templates/login.mako:75 rhodecode/templates/base/base.mako:332
4569 #: rhodecode/templates/debug_style/login.html:60
4675 #: rhodecode/templates/debug_style/login.html:60
4570 msgid "Sign In"
4676 msgid "Sign In"
4571 msgstr ""
4677 msgstr ""
@@ -4588,13 +4694,13 b' msgstr ""'
4588
4694
4589 #: rhodecode/templates/login.mako:68 rhodecode/templates/password_reset.mako:37
4695 #: rhodecode/templates/login.mako:68 rhodecode/templates/password_reset.mako:37
4590 #: rhodecode/templates/base/base.mako:46
4696 #: rhodecode/templates/base/base.mako:46
4591 #: rhodecode/templates/errors/error_document.mako:64
4697 #: rhodecode/templates/errors/error_document.mako:63
4592 msgid "Support"
4698 msgid "Support"
4593 msgstr ""
4699 msgstr ""
4594
4700
4595 #: rhodecode/templates/login.mako:69 rhodecode/templates/password_reset.mako:38
4701 #: rhodecode/templates/login.mako:69 rhodecode/templates/password_reset.mako:38
4596 #: rhodecode/templates/files/files_add.mako:54
4702 #: rhodecode/templates/files/files_add.mako:54
4597 #: rhodecode/templates/files/files_add.mako:65
4703 #: rhodecode/templates/files/files_add.mako:71
4598 msgid "or"
4704 msgid "or"
4599 msgstr ""
4705 msgstr ""
4600
4706
@@ -4653,7 +4759,7 b' msgstr ""'
4653 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:79
4759 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:79
4654 #: rhodecode/templates/admin/users/user_add.mako:68
4760 #: rhodecode/templates/admin/users/user_add.mako:68
4655 #: rhodecode/templates/admin/users/user_edit_profile.mako:47
4761 #: rhodecode/templates/admin/users/user_edit_profile.mako:47
4656 #: rhodecode/templates/admin/users/users.mako:66
4762 #: rhodecode/templates/admin/users/users.mako:67
4657 msgid "First Name"
4763 msgid "First Name"
4658 msgstr ""
4764 msgstr ""
4659
4765
@@ -4663,7 +4769,7 b' msgstr ""'
4663 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:88
4769 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:88
4664 #: rhodecode/templates/admin/users/user_add.mako:77
4770 #: rhodecode/templates/admin/users/user_add.mako:77
4665 #: rhodecode/templates/admin/users/user_edit_profile.mako:56
4771 #: rhodecode/templates/admin/users/user_edit_profile.mako:56
4666 #: rhodecode/templates/admin/users/users.mako:68
4772 #: rhodecode/templates/admin/users/users.mako:69
4667 msgid "Last Name"
4773 msgid "Last Name"
4668 msgstr ""
4774 msgstr ""
4669
4775
@@ -4675,36 +4781,32 b' msgstr ""'
4675 msgid "Create Account"
4781 msgid "Create Account"
4676 msgstr ""
4782 msgstr ""
4677
4783
4678 #: rhodecode/templates/admin/admin.mako:5
4784 #: rhodecode/templates/admin/admin_audit_logs.mako:5
4679 #: rhodecode/templates/admin/admin.mako:15
4680 #: rhodecode/templates/base/base.mako:75
4785 #: rhodecode/templates/base/base.mako:75
4681 msgid "Admin journal"
4786 msgid "Admin audit logs"
4682 msgstr ""
4787 msgstr ""
4683
4788
4684 #: rhodecode/templates/admin/admin.mako:13
4789 #: rhodecode/templates/admin/admin_audit_logs.mako:13
4685 msgid "journal filter..."
4790 msgid "filter..."
4686 msgstr ""
4791 msgstr ""
4687
4792
4688 #: rhodecode/templates/admin/admin.mako:14
4793 #: rhodecode/templates/admin/admin_audit_logs.mako:14
4689 #: rhodecode/templates/admin/users/user_edit_audit.mako:15
4794 #: rhodecode/templates/admin/users/user_edit_audit.mako:15
4690 msgid "filter"
4795 msgid "filter"
4691 msgstr ""
4796 msgstr ""
4692
4797
4693 #: rhodecode/templates/admin/admin.mako:15
4798 #: rhodecode/templates/admin/admin_audit_logs.mako:15
4694 #: rhodecode/templates/journal/journal.mako:14
4799 msgid "Audit logs"
4695 #, python-format
4800 msgstr ""
4696 msgid "%s entry"
4801
4697 msgid_plural "%s entries"
4802 #: rhodecode/templates/admin/admin_audit_logs.mako:17
4698 msgstr[0] ""
4699 msgstr[1] ""
4700
4701 #: rhodecode/templates/admin/admin.mako:17
4702 #: rhodecode/templates/admin/users/user_edit_audit.mako:17
4803 #: rhodecode/templates/admin/users/user_edit_audit.mako:17
4703 #: rhodecode/templates/journal/journal.mako:17
4804 #: rhodecode/templates/journal/journal.mako:17
4805 #: rhodecode/templates/search/search.mako:76
4704 msgid "Example Queries"
4806 msgid "Example Queries"
4705 msgstr ""
4807 msgstr ""
4706
4808
4707 #: rhodecode/templates/admin/admin_log.mako:8
4809 #: rhodecode/templates/admin/admin_log_base.mako:7
4708 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:18
4810 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:18
4709 #: rhodecode/templates/admin/my_account/my_account_repos.mako:37
4811 #: rhodecode/templates/admin/my_account/my_account_repos.mako:37
4710 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:62
4812 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:62
@@ -4712,38 +4814,45 b' msgstr ""'
4712 #: rhodecode/templates/admin/repos/repos.mako:69
4814 #: rhodecode/templates/admin/repos/repos.mako:69
4713 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:71
4815 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:71
4714 #: rhodecode/templates/admin/user_groups/user_groups.mako:68
4816 #: rhodecode/templates/admin/user_groups/user_groups.mako:68
4715 #: rhodecode/templates/admin/users/user_edit_audit.mako:23
4716 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:18
4817 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:18
4717 #: rhodecode/templates/admin/users/user_edit_groups.mako:73
4818 #: rhodecode/templates/admin/users/user_edit_groups.mako:73
4718 #: rhodecode/templates/admin/users/users.mako:79
4819 #: rhodecode/templates/admin/users/users.mako:80
4719 #: rhodecode/templates/files/files_detail.mako:58
4820 #: rhodecode/templates/files/files_detail.mako:58
4720 msgid "Action"
4821 msgid "Action"
4721 msgstr ""
4822 msgstr ""
4722
4823
4723 #: rhodecode/templates/admin/admin_log.mako:9
4824 #: rhodecode/templates/admin/admin_log_base.mako:8
4825 msgid "Action Data"
4826 msgstr ""
4827
4828 #: rhodecode/templates/admin/admin_log_base.mako:9
4724 #: rhodecode/templates/admin/defaults/defaults.mako:31
4829 #: rhodecode/templates/admin/defaults/defaults.mako:31
4725 #: rhodecode/templates/admin/permissions/permissions_objects.mako:13
4830 #: rhodecode/templates/admin/permissions/permissions_objects.mako:13
4726 #: rhodecode/templates/admin/users/user_edit_audit.mako:24
4727 #: rhodecode/templates/search/search_commit.mako:5
4831 #: rhodecode/templates/search/search_commit.mako:5
4728 #: rhodecode/templates/search/search_path.mako:3
4832 #: rhodecode/templates/search/search_path.mako:3
4729 msgid "Repository"
4833 msgid "Repository"
4730 msgstr ""
4834 msgstr ""
4731
4835
4732 #: rhodecode/templates/admin/admin_log.mako:10
4836 #: rhodecode/templates/admin/admin_log_base.mako:10
4733 #: rhodecode/templates/admin/users/user_edit_audit.mako:25
4734 #: rhodecode/templates/bookmarks/bookmarks.mako:61
4837 #: rhodecode/templates/bookmarks/bookmarks.mako:61
4735 #: rhodecode/templates/branches/branches.mako:60
4838 #: rhodecode/templates/branches/branches.mako:60
4736 #: rhodecode/templates/tags/tags.mako:61
4839 #: rhodecode/templates/tags/tags.mako:61
4737 msgid "Date"
4840 msgid "Date"
4738 msgstr ""
4841 msgstr ""
4739
4842
4740 #: rhodecode/templates/admin/admin_log.mako:11
4843 #: rhodecode/templates/admin/admin_log_base.mako:11
4741 #: rhodecode/templates/admin/users/user_edit_audit.mako:26
4844 msgid "IP"
4742 msgid "From IP"
4845 msgstr ""
4743 msgstr ""
4846
4744
4847 #: rhodecode/templates/admin/admin_log_base.mako:38
4745 #: rhodecode/templates/admin/admin_log.mako:46
4848 msgid "toggle"
4746 #: rhodecode/templates/admin/users/user_edit_audit.mako:61
4849 msgstr ""
4850
4851 #: rhodecode/templates/admin/admin_log_base.mako:43
4852 msgid "data not available for v1 entries type"
4853 msgstr ""
4854
4855 #: rhodecode/templates/admin/admin_log_base.mako:64
4747 msgid "No actions yet"
4856 msgid "No actions yet"
4748 msgstr ""
4857 msgstr ""
4749
4858
@@ -4784,8 +4893,8 b' msgstr ""'
4784 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:67
4893 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:67
4785 #: rhodecode/templates/admin/repos/repo_add_base.mako:101
4894 #: rhodecode/templates/admin/repos/repo_add_base.mako:101
4786 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:79
4895 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:79
4787 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:110
4896 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:109
4788 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:160
4897 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:194
4789 #: rhodecode/templates/admin/settings/settings_hooks.mako:63
4898 #: rhodecode/templates/admin/settings/settings_hooks.mako:63
4790 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:15
4899 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:15
4791 #: rhodecode/templates/admin/user_groups/user_group_add.mako:60
4900 #: rhodecode/templates/admin/user_groups/user_group_add.mako:60
@@ -4826,7 +4935,7 b' msgstr ""'
4826
4935
4827 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:27
4936 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:27
4828 #: rhodecode/templates/admin/repos/repo_add_base.mako:97
4937 #: rhodecode/templates/admin/repos/repo_add_base.mako:97
4829 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:112
4938 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:143
4830 #: rhodecode/templates/forks/fork.mako:87
4939 #: rhodecode/templates/forks/fork.mako:87
4831 msgid "Private repositories are only visible to people explicitly added as collaborators."
4940 msgid "Private repositories are only visible to people explicitly added as collaborators."
4832 msgstr ""
4941 msgstr ""
@@ -4877,7 +4986,7 b' msgstr ""'
4877
4986
4878 #: rhodecode/templates/admin/gists/edit.mako:56
4987 #: rhodecode/templates/admin/gists/edit.mako:56
4879 #: rhodecode/templates/admin/gists/new.mako:50
4988 #: rhodecode/templates/admin/gists/new.mako:50
4880 #: rhodecode/templates/files/files_add.mako:74
4989 #: rhodecode/templates/files/files_add.mako:80
4881 #: rhodecode/templates/files/files_edit.mako:78
4990 #: rhodecode/templates/files/files_edit.mako:78
4882 msgid "plain"
4991 msgid "plain"
4883 msgstr ""
4992 msgstr ""
@@ -4888,9 +4997,9 b' msgstr ""'
4888
4997
4889 #: rhodecode/templates/admin/gists/edit.mako:102
4998 #: rhodecode/templates/admin/gists/edit.mako:102
4890 #: rhodecode/templates/base/issue_tracker_settings.mako:73
4999 #: rhodecode/templates/base/issue_tracker_settings.mako:73
4891 #: rhodecode/templates/changeset/changeset_file_comment.mako:385
5000 #: rhodecode/templates/changeset/changeset_file_comment.mako:390
4892 #: rhodecode/templates/codeblocks/diffs.mako:76
5001 #: rhodecode/templates/codeblocks/diffs.mako:76
4893 #: rhodecode/templates/files/files_add.mako:102
5002 #: rhodecode/templates/files/files_add.mako:108
4894 #: rhodecode/templates/files/files_delete.mako:69
5003 #: rhodecode/templates/files/files_delete.mako:69
4895 #: rhodecode/templates/files/files_edit.mako:105
5004 #: rhodecode/templates/files/files_edit.mako:105
4896 #: rhodecode/templates/pullrequests/pullrequest_show.mako:64
5005 #: rhodecode/templates/pullrequests/pullrequest_show.mako:64
@@ -4957,14 +5066,13 b' msgstr ""'
4957 #: rhodecode/templates/bookmarks/bookmarks.mako:63
5066 #: rhodecode/templates/bookmarks/bookmarks.mako:63
4958 #: rhodecode/templates/branches/branches.mako:62
5067 #: rhodecode/templates/branches/branches.mako:62
4959 #: rhodecode/templates/changelog/changelog.mako:113
5068 #: rhodecode/templates/changelog/changelog.mako:113
4960 #: rhodecode/templates/changelog/changelog_summary_data.mako:11
5069 #: rhodecode/templates/changeset/changeset.mako:200
4961 #: rhodecode/templates/changeset/changeset.mako:180
5070 #: rhodecode/templates/compare/compare_commits.mako:18
4962 #: rhodecode/templates/compare/compare_commits.mako:17
4963 #: rhodecode/templates/files/files_browser_tree.mako:9
5071 #: rhodecode/templates/files/files_browser_tree.mako:9
4964 #: rhodecode/templates/pullrequests/pullrequest_show.mako:309
5072 #: rhodecode/templates/pullrequests/pullrequest_show.mako:484
4965 #: rhodecode/templates/pullrequests/pullrequest_show.mako:457
5073 #: rhodecode/templates/pullrequests/pullrequests.mako:112
4966 #: rhodecode/templates/pullrequests/pullrequests.mako:102
4967 #: rhodecode/templates/search/search_commit.mako:16
5074 #: rhodecode/templates/search/search_commit.mako:16
5075 #: rhodecode/templates/summary/summary_commits.mako:11
4968 #: rhodecode/templates/tags/tags.mako:63
5076 #: rhodecode/templates/tags/tags.mako:63
4969 msgid "Author"
5077 msgid "Author"
4970 msgstr ""
5078 msgstr ""
@@ -5025,12 +5133,12 b' msgstr ""'
5025 #: rhodecode/templates/data_table/_dt_elements.mako:193
5133 #: rhodecode/templates/data_table/_dt_elements.mako:193
5026 #: rhodecode/templates/data_table/_dt_elements.mako:206
5134 #: rhodecode/templates/data_table/_dt_elements.mako:206
5027 #: rhodecode/templates/debug_style/buttons.html:128
5135 #: rhodecode/templates/debug_style/buttons.html:128
5028 #: rhodecode/templates/files/files_add.mako:204
5136 #: rhodecode/templates/files/files_add.mako:208
5029 #: rhodecode/templates/files/files_edit.mako:165
5137 #: rhodecode/templates/files/files_edit.mako:165
5030 #: rhodecode/templates/files/files_source.mako:48
5138 #: rhodecode/templates/files/files_source.mako:48
5031 #: rhodecode/templates/files/files_source.mako:51
5139 #: rhodecode/templates/files/files_source.mako:51
5032 #: rhodecode/templates/pullrequests/pullrequest_show.mako:63
5140 #: rhodecode/templates/pullrequests/pullrequest_show.mako:63
5033 #: rhodecode/templates/pullrequests/pullrequest_show.mako:324
5141 #: rhodecode/templates/pullrequests/pullrequest_show.mako:339
5034 #: rhodecode/templates/users/user_profile.mako:7
5142 #: rhodecode/templates/users/user_profile.mako:7
5035 msgid "Edit"
5143 msgid "Edit"
5036 msgstr ""
5144 msgstr ""
@@ -5071,10 +5179,10 b' msgstr ""'
5071 #: rhodecode/templates/admin/integrations/new.mako:21
5179 #: rhodecode/templates/admin/integrations/new.mako:21
5072 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:48
5180 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:48
5073 #: rhodecode/templates/admin/repos/repo_edit.mako:15
5181 #: rhodecode/templates/admin/repos/repo_edit.mako:15
5074 #: rhodecode/templates/admin/repos/repo_edit.mako:43
5182 #: rhodecode/templates/admin/repos/repo_edit.mako:46
5075 #: rhodecode/templates/admin/settings/settings.mako:14
5183 #: rhodecode/templates/admin/settings/settings.mako:14
5076 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:33
5184 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:33
5077 #: rhodecode/templates/base/base.mako:84 rhodecode/templates/base/base.mako:249
5185 #: rhodecode/templates/base/base.mako:84 rhodecode/templates/base/base.mako:251
5078 msgid "Settings"
5186 msgid "Settings"
5079 msgstr ""
5187 msgstr ""
5080
5188
@@ -5177,7 +5285,7 b' msgid "No description available"'
5177 msgstr ""
5285 msgstr ""
5178
5286
5179 #: rhodecode/templates/admin/my_account/my_account.mako:5
5287 #: rhodecode/templates/admin/my_account/my_account.mako:5
5180 #: rhodecode/templates/base/base.mako:343
5288 #: rhodecode/templates/base/base.mako:346
5181 msgid "My account"
5289 msgid "My account"
5182 msgstr ""
5290 msgstr ""
5183
5291
@@ -5201,7 +5309,7 b' msgid "OAuth Identities"'
5201 msgstr ""
5309 msgstr ""
5202
5310
5203 #: rhodecode/templates/admin/my_account/my_account.mako:37
5311 #: rhodecode/templates/admin/my_account/my_account.mako:37
5204 #: rhodecode/templates/admin/users/user_edit.mako:38
5312 #: rhodecode/templates/admin/users/user_edit.mako:43
5205 msgid "Emails"
5313 msgid "Emails"
5206 msgstr ""
5314 msgstr ""
5207
5315
@@ -5218,7 +5326,7 b' msgstr ""'
5218 #: rhodecode/templates/admin/my_account/my_account.mako:41
5326 #: rhodecode/templates/admin/my_account/my_account.mako:41
5219 #: rhodecode/templates/admin/permissions/permissions.mako:14
5327 #: rhodecode/templates/admin/permissions/permissions.mako:14
5220 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:49
5328 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:49
5221 #: rhodecode/templates/admin/repos/repo_edit.mako:46
5329 #: rhodecode/templates/admin/repos/repo_edit.mako:49
5222 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:34
5330 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:34
5223 #: rhodecode/templates/base/base.mako:80
5331 #: rhodecode/templates/base/base.mako:80
5224 msgid "Permissions"
5332 msgid "Permissions"
@@ -5275,7 +5383,7 b' msgstr ""'
5275 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:65
5383 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:65
5276 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:82
5384 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:82
5277 #: rhodecode/templates/admin/users/user_edit_emails.mako:62
5385 #: rhodecode/templates/admin/users/user_edit_emails.mako:62
5278 #: rhodecode/templates/admin/users/user_edit_ips.mako:69
5386 #: rhodecode/templates/admin/users/user_edit_ips.mako:70
5279 msgid "Add"
5387 msgid "Add"
5280 msgstr ""
5388 msgstr ""
5281
5389
@@ -5294,9 +5402,7 b' msgid "Primary"'
5294 msgstr ""
5402 msgstr ""
5295
5403
5296 #: rhodecode/templates/admin/my_account/my_account_emails.mako:31
5404 #: rhodecode/templates/admin/my_account/my_account_emails.mako:31
5297 #: rhodecode/templates/admin/users/user_edit_emails.mako:30
5405 msgid "Confirm to delete this email: {}"
5298 #, python-format
5299 msgid "Confirm to delete this email: %s"
5300 msgstr ""
5406 msgstr ""
5301
5407
5302 #: rhodecode/templates/admin/my_account/my_account_emails.mako:42
5408 #: rhodecode/templates/admin/my_account/my_account_emails.mako:42
@@ -5380,13 +5486,13 b' msgstr ""'
5380 #: rhodecode/templates/admin/settings/settings_global.mako:9
5486 #: rhodecode/templates/admin/settings/settings_global.mako:9
5381 #: rhodecode/templates/email_templates/pull_request_review.mako:39
5487 #: rhodecode/templates/email_templates/pull_request_review.mako:39
5382 #: rhodecode/templates/email_templates/pull_request_review.mako:72
5488 #: rhodecode/templates/email_templates/pull_request_review.mako:72
5383 #: rhodecode/templates/pullrequests/pullrequest.mako:38
5489 #: rhodecode/templates/pullrequests/pullrequest.mako:45
5384 #: rhodecode/templates/pullrequests/pullrequests.mako:104
5490 #: rhodecode/templates/pullrequests/pullrequests.mako:114
5385 msgid "Title"
5491 msgid "Title"
5386 msgstr ""
5492 msgstr ""
5387
5493
5388 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:47
5494 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:47
5389 #: rhodecode/templates/pullrequests/pullrequests.mako:108
5495 #: rhodecode/templates/pullrequests/pullrequests.mako:118
5390 msgid "Last Update"
5496 msgid "Last Update"
5391 msgstr ""
5497 msgstr ""
5392
5498
@@ -5404,7 +5510,7 b' msgid "My Notifications"'
5404 msgstr ""
5510 msgstr ""
5405
5511
5406 #: rhodecode/templates/admin/notifications/notifications.mako:32
5512 #: rhodecode/templates/admin/notifications/notifications.mako:32
5407 #: rhodecode/templates/changeset/changeset.mako:140
5513 #: rhodecode/templates/changeset/changeset.mako:160
5408 msgid "Comments"
5514 msgid "Comments"
5409 msgstr ""
5515 msgstr ""
5410
5516
@@ -5425,6 +5531,10 b' msgstr ""'
5425 msgid "Notifications"
5531 msgid "Notifications"
5426 msgstr ""
5532 msgstr ""
5427
5533
5534 #: rhodecode/templates/admin/notifications/show_notification.mako:40
5535 msgid "Subject"
5536 msgstr ""
5537
5428 #: rhodecode/templates/admin/permissions/permissions.mako:5
5538 #: rhodecode/templates/admin/permissions/permissions.mako:5
5429 msgid "Permissions Administration"
5539 msgid "Permissions Administration"
5430 msgstr ""
5540 msgstr ""
@@ -5483,23 +5593,23 b' msgid "Default IP Whitelist For All User'
5483 msgstr ""
5593 msgstr ""
5484
5594
5485 #: rhodecode/templates/admin/permissions/permissions_ips.mako:27
5595 #: rhodecode/templates/admin/permissions/permissions_ips.mako:27
5486 #: rhodecode/templates/admin/users/user_edit_ips.mako:35
5596 #: rhodecode/templates/admin/users/user_edit_ips.mako:36
5487 #, python-format
5597 #, python-format
5488 msgid "Confirm to delete this ip: %s"
5598 msgid "Confirm to delete this ip: %s"
5489 msgstr ""
5599 msgstr ""
5490
5600
5491 #: rhodecode/templates/admin/permissions/permissions_ips.mako:34
5601 #: rhodecode/templates/admin/permissions/permissions_ips.mako:34
5492 #: rhodecode/templates/admin/users/user_edit_ips.mako:43
5602 #: rhodecode/templates/admin/users/user_edit_ips.mako:44
5493 msgid "All IP addresses are allowed"
5603 msgid "All IP addresses are allowed"
5494 msgstr ""
5604 msgstr ""
5495
5605
5496 #: rhodecode/templates/admin/permissions/permissions_ips.mako:49
5606 #: rhodecode/templates/admin/permissions/permissions_ips.mako:49
5497 #: rhodecode/templates/admin/users/user_edit_ips.mako:59
5607 #: rhodecode/templates/admin/users/user_edit_ips.mako:60
5498 msgid "New IP Address"
5608 msgid "New IP Address"
5499 msgstr ""
5609 msgstr ""
5500
5610
5501 #: rhodecode/templates/admin/permissions/permissions_ips.mako:53
5611 #: rhodecode/templates/admin/permissions/permissions_ips.mako:53
5502 #: rhodecode/templates/admin/users/user_edit_ips.mako:62
5612 #: rhodecode/templates/admin/users/user_edit_ips.mako:63
5503 msgid "Description..."
5613 msgid "Description..."
5504 msgstr ""
5614 msgstr ""
5505
5615
@@ -5583,9 +5693,9 b' msgid "Add Child Group"'
5583 msgstr ""
5693 msgstr ""
5584
5694
5585 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:50
5695 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:50
5586 #: rhodecode/templates/admin/repos/repo_edit.mako:49
5696 #: rhodecode/templates/admin/repos/repo_edit.mako:52
5587 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:35
5697 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:35
5588 #: rhodecode/templates/admin/users/user_edit.mako:35
5698 #: rhodecode/templates/admin/users/user_edit.mako:40
5589 msgid "Advanced"
5699 msgid "Advanced"
5590 msgstr ""
5700 msgstr ""
5591
5701
@@ -5642,26 +5752,26 b' msgid "Repository Group Permissions"'
5642 msgstr ""
5752 msgstr ""
5643
5753
5644 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:15
5754 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:15
5645 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:16
5755 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:15
5646 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:15
5756 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:15
5647 msgid "User/User Group"
5757 msgid "User/User Group"
5648 msgstr ""
5758 msgstr ""
5649
5759
5650 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:31
5760 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:31
5651 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:31
5761 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:30
5652 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:31
5762 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:31
5653 msgid "super admin"
5763 msgid "super admin"
5654 msgstr ""
5764 msgstr ""
5655
5765
5656 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:34
5766 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:34
5657 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:34
5767 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:33
5658 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:34
5768 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:34
5659 msgid "owner"
5769 msgid "owner"
5660 msgstr ""
5770 msgstr ""
5661
5771
5662 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:52
5772 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:52
5663 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:76
5773 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:76
5664 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:61
5774 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:60
5665 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:52
5775 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:52
5666 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:76
5776 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:76
5667 msgid "permission for all other users"
5777 msgid "permission for all other users"
@@ -5669,8 +5779,8 b' msgstr ""'
5669
5779
5670 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:62
5780 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:62
5671 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:109
5781 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:109
5672 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:71
5782 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:70
5673 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:99
5783 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:98
5674 msgid "Revoke"
5784 msgid "Revoke"
5675 msgstr ""
5785 msgstr ""
5676
5786
@@ -5680,7 +5790,7 b' msgid "delegated admin"'
5680 msgstr ""
5790 msgstr ""
5681
5791
5682 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:118
5792 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:118
5683 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:107
5793 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:106
5684 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:117
5794 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:117
5685 #: rhodecode/templates/base/issue_tracker_settings.mako:83
5795 #: rhodecode/templates/base/issue_tracker_settings.mako:83
5686 msgid "Add new"
5796 msgid "Add new"
@@ -5745,7 +5855,7 b' msgid "Clone from"'
5745 msgstr ""
5855 msgstr ""
5746
5856
5747 #: rhodecode/templates/admin/repos/repo_add_base.mako:47
5857 #: rhodecode/templates/admin/repos/repo_add_base.mako:47
5748 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:102
5858 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:132
5749 #: rhodecode/templates/forks/fork.mako:52
5859 #: rhodecode/templates/forks/fork.mako:52
5750 msgid "Keep it short and to the point. Use a README file for longer descriptions."
5860 msgid "Keep it short and to the point. Use a README file for longer descriptions."
5751 msgstr ""
5861 msgstr ""
@@ -5755,7 +5865,6 b' msgid "Repository Group"'
5755 msgstr ""
5865 msgstr ""
5756
5866
5757 #: rhodecode/templates/admin/repos/repo_add_base.mako:58
5867 #: rhodecode/templates/admin/repos/repo_add_base.mako:58
5758 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:62
5759 #: rhodecode/templates/forks/fork.mako:64
5868 #: rhodecode/templates/forks/fork.mako:64
5760 #, python-format
5869 #, python-format
5761 msgid "Select my personal group (%(repo_group_name)s)"
5870 msgid "Select my personal group (%(repo_group_name)s)"
@@ -5775,7 +5884,7 b' msgid "Set the type of repository to cre'
5775 msgstr ""
5884 msgstr ""
5776
5885
5777 #: rhodecode/templates/admin/repos/repo_add_base.mako:84
5886 #: rhodecode/templates/admin/repos/repo_add_base.mako:84
5778 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:70
5887 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:98
5779 #: rhodecode/templates/forks/fork.mako:73
5888 #: rhodecode/templates/forks/fork.mako:73
5780 msgid "Landing commit"
5889 msgid "Landing commit"
5781 msgstr ""
5890 msgstr ""
@@ -5803,36 +5912,36 b' msgstr ""'
5803 msgid "%s repository settings"
5912 msgid "%s repository settings"
5804 msgstr ""
5913 msgstr ""
5805
5914
5806 #: rhodecode/templates/admin/repos/repo_edit.mako:55
5915 #: rhodecode/templates/admin/repos/repo_edit.mako:58
5807 msgid "Extra Fields"
5916 msgid "Extra Fields"
5808 msgstr ""
5917 msgstr ""
5809
5918
5810 #: rhodecode/templates/admin/repos/repo_edit.mako:61
5919 #: rhodecode/templates/admin/repos/repo_edit.mako:64
5811 msgid "Caches"
5920 msgid "Caches"
5812 msgstr ""
5921 msgstr ""
5813
5922
5814 #: rhodecode/templates/admin/repos/repo_edit.mako:65
5923 #: rhodecode/templates/admin/repos/repo_edit.mako:68
5815 msgid "Remote"
5924 msgid "Remote"
5816 msgstr ""
5925 msgstr ""
5817
5926
5818 #: rhodecode/templates/admin/repos/repo_edit.mako:69
5927 #: rhodecode/templates/admin/repos/repo_edit.mako:72
5819 #: rhodecode/templates/summary/components.mako:135
5928 #: rhodecode/templates/summary/components.mako:135
5820 msgid "Statistics"
5929 msgid "Statistics"
5821 msgstr ""
5930 msgstr ""
5822
5931
5823 #: rhodecode/templates/admin/repos/repo_edit.mako:75
5932 #: rhodecode/templates/admin/repos/repo_edit.mako:79
5933 msgid "Reviewer Rules"
5934 msgstr ""
5935
5936 #: rhodecode/templates/admin/repos/repo_edit.mako:83
5824 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:3
5937 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:3
5825 msgid "Maintenance"
5938 msgid "Maintenance"
5826 msgstr ""
5939 msgstr ""
5827
5940
5828 #: rhodecode/templates/admin/repos/repo_edit.mako:78
5941 #: rhodecode/templates/admin/repos/repo_edit.mako:86
5829 msgid "Strip"
5942 msgid "Strip"
5830 msgstr ""
5943 msgstr ""
5831
5944
5832 #: rhodecode/templates/admin/repos/repo_edit.mako:93
5833 msgid "Reviewers"
5834 msgstr ""
5835
5836 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:7
5945 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:7
5837 msgid "Updated on"
5946 msgid "Updated on"
5838 msgstr ""
5947 msgstr ""
@@ -5867,84 +5976,77 b' msgstr ""'
5867 msgid "Public Journal Visibility"
5976 msgid "Public Journal Visibility"
5868 msgstr ""
5977 msgstr ""
5869
5978
5870 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:56
5979 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:55
5871 msgid "Remove from Public Journal"
5980 msgid "Remove from Public Journal"
5872 msgstr ""
5981 msgstr ""
5873
5982
5874 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:60
5983 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:59
5875 msgid "Add to Public Journal"
5984 msgid "Add to Public Journal"
5876 msgstr ""
5985 msgstr ""
5877
5986
5878 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:65
5987 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:64
5879 msgid "All actions made on this repository will be visible to everyone following the public journal."
5988 msgid "All actions made on this repository will be visible to everyone following the public journal."
5880 msgstr ""
5989 msgstr ""
5881
5990
5882 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:74
5991 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:73
5883 msgid "Locking state"
5992 msgid "Locking state"
5884 msgstr ""
5993 msgstr ""
5885
5994
5886 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:83
5995 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:82
5887 msgid "This Repository is not currently locked."
5996 msgid "This Repository is not currently locked."
5888 msgstr ""
5997 msgstr ""
5889
5998
5890 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:90
5999 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:89
5891 msgid "Confirm to unlock repository."
6000 msgid "Confirm to unlock repository."
5892 msgstr ""
6001 msgstr ""
5893
6002
5894 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:92
6003 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:91
5895 msgid "Unlock repository"
6004 msgid "Unlock repository"
5896 msgstr ""
6005 msgstr ""
5897
6006
5898 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:97
6007 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:96
5899 msgid "Confirm to lock repository."
6008 msgid "Confirm to lock repository."
5900 msgstr ""
6009 msgstr ""
5901
6010
5902 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:99
6011 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:98
5903 msgid "Lock Repository"
6012 msgid "Lock Repository"
5904 msgstr ""
6013 msgstr ""
5905
6014
5906 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:105
6015 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:104
5907 msgid "Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again."
6016 msgid "Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again."
5908 msgstr ""
6017 msgstr ""
5909
6018
5910 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:114
6019 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:113
5911 msgid "Delete repository"
6020 msgid "Delete repository"
5912 msgstr ""
6021 msgstr ""
5913
6022
5914 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:121
6023 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:124
5915 #, python-format
5916 msgid "This repository has %s fork."
5917 msgid_plural "This repository has %s forks."
5918 msgstr[0] ""
5919 msgstr[1] ""
5920
5921 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:125
5922 msgid "Detach forks"
6024 msgid "Detach forks"
5923 msgstr ""
6025 msgstr ""
5924
6026
5925 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:130
6027 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:129
5926 msgid "Delete forks"
6028 msgid "Delete forks"
5927 msgstr ""
6029 msgstr ""
5928
6030
5929 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:139
6031 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:138
5930 #: rhodecode/templates/data_table/_dt_elements.mako:124
6032 #: rhodecode/templates/data_table/_dt_elements.mako:124
5931 #, python-format
6033 #, python-format
5932 msgid "Confirm to delete this repository: %s"
6034 msgid "Confirm to delete this repository: %s"
5933 msgstr ""
6035 msgstr ""
5934
6036
5935 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:141
6037 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:140
5936 msgid "Delete This Repository"
6038 msgid "Delete This Repository"
5937 msgstr ""
6039 msgstr ""
5938
6040
5939 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:146
6041 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:145
5940 msgid "This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command."
6042 msgid "This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools."
5941 msgstr ""
6043 msgstr ""
5942
6044
5943 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:180
6045 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:179
5944 msgid "Change repository"
6046 msgid "Change repository"
5945 msgstr ""
6047 msgstr ""
5946
6048
5947 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:180
6049 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:179
5948 msgid "Pick repository"
6050 msgid "Pick repository"
5949 msgstr ""
6051 msgstr ""
5950
6052
@@ -5952,44 +6054,41 b' msgstr ""'
5952 msgid "Invalidate Cache for Repository"
6054 msgid "Invalidate Cache for Repository"
5953 msgstr ""
6055 msgstr ""
5954
6056
5955 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:10
6057 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:7
5956 msgid "Invalidate repository cache"
6058 msgid "Manually invalidate the repository cache. On the next access a repository cache will be recreated."
5957 msgstr ""
6059 msgstr ""
5958
6060
5959 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:10
6061 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:10
6062 msgid "Cache purge can be automated by such api call. Can be called periodically in crontab etc."
6063 msgstr ""
6064
6065 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:20
6066 msgid "Invalidate repository cache"
6067 msgstr ""
6068
6069 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:20
5960 msgid "Confirm to invalidate repository cache"
6070 msgid "Confirm to invalidate repository cache"
5961 msgstr ""
6071 msgstr ""
5962
6072
5963 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:14
6073 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:39
5964 msgid "Manually invalidate the repository cache. On the next access a repository cache will be recreated."
5965 msgstr ""
5966
5967 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:28
5968 #, python-format
5969 msgid "List of repository caches (%(count)s entry)"
5970 msgid_plural "List of repository caches (%(count)s entries)"
5971 msgstr[0] ""
5972 msgstr[1] ""
5973
5974 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:35
5975 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:32
6074 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:32
5976 #: rhodecode/templates/base/issue_tracker_settings.mako:13
6075 #: rhodecode/templates/base/issue_tracker_settings.mako:13
5977 msgid "Prefix"
6076 msgid "Prefix"
5978 msgstr ""
6077 msgstr ""
5979
6078
5980 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:36
6079 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:40
5981 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:11
6080 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:11
5982 msgid "Key"
6081 msgid "Key"
5983 msgstr ""
6082 msgstr ""
5984
6083
5985 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:37
6084 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:41
5986 #: rhodecode/templates/admin/user_groups/user_group_add.mako:52
6085 #: rhodecode/templates/admin/user_groups/user_group_add.mako:52
5987 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:51
6086 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:51
5988 #: rhodecode/templates/admin/user_groups/user_groups.mako:64
6087 #: rhodecode/templates/admin/user_groups/user_groups.mako:64
5989 #: rhodecode/templates/admin/users/user_add.mako:97
6088 #: rhodecode/templates/admin/users/user_add.mako:97
5990 #: rhodecode/templates/admin/users/user_edit_groups.mako:64
6089 #: rhodecode/templates/admin/users/user_edit_groups.mako:64
5991 #: rhodecode/templates/admin/users/user_edit_profile.mako:90
6090 #: rhodecode/templates/admin/users/user_edit_profile.mako:90
5992 #: rhodecode/templates/admin/users/users.mako:73
6091 #: rhodecode/templates/admin/users/users.mako:74
5993 msgid "Active"
6092 msgid "Active"
5994 msgstr ""
6093 msgstr ""
5995
6094
@@ -6063,19 +6162,27 b' msgstr ""'
6063 msgid "Test Patterns"
6162 msgid "Test Patterns"
6064 msgstr ""
6163 msgstr ""
6065
6164
6066 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:9
6165 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:8
6067 msgid "Perform maintenance tasks for this repo, following tasks will be performed"
6166 msgid "Perform maintenance tasks for this repo"
6068 msgstr ""
6167 msgstr ""
6069
6168
6070 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:16
6169 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:10
6170 msgid "Following tasks will be performed"
6171 msgstr ""
6172
6173 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:17
6174 msgid "Maintenance can be automated by such api call. Can be called periodically in crontab etc."
6175 msgstr ""
6176
6177 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:25
6071 msgid "No maintenance tasks for this repo available"
6178 msgid "No maintenance tasks for this repo available"
6072 msgstr ""
6179 msgstr ""
6073
6180
6074 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:26
6181 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:34
6075 msgid "Run Maintenance"
6182 msgid "Run Maintenance"
6076 msgstr ""
6183 msgstr ""
6077
6184
6078 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:41
6185 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:49
6079 msgid "Performing Maintenance"
6186 msgid "Performing Maintenance"
6080 msgstr ""
6187 msgstr ""
6081
6188
@@ -6083,11 +6190,11 b' msgstr ""'
6083 msgid "Repository Permissions"
6190 msgid "Repository Permissions"
6084 msgstr ""
6191 msgstr ""
6085
6192
6086 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:43
6193 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:42
6087 msgid "private repository"
6194 msgid "private repository"
6088 msgstr ""
6195 msgstr ""
6089
6196
6090 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:48
6197 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:47
6091 msgid "only users/user groups explicitly added here will have access"
6198 msgid "only users/user groups explicitly added here will have access"
6092 msgstr ""
6199 msgstr ""
6093
6200
@@ -6095,56 +6202,87 b' msgstr ""'
6095 msgid "Remote url"
6202 msgid "Remote url"
6096 msgstr ""
6203 msgstr ""
6097
6204
6098 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:9
6205 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:7
6206 msgid "Manually pull changes from external repository."
6207 msgstr ""
6208
6209 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:11
6099 msgid "Remote mirror url"
6210 msgid "Remote mirror url"
6100 msgstr ""
6211 msgstr ""
6101
6212
6102 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:12
6213 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:15
6103 msgid "Pull can be automated by such api call called periodically (in crontab etc)"
6214 msgid "Pull can be automated by such api call. Can be called periodically in crontab etc."
6104 msgstr ""
6215 msgstr ""
6105
6216
6106 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:22
6217 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:25
6107 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:30
6218 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:36
6108 msgid "Pull changes from remote location"
6219 msgid "Pull changes from remote location"
6109 msgstr ""
6220 msgstr ""
6110
6221
6111 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:22
6222 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:25
6112 msgid "Confirm to pull changes from remote side"
6223 msgid "Confirm to pull changes from remote side"
6113 msgstr ""
6224 msgstr ""
6114
6225
6115 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:27
6226 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:31
6116 msgid "This repository does not have any remote mirror url set."
6227 msgid "This repository does not have any remote mirror url set."
6117 msgstr ""
6228 msgstr ""
6118
6229
6230 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:32
6231 msgid "Set remote url."
6232 msgstr ""
6233
6234 #: rhodecode/templates/admin/repos/repo_edit_reviewers.mako:3
6235 msgid "Default Reviewer Rules"
6236 msgstr ""
6237
6238 #: rhodecode/templates/admin/repos/repo_edit_reviewers.mako:6
6239 msgid "This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license."
6240 msgstr ""
6241
6119 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:6
6242 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:6
6120 #, python-format
6243 #, python-format
6121 msgid "Settings for Repository: %s"
6244 msgid "Settings for Repository: %s"
6122 msgstr ""
6245 msgstr ""
6123
6246
6124 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:19
6247 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:22
6125 msgid "Non-changeable id"
6248 msgid "Non-changeable id"
6126 msgstr ""
6249 msgstr ""
6127
6250
6128 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:19
6129 msgid "what is that ?"
6130 msgstr ""
6131
6132 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:21
6133 msgid "URL by id"
6134 msgstr ""
6135
6136 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:22
6251 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:22
6252 msgid "what is that ?"
6253 msgstr ""
6254
6255 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:24
6256 msgid "URL by id"
6257 msgstr ""
6258
6259 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:25
6137 msgid ""
6260 msgid ""
6138 "In case this repository is renamed or moved into another group the repository url changes.\n"
6261 "In case this repository is renamed or moved into another group the repository url changes.\n"
6139 " Using above url guarantees that this repository will always be accessible under such url.\n"
6262 " Using above url guarantees that this repository will always be accessible under such url.\n"
6140 " Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service."
6263 " Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service."
6141 msgstr ""
6264 msgstr ""
6142
6265
6143 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:30
6266 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:33
6267 #: rhodecode/templates/data_table/_dt_elements.mako:164
6268 #: rhodecode/templates/forks/fork.mako:58
6269 msgid "Repository group"
6270 msgstr ""
6271
6272 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:41
6273 #, python-format
6274 msgid "Select my personal group (`%(repo_group_name)s`)"
6275 msgstr ""
6276
6277 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:44
6278 msgid "Optional select a group to put this repository into."
6279 msgstr ""
6280
6281 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:51
6144 msgid "Remote uri"
6282 msgid "Remote uri"
6145 msgstr ""
6283 msgstr ""
6146
6284
6147 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:36
6285 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:59
6148 #: rhodecode/templates/base/perms_summary.mako:79
6286 #: rhodecode/templates/base/perms_summary.mako:79
6149 #: rhodecode/templates/base/perms_summary.mako:149
6287 #: rhodecode/templates/base/perms_summary.mako:149
6150 #: rhodecode/templates/base/perms_summary.mako:151
6288 #: rhodecode/templates/base/perms_summary.mako:151
@@ -6152,63 +6290,56 b' msgstr ""'
6152 msgid "edit"
6290 msgid "edit"
6153 msgstr ""
6291 msgstr ""
6154
6292
6155 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:39
6293 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:65
6156 msgid "new value, leave empty to remove"
6294 msgid "enter new value, or leave empty to remove"
6157 msgstr ""
6295 msgstr ""
6158
6296
6159 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:41
6297 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:75
6160 msgid "cancel"
6298 msgid "cancel"
6161 msgstr ""
6299 msgstr ""
6162
6300
6163 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:48
6301 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:87
6164 msgid "http[s] url where from repository was imported, also used for doing remote pulls."
6302 msgid "http[s] url where from repository was imported, this field can used for doing {pull_link}."
6165 msgstr ""
6303 msgstr ""
6166
6304
6167 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:56
6305 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:88
6168 #: rhodecode/templates/data_table/_dt_elements.mako:164
6306 msgid "This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display."
6169 #: rhodecode/templates/forks/fork.mako:58
6307 msgstr ""
6170 msgid "Repository group"
6308
6171 msgstr ""
6309 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:103
6172
6310 msgid "Default commit for files page, downloads, full text search index and readme"
6173 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:65
6174 msgid "Optional select a group to put this repository into."
6175 msgstr ""
6176
6177 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:74
6178 #: rhodecode/templates/forks/fork.mako:77
6179 msgid "Default commit for files page, downloads, whoosh and readme"
6180 msgstr ""
6181
6182 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:92
6183 msgid "Change owner of this repository."
6184 msgstr ""
6185
6186 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:108
6187 #: rhodecode/templates/data_table/_dt_elements.mako:58
6188 msgid "Private repository"
6189 msgstr ""
6190
6191 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:117
6192 msgid "Enable statistics"
6193 msgstr ""
6311 msgstr ""
6194
6312
6195 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:121
6313 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:121
6314 msgid "Change owner of this repository."
6315 msgstr ""
6316
6317 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:138
6318 #: rhodecode/templates/data_table/_dt_elements.mako:58
6319 msgid "Private repository"
6320 msgstr ""
6321
6322 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:148
6323 msgid "Enable statistics"
6324 msgstr ""
6325
6326 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:153
6196 msgid "Enable statistics window on summary page."
6327 msgid "Enable statistics window on summary page."
6197 msgstr ""
6328 msgstr ""
6198
6329
6199 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:126
6330 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:158
6200 msgid "Enable downloads"
6331 msgid "Enable downloads"
6201 msgstr ""
6332 msgstr ""
6202
6333
6203 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:130
6334 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:163
6204 msgid "Enable download menu on summary page."
6335 msgid "Enable download menu on summary page."
6205 msgstr ""
6336 msgstr ""
6206
6337
6207 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:135
6338 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:168
6208 msgid "Enable automatic locking"
6339 msgid "Enable automatic locking"
6209 msgstr ""
6340 msgstr ""
6210
6341
6211 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:139
6342 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:173
6212 msgid "Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user"
6343 msgid "Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user"
6213 msgstr ""
6344 msgstr ""
6214
6345
@@ -6272,32 +6403,24 b' msgstr ""'
6272 msgid "Remove"
6403 msgid "Remove"
6273 msgstr ""
6404 msgstr ""
6274
6405
6275 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:112
6406 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:114
6276 msgid "Checking commits"
6407 msgid "Checking commits"
6277 msgstr ""
6408 msgstr ""
6278
6409
6279 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:127
6410 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:142
6280 msgid "author"
6281 msgstr ""
6282
6283 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:127
6284 msgid "comment"
6285 msgstr ""
6286
6287 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:128
6288 msgid " commit verified positive"
6411 msgid " commit verified positive"
6289 msgstr ""
6412 msgstr ""
6290
6413
6291 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:131
6414 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:154
6292 msgid " commit verified negative"
6415 msgid " commit verified negative"
6293 msgstr ""
6416 msgstr ""
6294
6417
6295 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:153
6418 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:179
6296 msgid " commit striped successful"
6419 msgid " commit striped successfully"
6297 msgstr ""
6420 msgstr ""
6298
6421
6299 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:156
6422 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:182
6300 msgid " commit striped failed"
6423 msgid " commit strip failed"
6301 msgstr ""
6424 msgstr ""
6302
6425
6303 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:13
6426 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:13
@@ -6835,12 +6958,12 b' msgid "%s user group settings"'
6835 msgstr ""
6958 msgstr ""
6836
6959
6837 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:36
6960 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:36
6838 #: rhodecode/templates/admin/users/user_edit.mako:36
6961 #: rhodecode/templates/admin/users/user_edit.mako:41
6839 msgid "Global permissions"
6962 msgid "Global permissions"
6840 msgstr ""
6963 msgstr ""
6841
6964
6842 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:37
6965 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:37
6843 #: rhodecode/templates/admin/users/user_edit.mako:37
6966 #: rhodecode/templates/admin/users/user_edit.mako:42
6844 msgid "Permissions summary"
6967 msgid "Permissions summary"
6845 msgstr ""
6968 msgstr ""
6846
6969
@@ -6877,43 +7000,35 b' msgid "This group is set to be automatic'
6877 msgstr ""
7000 msgstr ""
6878
7001
6879 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:37
7002 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:37
6880 msgid "Each member will be added or removed from this groups once they interact with RhodeCode system."
6881 msgstr ""
6882
6883 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:38
6884 msgid "This group synchronization was set by"
7003 msgid "This group synchronization was set by"
6885 msgstr ""
7004 msgstr ""
6886
7005
6887 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:42
7006 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:41
6888 msgid "This group is not set to be automatically synchronised"
7007 msgid "This group is not set to be automatically synchronised"
6889 msgstr ""
7008 msgstr ""
6890
7009
6891 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:51
7010 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:50
6892 msgid "Disable synchronization"
7011 msgid "Disable synchronization"
6893 msgstr ""
7012 msgstr ""
6894
7013
6895 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:53
7014 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:52
6896 msgid "Enable synchronization"
7015 msgid "Enable synchronization"
6897 msgstr ""
7016 msgstr ""
6898
7017
6899 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:60
7018 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:58
6900 msgid "User group will no longer synchronize membership"
7019 msgid "Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)"
6901 msgstr ""
7020 msgstr ""
6902
7021
6903 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:62
7022 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:71
6904 msgid "User group will start to synchronize membership"
6905 msgstr ""
6906
6907 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:75
6908 msgid "Delete User Group"
7023 msgid "Delete User Group"
6909 msgstr ""
7024 msgstr ""
6910
7025
6911 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:81
7026 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:77
6912 #, python-format
7027 #, python-format
6913 msgid "Confirm to delete user group `%(ugroup)s` with all permission assignments"
7028 msgid "Confirm to delete user group `%(ugroup)s` with all permission assignments"
6914 msgstr ""
7029 msgstr ""
6915
7030
6916 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:83
7031 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:79
6917 msgid "Delete This User Group"
7032 msgid "Delete This User Group"
6918 msgstr ""
7033 msgstr ""
6919
7034
@@ -7005,24 +7120,28 b' msgstr ""'
7005 msgid "%s user settings"
7120 msgid "%s user settings"
7006 msgstr ""
7121 msgstr ""
7007
7122
7008 #: rhodecode/templates/admin/users/user_edit.mako:33
7123 #: rhodecode/templates/admin/users/user_edit.mako:19
7124 msgid "This user is set as disabled"
7125 msgstr ""
7126
7127 #: rhodecode/templates/admin/users/user_edit.mako:38
7009 #: rhodecode/templates/admin/users/user_edit_profile.mako:5
7128 #: rhodecode/templates/admin/users/user_edit_profile.mako:5
7010 msgid "User Profile"
7129 msgid "User Profile"
7011 msgstr ""
7130 msgstr ""
7012
7131
7013 #: rhodecode/templates/admin/users/user_edit.mako:34
7014 msgid "Auth tokens"
7015 msgstr ""
7016
7017 #: rhodecode/templates/admin/users/user_edit.mako:39
7132 #: rhodecode/templates/admin/users/user_edit.mako:39
7133 msgid "Auth tokens"
7134 msgstr ""
7135
7136 #: rhodecode/templates/admin/users/user_edit.mako:44
7018 msgid "Ip Whitelist"
7137 msgid "Ip Whitelist"
7019 msgstr ""
7138 msgstr ""
7020
7139
7021 #: rhodecode/templates/admin/users/user_edit.mako:40
7140 #: rhodecode/templates/admin/users/user_edit.mako:45
7022 msgid "User Groups Management"
7141 msgid "User Groups Management"
7023 msgstr ""
7142 msgstr ""
7024
7143
7025 #: rhodecode/templates/admin/users/user_edit.mako:41
7144 #: rhodecode/templates/admin/users/user_edit.mako:46
7026 msgid "User audit"
7145 msgid "User audit"
7027 msgstr ""
7146 msgstr ""
7028
7147
@@ -7036,7 +7155,7 b' msgid "Last login"'
7036 msgstr ""
7155 msgstr ""
7037
7156
7038 #: rhodecode/templates/admin/users/user_edit_advanced.mako:9
7157 #: rhodecode/templates/admin/users/user_edit_advanced.mako:9
7039 #: rhodecode/templates/admin/users/users.mako:71
7158 #: rhodecode/templates/admin/users/users.mako:72
7040 msgid "Last activity"
7159 msgid "Last activity"
7041 msgstr ""
7160 msgstr ""
7042
7161
@@ -7151,6 +7270,11 b' msgstr ""'
7151 msgid "Additional Email Addresses"
7270 msgid "Additional Email Addresses"
7152 msgstr ""
7271 msgstr ""
7153
7272
7273 #: rhodecode/templates/admin/users/user_edit_emails.mako:30
7274 #, python-format
7275 msgid "Confirm to delete this email: %s"
7276 msgstr ""
7277
7154 #: rhodecode/templates/admin/users/user_edit_groups.mako:12
7278 #: rhodecode/templates/admin/users/user_edit_groups.mako:12
7155 #, python-format
7279 #, python-format
7156 msgid "Add `%s` to user group"
7280 msgid "Add `%s` to user group"
@@ -7160,12 +7284,24 b' msgstr ""'
7160 msgid "Custom IP Whitelist"
7284 msgid "Custom IP Whitelist"
7161 msgstr ""
7285 msgstr ""
7162
7286
7163 #: rhodecode/templates/admin/users/user_edit_ips.mako:19
7287 #: rhodecode/templates/admin/users/user_edit_ips.mako:7
7288 msgid "Current IP address"
7289 msgstr ""
7290
7291 #: rhodecode/templates/admin/users/user_edit_ips.mako:10
7292 msgid "IP Address"
7293 msgstr ""
7294
7295 #: rhodecode/templates/admin/users/user_edit_ips.mako:11
7296 msgid "IP Range"
7297 msgstr ""
7298
7299 #: rhodecode/templates/admin/users/user_edit_ips.mako:20
7164 #, python-format
7300 #, python-format
7165 msgid "Inherited from %s"
7301 msgid "Inherited from %s"
7166 msgstr ""
7302 msgstr ""
7167
7303
7168 #: rhodecode/templates/admin/users/user_edit_ips.mako:63
7304 #: rhodecode/templates/admin/users/user_edit_ips.mako:64
7169 msgid ""
7305 msgid ""
7170 "Enter comma separated list of ip addresses like 127.0.0.1,\n"
7306 "Enter comma separated list of ip addresses like 127.0.0.1,\n"
7171 "or use a ip address with a mask 127.0.0.1/24, to create a network range.\n"
7307 "or use a ip address with a mask 127.0.0.1/24, to create a network range.\n"
@@ -7210,7 +7346,7 b' msgstr ""'
7210 msgid "Users administration"
7346 msgid "Users administration"
7211 msgstr ""
7347 msgstr ""
7212
7348
7213 #: rhodecode/templates/admin/users/users.mako:77
7349 #: rhodecode/templates/admin/users/users.mako:78
7214 msgid "Auth type"
7350 msgid "Auth type"
7215 msgstr ""
7351 msgstr ""
7216
7352
@@ -7278,110 +7414,110 b' msgstr ""'
7278 msgid "Show Pull Requests for %s"
7414 msgid "Show Pull Requests for %s"
7279 msgstr ""
7415 msgstr ""
7280
7416
7281 #: rhodecode/templates/base/base.mako:246
7417 #: rhodecode/templates/base/base.mako:247
7282 msgid "Options"
7418 msgid "Options"
7283 msgstr ""
7419 msgstr ""
7284
7420
7285 #: rhodecode/templates/base/base.mako:253
7421 #: rhodecode/templates/base/base.mako:255
7286 #: rhodecode/templates/forks/forks_data.mako:30
7422 #: rhodecode/templates/forks/forks_data.mako:30
7287 msgid "Compare fork"
7423 msgid "Compare fork"
7288 msgstr ""
7424 msgstr ""
7289
7425
7290 #: rhodecode/templates/base/base.mako:256
7426 #: rhodecode/templates/base/base.mako:258
7291 #: rhodecode/templates/base/base.mako:403
7427 #: rhodecode/templates/base/base.mako:406
7292 #: rhodecode/templates/search/search.mako:64
7428 #: rhodecode/templates/search/search.mako:64
7293 msgid "Search"
7429 msgid "Search"
7294 msgstr ""
7430 msgstr ""
7295
7431
7296 #: rhodecode/templates/base/base.mako:260
7297 msgid "Unlock"
7298 msgstr ""
7299
7300 #: rhodecode/templates/base/base.mako:262
7432 #: rhodecode/templates/base/base.mako:262
7433 msgid "Unlock"
7434 msgstr ""
7435
7436 #: rhodecode/templates/base/base.mako:264
7301 msgid "Lock"
7437 msgid "Lock"
7302 msgstr ""
7438 msgstr ""
7303
7439
7304 #: rhodecode/templates/base/base.mako:267
7440 #: rhodecode/templates/base/base.mako:269
7305 #: rhodecode/templates/data_table/_dt_elements.mako:27
7441 #: rhodecode/templates/data_table/_dt_elements.mako:27
7306 #: rhodecode/templates/data_table/_dt_elements.mako:28
7442 #: rhodecode/templates/data_table/_dt_elements.mako:28
7307 #: rhodecode/templates/forks/forks_data.mako:8
7443 #: rhodecode/templates/forks/forks_data.mako:8
7308 #: rhodecode/templates/summary/components.mako:103
7309 msgid "Fork"
7444 msgid "Fork"
7310 msgid_plural "Forks"
7445 msgstr ""
7311 msgstr[0] ""
7446
7312 msgstr[1] ""
7447 #: rhodecode/templates/base/base.mako:270
7313
7314 #: rhodecode/templates/base/base.mako:268
7315 msgid "Create Pull Request"
7448 msgid "Create Pull Request"
7316 msgstr ""
7449 msgstr ""
7317
7450
7318 #: rhodecode/templates/base/base.mako:290
7451 #: rhodecode/templates/base/base.mako:292
7319 msgid "Sign in"
7452 msgid "Sign in"
7320 msgstr ""
7453 msgstr ""
7321
7454
7322 #: rhodecode/templates/base/base.mako:298
7455 #: rhodecode/templates/base/base.mako:300
7323 #: rhodecode/templates/debug_style/login.html:28
7456 #: rhodecode/templates/debug_style/login.html:28
7324 msgid "Sign in to your account"
7457 msgid "Sign in to your account"
7325 msgstr ""
7458 msgstr ""
7326
7459
7327 #: rhodecode/templates/base/base.mako:315
7460 #: rhodecode/templates/base/base.mako:317
7328 #: rhodecode/templates/debug_style/login.html:46
7461 #: rhodecode/templates/debug_style/login.html:46
7329 msgid "(Forgot password?)"
7462 msgid "(Forgot password?)"
7330 msgstr ""
7463 msgstr ""
7331
7464
7332 #: rhodecode/templates/base/base.mako:325
7465 #: rhodecode/templates/base/base.mako:327
7333 #: rhodecode/templates/debug_style/login.html:56
7466 msgid "Don't have an account?"
7334 msgid "Don't have an account ?"
7467 msgstr ""
7335 msgstr ""
7468
7336
7469 #: rhodecode/templates/base/base.mako:329
7337 #: rhodecode/templates/base/base.mako:345
7470 msgid "Using external auth? Sign In here."
7471 msgstr ""
7472
7473 #: rhodecode/templates/base/base.mako:348
7338 msgid "My personal group"
7474 msgid "My personal group"
7339 msgstr ""
7475 msgstr ""
7340
7476
7341 #: rhodecode/templates/base/base.mako:349
7477 #: rhodecode/templates/base/base.mako:352
7342 msgid "Sign Out"
7478 msgid "Sign Out"
7343 msgstr ""
7479 msgstr ""
7344
7480
7345 #: rhodecode/templates/base/base.mako:385
7481 #: rhodecode/templates/base/base.mako:388
7346 msgid "Show activity journal"
7482 msgid "Show activity journal"
7347 msgstr ""
7483 msgstr ""
7348
7484
7349 #: rhodecode/templates/base/base.mako:386
7485 #: rhodecode/templates/base/base.mako:389
7350 #: rhodecode/templates/journal/journal.mako:4
7486 #: rhodecode/templates/journal/journal.mako:4
7351 #: rhodecode/templates/journal/journal.mako:14
7487 #: rhodecode/templates/journal/journal.mako:14
7352 msgid "Journal"
7488 msgid "Journal"
7353 msgstr ""
7489 msgstr ""
7354
7490
7355 #: rhodecode/templates/base/base.mako:391
7491 #: rhodecode/templates/base/base.mako:394
7356 msgid "Show Public activity journal"
7492 msgid "Show Public activity journal"
7357 msgstr ""
7493 msgstr ""
7358
7494
7359 #: rhodecode/templates/base/base.mako:392
7495 #: rhodecode/templates/base/base.mako:395
7360 msgid "Public journal"
7496 msgid "Public journal"
7361 msgstr ""
7497 msgstr ""
7362
7498
7363 #: rhodecode/templates/base/base.mako:397
7499 #: rhodecode/templates/base/base.mako:400
7364 msgid "Show Gists"
7500 msgid "Show Gists"
7365 msgstr ""
7501 msgstr ""
7366
7502
7367 #: rhodecode/templates/base/base.mako:398
7503 #: rhodecode/templates/base/base.mako:401
7368 msgid "Gists"
7504 msgid "Gists"
7369 msgstr ""
7505 msgstr ""
7370
7506
7371 #: rhodecode/templates/base/base.mako:402
7507 #: rhodecode/templates/base/base.mako:405
7372 msgid "Search in repositories you have access to"
7508 msgid "Search in repositories you have access to"
7373 msgstr ""
7509 msgstr ""
7374
7510
7375 #: rhodecode/templates/base/base.mako:408
7511 #: rhodecode/templates/base/base.mako:411
7376 msgid "Admin settings"
7512 msgid "Admin settings"
7377 msgstr ""
7513 msgstr ""
7378
7514
7379 #: rhodecode/templates/base/base.mako:415
7515 #: rhodecode/templates/base/base.mako:418
7380 msgid "Delegated Admin settings"
7516 msgid "Delegated Admin settings"
7381 msgstr ""
7517 msgstr ""
7382
7518
7383 #: rhodecode/templates/base/base.mako:425
7519 #: rhodecode/templates/base/base.mako:428
7384 #: rhodecode/templates/base/base.mako:426
7520 #: rhodecode/templates/base/base.mako:429
7385 #: rhodecode/templates/debug_style/alerts.html:5
7521 #: rhodecode/templates/debug_style/alerts.html:5
7386 #: rhodecode/templates/debug_style/buttons.html:5
7522 #: rhodecode/templates/debug_style/buttons.html:5
7387 #: rhodecode/templates/debug_style/code-block.html:6
7523 #: rhodecode/templates/debug_style/code-block.html:6
@@ -7403,15 +7539,15 b' msgstr ""'
7403 msgid "Style"
7539 msgid "Style"
7404 msgstr ""
7540 msgstr ""
7405
7541
7406 #: rhodecode/templates/base/base.mako:483
7542 #: rhodecode/templates/base/base.mako:486
7407 msgid "Go to"
7543 msgid "Go to"
7408 msgstr ""
7544 msgstr ""
7409
7545
7410 #: rhodecode/templates/base/base.mako:536
7546 #: rhodecode/templates/base/base.mako:539
7411 msgid "Keyboard shortcuts"
7547 msgid "Keyboard shortcuts"
7412 msgstr ""
7548 msgstr ""
7413
7549
7414 #: rhodecode/templates/base/base.mako:544
7550 #: rhodecode/templates/base/base.mako:547
7415 msgid "Site-wide shortcuts"
7551 msgid "Site-wide shortcuts"
7416 msgstr ""
7552 msgstr ""
7417
7553
@@ -7503,10 +7639,10 b' msgid "Confirm to remove this pattern:"'
7503 msgstr ""
7639 msgstr ""
7504
7640
7505 #: rhodecode/templates/base/issue_tracker_settings.mako:191
7641 #: rhodecode/templates/base/issue_tracker_settings.mako:191
7506 #: rhodecode/templates/changeset/changeset_file_comment.mako:269
7642 #: rhodecode/templates/changeset/changeset_file_comment.mako:274
7507 #: rhodecode/templates/changeset/changeset_file_comment.mako:319
7643 #: rhodecode/templates/changeset/changeset_file_comment.mako:324
7508 #: rhodecode/templates/files/files_add.mako:78
7644 #: rhodecode/templates/files/files_add.mako:84
7509 #: rhodecode/templates/files/files_add.mako:224
7645 #: rhodecode/templates/files/files_add.mako:228
7510 #: rhodecode/templates/files/files_edit.mako:82
7646 #: rhodecode/templates/files/files_edit.mako:82
7511 #: rhodecode/templates/files/files_edit.mako:185
7647 #: rhodecode/templates/files/files_edit.mako:185
7512 msgid "Preview"
7648 msgid "Preview"
@@ -7586,7 +7722,7 b' msgstr ""'
7586 msgid "No permission defined"
7722 msgid "No permission defined"
7587 msgstr ""
7723 msgstr ""
7588
7724
7589 #: rhodecode/templates/base/root.mako:150
7725 #: rhodecode/templates/base/root.mako:155
7590 msgid "Please enable JavaScript to use RhodeCode Enterprise"
7726 msgid "Please enable JavaScript to use RhodeCode Enterprise"
7591 msgstr ""
7727 msgstr ""
7592
7728
@@ -7682,95 +7818,107 b' msgstr ""'
7682 msgid "Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type."
7818 msgid "Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type."
7683 msgstr ""
7819 msgstr ""
7684
7820
7685 #: rhodecode/templates/base/vcs_settings.mako:139
7821 #: rhodecode/templates/base/vcs_settings.mako:136
7822 msgid "Enable evolve extension"
7823 msgstr ""
7824
7825 #: rhodecode/templates/base/vcs_settings.mako:140
7826 msgid "Enable evolve extension for all repositories."
7827 msgstr ""
7828
7829 #: rhodecode/templates/base/vcs_settings.mako:142
7830 msgid "Enable evolve extension for this repository."
7831 msgstr ""
7832
7833 #: rhodecode/templates/base/vcs_settings.mako:152
7686 msgid "Mercurial Labs Settings"
7834 msgid "Mercurial Labs Settings"
7687 msgstr ""
7835 msgstr ""
7688
7836
7689 #: rhodecode/templates/base/vcs_settings.mako:139
7837 #: rhodecode/templates/base/vcs_settings.mako:152
7690 msgid "These features are considered experimental and may not work as expected."
7838 msgid "These features are considered experimental and may not work as expected."
7691 msgstr ""
7839 msgstr ""
7692
7840
7693 #: rhodecode/templates/base/vcs_settings.mako:145
7841 #: rhodecode/templates/base/vcs_settings.mako:158
7694 msgid "Use rebase as merge strategy"
7842 msgid "Use rebase as merge strategy"
7695 msgstr ""
7843 msgstr ""
7696
7844
7697 #: rhodecode/templates/base/vcs_settings.mako:148
7845 #: rhodecode/templates/base/vcs_settings.mako:161
7698 msgid "Use rebase instead of creating a merge commit when merging via web interface."
7846 msgid "Use rebase instead of creating a merge commit when merging via web interface."
7699 msgstr ""
7847 msgstr ""
7700
7848
7701 #: rhodecode/templates/base/vcs_settings.mako:160
7849 #: rhodecode/templates/base/vcs_settings.mako:173
7702 msgid "Git Settings"
7850 msgid "Git Settings"
7703 msgstr ""
7851 msgstr ""
7704
7852
7705 #: rhodecode/templates/base/vcs_settings.mako:165
7853 #: rhodecode/templates/base/vcs_settings.mako:178
7706 msgid "Enable lfs extension"
7854 msgid "Enable lfs extension"
7707 msgstr ""
7855 msgstr ""
7708
7856
7709 #: rhodecode/templates/base/vcs_settings.mako:169
7710 msgid "Enable lfs extensions for all repositories."
7711 msgstr ""
7712
7713 #: rhodecode/templates/base/vcs_settings.mako:171
7714 msgid "Enable lfs extensions for this repository."
7715 msgstr ""
7716
7717 #: rhodecode/templates/base/vcs_settings.mako:182
7857 #: rhodecode/templates/base/vcs_settings.mako:182
7858 msgid "Enable lfs extensions for all repositories."
7859 msgstr ""
7860
7861 #: rhodecode/templates/base/vcs_settings.mako:184
7862 msgid "Enable lfs extensions for this repository."
7863 msgstr ""
7864
7865 #: rhodecode/templates/base/vcs_settings.mako:195
7718 msgid "Filesystem location where Git lfs objects should be stored."
7866 msgid "Filesystem location where Git lfs objects should be stored."
7719 msgstr ""
7867 msgstr ""
7720
7868
7721 #: rhodecode/templates/base/vcs_settings.mako:193
7869 #: rhodecode/templates/base/vcs_settings.mako:206
7722 msgid "Global Subversion Settings"
7870 msgid "Global Subversion Settings"
7723 msgstr ""
7871 msgstr ""
7724
7872
7725 #: rhodecode/templates/base/vcs_settings.mako:199
7873 #: rhodecode/templates/base/vcs_settings.mako:212
7726 msgid "Proxy subversion HTTP requests"
7874 msgid "Proxy subversion HTTP requests"
7727 msgstr ""
7875 msgstr ""
7728
7876
7729 #: rhodecode/templates/base/vcs_settings.mako:203
7730 msgid "Subversion HTTP Support. Enables communication with SVN over HTTP protocol."
7731 msgstr ""
7732
7733 #: rhodecode/templates/base/vcs_settings.mako:204
7734 msgid "SVN Protocol setup Documentation"
7735 msgstr ""
7736
7737 #: rhodecode/templates/base/vcs_settings.mako:210
7738 msgid "Subversion HTTP Server URL"
7739 msgstr ""
7740
7741 #: rhodecode/templates/base/vcs_settings.mako:216
7877 #: rhodecode/templates/base/vcs_settings.mako:216
7878 msgid "Subversion HTTP Support. Enables communication with SVN over HTTP protocol."
7879 msgstr ""
7880
7881 #: rhodecode/templates/base/vcs_settings.mako:217
7882 msgid "SVN Protocol setup Documentation"
7883 msgstr ""
7884
7885 #: rhodecode/templates/base/vcs_settings.mako:223
7886 msgid "Subversion HTTP Server URL"
7887 msgstr ""
7888
7889 #: rhodecode/templates/base/vcs_settings.mako:229
7742 msgid "Generate Apache Config"
7890 msgid "Generate Apache Config"
7743 msgstr ""
7891 msgstr ""
7744
7892
7745 #: rhodecode/templates/base/vcs_settings.mako:228
7893 #: rhodecode/templates/base/vcs_settings.mako:241
7746 msgid "Subversion Settings"
7894 msgid "Subversion Settings"
7747 msgstr ""
7895 msgstr ""
7748
7896
7749 #: rhodecode/templates/base/vcs_settings.mako:233
7897 #: rhodecode/templates/base/vcs_settings.mako:246
7750 msgid "Repository patterns"
7898 msgid "Repository patterns"
7751 msgstr ""
7899 msgstr ""
7752
7900
7753 #: rhodecode/templates/base/vcs_settings.mako:237
7901 #: rhodecode/templates/base/vcs_settings.mako:250
7754 msgid "Patterns for identifying SVN branches and tags. For recursive search, use \"*\". Eg.: \"/branches/*\""
7902 msgid "Patterns for identifying SVN branches and tags. For recursive search, use \"*\". Eg.: \"/branches/*\""
7755 msgstr ""
7903 msgstr ""
7756
7904
7757 #: rhodecode/templates/base/vcs_settings.mako:301
7905 #: rhodecode/templates/base/vcs_settings.mako:314
7758 msgid "Pull Request Settings"
7906 msgid "Pull Request Settings"
7759 msgstr ""
7907 msgstr ""
7760
7908
7761 #: rhodecode/templates/base/vcs_settings.mako:306
7909 #: rhodecode/templates/base/vcs_settings.mako:319
7762 msgid "Enable server-side merge for pull requests"
7910 msgid "Enable server-side merge for pull requests"
7763 msgstr ""
7911 msgstr ""
7764
7912
7765 #: rhodecode/templates/base/vcs_settings.mako:309
7913 #: rhodecode/templates/base/vcs_settings.mako:322
7766 msgid "Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface."
7914 msgid "Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface."
7767 msgstr ""
7915 msgstr ""
7768
7916
7769 #: rhodecode/templates/base/vcs_settings.mako:313
7917 #: rhodecode/templates/base/vcs_settings.mako:326
7770 msgid "Invalidate and relocate inline comments during update"
7918 msgid "Invalidate and relocate inline comments during update"
7771 msgstr ""
7919 msgstr ""
7772
7920
7773 #: rhodecode/templates/base/vcs_settings.mako:316
7921 #: rhodecode/templates/base/vcs_settings.mako:329
7774 msgid "During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden."
7922 msgid "During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden."
7775 msgstr ""
7923 msgstr ""
7776
7924
@@ -7788,10 +7936,10 b' msgid "Compare Selected Bookmarks"'
7788 msgstr ""
7936 msgstr ""
7789
7937
7790 #: rhodecode/templates/bookmarks/bookmarks_data.mako:13
7938 #: rhodecode/templates/bookmarks/bookmarks_data.mako:13
7791 #: rhodecode/templates/changelog/changelog_elements.mako:91
7939 #: rhodecode/templates/changelog/changelog_elements.mako:111
7792 #: rhodecode/templates/changelog/changelog_summary_data.mako:62
7940 #: rhodecode/templates/changeset/changeset.mako:112
7793 #: rhodecode/templates/changeset/changeset.mako:92
7794 #: rhodecode/templates/files/base.mako:10
7941 #: rhodecode/templates/files/base.mako:10
7942 #: rhodecode/templates/summary/summary_commits.mako:62
7795 #, python-format
7943 #, python-format
7796 msgid "Bookmark %s"
7944 msgid "Bookmark %s"
7797 msgstr ""
7945 msgstr ""
@@ -7810,10 +7958,10 b' msgid "Compare Selected Branches"'
7810 msgstr ""
7958 msgstr ""
7811
7959
7812 #: rhodecode/templates/branches/branches_data.mako:12
7960 #: rhodecode/templates/branches/branches_data.mako:12
7813 #: rhodecode/templates/changelog/changelog_elements.mako:83
7961 #: rhodecode/templates/changelog/changelog_elements.mako:103
7814 #: rhodecode/templates/changelog/changelog_summary_data.mako:76
7962 #: rhodecode/templates/changeset/changeset.mako:125
7815 #: rhodecode/templates/changeset/changeset.mako:105
7816 #: rhodecode/templates/files/base.mako:23
7963 #: rhodecode/templates/files/base.mako:23
7964 #: rhodecode/templates/summary/summary_commits.mako:76
7817 #, python-format
7965 #, python-format
7818 msgid "Branch %s"
7966 msgid "Branch %s"
7819 msgstr ""
7967 msgstr ""
@@ -7851,19 +7999,19 b' msgstr[0] ""'
7851 msgstr[1] ""
7999 msgstr[1] ""
7852
8000
7853 #: rhodecode/templates/changelog/changelog.mako:110
8001 #: rhodecode/templates/changelog/changelog.mako:110
7854 #: rhodecode/templates/files/files_add.mako:93
8002 #: rhodecode/templates/files/files_add.mako:99
7855 #: rhodecode/templates/files/files_delete.mako:60
8003 #: rhodecode/templates/files/files_delete.mako:60
7856 #: rhodecode/templates/files/files_edit.mako:96
8004 #: rhodecode/templates/files/files_edit.mako:96
7857 msgid "Commit Message"
8005 msgid "Commit Message"
7858 msgstr ""
8006 msgstr ""
7859
8007
7860 #: rhodecode/templates/changelog/changelog.mako:112
8008 #: rhodecode/templates/changelog/changelog.mako:112
7861 #: rhodecode/templates/changelog/changelog_summary_data.mako:10
8009 #: rhodecode/templates/summary/summary_commits.mako:10
7862 msgid "Age"
8010 msgid "Age"
7863 msgstr ""
8011 msgstr ""
7864
8012
7865 #: rhodecode/templates/changelog/changelog.mako:115
8013 #: rhodecode/templates/changelog/changelog.mako:115
7866 #: rhodecode/templates/changelog/changelog_summary_data.mako:12
8014 #: rhodecode/templates/summary/summary_commits.mako:12
7867 msgid "Refs"
8015 msgid "Refs"
7868 msgstr ""
8016 msgstr ""
7869
8017
@@ -7880,7 +8028,7 b' msgid "load previous"'
7880 msgstr ""
8028 msgstr ""
7881
8029
7882 #: rhodecode/templates/changelog/changelog_elements.mako:26
8030 #: rhodecode/templates/changelog/changelog_elements.mako:26
7883 #: rhodecode/templates/changelog/changelog_summary_data.mako:21
8031 #: rhodecode/templates/summary/summary_commits.mako:21
7884 #, python-format
8032 #, python-format
7885 msgid ""
8033 msgid ""
7886 "Commit status: %s\n"
8034 "Commit status: %s\n"
@@ -7888,44 +8036,66 b' msgid ""'
7888 msgstr ""
8036 msgstr ""
7889
8037
7890 #: rhodecode/templates/changelog/changelog_elements.mako:30
8038 #: rhodecode/templates/changelog/changelog_elements.mako:30
7891 #: rhodecode/templates/changelog/changelog_summary_data.mako:25
8039 #: rhodecode/templates/summary/summary_commits.mako:25
7892 #, python-format
8040 #, python-format
7893 msgid "Commit status: %s"
8041 msgid "Commit status: %s"
7894 msgstr ""
8042 msgstr ""
7895
8043
7896 #: rhodecode/templates/changelog/changelog_elements.mako:36
8044 #: rhodecode/templates/changelog/changelog_elements.mako:36
7897 #: rhodecode/templates/changelog/changelog_summary_data.mako:31
8045 #: rhodecode/templates/summary/summary_commits.mako:31
7898 msgid "Commit status: Not Reviewed"
8046 msgid "Commit status: Not Reviewed"
7899 msgstr ""
8047 msgstr ""
7900
8048
7901 #: rhodecode/templates/changelog/changelog_elements.mako:41
8049 #: rhodecode/templates/changelog/changelog_elements.mako:41
7902 #: rhodecode/templates/changelog/changelog_summary_data.mako:36
8050 #: rhodecode/templates/summary/summary_commits.mako:36
7903 msgid "Commit has comments"
8051 msgid "Commit has comments"
7904 msgstr ""
8052 msgstr ""
7905
8053
7906 #: rhodecode/templates/changelog/changelog_elements.mako:53
8054 #: rhodecode/templates/changelog/changelog_elements.mako:53
7907 #: rhodecode/templates/compare/compare_commits.mako:46
8055 #: rhodecode/templates/changeset/changeset.mako:40
7908 #: rhodecode/templates/pullrequests/pullrequest_show.mako:495
8056 msgid "Commit phase"
8057 msgstr ""
8058
8059 #: rhodecode/templates/changelog/changelog_elements.mako:60
8060 #: rhodecode/templates/changelog/changelog_elements.mako:67
8061 #: rhodecode/templates/changeset/changeset.mako:46
8062 #: rhodecode/templates/changeset/changeset.mako:53
8063 msgid "Evolve State"
8064 msgstr ""
8065
8066 #: rhodecode/templates/changelog/changelog_elements.mako:60
8067 #: rhodecode/templates/changeset/changeset.mako:46
8068 msgid "obsolete"
8069 msgstr ""
8070
8071 #: rhodecode/templates/changelog/changelog_elements.mako:67
8072 #: rhodecode/templates/changeset/changeset.mako:53
8073 msgid "hidden"
8074 msgstr ""
8075
8076 #: rhodecode/templates/changelog/changelog_elements.mako:73
8077 #: rhodecode/templates/compare/compare_commits.mako:47
8078 #: rhodecode/templates/pullrequests/pullrequest_show.mako:522
7909 #: rhodecode/templates/search/search_commit.mako:36
8079 #: rhodecode/templates/search/search_commit.mako:36
7910 msgid "Expand commit message"
8080 msgid "Expand commit message"
7911 msgstr ""
8081 msgstr ""
7912
8082
7913 #: rhodecode/templates/changelog/changelog_elements.mako:77
8083 #: rhodecode/templates/changelog/changelog_elements.mako:97
7914 #: rhodecode/templates/changeset/changeset.mako:86
8084 #: rhodecode/templates/changeset/changeset.mako:106
7915 #: rhodecode/templates/files/base.mako:4
8085 #: rhodecode/templates/files/base.mako:4
7916 msgid "merge"
8086 msgid "merge"
7917 msgstr ""
8087 msgstr ""
7918
8088
7919 #: rhodecode/templates/changelog/changelog_elements.mako:99
8089 #: rhodecode/templates/changelog/changelog_elements.mako:119
7920 #: rhodecode/templates/changelog/changelog_summary_data.mako:69
8090 #: rhodecode/templates/changeset/changeset.mako:119
7921 #: rhodecode/templates/changeset/changeset.mako:99
7922 #: rhodecode/templates/files/base.mako:17
8091 #: rhodecode/templates/files/base.mako:17
8092 #: rhodecode/templates/summary/summary_commits.mako:69
7923 #: rhodecode/templates/tags/tags_data.mako:12
8093 #: rhodecode/templates/tags/tags_data.mako:12
7924 #, python-format
8094 #, python-format
7925 msgid "Tag %s"
8095 msgid "Tag %s"
7926 msgstr ""
8096 msgstr ""
7927
8097
7928 #: rhodecode/templates/changelog/changelog_elements.mako:113
8098 #: rhodecode/templates/changelog/changelog_elements.mako:133
7929 msgid "load next"
8099 msgid "load next"
7930 msgstr ""
8100 msgstr ""
7931
8101
@@ -7933,100 +8103,78 b' msgstr ""'
7933 msgid "Show File"
8103 msgid "Show File"
7934 msgstr ""
8104 msgstr ""
7935
8105
7936 #: rhodecode/templates/changelog/changelog_summary_data.mako:9
7937 #: rhodecode/templates/search/search_commit.mako:8
7938 msgid "Commit message"
7939 msgstr ""
7940
7941 #: rhodecode/templates/changelog/changelog_summary_data.mako:100
7942 msgid "Add or upload files directly via RhodeCode:"
7943 msgstr ""
7944
7945 #: rhodecode/templates/changelog/changelog_summary_data.mako:103
7946 #: rhodecode/templates/files/files_browser.mako:25
7947 msgid "Add New File"
7948 msgstr ""
7949
7950 #: rhodecode/templates/changelog/changelog_summary_data.mako:111
7951 msgid "Push new repo:"
7952 msgstr ""
7953
7954 #: rhodecode/templates/changelog/changelog_summary_data.mako:122
7955 msgid "Existing repository?"
7956 msgstr ""
7957
7958 #: rhodecode/templates/changeset/changeset.mako:7
8106 #: rhodecode/templates/changeset/changeset.mako:7
7959 #, python-format
8107 #, python-format
7960 msgid "%s Commit"
8108 msgid "%s Commit"
7961 msgstr ""
8109 msgstr ""
7962
8110
7963 #: rhodecode/templates/changeset/changeset.mako:43
8111 #: rhodecode/templates/changeset/changeset.mako:62
7964 msgid "Parent Commit"
8112 msgid "Parent Commit"
7965 msgstr ""
8113 msgstr ""
7966
8114
7967 #: rhodecode/templates/changeset/changeset.mako:43
8115 #: rhodecode/templates/changeset/changeset.mako:62
7968 msgid "Parent"
8116 msgid "Parent"
7969 msgstr ""
8117 msgstr ""
7970
8118
7971 #: rhodecode/templates/changeset/changeset.mako:47
8119 #: rhodecode/templates/changeset/changeset.mako:66
7972 msgid "Child Commit"
8120 msgid "Child Commit"
7973 msgstr ""
8121 msgstr ""
7974
8122
7975 #: rhodecode/templates/changeset/changeset.mako:47
7976 msgid "Child"
7977 msgstr ""
7978
7979 #: rhodecode/templates/changeset/changeset.mako:58
7980 msgid "Expand"
7981 msgstr ""
7982
7983 #: rhodecode/templates/changeset/changeset.mako:66
8123 #: rhodecode/templates/changeset/changeset.mako:66
7984 #: rhodecode/templates/changeset/changeset.mako:72
8124 msgid "Child"
8125 msgstr ""
8126
8127 #: rhodecode/templates/changeset/changeset.mako:78
8128 msgid "Expand"
8129 msgstr ""
8130
8131 #: rhodecode/templates/changeset/changeset.mako:86
8132 #: rhodecode/templates/changeset/changeset.mako:92
7985 #: rhodecode/templates/changeset/changeset_file_comment.mako:81
8133 #: rhodecode/templates/changeset/changeset_file_comment.mako:81
7986 #: rhodecode/templates/compare/compare_diff.mako:159
8134 #: rhodecode/templates/compare/compare_diff.mako:159
7987 msgid "Commit status"
8135 msgid "Commit status"
7988 msgstr ""
8136 msgstr ""
7989
8137
7990 #: rhodecode/templates/changeset/changeset.mako:79
8138 #: rhodecode/templates/changeset/changeset.mako:99
7991 #: rhodecode/templates/files/file_tree_detail.mako:21
8139 #: rhodecode/templates/files/file_tree_detail.mako:21
7992 #: rhodecode/templates/files/files_detail.mako:20
8140 #: rhodecode/templates/files/files_detail.mako:20
7993 msgid "References"
8141 msgid "References"
7994 msgstr ""
8142 msgstr ""
7995
8143
7996 #: rhodecode/templates/changeset/changeset.mako:115
8144 #: rhodecode/templates/changeset/changeset.mako:135
7997 msgid "Diff options"
8145 msgid "Diff options"
7998 msgstr ""
8146 msgstr ""
7999
8147
8000 #: rhodecode/templates/changeset/changeset.mako:119
8148 #: rhodecode/templates/changeset/changeset.mako:139
8001 #: rhodecode/templates/codeblocks/diffs.mako:445
8149 #: rhodecode/templates/codeblocks/diffs.mako:445
8002 #: rhodecode/templates/codeblocks/diffs.mako:448
8150 #: rhodecode/templates/codeblocks/diffs.mako:448
8003 msgid "Raw diff"
8151 msgid "Raw diff"
8004 msgstr ""
8152 msgstr ""
8005
8153
8006 #: rhodecode/templates/changeset/changeset.mako:120
8154 #: rhodecode/templates/changeset/changeset.mako:140
8007 msgid "Raw Diff"
8155 msgid "Raw Diff"
8008 msgstr ""
8156 msgstr ""
8009
8157
8010 #: rhodecode/templates/changeset/changeset.mako:123
8158 #: rhodecode/templates/changeset/changeset.mako:143
8011 msgid "Patch diff"
8159 msgid "Patch diff"
8012 msgstr ""
8160 msgstr ""
8013
8161
8014 #: rhodecode/templates/changeset/changeset.mako:124
8162 #: rhodecode/templates/changeset/changeset.mako:144
8015 msgid "Patch Diff"
8163 msgid "Patch Diff"
8016 msgstr ""
8164 msgstr ""
8017
8165
8018 #: rhodecode/templates/changeset/changeset.mako:127
8166 #: rhodecode/templates/changeset/changeset.mako:147
8019 #: rhodecode/templates/codeblocks/diffs.mako:452
8167 #: rhodecode/templates/codeblocks/diffs.mako:452
8020 #: rhodecode/templates/codeblocks/diffs.mako:455
8168 #: rhodecode/templates/codeblocks/diffs.mako:455
8021 msgid "Download diff"
8169 msgid "Download diff"
8022 msgstr ""
8170 msgstr ""
8023
8171
8024 #: rhodecode/templates/changeset/changeset.mako:128
8172 #: rhodecode/templates/changeset/changeset.mako:148
8025 msgid "Download Diff"
8173 msgid "Download Diff"
8026 msgstr ""
8174 msgstr ""
8027
8175
8028 #: rhodecode/templates/changeset/changeset.mako:145
8176 #: rhodecode/templates/changeset/changeset.mako:165
8029 #: rhodecode/templates/changeset/changeset.mako:147
8177 #: rhodecode/templates/changeset/changeset.mako:167
8030 #: rhodecode/tests/functional/test_commit_comments.py:275
8178 #: rhodecode/tests/functional/test_commit_comments.py:275
8031 #, python-format
8179 #, python-format
8032 msgid "%d Commit comment"
8180 msgid "%d Commit comment"
@@ -8034,8 +8182,8 b' msgid_plural "%d Commit comments"'
8034 msgstr[0] ""
8182 msgstr[0] ""
8035 msgstr[1] ""
8183 msgstr[1] ""
8036
8184
8037 #: rhodecode/templates/changeset/changeset.mako:150
8185 #: rhodecode/templates/changeset/changeset.mako:170
8038 #: rhodecode/templates/changeset/changeset.mako:152
8186 #: rhodecode/templates/changeset/changeset.mako:172
8039 #: rhodecode/tests/functional/test_commit_comments.py:282
8187 #: rhodecode/tests/functional/test_commit_comments.py:282
8040 #, python-format
8188 #, python-format
8041 msgid "%d Inline Comment"
8189 msgid "%d Inline Comment"
@@ -8043,19 +8191,19 b' msgid_plural "%d Inline Comments"'
8043 msgstr[0] ""
8191 msgstr[0] ""
8044 msgstr[1] ""
8192 msgstr[1] ""
8045
8193
8046 #: rhodecode/templates/changeset/changeset.mako:160
8194 #: rhodecode/templates/changeset/changeset.mako:180
8047 msgid "Unresolved TODOs"
8195 msgid "Unresolved TODOs"
8048 msgstr ""
8196 msgstr ""
8049
8197
8050 #: rhodecode/templates/changeset/changeset.mako:169
8198 #: rhodecode/templates/changeset/changeset.mako:189
8051 msgid "There are no unresolved TODOs"
8199 msgid "There are no unresolved TODOs"
8052 msgstr ""
8200 msgstr ""
8053
8201
8054 #: rhodecode/templates/changeset/changeset.mako:249
8202 #: rhodecode/templates/changeset/changeset.mako:269
8055 msgid "No Child Commits"
8203 msgid "No Child Commits"
8056 msgstr ""
8204 msgstr ""
8057
8205
8058 #: rhodecode/templates/changeset/changeset.mako:285
8206 #: rhodecode/templates/changeset/changeset.mako:305
8059 msgid "No Parent Commits"
8207 msgid "No Parent Commits"
8060 msgstr ""
8208 msgstr ""
8061
8209
@@ -8081,72 +8229,80 b' msgstr ""'
8081 msgid "resolves comment #{}"
8229 msgid "resolves comment #{}"
8082 msgstr ""
8230 msgstr ""
8083
8231
8084 #: rhodecode/templates/changeset/changeset_file_comment.mako:100
8232 #: rhodecode/templates/changeset/changeset_file_comment.mako:96
8233 msgid "Pull request author"
8234 msgstr ""
8235
8236 #: rhodecode/templates/changeset/changeset_file_comment.mako:97
8237 msgid "author"
8238 msgstr ""
8239
8240 #: rhodecode/templates/changeset/changeset_file_comment.mako:105
8085 msgid "Outdated comment from pull request version {0}"
8241 msgid "Outdated comment from pull request version {0}"
8086 msgstr ""
8242 msgstr ""
8087
8243
8088 #: rhodecode/templates/changeset/changeset_file_comment.mako:104
8244 #: rhodecode/templates/changeset/changeset_file_comment.mako:109
8089 #: rhodecode/templates/changeset/changeset_file_comment.mako:119
8245 #: rhodecode/templates/changeset/changeset_file_comment.mako:124
8090 msgid "Comment from pull request version {0}"
8246 msgid "Comment from pull request version {0}"
8091 msgstr ""
8247 msgstr ""
8092
8248
8093 #: rhodecode/templates/changeset/changeset_file_comment.mako:116
8249 #: rhodecode/templates/changeset/changeset_file_comment.mako:121
8094 msgid "Outdated comment from pull request version {}"
8250 msgid "Outdated comment from pull request version {}"
8095 msgstr ""
8251 msgstr ""
8096
8252
8097 #: rhodecode/templates/changeset/changeset_file_comment.mako:146
8253 #: rhodecode/templates/changeset/changeset_file_comment.mako:151
8098 #: rhodecode/templates/changeset/changeset_file_comment.mako:149
8254 #: rhodecode/templates/changeset/changeset_file_comment.mako:154
8099 msgid "Prev"
8255 msgid "Prev"
8100 msgstr ""
8256 msgstr ""
8101
8257
8102 #: rhodecode/templates/changeset/changeset_file_comment.mako:147
8258 #: rhodecode/templates/changeset/changeset_file_comment.mako:152
8103 #: rhodecode/templates/changeset/changeset_file_comment.mako:150
8259 #: rhodecode/templates/changeset/changeset_file_comment.mako:155
8104 msgid "Next"
8260 msgid "Next"
8105 msgstr ""
8261 msgstr ""
8106
8262
8107 #: rhodecode/templates/changeset/changeset_file_comment.mako:185
8263 #: rhodecode/templates/changeset/changeset_file_comment.mako:190
8108 msgid "Leave a comment on this Pull Request."
8264 msgid "Leave a comment on this Pull Request."
8109 msgstr ""
8265 msgstr ""
8110
8266
8111 #: rhodecode/templates/changeset/changeset_file_comment.mako:187
8267 #: rhodecode/templates/changeset/changeset_file_comment.mako:192
8112 msgid "Leave a comment on {} commits in this range."
8268 msgid "Leave a comment on {} commits in this range."
8113 msgstr ""
8269 msgstr ""
8114
8270
8115 #: rhodecode/templates/changeset/changeset_file_comment.mako:189
8271 #: rhodecode/templates/changeset/changeset_file_comment.mako:194
8116 msgid "Leave a comment on this Commit."
8272 msgid "Leave a comment on this Commit."
8117 msgstr ""
8273 msgstr ""
8118
8274
8119 #: rhodecode/templates/changeset/changeset_file_comment.mako:277
8275 #: rhodecode/templates/changeset/changeset_file_comment.mako:282
8120 #: rhodecode/templates/codeblocks/diffs.mako:71
8276 #: rhodecode/templates/codeblocks/diffs.mako:71
8121 msgid "You need to be logged in to leave comments."
8277 msgid "You need to be logged in to leave comments."
8122 msgstr ""
8278 msgstr ""
8123
8279
8124 #: rhodecode/templates/changeset/changeset_file_comment.mako:278
8280 #: rhodecode/templates/changeset/changeset_file_comment.mako:283
8125 #: rhodecode/templates/codeblocks/diffs.mako:71
8281 #: rhodecode/templates/codeblocks/diffs.mako:71
8126 msgid "Login now"
8282 msgid "Login now"
8127 msgstr ""
8283 msgstr ""
8128
8284
8129 #: rhodecode/templates/changeset/changeset_file_comment.mako:343
8285 #: rhodecode/templates/changeset/changeset_file_comment.mako:348
8130 #, python-format
8286 #, python-format
8131 msgid "Comments parsed using %s syntax with %s, and %s actions support."
8287 msgid "Comments parsed using %s syntax with %s, and %s actions support."
8132 msgstr ""
8288 msgstr ""
8133
8289
8134 #: rhodecode/templates/changeset/changeset_file_comment.mako:345
8290 #: rhodecode/templates/changeset/changeset_file_comment.mako:350
8135 msgid "Use @username inside this text to send notification to this RhodeCode user"
8291 msgid "Use @username inside this text to send notification to this RhodeCode user"
8136 msgstr ""
8292 msgstr ""
8137
8293
8138 #: rhodecode/templates/changeset/changeset_file_comment.mako:346
8294 #: rhodecode/templates/changeset/changeset_file_comment.mako:351
8139 msgid "Start typing with / for certain actions to be triggered via text box."
8295 msgid "Start typing with / for certain actions to be triggered via text box."
8140 msgstr ""
8296 msgstr ""
8141
8297
8142 #: rhodecode/templates/changeset/changeset_file_comment.mako:363
8298 #: rhodecode/templates/changeset/changeset_file_comment.mako:368
8143 #: rhodecode/templates/pullrequests/pullrequest_show.mako:15
8299 #: rhodecode/templates/pullrequests/pullrequest_show.mako:15
8144 #: rhodecode/templates/pullrequests/pullrequest_show.mako:153
8300 #: rhodecode/templates/pullrequests/pullrequest_show.mako:153
8145 #: rhodecode/templates/pullrequests/pullrequests.mako:52
8301 #: rhodecode/templates/pullrequests/pullrequests.mako:52
8146 msgid "Closed"
8302 msgid "Closed"
8147 msgstr ""
8303 msgstr ""
8148
8304
8149 #: rhodecode/templates/changeset/changeset_file_comment.mako:393
8305 #: rhodecode/templates/changeset/changeset_file_comment.mako:398
8150 #: rhodecode/templates/compare/compare_diff.mako:104
8306 #: rhodecode/templates/compare/compare_diff.mako:104
8151 #: rhodecode/templates/compare/compare_diff.mako:112
8307 #: rhodecode/templates/compare/compare_diff.mako:112
8152 #: rhodecode/templates/compare/compare_diff.mako:120
8308 #: rhodecode/templates/compare/compare_diff.mako:120
@@ -8185,7 +8341,7 b' msgstr ""'
8185
8341
8186 #: rhodecode/templates/changeset/changeset_range.mako:99
8342 #: rhodecode/templates/changeset/changeset_range.mako:99
8187 #: rhodecode/templates/compare/compare_diff.mako:312
8343 #: rhodecode/templates/compare/compare_diff.mako:312
8188 #: rhodecode/templates/pullrequests/pullrequest_show.mako:419
8344 #: rhodecode/templates/pullrequests/pullrequest_show.mako:446
8189 #, python-format
8345 #, python-format
8190 msgid "Expand %s commit"
8346 msgid "Expand %s commit"
8191 msgid_plural "Expand %s commits"
8347 msgid_plural "Expand %s commits"
@@ -8194,7 +8350,7 b' msgstr[1] ""'
8194
8350
8195 #: rhodecode/templates/changeset/changeset_range.mako:105
8351 #: rhodecode/templates/changeset/changeset_range.mako:105
8196 #: rhodecode/templates/compare/compare_diff.mako:318
8352 #: rhodecode/templates/compare/compare_diff.mako:318
8197 #: rhodecode/templates/pullrequests/pullrequest_show.mako:425
8353 #: rhodecode/templates/pullrequests/pullrequest_show.mako:452
8198 #, python-format
8354 #, python-format
8199 msgid "Collapse %s commit"
8355 msgid "Collapse %s commit"
8200 msgid_plural "Collapse %s commits"
8356 msgid_plural "Collapse %s commits"
@@ -8363,26 +8519,26 b' msgstr ""'
8363 msgid "Compare was calculated based on this shared commit."
8519 msgid "Compare was calculated based on this shared commit."
8364 msgstr ""
8520 msgstr ""
8365
8521
8366 #: rhodecode/templates/compare/compare_commits.mako:16
8522 #: rhodecode/templates/compare/compare_commits.mako:17
8367 #: rhodecode/templates/pullrequests/pullrequest_show.mako:456
8523 #: rhodecode/templates/pullrequests/pullrequest_show.mako:483
8368 msgid "Time"
8524 msgid "Time"
8369 msgstr ""
8525 msgstr ""
8370
8526
8371 #: rhodecode/templates/compare/compare_commits.mako:67
8527 #: rhodecode/templates/compare/compare_commits.mako:68
8372 #, python-format
8528 #, python-format
8373 msgid "%s commit hidden"
8529 msgid "%s commit hidden"
8374 msgid_plural "%s commits hidden"
8530 msgid_plural "%s commits hidden"
8375 msgstr[0] ""
8531 msgstr[0] ""
8376 msgstr[1] ""
8532 msgstr[1] ""
8377
8533
8378 #: rhodecode/templates/compare/compare_commits.mako:68
8534 #: rhodecode/templates/compare/compare_commits.mako:69
8379 #: rhodecode/templates/pullrequests/pullrequest_show.mako:573
8535 #: rhodecode/templates/pullrequests/pullrequest_show.mako:600
8380 msgid "show it"
8536 msgid "show it"
8381 msgid_plural "show them"
8537 msgid_plural "show them"
8382 msgstr[0] ""
8538 msgstr[0] ""
8383 msgstr[1] ""
8539 msgstr[1] ""
8384
8540
8385 #: rhodecode/templates/compare/compare_commits.mako:74
8541 #: rhodecode/templates/compare/compare_commits.mako:75
8386 msgid "No commits in this compare"
8542 msgid "No commits in this compare"
8387 msgstr ""
8543 msgstr ""
8388
8544
@@ -8420,6 +8576,7 b' msgstr ""'
8420 #: rhodecode/templates/email_templates/pull_request_comment.mako:90
8576 #: rhodecode/templates/email_templates/pull_request_comment.mako:90
8421 #: rhodecode/templates/email_templates/pull_request_review.mako:73
8577 #: rhodecode/templates/email_templates/pull_request_review.mako:73
8422 #: rhodecode/templates/files/files_source.mako:23
8578 #: rhodecode/templates/files/files_source.mako:23
8579 #: rhodecode/templates/pullrequests/pullrequest_show.mako:71
8423 msgid "Source"
8580 msgid "Source"
8424 msgstr ""
8581 msgstr ""
8425
8582
@@ -8633,6 +8790,10 b' msgstr ""'
8633 msgid "Form vertical"
8790 msgid "Form vertical"
8634 msgstr ""
8791 msgstr ""
8635
8792
8793 #: rhodecode/templates/debug_style/login.html:56
8794 msgid "Don't have an account ?"
8795 msgstr ""
8796
8636 #: rhodecode/templates/email_templates/base.mako:32
8797 #: rhodecode/templates/email_templates/base.mako:32
8637 #, python-format
8798 #, python-format
8638 msgid "This is a notification from RhodeCode. %(instance_url)s"
8799 msgid "This is a notification from RhodeCode. %(instance_url)s"
@@ -8746,6 +8907,7 b' msgid "%(user)s left %(comment_type)s on'
8746 msgstr ""
8907 msgstr ""
8747
8908
8748 #: rhodecode/templates/email_templates/pull_request_comment.mako:49
8909 #: rhodecode/templates/email_templates/pull_request_comment.mako:49
8910 #: rhodecode/templates/pullrequests/pullrequest.mako:72
8749 msgid "Source repository"
8911 msgid "Source repository"
8750 msgstr ""
8912 msgstr ""
8751
8913
@@ -8814,8 +8976,6 b' msgid "%(target_ref_type)s of %(target_r'
8814 msgstr ""
8976 msgstr ""
8815
8977
8816 #: rhodecode/templates/email_templates/pull_request_review.mako:76
8978 #: rhodecode/templates/email_templates/pull_request_review.mako:76
8817 #: rhodecode/templates/summary/components.mako:95
8818 #: rhodecode/templates/summary/components.mako:98
8819 #, python-format
8979 #, python-format
8820 msgid "%(num)s Commit"
8980 msgid "%(num)s Commit"
8821 msgid_plural "%(num)s Commits"
8981 msgid_plural "%(num)s Commits"
@@ -8839,7 +8999,7 b' msgstr ""'
8839 msgid "Full Name"
8999 msgid "Full Name"
8840 msgstr ""
9000 msgstr ""
8841
9001
8842 #: rhodecode/templates/errors/error_document.mako:46
9002 #: rhodecode/templates/errors/error_document.mako:45
8843 #, python-format
9003 #, python-format
8844 msgid "You will be redirected to %s in %s seconds"
9004 msgid "You will be redirected to %s in %s seconds"
8845 msgstr ""
9005 msgstr ""
@@ -8910,6 +9070,7 b' msgid "Remove Custom Path"'
8910 msgstr ""
9070 msgstr ""
8911
9071
8912 #: rhodecode/templates/files/files_add.mako:50
9072 #: rhodecode/templates/files/files_add.mako:50
9073 #: rhodecode/templates/files/files_add.mako:59
8913 msgid "Filename"
9074 msgid "Filename"
8914 msgstr ""
9075 msgstr ""
8915
9076
@@ -8917,34 +9078,34 b' msgstr ""'
8917 msgid "Upload File"
9078 msgid "Upload File"
8918 msgstr ""
9079 msgstr ""
8919
9080
8920 #: rhodecode/templates/files/files_add.mako:59
9081 #: rhodecode/templates/files/files_add.mako:62
8921 msgid "Upload file"
8922 msgstr ""
8923
8924 #: rhodecode/templates/files/files_add.mako:63
8925 msgid "No file selected"
9082 msgid "No file selected"
8926 msgstr ""
9083 msgstr ""
8927
9084
8928 #: rhodecode/templates/files/files_add.mako:65
9085 #: rhodecode/templates/files/files_add.mako:65
9086 msgid "Upload file"
9087 msgstr ""
9088
9089 #: rhodecode/templates/files/files_add.mako:71
8929 msgid "Create New File"
9090 msgid "Create New File"
8930 msgstr ""
9091 msgstr ""
8931
9092
8932 #: rhodecode/templates/files/files_add.mako:75
9093 #: rhodecode/templates/files/files_add.mako:81
8933 #: rhodecode/templates/files/files_edit.mako:79
9094 #: rhodecode/templates/files/files_edit.mako:79
8934 msgid "line wraps"
9095 msgid "line wraps"
8935 msgstr ""
9096 msgstr ""
8936
9097
8937 #: rhodecode/templates/files/files_add.mako:76
9098 #: rhodecode/templates/files/files_add.mako:82
8938 #: rhodecode/templates/files/files_edit.mako:80
9099 #: rhodecode/templates/files/files_edit.mako:80
8939 msgid "on"
9100 msgid "on"
8940 msgstr ""
9101 msgstr ""
8941
9102
8942 #: rhodecode/templates/files/files_add.mako:76
9103 #: rhodecode/templates/files/files_add.mako:82
8943 #: rhodecode/templates/files/files_edit.mako:80
9104 #: rhodecode/templates/files/files_edit.mako:80
8944 msgid "off"
9105 msgid "off"
8945 msgstr ""
9106 msgstr ""
8946
9107
8947 #: rhodecode/templates/files/files_add.mako:103
9108 #: rhodecode/templates/files/files_add.mako:109
8948 #: rhodecode/templates/files/files_edit.mako:106
9109 #: rhodecode/templates/files/files_edit.mako:106
8949 msgid "Commit changes"
9110 msgid "Commit changes"
8950 msgstr ""
9111 msgstr ""
@@ -8965,6 +9126,11 b' msgstr ""'
8965 msgid "Close File List"
9126 msgid "Close File List"
8966 msgstr ""
9127 msgstr ""
8967
9128
9129 #: rhodecode/templates/files/files_browser.mako:25
9130 #: rhodecode/templates/summary/summary_commits.mako:103
9131 msgid "Add New File"
9132 msgstr ""
9133
8968 #: rhodecode/templates/files/files_browser.mako:27
9134 #: rhodecode/templates/files/files_browser.mako:27
8969 msgid "Add File"
9135 msgid "Add File"
8970 msgstr ""
9136 msgstr ""
@@ -9077,7 +9243,6 b' msgid "LargeFile"'
9077 msgstr ""
9243 msgstr ""
9078
9244
9079 #: rhodecode/templates/files/files_source.mako:10
9245 #: rhodecode/templates/files/files_source.mako:10
9080 #: rhodecode/templates/search/search_content.mako:57
9081 msgid "line"
9246 msgid "line"
9082 msgid_plural "lines"
9247 msgid_plural "lines"
9083 msgstr[0] ""
9248 msgstr[0] ""
@@ -9141,6 +9306,10 b' msgstr ""'
9141 msgid "Fork name"
9306 msgid "Fork name"
9142 msgstr ""
9307 msgstr ""
9143
9308
9309 #: rhodecode/templates/forks/fork.mako:77
9310 msgid "Default commit for files page, downloads, whoosh and readme"
9311 msgstr ""
9312
9144 #: rhodecode/templates/forks/fork.mako:93
9313 #: rhodecode/templates/forks/fork.mako:93
9145 msgid "Copy permissions"
9314 msgid "Copy permissions"
9146 msgstr ""
9315 msgstr ""
@@ -9178,6 +9347,13 b' msgstr ""'
9178 msgid "Filter"
9347 msgid "Filter"
9179 msgstr ""
9348 msgstr ""
9180
9349
9350 #: rhodecode/templates/journal/journal.mako:14
9351 #, python-format
9352 msgid "%s entry"
9353 msgid_plural "%s entries"
9354 msgstr[0] ""
9355 msgstr[1] ""
9356
9181 #: rhodecode/templates/journal/journal.mako:23
9357 #: rhodecode/templates/journal/journal.mako:23
9182 msgid "ATOM journal feed"
9358 msgid "ATOM journal feed"
9183 msgstr ""
9359 msgstr ""
@@ -9208,65 +9384,75 b' msgstr ""'
9208 msgid "New pull request"
9384 msgid "New pull request"
9209 msgstr ""
9385 msgstr ""
9210
9386
9211 #: rhodecode/templates/pullrequests/pullrequest.mako:51
9387 #: rhodecode/templates/pullrequests/pullrequest.mako:35
9388 msgid "Pull request summary"
9389 msgstr ""
9390
9391 #: rhodecode/templates/pullrequests/pullrequest.mako:58
9212 msgid "Write a short description on this pull request"
9392 msgid "Write a short description on this pull request"
9213 msgstr ""
9393 msgstr ""
9214
9394
9215 #: rhodecode/templates/pullrequests/pullrequest.mako:57
9395 #: rhodecode/templates/pullrequests/pullrequest.mako:64
9216 msgid "Commit flow"
9396 msgid "Commit flow"
9217 msgstr ""
9397 msgstr ""
9218
9398
9219 #: rhodecode/templates/pullrequests/pullrequest.mako:65
9399 #: rhodecode/templates/pullrequests/pullrequest.mako:90
9220 msgid "Origin repository"
9221 msgstr ""
9222
9223 #: rhodecode/templates/pullrequests/pullrequest.mako:83
9224 msgid "Loading refs..."
9400 msgid "Loading refs..."
9225 msgstr ""
9401 msgstr ""
9226
9402
9227 #: rhodecode/templates/pullrequests/pullrequest.mako:94
9403 #: rhodecode/templates/pullrequests/pullrequest.mako:101
9228 msgid "Submit Pull Request"
9404 msgid "Submit Pull Request"
9229 msgstr ""
9405 msgstr ""
9230
9406
9231 #: rhodecode/templates/pullrequests/pullrequest.mako:107
9407 #: rhodecode/templates/pullrequests/pullrequest.mako:115
9232 #: rhodecode/templates/pullrequests/pullrequest_show.mako:322
9408 #: rhodecode/templates/pullrequests/pullrequest_show.mako:309
9409 msgid "Author of this pull request"
9410 msgstr ""
9411
9412 #: rhodecode/templates/pullrequests/pullrequest.mako:129
9413 #: rhodecode/templates/pullrequests/pullrequest_show.mako:323
9414 msgid "Reviewer rules"
9415 msgstr ""
9416
9417 #: rhodecode/templates/pullrequests/pullrequest.mako:139
9418 #: rhodecode/templates/pullrequests/pullrequest_show.mako:337
9233 msgid "Pull request reviewers"
9419 msgid "Pull request reviewers"
9234 msgstr ""
9420 msgstr ""
9235
9421
9236 #: rhodecode/templates/pullrequests/pullrequest.mako:118
9422 #: rhodecode/templates/pullrequests/pullrequest.mako:150
9237 #: rhodecode/templates/pullrequests/pullrequest_show.mako:366
9423 #: rhodecode/templates/pullrequests/pullrequest_show.mako:392
9238 msgid "Add reviewer"
9424 msgid "Add reviewer or reviewer group"
9239 msgstr ""
9425 msgstr ""
9240
9426
9241 #: rhodecode/templates/pullrequests/pullrequest.mako:297
9427 #: rhodecode/templates/pullrequests/pullrequest.mako:302
9242 #: rhodecode/templates/pullrequests/pullrequest.mako:570
9428 #: rhodecode/templates/pullrequests/pullrequest.mako:504
9243 msgid "Please select origin and destination"
9429 msgid "Please select source and target"
9244 msgstr ""
9430 msgstr ""
9245
9431
9246 #: rhodecode/templates/pullrequests/pullrequest.mako:303
9432 #: rhodecode/templates/pullrequests/pullrequest.mako:308
9247 msgid "Loading compare ..."
9433 msgid "Loading compare ..."
9248 msgstr ""
9434 msgstr ""
9249
9435
9250 #: rhodecode/templates/pullrequests/pullrequest.mako:350
9436 #: rhodecode/templates/pullrequests/pullrequest.mako:356
9251 #: rhodecode/templates/pullrequests/pullrequest.mako:352
9437 #: rhodecode/templates/pullrequests/pullrequest.mako:358
9252 msgid "This pull request will consist of __COMMITS__ commit."
9438 msgid "This pull request will consist of __COMMITS__ commit."
9253 msgid_plural "This pull request will consist of __COMMITS__ commits."
9439 msgid_plural "This pull request will consist of __COMMITS__ commits."
9254 msgstr[0] ""
9440 msgstr[0] ""
9255 msgstr[1] ""
9441 msgstr[1] ""
9256
9442
9257 #: rhodecode/templates/pullrequests/pullrequest.mako:355
9443 #: rhodecode/templates/pullrequests/pullrequest.mako:361
9258 msgid "Show detailed compare."
9444 msgid "Show detailed compare."
9259 msgstr ""
9445 msgstr ""
9260
9446
9261 #: rhodecode/templates/pullrequests/pullrequest.mako:362
9447 #: rhodecode/templates/pullrequests/pullrequest.mako:368
9262 msgid "There are no commits to merge."
9448 msgid "There are no commits to merge."
9263 msgstr ""
9449 msgstr ""
9264
9450
9265 #: rhodecode/templates/pullrequests/pullrequest.mako:462
9451 #: rhodecode/templates/pullrequests/pullrequest.mako:431
9266 msgid "Destination repository"
9452 msgid "Target repository"
9267 msgstr ""
9453 msgstr ""
9268
9454
9269 #: rhodecode/templates/pullrequests/pullrequest.mako:473
9455 #: rhodecode/templates/pullrequests/pullrequest.mako:441
9270 msgid "Select commit reference"
9456 msgid "Select commit reference"
9271 msgstr ""
9457 msgstr ""
9272
9458
@@ -9314,10 +9500,6 b' msgstr ""'
9314 msgid "Confirm to delete this pull request"
9500 msgid "Confirm to delete this pull request"
9315 msgstr ""
9501 msgstr ""
9316
9502
9317 #: rhodecode/templates/pullrequests/pullrequest_show.mako:71
9318 msgid "Origin"
9319 msgstr ""
9320
9321 #: rhodecode/templates/pullrequests/pullrequest_show.mako:88
9503 #: rhodecode/templates/pullrequests/pullrequest_show.mako:88
9322 msgid "Common ancestor"
9504 msgid "Common ancestor"
9323 msgstr ""
9505 msgstr ""
@@ -9416,69 +9598,69 b' msgid "Pull request versions not availab'
9416 msgstr ""
9598 msgstr ""
9417
9599
9418 #: rhodecode/templates/pullrequests/pullrequest_show.mako:300
9600 #: rhodecode/templates/pullrequests/pullrequest_show.mako:300
9419 #: rhodecode/templates/pullrequests/pullrequest_show.mako:370
9601 #: rhodecode/templates/pullrequests/pullrequest_show.mako:397
9420 msgid "Save Changes"
9602 msgid "Save Changes"
9421 msgstr ""
9603 msgstr ""
9422
9604
9423 #: rhodecode/templates/pullrequests/pullrequest_show.mako:387
9605 #: rhodecode/templates/pullrequests/pullrequest_show.mako:414
9424 msgid "Missing requirements:"
9606 msgid "Missing requirements:"
9425 msgstr ""
9607 msgstr ""
9426
9608
9427 #: rhodecode/templates/pullrequests/pullrequest_show.mako:388
9609 #: rhodecode/templates/pullrequests/pullrequest_show.mako:415
9428 msgid "These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled."
9610 msgid "These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled."
9429 msgstr ""
9611 msgstr ""
9430
9612
9431 #: rhodecode/templates/pullrequests/pullrequest_show.mako:396
9613 #: rhodecode/templates/pullrequests/pullrequest_show.mako:423
9432 msgid "Missing commits"
9614 msgid "Missing commits"
9433 msgstr ""
9615 msgstr ""
9434
9616
9435 #: rhodecode/templates/pullrequests/pullrequest_show.mako:397
9617 #: rhodecode/templates/pullrequests/pullrequest_show.mako:424
9436 msgid "This pull request cannot be displayed, because one or more commits no longer exist in the source repository."
9618 msgid "This pull request cannot be displayed, because one or more commits no longer exist in the source repository."
9437 msgstr ""
9619 msgstr ""
9438
9620
9439 #: rhodecode/templates/pullrequests/pullrequest_show.mako:398
9621 #: rhodecode/templates/pullrequests/pullrequest_show.mako:425
9440 msgid "Please update this pull request, push the commits back into the source repository, or consider closing this pull request."
9622 msgid "Please update this pull request, push the commits back into the source repository, or consider closing this pull request."
9441 msgstr ""
9623 msgstr ""
9442
9624
9443 #: rhodecode/templates/pullrequests/pullrequest_show.mako:409
9625 #: rhodecode/templates/pullrequests/pullrequest_show.mako:436
9444 #, python-format
9626 #, python-format
9445 msgid "Showing changes at v%d, commenting is disabled."
9627 msgid "Showing changes at v%d, commenting is disabled."
9446 msgstr ""
9628 msgstr ""
9447
9629
9448 #: rhodecode/templates/pullrequests/pullrequest_show.mako:432
9630 #: rhodecode/templates/pullrequests/pullrequest_show.mako:459
9449 #: rhodecode/templates/pullrequests/pullrequest_show.mako:434
9631 #: rhodecode/templates/pullrequests/pullrequest_show.mako:461
9450 msgid "Update commits"
9632 msgid "Update commits"
9451 msgstr ""
9633 msgstr ""
9452
9634
9453 #: rhodecode/templates/pullrequests/pullrequest_show.mako:434
9635 #: rhodecode/templates/pullrequests/pullrequest_show.mako:461
9454 msgid "Update is disabled for current view"
9636 msgid "Update is disabled for current view"
9455 msgstr ""
9637 msgstr ""
9456
9638
9457 #: rhodecode/templates/pullrequests/pullrequest_show.mako:445
9639 #: rhodecode/templates/pullrequests/pullrequest_show.mako:472
9458 msgid "Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled"
9640 msgid "Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled"
9459 msgstr ""
9641 msgstr ""
9460
9642
9461 #: rhodecode/templates/pullrequests/pullrequest_show.mako:449
9643 #: rhodecode/templates/pullrequests/pullrequest_show.mako:476
9462 msgid "commits added: {}, removed: {}"
9644 msgid "commits added: {}, removed: {}"
9463 msgstr ""
9645 msgstr ""
9464
9646
9465 #: rhodecode/templates/pullrequests/pullrequest_show.mako:467
9647 #: rhodecode/templates/pullrequests/pullrequest_show.mako:494
9466 msgid "Commit added in displayed changes"
9648 msgid "Commit added in displayed changes"
9467 msgstr ""
9649 msgstr ""
9468
9650
9469 #: rhodecode/templates/pullrequests/pullrequest_show.mako:469
9651 #: rhodecode/templates/pullrequests/pullrequest_show.mako:496
9470 msgid "Commit removed in displayed changes"
9652 msgid "Commit removed in displayed changes"
9471 msgstr ""
9653 msgstr ""
9472
9654
9473 #: rhodecode/templates/pullrequests/pullrequest_show.mako:572
9655 #: rhodecode/templates/pullrequests/pullrequest_show.mako:599
9474 msgid "there is {num} general comment from older versions"
9656 msgid "there is {num} general comment from older versions"
9475 msgstr ""
9657 msgstr ""
9476
9658
9477 #: rhodecode/templates/pullrequests/pullrequest_show.mako:575
9659 #: rhodecode/templates/pullrequests/pullrequest_show.mako:602
9478 msgid "there are {num} general comments from older versions"
9660 msgid "there are {num} general comments from older versions"
9479 msgstr ""
9661 msgstr ""
9480
9662
9481 #: rhodecode/templates/pullrequests/pullrequest_show.mako:576
9663 #: rhodecode/templates/pullrequests/pullrequest_show.mako:603
9482 msgid "show them"
9664 msgid "show them"
9483 msgstr ""
9665 msgstr ""
9484
9666
@@ -9568,6 +9750,11 b' msgstr ""'
9568 msgid "File names"
9750 msgid "File names"
9569 msgstr ""
9751 msgstr ""
9570
9752
9753 #: rhodecode/templates/search/search_commit.mako:8
9754 #: rhodecode/templates/summary/summary_commits.mako:9
9755 msgid "Commit message"
9756 msgstr ""
9757
9571 #: rhodecode/templates/search/search_commit.mako:11
9758 #: rhodecode/templates/search/search_commit.mako:11
9572 msgid "Age (new first)"
9759 msgid "Age (new first)"
9573 msgstr ""
9760 msgstr ""
@@ -9599,34 +9786,6 b' msgstr ""'
9599 msgid "%s RSS feed"
9786 msgid "%s RSS feed"
9600 msgstr ""
9787 msgstr ""
9601
9788
9602 #: rhodecode/templates/summary/components.mako:5
9603 #, python-format
9604 msgid "%(num)s Branch"
9605 msgid_plural "%(num)s Branches"
9606 msgstr[0] ""
9607 msgstr[1] ""
9608
9609 #: rhodecode/templates/summary/components.mako:12
9610 #, python-format
9611 msgid "%(num)s Closed Branch"
9612 msgid_plural "%(num)s Closed Branches"
9613 msgstr[0] ""
9614 msgstr[1] ""
9615
9616 #: rhodecode/templates/summary/components.mako:19
9617 #, python-format
9618 msgid "%(num)s Tag"
9619 msgid_plural "%(num)s Tags"
9620 msgstr[0] ""
9621 msgstr[1] ""
9622
9623 #: rhodecode/templates/summary/components.mako:26
9624 #, python-format
9625 msgid "%(num)s Bookmark"
9626 msgid_plural "%(num)s Bookmarks"
9627 msgstr[0] ""
9628 msgstr[1] ""
9629
9630 #: rhodecode/templates/summary/components.mako:49
9789 #: rhodecode/templates/summary/components.mako:49
9631 msgid "Read-only url"
9790 msgid "Read-only url"
9632 msgstr ""
9791 msgstr ""
@@ -9707,6 +9866,18 b' msgstr ""'
9707 msgid "Readme file from commit %s:%s"
9866 msgid "Readme file from commit %s:%s"
9708 msgstr ""
9867 msgstr ""
9709
9868
9869 #: rhodecode/templates/summary/summary_commits.mako:100
9870 msgid "Add or upload files directly via RhodeCode:"
9871 msgstr ""
9872
9873 #: rhodecode/templates/summary/summary_commits.mako:111
9874 msgid "Push new repo:"
9875 msgstr ""
9876
9877 #: rhodecode/templates/summary/summary_commits.mako:122
9878 msgid "Existing repository?"
9879 msgstr ""
9880
9710 #: rhodecode/templates/tags/tags.mako:5
9881 #: rhodecode/templates/tags/tags.mako:5
9711 #, python-format
9882 #, python-format
9712 msgid "%s Tags"
9883 msgid "%s Tags"
@@ -35,12 +35,14 b' from rhodecode.integrations.types.base i'
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38 # updating this required to update the `base_vars` passed in url calling func
38 # updating this required to update the `common_vars` passed in url calling func
39 WEBHOOK_URL_VARS = [
39 WEBHOOK_URL_VARS = [
40 'repo_name',
40 'repo_name',
41 'repo_type',
41 'repo_type',
42 'repo_id',
42 'repo_id',
43 'repo_url',
43 'repo_url',
44 # extra repo fields
45 'extra:<extra_key_name>',
44
46
45 # special attrs below that we handle, using multi-call
47 # special attrs below that we handle, using multi-call
46 'branch',
48 'branch',
@@ -50,6 +52,10 b' WEBHOOK_URL_VARS = ['
50 'pull_request_id',
52 'pull_request_id',
51 'pull_request_url',
53 'pull_request_url',
52
54
55 # user who triggers the call
56 'username',
57 'user_id',
58
53 ]
59 ]
54 URL_VARS = ', '.join('${' + x + '}' for x in WEBHOOK_URL_VARS)
60 URL_VARS = ', '.join('${' + x + '}' for x in WEBHOOK_URL_VARS)
55
61
@@ -70,7 +76,13 b' class WebhookHandler(object):'
70 'repo_type': data['repo']['repo_type'],
76 'repo_type': data['repo']['repo_type'],
71 'repo_id': data['repo']['repo_id'],
77 'repo_id': data['repo']['repo_id'],
72 'repo_url': data['repo']['url'],
78 'repo_url': data['repo']['url'],
79 'username': data['actor']['username'],
80 'user_id': data['actor']['user_id']
73 }
81 }
82 extra_vars = {}
83 for extra_key, extra_val in data['repo']['extra_fields'].items():
84 extra_vars['extra:{}'.format(extra_key)] = extra_val
85 common_vars.update(extra_vars)
74
86
75 return string.Template(
87 return string.Template(
76 self.template_url).safe_substitute(**common_vars)
88 self.template_url).safe_substitute(**common_vars)
@@ -42,8 +42,13 b' def action_parser(user_log, feed=False, '
42 :param feed: use output for feeds (no html and fancy icons)
42 :param feed: use output for feeds (no html and fancy icons)
43 :param parse_cs: parse Changesets into VCS instances
43 :param parse_cs: parse Changesets into VCS instances
44 """
44 """
45 ap = ActionParser(user_log, feed=False, parse_commits=False)
45 if user_log.version == 'v2':
46 return ap.callbacks()
46 ap = AuditLogParser(user_log)
47 return ap.callbacks()
48 else:
49 # old style
50 ap = ActionParser(user_log, feed=False, parse_commits=False)
51 return ap.callbacks()
47
52
48
53
49 class ActionParser(object):
54 class ActionParser(object):
@@ -161,8 +166,9 b' class ActionParser(object):'
161 return action_map
166 return action_map
162
167
163 def get_fork_name(self):
168 def get_fork_name(self):
169 from rhodecode.lib import helpers as h
164 repo_name = self.action_params
170 repo_name = self.action_params
165 _url = url('summary_home', repo_name=repo_name)
171 _url = h.route_path('repo_summary', repo_name=repo_name)
166 return _('fork name %s') % link_to(self.action_params, _url)
172 return _('fork name %s') % link_to(self.action_params, _url)
167
173
168 def get_user_name(self):
174 def get_user_name(self):
@@ -174,6 +180,7 b' class ActionParser(object):'
174 return group_name
180 return group_name
175
181
176 def get_pull_request(self):
182 def get_pull_request(self):
183 from rhodecode.lib import helpers as h
177 pull_request_id = self.action_params
184 pull_request_id = self.action_params
178 if self.is_deleted():
185 if self.is_deleted():
179 repo_name = self.user_log.repository_name
186 repo_name = self.user_log.repository_name
@@ -181,8 +188,8 b' class ActionParser(object):'
181 repo_name = self.user_log.repository.repo_name
188 repo_name = self.user_log.repository.repo_name
182 return link_to(
189 return link_to(
183 _('Pull request #%s') % pull_request_id,
190 _('Pull request #%s') % pull_request_id,
184 url('pullrequest_show', repo_name=repo_name,
191 h.route_path('pullrequest_show', repo_name=repo_name,
185 pull_request_id=pull_request_id))
192 pull_request_id=pull_request_id))
186
193
187 def get_archive_name(self):
194 def get_archive_name(self):
188 archive_name = self.action_params
195 archive_name = self.action_params
@@ -302,6 +309,34 b' class ActionParser(object):'
302 return self.user_log.repository is None
309 return self.user_log.repository is None
303
310
304
311
312 class AuditLogParser(object):
313 def __init__(self, audit_log_entry):
314 self.audit_log_entry = audit_log_entry
315
316 def get_icon(self, action):
317 return 'icon-rhodecode'
318
319 def callbacks(self):
320 action_str = self.audit_log_entry.action
321
322 def callback():
323 # returned callbacks we need to call to get
324 action = action_str \
325 .replace('[', '<span class="journal_highlight">')\
326 .replace(']', '</span>')
327 return literal(action)
328
329 def icon():
330 tmpl = """<i class="%s" alt="%s"></i>"""
331 ico = self.get_icon(action_str)
332 return literal(tmpl % (ico, action_str))
333
334 action_params_func = _no_params_func
335
336 return [
337 callback, action_params_func, icon]
338
339
305 def _no_params_func():
340 def _no_params_func():
306 return ""
341 return ""
307
342
@@ -34,10 +34,10 b' import traceback'
34 from functools import wraps
34 from functools import wraps
35
35
36 import ipaddress
36 import ipaddress
37 from pyramid.httpexceptions import HTTPForbidden, HTTPFound
37 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
38 from pylons import url, request
39 from pylons.controllers.util import abort, redirect
40 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39 # NOTE(marcink): this has to be removed only after pyramid migration,
40 # replace with _ = request.translate
41 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm.exc import ObjectDeletedError
42 from sqlalchemy.orm import joinedload
42 from sqlalchemy.orm import joinedload
43 from zope.cachedescriptors.property import Lazy as LazyProperty
43 from zope.cachedescriptors.property import Lazy as LazyProperty
@@ -138,7 +138,7 b' class _RhodeCodeCryptoBase(object):'
138
138
139
139
140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141 ENC_PREF = '$2a$10'
141 ENC_PREF = ('$2a$10', '$2b$10')
142
142
143 def hash_create(self, str_):
143 def hash_create(self, str_):
144 self._assert_bytes(str_)
144 self._assert_bytes(str_)
@@ -302,7 +302,8 b' def _cached_perms_data(user_id, scope, u'
302 explicit, algo)
302 explicit, algo)
303 return permissions.calculate()
303 return permissions.calculate()
304
304
305 class PermOrigin:
305
306 class PermOrigin(object):
306 ADMIN = 'superadmin'
307 ADMIN = 'superadmin'
307
308
308 REPO_USER = 'user:%s'
309 REPO_USER = 'user:%s'
@@ -341,7 +342,6 b' class PermOriginDict(dict):'
341 {'resource': [('read', 'default'), ('write', 'admin')]}
342 {'resource': [('read', 'default'), ('write', 'admin')]}
342 """
343 """
343
344
344
345 def __init__(self, *args, **kw):
345 def __init__(self, *args, **kw):
346 dict.__init__(self, *args, **kw)
346 dict.__init__(self, *args, **kw)
347 self.perm_origin_stack = {}
347 self.perm_origin_stack = {}
@@ -807,6 +807,8 b' class AuthUser(object):'
807 self.ip_addr = ip_addr
807 self.ip_addr = ip_addr
808 self.name = ''
808 self.name = ''
809 self.lastname = ''
809 self.lastname = ''
810 self.first_name = ''
811 self.last_name = ''
810 self.email = ''
812 self.email = ''
811 self.is_authenticated = False
813 self.is_authenticated = False
812 self.admin = False
814 self.admin = False
@@ -1045,8 +1047,8 b' class AuthUser(object):'
1045 default_ips = UserIpMap.query().filter(
1047 default_ips = UserIpMap.query().filter(
1046 UserIpMap.user == User.get_default_user(cache=True))
1048 UserIpMap.user == User.get_default_user(cache=True))
1047 if cache:
1049 if cache:
1048 default_ips = default_ips.options(FromCache("sql_cache_short",
1050 default_ips = default_ips.options(
1049 "get_user_ips_default"))
1051 FromCache("sql_cache_short", "get_user_ips_default"))
1050
1052
1051 # populate from default user
1053 # populate from default user
1052 for ip in default_ips:
1054 for ip in default_ips:
@@ -1059,8 +1061,8 b' class AuthUser(object):'
1059
1061
1060 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1062 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1061 if cache:
1063 if cache:
1062 user_ips = user_ips.options(FromCache("sql_cache_short",
1064 user_ips = user_ips.options(
1063 "get_user_ips_%s" % user_id))
1065 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1064
1066
1065 for ip in user_ips:
1067 for ip in user_ips:
1066 try:
1068 try:
@@ -1114,6 +1116,17 b' def get_csrf_token(session=None, force_n'
1114 return session.get(csrf_token_key)
1116 return session.get(csrf_token_key)
1115
1117
1116
1118
1119 def get_request(perm_class):
1120 from pyramid.threadlocal import get_current_request
1121 pyramid_request = get_current_request()
1122 if not pyramid_request:
1123 # return global request of pylons in case pyramid isn't available
1124 # NOTE(marcink): this should be removed after migration to pyramid
1125 from pylons import request
1126 return request
1127 return pyramid_request
1128
1129
1117 # CHECK DECORATORS
1130 # CHECK DECORATORS
1118 class CSRFRequired(object):
1131 class CSRFRequired(object):
1119 """
1132 """
@@ -1144,7 +1157,12 b' class CSRFRequired(object):'
1144 supplied_token = self._get_csrf(_request)
1157 supplied_token = self._get_csrf(_request)
1145 return supplied_token and supplied_token == cur_token
1158 return supplied_token and supplied_token == cur_token
1146
1159
1160 def _get_request(self):
1161 return get_request(self)
1162
1147 def __wrapper(self, func, *fargs, **fkwargs):
1163 def __wrapper(self, func, *fargs, **fkwargs):
1164 request = self._get_request()
1165
1148 if request.method in self.except_methods:
1166 if request.method in self.except_methods:
1149 return func(*fargs, **fkwargs)
1167 return func(*fargs, **fkwargs)
1150
1168
@@ -1157,8 +1175,8 b' class CSRFRequired(object):'
1157 reason = 'token-missing'
1175 reason = 'token-missing'
1158 supplied_token = self._get_csrf(request)
1176 supplied_token = self._get_csrf(request)
1159 if supplied_token and cur_token != supplied_token:
1177 if supplied_token and cur_token != supplied_token:
1160 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1178 reason = 'token-mismatch [%s:%s]' % (
1161 supplied_token or ''[:6])
1179 cur_token or ''[:6], supplied_token or ''[:6])
1162
1180
1163 csrf_message = \
1181 csrf_message = \
1164 ("Cross-site request forgery detected, request denied. See "
1182 ("Cross-site request forgery detected, request denied. See "
@@ -1185,10 +1203,15 b' class LoginRequired(object):'
1185 def __call__(self, func):
1203 def __call__(self, func):
1186 return get_cython_compat_decorator(self.__wrapper, func)
1204 return get_cython_compat_decorator(self.__wrapper, func)
1187
1205
1206 def _get_request(self):
1207 return get_request(self)
1208
1188 def __wrapper(self, func, *fargs, **fkwargs):
1209 def __wrapper(self, func, *fargs, **fkwargs):
1189 from rhodecode.lib import helpers as h
1210 from rhodecode.lib import helpers as h
1190 cls = fargs[0]
1211 cls = fargs[0]
1191 user = cls._rhodecode_user
1212 user = cls._rhodecode_user
1213 request = self._get_request()
1214
1192 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1215 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1193 log.debug('Starting login restriction checks for user: %s' % (user,))
1216 log.debug('Starting login restriction checks for user: %s' % (user,))
1194 # check if our IP is allowed
1217 # check if our IP is allowed
@@ -1255,22 +1278,27 b' class LoginRequired(object):'
1255 # we preserve the get PARAM
1278 # we preserve the get PARAM
1256 came_from = request.path_qs
1279 came_from = request.path_qs
1257 log.debug('redirecting to login page with %s' % (came_from,))
1280 log.debug('redirecting to login page with %s' % (came_from,))
1258 return redirect(
1281 raise HTTPFound(
1259 h.route_path('login', _query={'came_from': came_from}))
1282 h.route_path('login', _query={'came_from': came_from}))
1260
1283
1261
1284
1262 class NotAnonymous(object):
1285 class NotAnonymous(object):
1263 """
1286 """
1264 Must be logged in to execute this function else
1287 Must be logged in to execute this function else
1265 redirect to login page"""
1288 redirect to login page
1289 """
1266
1290
1267 def __call__(self, func):
1291 def __call__(self, func):
1268 return get_cython_compat_decorator(self.__wrapper, func)
1292 return get_cython_compat_decorator(self.__wrapper, func)
1269
1293
1294 def _get_request(self):
1295 return get_request(self)
1296
1270 def __wrapper(self, func, *fargs, **fkwargs):
1297 def __wrapper(self, func, *fargs, **fkwargs):
1271 import rhodecode.lib.helpers as h
1298 import rhodecode.lib.helpers as h
1272 cls = fargs[0]
1299 cls = fargs[0]
1273 self.user = cls._rhodecode_user
1300 self.user = cls._rhodecode_user
1301 request = self._get_request()
1274
1302
1275 log.debug('Checking if user is not anonymous @%s' % cls)
1303 log.debug('Checking if user is not anonymous @%s' % cls)
1276
1304
@@ -1281,19 +1309,28 b' class NotAnonymous(object):'
1281 h.flash(_('You need to be a registered user to '
1309 h.flash(_('You need to be a registered user to '
1282 'perform this action'),
1310 'perform this action'),
1283 category='warning')
1311 category='warning')
1284 return redirect(
1312 raise HTTPFound(
1285 h.route_path('login', _query={'came_from': came_from}))
1313 h.route_path('login', _query={'came_from': came_from}))
1286 else:
1314 else:
1287 return func(*fargs, **fkwargs)
1315 return func(*fargs, **fkwargs)
1288
1316
1289
1317
1290 class XHRRequired(object):
1318 class XHRRequired(object):
1319 # TODO(marcink): remove this in favor of the predicates in pyramid routes
1320
1291 def __call__(self, func):
1321 def __call__(self, func):
1292 return get_cython_compat_decorator(self.__wrapper, func)
1322 return get_cython_compat_decorator(self.__wrapper, func)
1293
1323
1324 def _get_request(self):
1325 return get_request(self)
1326
1294 def __wrapper(self, func, *fargs, **fkwargs):
1327 def __wrapper(self, func, *fargs, **fkwargs):
1328 from pylons.controllers.util import abort
1329 request = self._get_request()
1330
1295 log.debug('Checking if request is XMLHttpRequest (XHR)')
1331 log.debug('Checking if request is XMLHttpRequest (XHR)')
1296 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1332 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1333
1297 if not request.is_xhr:
1334 if not request.is_xhr:
1298 abort(400, detail=xhr_message)
1335 abort(400, detail=xhr_message)
1299
1336
@@ -1303,9 +1340,9 b' class XHRRequired(object):'
1303 class HasAcceptedRepoType(object):
1340 class HasAcceptedRepoType(object):
1304 """
1341 """
1305 Check if requested repo is within given repo type aliases
1342 Check if requested repo is within given repo type aliases
1343 """
1306
1344
1307 TODO: anderson: not sure where to put this decorator
1345 # TODO(marcink): remove this in favor of the predicates in pyramid routes
1308 """
1309
1346
1310 def __init__(self, *repo_type_list):
1347 def __init__(self, *repo_type_list):
1311 self.repo_type_list = set(repo_type_list)
1348 self.repo_type_list = set(repo_type_list)
@@ -1328,8 +1365,9 b' class HasAcceptedRepoType(object):'
1328 h.flash(h.literal(
1365 h.flash(h.literal(
1329 _('Action not supported for %s.' % rhodecode_repo.alias)),
1366 _('Action not supported for %s.' % rhodecode_repo.alias)),
1330 category='warning')
1367 category='warning')
1331 return redirect(
1368 raise HTTPFound(
1332 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1369 h.route_path('repo_summary',
1370 repo_name=cls.rhodecode_db_repo.repo_name))
1333
1371
1334
1372
1335 class PermsDecorator(object):
1373 class PermsDecorator(object):
@@ -1345,12 +1383,7 b' class PermsDecorator(object):'
1345 return get_cython_compat_decorator(self.__wrapper, func)
1383 return get_cython_compat_decorator(self.__wrapper, func)
1346
1384
1347 def _get_request(self):
1385 def _get_request(self):
1348 from pyramid.threadlocal import get_current_request
1386 return get_request(self)
1349 pyramid_request = get_current_request()
1350 if not pyramid_request:
1351 # return global request of pylons in case pyramid isn't available
1352 return request
1353 return pyramid_request
1354
1387
1355 def _get_came_from(self):
1388 def _get_came_from(self):
1356 _request = self._get_request()
1389 _request = self._get_request()
@@ -1377,13 +1410,13 b' class PermsDecorator(object):'
1377 if anonymous:
1410 if anonymous:
1378 came_from = self._get_came_from()
1411 came_from = self._get_came_from()
1379 h.flash(_('You need to be signed in to view this page'),
1412 h.flash(_('You need to be signed in to view this page'),
1380 category='warning')
1413 category='warning')
1381 raise HTTPFound(
1414 raise HTTPFound(
1382 h.route_path('login', _query={'came_from': came_from}))
1415 h.route_path('login', _query={'came_from': came_from}))
1383
1416
1384 else:
1417 else:
1385 # redirect with forbidden ret code
1418 # redirect with 404 to prevent resource discovery
1386 raise HTTPForbidden()
1419 raise HTTPNotFound()
1387
1420
1388 def check_permissions(self, user):
1421 def check_permissions(self, user):
1389 """Dummy function for overriding"""
1422 """Dummy function for overriding"""
@@ -1429,10 +1462,16 b' class HasRepoPermissionAllDecorator(Perm'
1429 def check_permissions(self, user):
1462 def check_permissions(self, user):
1430 perms = user.permissions
1463 perms = user.permissions
1431 repo_name = self._get_repo_name()
1464 repo_name = self._get_repo_name()
1465
1432 try:
1466 try:
1433 user_perms = set([perms['repositories'][repo_name]])
1467 user_perms = set([perms['repositories'][repo_name]])
1434 except KeyError:
1468 except KeyError:
1469 log.debug('cannot locate repo with name: `%s` in permissions defs',
1470 repo_name)
1435 return False
1471 return False
1472
1473 log.debug('checking `%s` permissions for repo `%s`',
1474 user_perms, repo_name)
1436 if self.required_perms.issubset(user_perms):
1475 if self.required_perms.issubset(user_perms):
1437 return True
1476 return True
1438 return False
1477 return False
@@ -1450,11 +1489,16 b' class HasRepoPermissionAnyDecorator(Perm'
1450 def check_permissions(self, user):
1489 def check_permissions(self, user):
1451 perms = user.permissions
1490 perms = user.permissions
1452 repo_name = self._get_repo_name()
1491 repo_name = self._get_repo_name()
1492
1453 try:
1493 try:
1454 user_perms = set([perms['repositories'][repo_name]])
1494 user_perms = set([perms['repositories'][repo_name]])
1455 except KeyError:
1495 except KeyError:
1496 log.debug('cannot locate repo with name: `%s` in permissions defs',
1497 repo_name)
1456 return False
1498 return False
1457
1499
1500 log.debug('checking `%s` permissions for repo `%s`',
1501 user_perms, repo_name)
1458 if self.required_perms.intersection(user_perms):
1502 if self.required_perms.intersection(user_perms):
1459 return True
1503 return True
1460 return False
1504 return False
@@ -1476,8 +1520,12 b' class HasRepoGroupPermissionAllDecorator'
1476 try:
1520 try:
1477 user_perms = set([perms['repositories_groups'][group_name]])
1521 user_perms = set([perms['repositories_groups'][group_name]])
1478 except KeyError:
1522 except KeyError:
1523 log.debug('cannot locate repo group with name: `%s` in permissions defs',
1524 group_name)
1479 return False
1525 return False
1480
1526
1527 log.debug('checking `%s` permissions for repo group `%s`',
1528 user_perms, group_name)
1481 if self.required_perms.issubset(user_perms):
1529 if self.required_perms.issubset(user_perms):
1482 return True
1530 return True
1483 return False
1531 return False
@@ -1496,11 +1544,16 b' class HasRepoGroupPermissionAnyDecorator'
1496 def check_permissions(self, user):
1544 def check_permissions(self, user):
1497 perms = user.permissions
1545 perms = user.permissions
1498 group_name = self._get_repo_group_name()
1546 group_name = self._get_repo_group_name()
1547
1499 try:
1548 try:
1500 user_perms = set([perms['repositories_groups'][group_name]])
1549 user_perms = set([perms['repositories_groups'][group_name]])
1501 except KeyError:
1550 except KeyError:
1551 log.debug('cannot locate repo group with name: `%s` in permissions defs',
1552 group_name)
1502 return False
1553 return False
1503
1554
1555 log.debug('checking `%s` permissions for repo group `%s`',
1556 user_perms, group_name)
1504 if self.required_perms.intersection(user_perms):
1557 if self.required_perms.intersection(user_perms):
1505 return True
1558 return True
1506 return False
1559 return False
@@ -1575,6 +1628,7 b' class PermsFunction(object):'
1575 if not user:
1628 if not user:
1576 log.debug('Using user attribute from global request')
1629 log.debug('Using user attribute from global request')
1577 # TODO: remove this someday,put as user as attribute here
1630 # TODO: remove this someday,put as user as attribute here
1631 request = self._get_request()
1578 user = request.user
1632 user = request.user
1579
1633
1580 # init auth user if not already given
1634 # init auth user if not already given
@@ -1603,12 +1657,7 b' class PermsFunction(object):'
1603 return False
1657 return False
1604
1658
1605 def _get_request(self):
1659 def _get_request(self):
1606 from pyramid.threadlocal import get_current_request
1660 return get_request(self)
1607 pyramid_request = get_current_request()
1608 if not pyramid_request:
1609 # return global request of pylons incase pyramid one isn't available
1610 return request
1611 return pyramid_request
1612
1661
1613 def _get_check_scope(self, cls_name):
1662 def _get_check_scope(self, cls_name):
1614 return {
1663 return {
@@ -1673,7 +1722,8 b' class HasRepoPermissionAny(PermsFunction'
1673
1722
1674 def _get_repo_name(self):
1723 def _get_repo_name(self):
1675 if not self.repo_name:
1724 if not self.repo_name:
1676 self.repo_name = get_repo_slug(request)
1725 _request = self._get_request()
1726 self.repo_name = get_repo_slug(_request)
1677 return self.repo_name
1727 return self.repo_name
1678
1728
1679 def check_permissions(self, user):
1729 def check_permissions(self, user):
@@ -162,6 +162,10 b' def get_access_path(environ):'
162 return path
162 return path
163
163
164
164
165 def get_user_agent(environ):
166 return environ.get('HTTP_USER_AGENT')
167
168
165 def vcs_operation_context(
169 def vcs_operation_context(
166 environ, repo_name, username, action, scm, check_locking=True,
170 environ, repo_name, username, action, scm, check_locking=True,
167 is_shadow_repo=False):
171 is_shadow_repo=False):
@@ -200,6 +204,7 b' def vcs_operation_context('
200 'make_lock': make_lock,
204 'make_lock': make_lock,
201 'locked_by': locked_by,
205 'locked_by': locked_by,
202 'server_url': utils2.get_server_url(environ),
206 'server_url': utils2.get_server_url(environ),
207 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
208 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
209 'is_shadow_repo': is_shadow_repo,
205 }
210 }
@@ -260,7 +265,7 b' class BasicAuth(AuthBasicAuthenticator):'
260 __call__ = authenticate
265 __call__ = authenticate
261
266
262
267
263 def attach_context_attributes(context, request):
268 def attach_context_attributes(context, request, user_id, attach_to_request=False):
264 """
269 """
265 Attach variables into template context called `c`, please note that
270 Attach variables into template context called `c`, please note that
266 request could be pylons or pyramid request in here.
271 request could be pylons or pyramid request in here.
@@ -302,7 +307,7 b' def attach_context_attributes(context, r'
302 'rhodecode_markup_renderer', 'rst')
307 'rhodecode_markup_renderer', 'rst')
303 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
308 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
304 context.visual.rhodecode_support_url = \
309 context.visual.rhodecode_support_url = \
305 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
310 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
306
311
307 context.pre_code = rc_config.get('rhodecode_pre_code')
312 context.pre_code = rc_config.get('rhodecode_pre_code')
308 context.post_code = rc_config.get('rhodecode_post_code')
313 context.post_code = rc_config.get('rhodecode_post_code')
@@ -325,6 +330,11 b' def attach_context_attributes(context, r'
325
330
326 context.rhodecode_instanceid = config.get('instance_id')
331 context.rhodecode_instanceid = config.get('instance_id')
327
332
333 context.visual.cut_off_limit_diff = safe_int(
334 config.get('cut_off_limit_diff'))
335 context.visual.cut_off_limit_file = safe_int(
336 config.get('cut_off_limit_file'))
337
328 # AppEnlight
338 # AppEnlight
329 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
339 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
330 context.appenlight_api_public_key = config.get(
340 context.appenlight_api_public_key = config.get(
@@ -382,10 +392,12 b' def attach_context_attributes(context, r'
382 context.csrf_token = auth.get_csrf_token()
392 context.csrf_token = auth.get_csrf_token()
383 context.backends = rhodecode.BACKENDS.keys()
393 context.backends = rhodecode.BACKENDS.keys()
384 context.backends.sort()
394 context.backends.sort()
385 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
395 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
386 context.rhodecode_user.user_id)
396 if attach_to_request:
397 request.call_context = context
398 else:
399 context.pyramid_request = pyramid.threadlocal.get_current_request()
387
400
388 context.pyramid_request = pyramid.threadlocal.get_current_request()
389
401
390
402
391 def get_auth_user(environ):
403 def get_auth_user(environ):
@@ -435,7 +447,7 b' class BaseController(WSGIController):'
435 """
447 """
436 # on each call propagate settings calls into global settings.
448 # on each call propagate settings calls into global settings.
437 set_rhodecode_config(config)
449 set_rhodecode_config(config)
438 attach_context_attributes(c, request)
450 attach_context_attributes(c, request, c.rhodecode_user.user_id)
439
451
440 # TODO: Remove this when fixed in attach_context_attributes()
452 # TODO: Remove this when fixed in attach_context_attributes()
441 c.repo_name = get_repo_slug(request) # can be empty
453 c.repo_name = get_repo_slug(request) # can be empty
@@ -550,7 +562,7 b' class BaseRepoController(BaseController)'
550 "The repository at %(repo_name)s cannot be located.") %
562 "The repository at %(repo_name)s cannot be located.") %
551 {'repo_name': c.repo_name},
563 {'repo_name': c.repo_name},
552 category='error', ignore_duplicate=True)
564 category='error', ignore_duplicate=True)
553 redirect(url('home'))
565 redirect(h.route_path('home'))
554
566
555 # update last change according to VCS data
567 # update last change according to VCS data
556 if not missing_requirements:
568 if not missing_requirements:
@@ -577,7 +589,7 b' class BaseRepoController(BaseController)'
577 'Requirements are missing for repository %s: %s',
589 'Requirements are missing for repository %s: %s',
578 c.repo_name, error.message)
590 c.repo_name, error.message)
579
591
580 summary_url = url('summary_home', repo_name=c.repo_name)
592 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
581 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
593 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
582 settings_update_url = url('repo', repo_name=c.repo_name)
594 settings_update_url = url('repo', repo_name=c.repo_name)
583 path = request.path
595 path = request.path
@@ -31,12 +31,13 b' from celery.task import task'
31 from pylons import config
31 from pylons import config
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib.celerylib import (
35 from rhodecode.lib.celerylib import (
35 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 get_session, vcsconnection, RhodecodeCeleryTask)
37 get_session, vcsconnection, RhodecodeCeleryTask)
37 from rhodecode.lib.hooks_base import log_create_repository
38 from rhodecode.lib.hooks_base import log_create_repository
38 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 from rhodecode.lib.utils import add_cache, action_logger
40 from rhodecode.lib.utils import add_cache
40 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.model.db import Repository, User
42 from rhodecode.model.db import Repository, User
42
43
@@ -141,7 +142,7 b' def create_repo(form_data, cur_user):'
141 'enable_downloads', defs.get('repo_enable_downloads'))
142 'enable_downloads', defs.get('repo_enable_downloads'))
142
143
143 try:
144 try:
144 RepoModel(DBS)._create_repo(
145 repo = RepoModel(DBS)._create_repo(
145 repo_name=repo_name_full,
146 repo_name=repo_name_full,
146 repo_type=repo_type,
147 repo_type=repo_type,
147 description=description,
148 description=description,
@@ -158,8 +159,6 b' def create_repo(form_data, cur_user):'
158 enable_downloads=enable_downloads,
159 enable_downloads=enable_downloads,
159 state=state
160 state=state
160 )
161 )
161
162 action_logger(cur_user, 'user_created_repo', repo_name_full, '', DBS)
163 DBS.commit()
162 DBS.commit()
164
163
165 # now create this repo on Filesystem
164 # now create this repo on Filesystem
@@ -177,6 +176,14 b' def create_repo(form_data, cur_user):'
177
176
178 # set new created state
177 # set new created state
179 repo.set_state(Repository.STATE_CREATED)
178 repo.set_state(Repository.STATE_CREATED)
179 repo_id = repo.repo_id
180 repo_data = repo.get_api_data()
181
182 audit_logger.store(
183 'repo.create', action_data={'data': repo_data},
184 user=cur_user,
185 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
186
180 DBS.commit()
187 DBS.commit()
181 except Exception:
188 except Exception:
182 log.warning('Exception occurred when creating repository, '
189 log.warning('Exception occurred when creating repository, '
@@ -240,8 +247,7 b' def create_repo_fork(form_data, cur_user'
240 fork_of=fork_of,
247 fork_of=fork_of,
241 copy_fork_permissions=copy_fork_permissions
248 copy_fork_permissions=copy_fork_permissions
242 )
249 )
243 action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
250
244 fork_of.repo_name, '', DBS)
245 DBS.commit()
251 DBS.commit()
246
252
247 base_path = Repository.base_path()
253 base_path = Repository.base_path()
@@ -264,6 +270,14 b' def create_repo_fork(form_data, cur_user'
264
270
265 # set new created state
271 # set new created state
266 repo.set_state(Repository.STATE_CREATED)
272 repo.set_state(Repository.STATE_CREATED)
273
274 repo_id = repo.repo_id
275 repo_data = repo.get_api_data()
276 audit_logger.store(
277 'repo.fork', action_data={'data': repo_data},
278 user=cur_user,
279 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
280
267 DBS.commit()
281 DBS.commit()
268 except Exception as e:
282 except Exception as e:
269 log.warning('Exception %s occurred when forking repository, '
283 log.warning('Exception %s occurred when forking repository, '
@@ -77,8 +77,8 b' def get_user_data(user_id):'
77 return {
77 return {
78 'id': user.user_id,
78 'id': user.user_id,
79 'username': user.username,
79 'username': user.username,
80 'first_name': user.name,
80 'first_name': user.first_name,
81 'last_name': user.lastname,
81 'last_name': user.last_name,
82 'icon_link': h.gravatar_url(user.email, 60),
82 'icon_link': h.gravatar_url(user.email, 60),
83 'display_name': h.person(user, 'username_or_name_or_email'),
83 'display_name': h.person(user, 'username_or_name_or_email'),
84 'display_link': h.link_to_user(user),
84 'display_link': h.link_to_user(user),
@@ -471,31 +471,29 b' class DiffSet(object):'
471 source_file_type = source_lexer.name
471 source_file_type = source_lexer.name
472 target_file_type = target_lexer.name
472 target_file_type = target_lexer.name
473
473
474 op_hunks = patch['chunks'][0]
475 hunks = patch['chunks'][1:]
476
477 filediff = AttributeDict({
474 filediff = AttributeDict({
478 'source_file_path': source_file_path,
475 'source_file_path': source_file_path,
479 'target_file_path': target_file_path,
476 'target_file_path': target_file_path,
480 'source_filenode': source_filenode,
477 'source_filenode': source_filenode,
481 'target_filenode': target_filenode,
478 'target_filenode': target_filenode,
482 'hunks': [],
483 'source_file_type': target_file_type,
479 'source_file_type': target_file_type,
484 'target_file_type': source_file_type,
480 'target_file_type': source_file_type,
485 'patch': patch,
481 'patch': {'filename': patch['filename'], 'stats': patch['stats']},
482 'operation': patch['operation'],
486 'source_mode': patch['stats']['old_mode'],
483 'source_mode': patch['stats']['old_mode'],
487 'target_mode': patch['stats']['new_mode'],
484 'target_mode': patch['stats']['new_mode'],
488 'limited_diff': isinstance(patch, LimitedDiffContainer),
485 'limited_diff': isinstance(patch, LimitedDiffContainer),
486 'hunks': [],
489 'diffset': self,
487 'diffset': self,
490 })
488 })
491
489
492 for hunk in hunks:
490 for hunk in patch['chunks'][1:]:
493 hunkbit = self.parse_hunk(hunk, source_file, target_file)
491 hunkbit = self.parse_hunk(hunk, source_file, target_file)
494 hunkbit.filediff = filediff
492 hunkbit.source_file_path = source_file_path
493 hunkbit.target_file_path = target_file_path
495 filediff.hunks.append(hunkbit)
494 filediff.hunks.append(hunkbit)
496
495
497 left_comments = {}
496 left_comments = {}
498
499 if source_file_path in self.comments_store:
497 if source_file_path in self.comments_store:
500 for lineno, comments in self.comments_store[source_file_path].items():
498 for lineno, comments in self.comments_store[source_file_path].items():
501 left_comments[lineno] = comments
499 left_comments[lineno] = comments
@@ -503,8 +501,8 b' class DiffSet(object):'
503 if target_file_path in self.comments_store:
501 if target_file_path in self.comments_store:
504 for lineno, comments in self.comments_store[target_file_path].items():
502 for lineno, comments in self.comments_store[target_file_path].items():
505 left_comments[lineno] = comments
503 left_comments[lineno] = comments
504 filediff.left_comments = left_comments
506
505
507 filediff.left_comments = left_comments
508 return filediff
506 return filediff
509
507
510 def parse_hunk(self, hunk, source_file, target_file):
508 def parse_hunk(self, hunk, source_file, target_file):
@@ -519,6 +517,7 b' class DiffSet(object):'
519 before, after = [], []
517 before, after = [], []
520
518
521 for line in hunk['lines']:
519 for line in hunk['lines']:
520
522 if line['action'] == 'unmod':
521 if line['action'] == 'unmod':
523 result.lines.extend(
522 result.lines.extend(
524 self.parse_lines(before, after, source_file, target_file))
523 self.parse_lines(before, after, source_file, target_file))
@@ -567,7 +566,8 b' class DiffSet(object):'
567 before_tokens = [('nonl', before['line'])]
566 before_tokens = [('nonl', before['line'])]
568 else:
567 else:
569 before_tokens = self.get_line_tokens(
568 before_tokens = self.get_line_tokens(
570 line_text=before['line'], line_number=before['old_lineno'],
569 line_text=before['line'],
570 line_number=before['old_lineno'],
571 file=source_file)
571 file=source_file)
572 original.lineno = before['old_lineno']
572 original.lineno = before['old_lineno']
573 original.content = before['line']
573 original.content = before['line']
@@ -319,6 +319,7 b' class DbManage(object):'
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
322
323
323 ]
324 ]
324
325
@@ -363,6 +364,14 b' class DbManage(object):'
363 hgsubversion.ui_active = False
364 hgsubversion.ui_active = False
364 self.sa.add(hgsubversion)
365 self.sa.add(hgsubversion)
365
366
367 # enable hgevolve disabled by default
368 hgevolve = RhodeCodeUi()
369 hgevolve.ui_section = 'extensions'
370 hgevolve.ui_key = 'evolve'
371 hgevolve.ui_value = ''
372 hgevolve.ui_active = False
373 self.sa.add(hgevolve)
374
366 # enable hggit disabled by default
375 # enable hggit disabled by default
367 hggit = RhodeCodeUi()
376 hggit = RhodeCodeUi()
368 hggit.ui_section = 'extensions'
377 hggit.ui_section = 'extensions'
@@ -36,6 +36,8 b' import urlparse'
36 import time
36 import time
37 import string
37 import string
38 import hashlib
38 import hashlib
39 from collections import OrderedDict
40
39 import pygments
41 import pygments
40 import itertools
42 import itertools
41 import fnmatch
43 import fnmatch
@@ -891,8 +893,9 b' def author_string(email):'
891 if email:
893 if email:
892 user = User.get_by_email(email, case_insensitive=True, cache=True)
894 user = User.get_by_email(email, case_insensitive=True, cache=True)
893 if user:
895 if user:
894 if user.firstname or user.lastname:
896 if user.first_name or user.last_name:
895 return '%s %s &lt;%s&gt;' % (user.firstname, user.lastname, email)
897 return '%s %s &lt;%s&gt;' % (
898 user.first_name, user.last_name, email)
896 else:
899 else:
897 return email
900 return email
898 else:
901 else:
@@ -1141,14 +1144,14 b' class InitialsGravatar(object):'
1141 # first push the email initials
1144 # first push the email initials
1142 prefix, server = email_address.split('@', 1)
1145 prefix, server = email_address.split('@', 1)
1143
1146
1144 # check if prefix is maybe a 'firstname.lastname' syntax
1147 # check if prefix is maybe a 'first_name.last_name' syntax
1145 _dot_split = prefix.rsplit('.', 1)
1148 _dot_split = prefix.rsplit('.', 1)
1146 if len(_dot_split) == 2:
1149 if len(_dot_split) == 2:
1147 initials = [_dot_split[0][0], _dot_split[1][0]]
1150 initials = [_dot_split[0][0], _dot_split[1][0]]
1148 else:
1151 else:
1149 initials = [prefix[0], server[0]]
1152 initials = [prefix[0], server[0]]
1150
1153
1151 # then try to replace either firtname or lastname
1154 # then try to replace either first_name or last_name
1152 fn_letter = (first_name or " ")[0].strip()
1155 fn_letter = (first_name or " ")[0].strip()
1153 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1156 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1154
1157
@@ -1245,12 +1248,19 b' def initials_gravatar(email_address, fir'
1245 return klass.generate_svg(svg_type=svg_type)
1248 return klass.generate_svg(svg_type=svg_type)
1246
1249
1247
1250
1248 def gravatar_url(email_address, size=30):
1251 def gravatar_url(email_address, size=30, request=None):
1249 # doh, we need to re-import those to mock it later
1252 request = get_current_request()
1250 from pylons import tmpl_context as c
1253 if request and hasattr(request, 'call_context'):
1254 _use_gravatar = request.call_context.visual.use_gravatar
1255 _gravatar_url = request.call_context.visual.gravatar_url
1256 else:
1257 # doh, we need to re-import those to mock it later
1258 from pylons import tmpl_context as c
1251
1259
1252 _use_gravatar = c.visual.use_gravatar
1260 _use_gravatar = c.visual.use_gravatar
1253 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1261 _gravatar_url = c.visual.gravatar_url
1262
1263 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1254
1264
1255 email_address = email_address or User.DEFAULT_USER_EMAIL
1265 email_address = email_address or User.DEFAULT_USER_EMAIL
1256 if isinstance(email_address, unicode):
1266 if isinstance(email_address, unicode):
@@ -1532,10 +1542,10 b' def breadcrumb_repo_link(repo):'
1532 """
1542 """
1533
1543
1534 path = [
1544 path = [
1535 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1545 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1536 for group in repo.groups_with_parents
1546 for group in repo.groups_with_parents
1537 ] + [
1547 ] + [
1538 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1548 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1539 ]
1549 ]
1540
1550
1541 return literal(' &raquo; '.join(path))
1551 return literal(' &raquo; '.join(path))
@@ -1602,16 +1612,24 b' def urlify_commits(text_, repository):'
1602
1612
1603
1613
1604 def _process_url_func(match_obj, repo_name, uid, entry,
1614 def _process_url_func(match_obj, repo_name, uid, entry,
1605 return_raw_data=False):
1615 return_raw_data=False, link_format='html'):
1606 pref = ''
1616 pref = ''
1607 if match_obj.group().startswith(' '):
1617 if match_obj.group().startswith(' '):
1608 pref = ' '
1618 pref = ' '
1609
1619
1610 issue_id = ''.join(match_obj.groups())
1620 issue_id = ''.join(match_obj.groups())
1611 tmpl = (
1621
1612 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1622 if link_format == 'html':
1613 '%(issue-prefix)s%(id-repr)s'
1623 tmpl = (
1614 '</a>')
1624 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1625 '%(issue-prefix)s%(id-repr)s'
1626 '</a>')
1627 elif link_format == 'rst':
1628 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1629 elif link_format == 'markdown':
1630 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1631 else:
1632 raise ValueError('Bad link_format:{}'.format(link_format))
1615
1633
1616 (repo_name_cleaned,
1634 (repo_name_cleaned,
1617 parent_group_name) = RepoGroupModel().\
1635 parent_group_name) = RepoGroupModel().\
@@ -1644,7 +1662,12 b' def _process_url_func(match_obj, repo_na'
1644 return tmpl % data
1662 return tmpl % data
1645
1663
1646
1664
1647 def process_patterns(text_string, repo_name, config=None):
1665 def process_patterns(text_string, repo_name, link_format='html'):
1666 allowed_formats = ['html', 'rst', 'markdown']
1667 if link_format not in allowed_formats:
1668 raise ValueError('Link format can be only one of:{} got {}'.format(
1669 allowed_formats, link_format))
1670
1648 repo = None
1671 repo = None
1649 if repo_name:
1672 if repo_name:
1650 # Retrieving repo_name to avoid invalid repo_name to explode on
1673 # Retrieving repo_name to avoid invalid repo_name to explode on
@@ -1656,6 +1679,7 b' def process_patterns(text_string, repo_n'
1656
1679
1657 issues_data = []
1680 issues_data = []
1658 newtext = text_string
1681 newtext = text_string
1682
1659 for uid, entry in active_entries.items():
1683 for uid, entry in active_entries.items():
1660 log.debug('found issue tracker entry with uid %s' % (uid,))
1684 log.debug('found issue tracker entry with uid %s' % (uid,))
1661
1685
@@ -1682,7 +1706,8 b' def process_patterns(text_string, repo_n'
1682 issues_data.append(data_func(match_obj))
1706 issues_data.append(data_func(match_obj))
1683
1707
1684 url_func = partial(
1708 url_func = partial(
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1709 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1710 link_format=link_format)
1686
1711
1687 newtext = pattern.sub(url_func, newtext)
1712 newtext = pattern.sub(url_func, newtext)
1688 log.debug('processed prefix:uid `%s`' % (uid,))
1713 log.debug('processed prefix:uid `%s`' % (uid,))
@@ -1750,7 +1775,8 b' def renderer_from_filename(filename, exc'
1750 return None
1775 return None
1751
1776
1752
1777
1753 def render(source, renderer='rst', mentions=False, relative_url=None):
1778 def render(source, renderer='rst', mentions=False, relative_url=None,
1779 repo_name=None):
1754
1780
1755 def maybe_convert_relative_links(html_source):
1781 def maybe_convert_relative_links(html_source):
1756 if relative_url:
1782 if relative_url:
@@ -1758,11 +1784,21 b" def render(source, renderer='rst', menti"
1758 return html_source
1784 return html_source
1759
1785
1760 if renderer == 'rst':
1786 if renderer == 'rst':
1787 if repo_name:
1788 # process patterns on comments if we pass in repo name
1789 source, issues = process_patterns(
1790 source, repo_name, link_format='rst')
1791
1761 return literal(
1792 return literal(
1762 '<div class="rst-block">%s</div>' %
1793 '<div class="rst-block">%s</div>' %
1763 maybe_convert_relative_links(
1794 maybe_convert_relative_links(
1764 MarkupRenderer.rst(source, mentions=mentions)))
1795 MarkupRenderer.rst(source, mentions=mentions)))
1765 elif renderer == 'markdown':
1796 elif renderer == 'markdown':
1797 if repo_name:
1798 # process patterns on comments if we pass in repo name
1799 source, issues = process_patterns(
1800 source, repo_name, link_format='markdown')
1801
1766 return literal(
1802 return literal(
1767 '<div class="markdown-block">%s</div>' %
1803 '<div class="markdown-block">%s</div>' %
1768 maybe_convert_relative_links(
1804 maybe_convert_relative_links(
@@ -1801,6 +1837,7 b' def journal_filter_help():'
1801 'Example filter terms:\n' +
1837 'Example filter terms:\n' +
1802 ' repository:vcs\n' +
1838 ' repository:vcs\n' +
1803 ' username:marcin\n' +
1839 ' username:marcin\n' +
1840 ' username:(NOT marcin)\n' +
1804 ' action:*push*\n' +
1841 ' action:*push*\n' +
1805 ' ip:127.0.0.1\n' +
1842 ' ip:127.0.0.1\n' +
1806 ' date:20120101\n' +
1843 ' date:20120101\n' +
@@ -1816,6 +1853,24 b' def journal_filter_help():'
1816 )
1853 )
1817
1854
1818
1855
1856 def search_filter_help(searcher):
1857
1858 terms = ''
1859 return _(
1860 'Example filter terms for `{searcher}` search:\n' +
1861 '{terms}\n' +
1862 'Generate wildcards using \'*\' character:\n' +
1863 ' "repo_name:vcs*" - search everything starting with \'vcs\'\n' +
1864 ' "repo_name:*vcs*" - search for repository containing \'vcs\'\n' +
1865 '\n' +
1866 'Optional AND / OR operators in queries\n' +
1867 ' "repo_name:vcs OR repo_name:test"\n' +
1868 ' "owner:test AND repo_name:test*"\n' +
1869 'More: {search_doc}'
1870 ).format(searcher=searcher.name,
1871 terms=terms, search_doc=searcher.query_lang_doc)
1872
1873
1819 def not_mapped_error(repo_name):
1874 def not_mapped_error(repo_name):
1820 flash(_('%s repository is not mapped to db perhaps'
1875 flash(_('%s repository is not mapped to db perhaps'
1821 ' it was created or renamed from the filesystem'
1876 ' it was created or renamed from the filesystem'
@@ -1914,24 +1969,24 b' def get_last_path_part(file_node):'
1914 return u'../' + path
1969 return u'../' + path
1915
1970
1916
1971
1917 def route_url(*args, **kwds):
1972 def route_url(*args, **kwargs):
1918 """
1973 """
1919 Wrapper around pyramids `route_url` function. It is used to generate
1974 Wrapper around pyramids `route_url` (fully qualified url) function.
1920 URLs from within pylons views or templates. This will be removed when
1975 It is used to generate URLs from within pylons views or templates.
1921 pyramid migration if finished.
1976 This will be removed when pyramid migration if finished.
1922 """
1977 """
1923 req = get_current_request()
1978 req = get_current_request()
1924 return req.route_url(*args, **kwds)
1979 return req.route_url(*args, **kwargs)
1925
1980
1926
1981
1927 def route_path(*args, **kwds):
1982 def route_path(*args, **kwargs):
1928 """
1983 """
1929 Wrapper around pyramids `route_path` function. It is used to generate
1984 Wrapper around pyramids `route_path` function. It is used to generate
1930 URLs from within pylons views or templates. This will be removed when
1985 URLs from within pylons views or templates. This will be removed when
1931 pyramid migration if finished.
1986 pyramid migration if finished.
1932 """
1987 """
1933 req = get_current_request()
1988 req = get_current_request()
1934 return req.route_path(*args, **kwds)
1989 return req.route_path(*args, **kwargs)
1935
1990
1936
1991
1937 def route_path_or_none(*args, **kwargs):
1992 def route_path_or_none(*args, **kwargs):
@@ -1959,3 +2014,23 b' def resource_path(*args, **kwds):'
1959 """
2014 """
1960 req = get_current_request()
2015 req = get_current_request()
1961 return req.resource_path(*args, **kwds)
2016 return req.resource_path(*args, **kwds)
2017
2018
2019 def api_call_example(method, args):
2020 """
2021 Generates an API call example via CURL
2022 """
2023 args_json = json.dumps(OrderedDict([
2024 ('id', 1),
2025 ('auth_token', 'SECRET'),
2026 ('method', method),
2027 ('args', args)
2028 ]))
2029 return literal(
2030 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2031 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2032 "and needs to be of `api calls` role."
2033 .format(
2034 api_url=route_url('apiv2'),
2035 token_url=route_url('my_account_auth_tokens'),
2036 data=args_json))
@@ -30,7 +30,7 b' import logging'
30 import rhodecode
30 import rhodecode
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib.utils import action_logger
33 from rhodecode.lib import audit_logger
34 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.utils2 import safe_str
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
36 from rhodecode.model.db import Repository, User
36 from rhodecode.model.db import Repository, User
@@ -152,9 +152,15 b' def pre_pull(extras):'
152
152
153 def post_pull(extras):
153 def post_pull(extras):
154 """Hook executed after client pulls the code."""
154 """Hook executed after client pulls the code."""
155 user = User.get_by_username(extras.username)
155
156 action = 'pull'
156 audit_user = audit_logger.UserWrap(
157 action_logger(user, action, extras.repository, extras.ip, commit=True)
157 username=extras.username,
158 ip_addr=extras.ip)
159 repo = audit_logger.RepoWrap(repo_name=extras.repository)
160 audit_logger.store(
161 'user.pull', action_data={
162 'user_agent': extras.user_agent},
163 user=audit_user, repo=repo, commit=True)
158
164
159 # Propagate to external components.
165 # Propagate to external components.
160 if not is_shadow_repo(extras):
166 if not is_shadow_repo(extras):
@@ -165,6 +171,7 b' def post_pull(extras):'
165 output = ''
171 output = ''
166 # make lock is a tri state False, True, None. We only make lock on True
172 # make lock is a tri state False, True, None. We only make lock on True
167 if extras.make_lock is True and not is_shadow_repo(extras):
173 if extras.make_lock is True and not is_shadow_repo(extras):
174 user = User.get_by_username(extras.username)
168 Repository.lock(Repository.get_by_repo_name(extras.repository),
175 Repository.lock(Repository.get_by_repo_name(extras.repository),
169 user.user_id,
176 user.user_id,
170 lock_reason=Repository.LOCK_PULL)
177 lock_reason=Repository.LOCK_PULL)
@@ -185,12 +192,17 b' def post_pull(extras):'
185
192
186 def post_push(extras):
193 def post_push(extras):
187 """Hook executed after user pushes to the repository."""
194 """Hook executed after user pushes to the repository."""
188 action_tmpl = extras.action + ':%s'
195 commit_ids = extras.commit_ids
189 commit_ids = extras.commit_ids[:29000]
190
196
191 action = action_tmpl % ','.join(commit_ids)
197 # log the push call
192 action_logger(
198 audit_user = audit_logger.UserWrap(
193 extras.username, action, extras.repository, extras.ip, commit=True)
199 username=extras.username, ip_addr=extras.ip)
200 repo = audit_logger.RepoWrap(repo_name=extras.repository)
201 audit_logger.store(
202 'user.push', action_data={
203 'user_agent': extras.user_agent,
204 'commit_ids': commit_ids[:10000]},
205 user=audit_user, repo=repo, commit=True)
194
206
195 # Propagate to external components.
207 # Propagate to external components.
196 if not is_shadow_repo(extras):
208 if not is_shadow_repo(extras):
@@ -220,8 +232,20 b' def post_push(extras):'
220 # 2xx Codes don't raise exceptions
232 # 2xx Codes don't raise exceptions
221 output += _http_ret.title
233 output += _http_ret.title
222
234
235 if extras.new_refs:
236 tmpl = \
237 extras.server_url + '/' + \
238 extras.repository + \
239 "/pull-request/new?{ref_type}={ref_name}"
240 for branch_name in extras.new_refs['branches']:
241 output += 'RhodeCode: open pull request link: {}\n'.format(
242 tmpl.format(ref_type='branch', ref_name=branch_name))
243
244 for book_name in extras.new_refs['bookmarks']:
245 output += 'RhodeCode: open pull request link: {}\n'.format(
246 tmpl.format(ref_type='bookmark', ref_name=book_name))
247
223 output += 'RhodeCode: push completed\n'
248 output += 'RhodeCode: push completed\n'
224
225 return HookResponse(0, output)
249 return HookResponse(0, output)
226
250
227
251
@@ -33,6 +33,8 b" default_location = '%(here)s/data/index'"
33
33
34
34
35 class BaseSearch(object):
35 class BaseSearch(object):
36 query_lang_doc = ''
37
36 def __init__(self):
38 def __init__(self):
37 pass
39 pass
38
40
@@ -61,7 +61,8 b' log = logging.getLogger(__name__)'
61
61
62
62
63 class Search(BaseSearch):
63 class Search(BaseSearch):
64
64 # this also shows in UI
65 query_lang_doc = 'http://whoosh.readthedocs.io/en/latest/querylang.html'
65 name = 'whoosh'
66 name = 'whoosh'
66
67
67 def __init__(self, config):
68 def __init__(self, config):
@@ -34,6 +34,8 b' from mako.template import Template as Ma'
34
34
35 from docutils.core import publish_parts
35 from docutils.core import publish_parts
36 from docutils.parsers.rst import directives
36 from docutils.parsers.rst import directives
37 from docutils import writers
38 from docutils.writers import html4css1
37 import markdown
39 import markdown
38
40
39 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
41 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
@@ -46,6 +48,31 b' log = logging.getLogger(__name__)'
46 DEFAULT_COMMENTS_RENDERER = 'rst'
48 DEFAULT_COMMENTS_RENDERER = 'rst'
47
49
48
50
51 class CustomHTMLTranslator(writers.html4css1.HTMLTranslator):
52 """
53 Custom HTML Translator used for sandboxing potential
54 JS injections in ref links
55 """
56
57 def visit_reference(self, node):
58 if 'refuri' in node.attributes:
59 refuri = node['refuri']
60 if ':' in refuri:
61 prefix, link = refuri.lstrip().split(':', 1)
62 if prefix == 'javascript':
63 # we don't allow javascript type of refs...
64 node['refuri'] = 'javascript:alert("SandBoxedJavascript")'
65
66 # old style class requires this...
67 return html4css1.HTMLTranslator.visit_reference(self, node)
68
69
70 class RhodeCodeWriter(writers.html4css1.Writer):
71 def __init__(self):
72 writers.Writer.__init__(self)
73 self.translator_class = CustomHTMLTranslator
74
75
49 def relative_links(html_source, server_path):
76 def relative_links(html_source, server_path):
50 if not html_source:
77 if not html_source:
51 return html_source
78 return html_source
@@ -56,12 +83,12 b' def relative_links(html_source, server_p'
56 return html_source
83 return html_source
57
84
58 for el in doc.cssselect('img, video'):
85 for el in doc.cssselect('img, video'):
59 src = el.attrib['src']
86 src = el.attrib.get('src')
60 if src:
87 if src:
61 el.attrib['src'] = relative_path(src, server_path)
88 el.attrib['src'] = relative_path(src, server_path)
62
89
63 for el in doc.cssselect('a:not(.gfm)'):
90 for el in doc.cssselect('a:not(.gfm)'):
64 src = el.attrib['href']
91 src = el.attrib.get('href')
65 if src:
92 if src:
66 el.attrib['href'] = relative_path(src, server_path)
93 el.attrib['href'] = relative_path(src, server_path)
67
94
@@ -341,7 +368,7 b' class MarkupRenderer(object):'
341 directives.register_directive(k, v)
368 directives.register_directive(k, v)
342
369
343 parts = publish_parts(source=source,
370 parts = publish_parts(source=source,
344 writer_name="html4css1",
371 writer=RhodeCodeWriter(),
345 settings_overrides=docutils_settings)
372 settings_overrides=docutils_settings)
346
373
347 return parts['html_title'] + parts["fragment"]
374 return parts['html_title'] + parts["fragment"]
@@ -36,7 +36,8 b' from webob.exc import ('
36 import rhodecode
36 import rhodecode
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 from rhodecode.lib.base import (
40 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
40 from rhodecode.lib.exceptions import (
41 from rhodecode.lib.exceptions import (
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
42 HTTPLockedRC, HTTPRequirementError, UserCreationError,
42 NotAllowedToCreateUserError)
43 NotAllowedToCreateUserError)
@@ -310,6 +311,7 b' class SimpleVCS(object):'
310 log.debug('Extracted repo name is %s', self.url_repo_name)
311 log.debug('Extracted repo name is %s', self.url_repo_name)
311
312
312 ip_addr = get_ip_addr(environ)
313 ip_addr = get_ip_addr(environ)
314 user_agent = get_user_agent(environ)
313 username = None
315 username = None
314
316
315 # skip passing error to error controller
317 # skip passing error to error controller
@@ -429,9 +431,9 b' class SimpleVCS(object):'
429 fix_PATH()
431 fix_PATH()
430
432
431 log.info(
433 log.info(
432 '%s action on %s repo "%s" by "%s" from %s',
434 '%s action on %s repo "%s" by "%s" from %s %s',
433 action, self.SCM, safe_str(self.url_repo_name),
435 action, self.SCM, safe_str(self.url_repo_name),
434 safe_str(username), ip_addr)
436 safe_str(username), ip_addr, user_agent)
435
437
436 return self._generate_vcs_response(
438 return self._generate_vcs_response(
437 environ, start_response, repo_path, extras, action)
439 environ, start_response, repo_path, extras, action)
@@ -114,14 +114,18 b' class Message(object):'
114
114
115 return response
115 return response
116
116
117 def _get_headers(self):
118 headers = [self.subject, self.sender]
119 headers += list(self.send_to)
120 headers += self.extra_headers.values()
121 return headers
122
117 def is_bad_headers(self):
123 def is_bad_headers(self):
118 """
124 """
119 Checks for bad headers i.e. newlines in subject, sender or recipients.
125 Checks for bad headers i.e. newlines in subject, sender or recipients.
120 """
126 """
121
127
122 headers = [self.subject, self.sender]
128 headers = self._get_headers()
123 headers += list(self.send_to)
124 headers += self.extra_headers.values()
125
129
126 for val in headers:
130 for val in headers:
127 for c in '\r\n':
131 for c in '\r\n':
@@ -144,7 +148,8 b' class Message(object):'
144 raise InvalidMessage("No sender address has been set")
148 raise InvalidMessage("No sender address has been set")
145
149
146 if self.is_bad_headers():
150 if self.is_bad_headers():
147 raise BadHeaders
151 headers = self._get_headers()
152 raise BadHeaders(headers)
148
153
149 def add_recipient(self, recipient):
154 def add_recipient(self, recipient):
150 """
155 """
@@ -83,6 +83,15 b' class HGVerify(MaintenanceTask):'
83 return res
83 return res
84
84
85
85
86 class SVNVerify(MaintenanceTask):
87 human_name = 'SVN Verify repo'
88
89 def run(self):
90 instance = self.db_repo.scm_instance()
91 res = instance.verify()
92 return res
93
94
86 class RepoMaintenance(object):
95 class RepoMaintenance(object):
87 """
96 """
88 Performs maintenance of repository based on it's type
97 Performs maintenance of repository based on it's type
@@ -90,7 +99,7 b' class RepoMaintenance(object):'
90 tasks = {
99 tasks = {
91 'hg': [HGVerify],
100 'hg': [HGVerify],
92 'git': [GitGC],
101 'git': [GitGC],
93 'svn': [],
102 'svn': [SVNVerify],
94 }
103 }
95
104
96 def get_tasks_for_repo(self, db_repo):
105 def get_tasks_for_repo(self, db_repo):
@@ -570,6 +570,10 b' def rhodecode_config():'
570 log.exception('Failed to read .ini file for display')
570 log.exception('Failed to read .ini file for display')
571 parsed_ini = {}
571 parsed_ini = {}
572
572
573 cert_path = os.path.join(
574 os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(path)))),
575 '.rccontrol-profile/etc/ca-bundle.crt')
576
573 rhodecode_ini_safe['server:main'] = parsed_ini
577 rhodecode_ini_safe['server:main'] = parsed_ini
574
578
575 blacklist = [
579 blacklist = [
@@ -608,7 +612,8 b' def rhodecode_config():'
608 rhodecode_ini_safe.pop(k, None)
612 rhodecode_ini_safe.pop(k, None)
609
613
610 # TODO: maybe put some CONFIG checks here ?
614 # TODO: maybe put some CONFIG checks here ?
611 return SysInfoRes(value={'config': rhodecode_ini_safe, 'path': path})
615 return SysInfoRes(value={'config': rhodecode_ini_safe,
616 'path': path, 'cert_path': cert_path})
612
617
613
618
614 def database_info():
619 def database_info():
@@ -23,10 +23,10 b' import logging'
23 from whoosh.qparser.default import QueryParser, query
23 from whoosh.qparser.default import QueryParser, query
24 from whoosh.qparser.dateparse import DateParserPlugin
24 from whoosh.qparser.dateparse import DateParserPlugin
25 from whoosh.fields import (TEXT, Schema, DATETIME)
25 from whoosh.fields import (TEXT, Schema, DATETIME)
26 from sqlalchemy.sql.expression import or_, and_, func
26 from sqlalchemy.sql.expression import or_, and_, not_, func
27
27
28 from rhodecode.model.db import UserLog
28 from rhodecode.model.db import UserLog
29 from rhodecode.lib.utils2 import remove_prefix, remove_suffix
29 from rhodecode.lib.utils2 import remove_prefix, remove_suffix, safe_unicode
30
30
31 # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh
31 # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh
32 # querylang to build sql queries and filter journals
32 # querylang to build sql queries and filter journals
@@ -54,7 +54,7 b' def user_log_filter(user_log, search_ter'
54 if search_term:
54 if search_term:
55 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
55 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
56 qp.add_plugin(DateParserPlugin())
56 qp.add_plugin(DateParserPlugin())
57 qry = qp.parse(unicode(search_term))
57 qry = qp.parse(safe_unicode(search_term))
58 log.debug('Filtering using parsed query %r' % qry)
58 log.debug('Filtering using parsed query %r' % qry)
59
59
60 def wildcard_handler(col, wc_term):
60 def wildcard_handler(col, wc_term):
@@ -89,16 +89,27 b' def user_log_filter(user_log, search_ter'
89 return func.lower(field).startswith(func.lower(val))
89 return func.lower(field).startswith(func.lower(val))
90 elif isinstance(term, query.DateRange):
90 elif isinstance(term, query.DateRange):
91 return and_(field >= val[0], field <= val[1])
91 return and_(field >= val[0], field <= val[1])
92 elif isinstance(term, query.Not):
93 return not_(field == val)
92 return func.lower(field) == func.lower(val)
94 return func.lower(field) == func.lower(val)
93
95
94 if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
96 if isinstance(qry, (query.And, query.Not, query.Term, query.Prefix,
95 query.DateRange)):
97 query.Wildcard, query.DateRange)):
96 if not isinstance(qry, query.And):
98 if not isinstance(qry, query.And):
97 qry = [qry]
99 qry = [qry]
100
98 for term in qry:
101 for term in qry:
99 field = term.fieldname
102 if isinstance(term, query.Not):
100 val = (term.text if not isinstance(term, query.DateRange)
103 not_term = [z for z in term.leaves()][0]
101 else [term.startdate, term.enddate])
104 field = not_term.fieldname
105 val = not_term.text
106 elif isinstance(term, query.DateRange):
107 field = term.fieldname
108 val = [term.startdate, term.enddate]
109 else:
110 field = term.fieldname
111 val = term.text
112
102 user_log = user_log.filter(get_filterion(field, val, term))
113 user_log = user_log.filter(get_filterion(field, val, term))
103 elif isinstance(qry, query.Or):
114 elif isinstance(qry, query.Or):
104 filters = []
115 filters = []
@@ -96,10 +96,11 b' def repo_name_slug(value):'
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 if isinstance(request, Request) and getattr(request, 'matchdict', None):
99 if isinstance(request, Request) and getattr(request, 'db_repo', None):
100 # pyramid
100 # pyramid
101 _repo = request.matchdict.get('repo_name')
101 _repo = request.db_repo.repo_name
102 else:
102 else:
103 # TODO(marcink): remove after pylons migration...
103 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104
105
105 if _repo:
106 if _repo:
@@ -110,7 +111,7 b' def get_repo_slug(request):'
110 def get_repo_group_slug(request):
111 def get_repo_group_slug(request):
111 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 # pyramid
113 # pyramid
113 _group = request.matchdict.get('group_name')
114 _group = request.matchdict.get('repo_group_name')
114 else:
115 else:
115 _group = request.environ['pylons.routes_dict'].get('group_name')
116 _group = request.environ['pylons.routes_dict'].get('group_name')
116
117
@@ -138,68 +139,6 b' def get_user_group_slug(request):'
138 return _group
139 return _group
139
140
140
141
141 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
142 """
143 Action logger for various actions made by users
144
145 :param user: user that made this action, can be a unique username string or
146 object containing user_id attribute
147 :param action: action to log, should be on of predefined unique actions for
148 easy translations
149 :param repo: string name of repository or object containing repo_id,
150 that action was made on
151 :param ipaddr: optional ip address from what the action was made
152 :param sa: optional sqlalchemy session
153
154 """
155
156 if not sa:
157 sa = meta.Session()
158 # if we don't get explicit IP address try to get one from registered user
159 # in tmpl context var
160 if not ipaddr:
161 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
162
163 try:
164 if getattr(user, 'user_id', None):
165 user_obj = User.get(user.user_id)
166 elif isinstance(user, basestring):
167 user_obj = User.get_by_username(user)
168 else:
169 raise Exception('You have to provide a user object or a username')
170
171 if getattr(repo, 'repo_id', None):
172 repo_obj = Repository.get(repo.repo_id)
173 repo_name = repo_obj.repo_name
174 elif isinstance(repo, basestring):
175 repo_name = repo.lstrip('/')
176 repo_obj = Repository.get_by_repo_name(repo_name)
177 else:
178 repo_obj = None
179 repo_name = ''
180
181 user_log = UserLog()
182 user_log.user_id = user_obj.user_id
183 user_log.username = user_obj.username
184 action = safe_unicode(action)
185 user_log.action = action[:1200000]
186
187 user_log.repository = repo_obj
188 user_log.repository_name = repo_name
189
190 user_log.action_date = datetime.datetime.now()
191 user_log.user_ip = ipaddr
192 sa.add(user_log)
193
194 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
195 action, safe_unicode(repo), user_obj, ipaddr)
196 if commit:
197 sa.commit()
198 except Exception:
199 log.error(traceback.format_exc())
200 raise
201
202
203 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
204 """
143 """
205 Scans given path for repos and return (name,(type,path)) tuple
144 Scans given path for repos and return (name,(type,path)) tuple
@@ -428,6 +367,7 b' def config_data_from_db(clear_session=Tr'
428 if 'push' not in enabled_hook_classes:
367 if 'push' not in enabled_hook_classes:
429 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
430 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
431
371
432 config = [entry for entry in config if entry[:2] not in skip_entries]
372 config = [entry for entry in config if entry[:2] not in skip_entries]
433
373
@@ -572,7 +572,8 b' def credentials_filter(uri):'
572 return ''.join(uri)
572 return ''.join(uri)
573
573
574
574
575 def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
575 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
576 qualifed_home_url = request.route_url('home')
576 parsed_url = urlobject.URLObject(qualifed_home_url)
577 parsed_url = urlobject.URLObject(qualifed_home_url)
577 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
578 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
578 args = {
579 args = {
@@ -130,7 +130,7 b' class UpdateFailureReason(object):'
130 NO_CHANGE = 2
130 NO_CHANGE = 2
131
131
132 # The pull request has a reference type that is not supported for update.
132 # The pull request has a reference type that is not supported for update.
133 WRONG_REF_TPYE = 3
133 WRONG_REF_TYPE = 3
134
134
135 # Update failed because the target reference is missing.
135 # Update failed because the target reference is missing.
136 MISSING_TARGET_REF = 4
136 MISSING_TARGET_REF = 4
@@ -1065,8 +1065,8 b' class BaseCommit(object):'
1065
1065
1066 def no_node_at_path(self, path):
1066 def no_node_at_path(self, path):
1067 return NodeDoesNotExistError(
1067 return NodeDoesNotExistError(
1068 "There is no file nor directory at the given path: "
1068 u"There is no file nor directory at the given path: "
1069 "'%s' at commit %s" % (path, self.short_id))
1069 u"`%s` at commit %s" % (safe_unicode(path), self.short_id))
1070
1070
1071 def _fix_path(self, path):
1071 def _fix_path(self, path):
1072 """
1072 """
@@ -160,6 +160,27 b' class MercurialCommit(base.BaseCommit):'
160 return self._make_commits(parents)
160 return self._make_commits(parents)
161
161
162 @LazyProperty
162 @LazyProperty
163 def phase(self):
164 phase_id = self._remote.ctx_phase(self.idx)
165 phase_text = {
166 0: 'public',
167 1: 'draft',
168 2: 'secret',
169 }.get(phase_id) or ''
170
171 return safe_unicode(phase_text)
172
173 @LazyProperty
174 def obsolete(self):
175 obsolete = self._remote.ctx_obsolete(self.idx)
176 return obsolete
177
178 @LazyProperty
179 def hidden(self):
180 hidden = self._remote.ctx_hidden(self.idx)
181 return hidden
182
183 @LazyProperty
163 def children(self):
184 def children(self):
164 """
185 """
165 Returns list of child commits.
186 Returns list of child commits.
@@ -158,6 +158,12 b' class SubversionRepository(base.BaseRepo'
158 return commit_id1
158 return commit_id1
159 return commit_id2
159 return commit_id2
160
160
161 def verify(self):
162 verify = self._remote.verify()
163
164 self._remote.invalidate_vcs_cache()
165 return verify
166
161 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
167 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
162 # TODO: johbo: Implement better comparison, this is a very naive
168 # TODO: johbo: Implement better comparison, this is a very naive
163 # version which does not allow to compare branches, tags or folders
169 # version which does not allow to compare branches, tags or folders
@@ -59,23 +59,25 b' class AuthTokenModel(BaseModel):'
59
59
60 return new_auth_token
60 return new_auth_token
61
61
62 def delete(self, api_key, user=None):
62 def delete(self, auth_token_id, user=None):
63 """
63 """
64 Deletes given api_key, if user is set it also filters the object for
64 Deletes given api_key, if user is set it also filters the object for
65 deletion by given user.
65 deletion by given user.
66 """
66 """
67 api_key = UserApiKeys.query().filter(UserApiKeys.api_key == api_key)
67 auth_token = UserApiKeys.query().filter(
68 UserApiKeys.user_api_key_id == auth_token_id)
68
69
69 if user:
70 if user:
70 user = self._get_user(user)
71 user = self._get_user(user)
71 api_key = api_key.filter(UserApiKeys.user_id == user.user_id)
72 auth_token = auth_token.filter(UserApiKeys.user_id == user.user_id)
73 auth_token = auth_token.scalar()
72
74
73 api_key = api_key.scalar()
75 if auth_token:
74 try:
76 try:
75 Session().delete(api_key)
77 Session().delete(auth_token)
76 except Exception:
78 except Exception:
77 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
78 raise
80 raise
79
81
80 def get_auth_tokens(self, user, show_expired=True):
82 def get_auth_tokens(self, user, show_expired=True):
81 user = self._get_user(user)
83 user = self._get_user(user)
@@ -80,7 +80,7 b' class ChangesetStatusModel(BaseModel):'
80 """
80 """
81 votes = defaultdict(int)
81 votes = defaultdict(int)
82 reviewers_number = len(statuses_by_reviewers)
82 reviewers_number = len(statuses_by_reviewers)
83 for user, reasons, statuses in statuses_by_reviewers:
83 for user, reasons, mandatory, statuses in statuses_by_reviewers:
84 if statuses:
84 if statuses:
85 ver, latest = statuses[0]
85 ver, latest = statuses[0]
86 votes[latest.status] += 1
86 votes[latest.status] += 1
@@ -248,13 +248,14 b' class ChangesetStatusModel(BaseModel):'
248 for o in pull_request.reviewers:
248 for o in pull_request.reviewers:
249 if not o.user:
249 if not o.user:
250 continue
250 continue
251 st = commit_statuses.get(o.user.username, None)
251 statuses = commit_statuses.get(o.user.username, None)
252 if st:
252 if statuses:
253 st = [(x, list(y)[0])
253 statuses = [(x, list(y)[0])
254 for x, y in (itertools.groupby(sorted(st, key=version),
254 for x, y in (itertools.groupby(
255 version))]
255 sorted(statuses, key=version),version))]
256
256
257 pull_request_reviewers.append((o.user, o.reasons, st))
257 pull_request_reviewers.append(
258 (o.user, o.reasons, o.mandatory, statuses))
258 return pull_request_reviewers
259 return pull_request_reviewers
259
260
260 def calculated_review_status(self, pull_request, reviewers_statuses=None):
261 def calculated_review_status(self, pull_request, reviewers_statuses=None):
@@ -29,14 +29,14 b' import collections'
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pyramid.threadlocal import get_current_registry
32 from pyramid.threadlocal import get_current_registry, get_current_request
33 from sqlalchemy.sql.expression import null
33 from sqlalchemy.sql.expression import null
34 from sqlalchemy.sql.functions import coalesce
34 from sqlalchemy.sql.functions import coalesce
35
35
36 from rhodecode.lib import helpers as h, diffs
36 from rhodecode.lib import helpers as h, diffs
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib.channelstream import channelstream_request
38 from rhodecode.lib.channelstream import channelstream_request
38 from rhodecode.lib.utils import action_logger
39 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
39 from rhodecode.lib.utils2 import extract_mentioned_users
40 from rhodecode.model import BaseModel
40 from rhodecode.model import BaseModel
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 ChangesetComment, User, Notification, PullRequest, AttributeDict)
42 ChangesetComment, User, Notification, PullRequest, AttributeDict)
@@ -163,6 +163,13 b' class CommentsModel(BaseModel):'
163
163
164 return todos
164 return todos
165
165
166 def _log_audit_action(self, action, action_data, user, comment):
167 audit_logger.store(
168 action=action,
169 action_data=action_data,
170 user=user,
171 repo=comment.repo)
172
166 def create(self, text, repo, user, commit_id=None, pull_request=None,
173 def create(self, text, repo, user, commit_id=None, pull_request=None,
167 f_path=None, line_no=None, status_change=None,
174 f_path=None, line_no=None, status_change=None,
168 status_change_type=None, comment_type=None,
175 status_change_type=None, comment_type=None,
@@ -268,8 +275,7 b' class CommentsModel(BaseModel):'
268
275
269 target_repo_url = h.link_to(
276 target_repo_url = h.link_to(
270 repo.repo_name,
277 repo.repo_name,
271 h.url('summary_home',
278 h.route_url('repo_summary', repo_name=repo.repo_name))
272 repo_name=repo.repo_name, qualified=True))
273
279
274 # commit specifics
280 # commit specifics
275 kwargs.update({
281 kwargs.update({
@@ -300,13 +306,11 b' class CommentsModel(BaseModel):'
300 qualified=True,)
306 qualified=True,)
301
307
302 # set some variables for email notification
308 # set some variables for email notification
303 pr_target_repo_url = h.url(
309 pr_target_repo_url = h.route_url(
304 'summary_home', repo_name=pr_target_repo.repo_name,
310 'repo_summary', repo_name=pr_target_repo.repo_name)
305 qualified=True)
306
311
307 pr_source_repo_url = h.url(
312 pr_source_repo_url = h.route_url(
308 'summary_home', repo_name=pr_source_repo.repo_name,
313 'repo_summary', repo_name=pr_source_repo.repo_name)
309 qualified=True)
310
314
311 # pull request specifics
315 # pull request specifics
312 kwargs.update({
316 kwargs.update({
@@ -340,13 +344,15 b' class CommentsModel(BaseModel):'
340 email_kwargs=kwargs,
344 email_kwargs=kwargs,
341 )
345 )
342
346
343 action = (
347 Session().flush()
344 'user_commented_pull_request:{}'.format(
348 if comment.pull_request:
345 comment.pull_request.pull_request_id)
349 action = 'repo.pull_request.comment.create'
346 if comment.pull_request
350 else:
347 else 'user_commented_revision:{}'.format(comment.revision)
351 action = 'repo.commit.comment.create'
348 )
352
349 action_logger(user, action, comment.repo)
353 comment_data = comment.get_api_data()
354 self._log_audit_action(
355 action, {'data': comment_data}, user, comment)
350
356
351 registry = get_current_registry()
357 registry = get_current_registry()
352 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
358 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
@@ -388,15 +394,22 b' class CommentsModel(BaseModel):'
388
394
389 return comment
395 return comment
390
396
391 def delete(self, comment):
397 def delete(self, comment, user):
392 """
398 """
393 Deletes given comment
399 Deletes given comment
394
395 :param comment_id:
396 """
400 """
397 comment = self.__get_commit_comment(comment)
401 comment = self.__get_commit_comment(comment)
402 old_data = comment.get_api_data()
398 Session().delete(comment)
403 Session().delete(comment)
399
404
405 if comment.pull_request:
406 action = 'repo.pull_request.comment.delete'
407 else:
408 action = 'repo.commit.comment.delete'
409
410 self._log_audit_action(
411 action, {'old_data': old_data}, user, comment)
412
400 return comment
413 return comment
401
414
402 def get_all_comments(self, repo_id, revision=None, pull_request=None):
415 def get_all_comments(self, repo_id, revision=None, pull_request=None):
@@ -412,22 +425,39 b' class CommentsModel(BaseModel):'
412 q = q.order_by(ChangesetComment.created_on)
425 q = q.order_by(ChangesetComment.created_on)
413 return q.all()
426 return q.all()
414
427
415 def get_url(self, comment):
428 def get_url(self, comment, request=None, permalink=False):
429 if not request:
430 request = get_current_request()
431
416 comment = self.__get_commit_comment(comment)
432 comment = self.__get_commit_comment(comment)
417 if comment.pull_request:
433 if comment.pull_request:
418 return h.url(
434 pull_request = comment.pull_request
419 'pullrequest_show',
435 if permalink:
420 repo_name=comment.pull_request.target_repo.repo_name,
436 return request.route_url(
421 pull_request_id=comment.pull_request.pull_request_id,
437 'pull_requests_global',
422 anchor='comment-%s' % comment.comment_id,
438 pull_request_id=pull_request.pull_request_id,
423 qualified=True,)
439 _anchor='comment-%s' % comment.comment_id)
440 else:
441 return request.route_url('pullrequest_show',
442 repo_name=safe_str(pull_request.target_repo.repo_name),
443 pull_request_id=pull_request.pull_request_id,
444 _anchor='comment-%s' % comment.comment_id)
445
424 else:
446 else:
425 return h.url(
447 repo = comment.repo
426 'changeset_home',
448 commit_id = comment.revision
427 repo_name=comment.repo.repo_name,
449
428 revision=comment.revision,
450 if permalink:
429 anchor='comment-%s' % comment.comment_id,
451 return request.route_url(
430 qualified=True,)
452 'repo_commit', repo_name=safe_str(repo.repo_id),
453 commit_id=commit_id,
454 _anchor='comment-%s' % comment.comment_id)
455
456 else:
457 return request.route_url(
458 'repo_commit', repo_name=safe_str(repo.repo_name),
459 commit_id=commit_id,
460 _anchor='comment-%s' % comment.comment_id)
431
461
432 def get_comments(self, repo_id, revision=None, pull_request=None):
462 def get_comments(self, repo_id, revision=None, pull_request=None):
433 """
463 """
@@ -44,8 +44,8 b' from sqlalchemy.sql.expression import tr'
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pylons import url
48 from pylons.i18n.translation import lazy_ugettext as _
47 from pylons.i18n.translation import lazy_ugettext as _
48 from pyramid.threadlocal import get_current_request
49
49
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
@@ -358,6 +358,7 b' class RhodeCodeUi(Base, BaseModel):'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
361
362
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # git part is currently hardcoded.
364 # git part is currently hardcoded.
@@ -571,6 +572,20 b' class User(Base, BaseModel):'
571 self._email = val.lower() if val else None
572 self._email = val.lower() if val else None
572
573
573 @hybrid_property
574 @hybrid_property
575 def first_name(self):
576 from rhodecode.lib import helpers as h
577 if self.name:
578 return h.escape(self.name)
579 return self.name
580
581 @hybrid_property
582 def last_name(self):
583 from rhodecode.lib import helpers as h
584 if self.lastname:
585 return h.escape(self.lastname)
586 return self.lastname
587
588 @hybrid_property
574 def api_key(self):
589 def api_key(self):
575 """
590 """
576 Fetch if exist an auth-token with role ALL connected to this user
591 Fetch if exist an auth-token with role ALL connected to this user
@@ -689,7 +704,7 b' class User(Base, BaseModel):'
689
704
690 @property
705 @property
691 def username_and_name(self):
706 def username_and_name(self):
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
707 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
693
708
694 @property
709 @property
695 def username_or_name_or_email(self):
710 def username_or_name_or_email(self):
@@ -698,20 +713,20 b' class User(Base, BaseModel):'
698
713
699 @property
714 @property
700 def full_name(self):
715 def full_name(self):
701 return '%s %s' % (self.firstname, self.lastname)
716 return '%s %s' % (self.first_name, self.last_name)
702
717
703 @property
718 @property
704 def full_name_or_username(self):
719 def full_name_or_username(self):
705 return ('%s %s' % (self.firstname, self.lastname)
720 return ('%s %s' % (self.first_name, self.last_name)
706 if (self.firstname and self.lastname) else self.username)
721 if (self.first_name and self.last_name) else self.username)
707
722
708 @property
723 @property
709 def full_contact(self):
724 def full_contact(self):
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
725 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
711
726
712 @property
727 @property
713 def short_contact(self):
728 def short_contact(self):
714 return '%s %s' % (self.firstname, self.lastname)
729 return '%s %s' % (self.first_name, self.last_name)
715
730
716 @property
731 @property
717 def is_admin(self):
732 def is_admin(self):
@@ -761,9 +776,9 b' class User(Base, BaseModel):'
761 if val:
776 if val:
762 return val
777 return val
763 else:
778 else:
779 cache_key = "get_user_by_name_%s" % _hash_key(username)
764 q = q.options(
780 q = q.options(
765 FromCache("sql_cache_short",
781 FromCache("sql_cache_short", cache_key))
766 "get_user_by_name_%s" % _hash_key(username)))
767
782
768 return q.scalar()
783 return q.scalar()
769
784
@@ -774,8 +789,8 b' class User(Base, BaseModel):'
774 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
775 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
776 if cache:
791 if cache:
777 q = q.options(FromCache("sql_cache_short",
792 q = q.options(
778 "get_auth_token_%s" % auth_token))
793 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
779
794
780 match = q.first()
795 match = q.first()
781 if match:
796 if match:
@@ -790,9 +805,10 b' class User(Base, BaseModel):'
790 else:
805 else:
791 q = cls.query().filter(cls.email == email)
806 q = cls.query().filter(cls.email == email)
792
807
808 email_key = _hash_key(email)
793 if cache:
809 if cache:
794 q = q.options(FromCache("sql_cache_short",
810 q = q.options(
795 "get_email_key_%s" % _hash_key(email)))
811 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
796
812
797 ret = q.scalar()
813 ret = q.scalar()
798 if ret is None:
814 if ret is None:
@@ -804,8 +820,8 b' class User(Base, BaseModel):'
804 q = q.filter(UserEmailMap.email == email)
820 q = q.filter(UserEmailMap.email == email)
805 q = q.options(joinedload(UserEmailMap.user))
821 q = q.options(joinedload(UserEmailMap.user))
806 if cache:
822 if cache:
807 q = q.options(FromCache("sql_cache_short",
823 q = q.options(
808 "get_email_map_key_%s" % email))
824 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
809 ret = getattr(q.scalar(), 'user', None)
825 ret = getattr(q.scalar(), 'user', None)
810
826
811 return ret
827 return ret
@@ -872,10 +888,16 b' class User(Base, BaseModel):'
872 .order_by(User.username.asc()).all()
888 .order_by(User.username.asc()).all()
873
889
874 @classmethod
890 @classmethod
875 def get_default_user(cls, cache=False):
891 def get_default_user(cls, cache=False, refresh=False):
876 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
892 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
877 if user is None:
893 if user is None:
878 raise Exception('FATAL: Missing default account!')
894 raise Exception('FATAL: Missing default account!')
895 if refresh:
896 # The default user might be based on outdated state which
897 # has been loaded from the cache.
898 # A call to refresh() ensures that the
899 # latest state from the database is used.
900 Session().refresh(user)
879 return user
901 return user
880
902
881 def _get_default_perms(self, user, suffix=''):
903 def _get_default_perms(self, user, suffix=''):
@@ -996,6 +1018,19 b' class UserApiKeys(Base, BaseModel):'
996 }
1018 }
997 return data
1019 return data
998
1020
1021 def get_api_data(self, include_secrets=False):
1022 data = self.__json__()
1023 if include_secrets:
1024 return data
1025 else:
1026 data['auth_token'] = self.token_obfuscated
1027 return data
1028
1029 @hybrid_property
1030 def description_safe(self):
1031 from rhodecode.lib import helpers as h
1032 return h.escape(self.description)
1033
999 @property
1034 @property
1000 def expired(self):
1035 def expired(self):
1001 if self.expires == -1:
1036 if self.expires == -1:
@@ -1027,6 +1062,11 b' class UserApiKeys(Base, BaseModel):'
1027 def scope_humanized(self):
1062 def scope_humanized(self):
1028 return self._get_scope()
1063 return self._get_scope()
1029
1064
1065 @property
1066 def token_obfuscated(self):
1067 if self.api_key:
1068 return self.api_key[:4] + "****"
1069
1030
1070
1031 class UserEmailMap(Base, BaseModel):
1071 class UserEmailMap(Base, BaseModel):
1032 __tablename__ = 'user_email_map'
1072 __tablename__ = 'user_email_map'
@@ -1076,6 +1116,11 b' class UserIpMap(Base, BaseModel):'
1076 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1116 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1077 user = relationship('User', lazy='joined')
1117 user = relationship('User', lazy='joined')
1078
1118
1119 @hybrid_property
1120 def description_safe(self):
1121 from rhodecode.lib import helpers as h
1122 return h.escape(self.description)
1123
1079 @classmethod
1124 @classmethod
1080 def _get_ip_range(cls, ip_addr):
1125 def _get_ip_range(cls, ip_addr):
1081 net = ipaddress.ip_network(ip_addr, strict=False)
1126 net = ipaddress.ip_network(ip_addr, strict=False)
@@ -1098,6 +1143,10 b' class UserLog(Base, BaseModel):'
1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1100 )
1145 )
1146 VERSION_1 = 'v1'
1147 VERSION_2 = 'v2'
1148 VERSIONS = [VERSION_1, VERSION_2]
1149
1101 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1150 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1102 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1151 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1103 username = Column("username", String(255), nullable=True, unique=None, default=None)
1152 username = Column("username", String(255), nullable=True, unique=None, default=None)
@@ -1107,6 +1156,10 b' class UserLog(Base, BaseModel):'
1107 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1156 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1108 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1157 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1109
1158
1159 version = Column("version", String(255), nullable=True, default=VERSION_1)
1160 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1161 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1162
1110 def __unicode__(self):
1163 def __unicode__(self):
1111 return u"<%s('id:%s:%s')>" % (
1164 return u"<%s('id:%s:%s')>" % (
1112 self.__class__.__name__, self.repository_name, self.action)
1165 self.__class__.__name__, self.repository_name, self.action)
@@ -1156,6 +1209,11 b' class UserGroup(Base, BaseModel):'
1156 user = relationship('User')
1209 user = relationship('User')
1157
1210
1158 @hybrid_property
1211 @hybrid_property
1212 def description_safe(self):
1213 from rhodecode.lib import helpers as h
1214 return h.escape(self.description)
1215
1216 @hybrid_property
1159 def group_data(self):
1217 def group_data(self):
1160 if not self._group_data:
1218 if not self._group_data:
1161 return {}
1219 return {}
@@ -1187,17 +1245,16 b' class UserGroup(Base, BaseModel):'
1187 else:
1245 else:
1188 q = cls.query().filter(cls.users_group_name == group_name)
1246 q = cls.query().filter(cls.users_group_name == group_name)
1189 if cache:
1247 if cache:
1190 q = q.options(FromCache(
1248 q = q.options(
1191 "sql_cache_short",
1249 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1192 "get_group_%s" % _hash_key(group_name)))
1193 return q.scalar()
1250 return q.scalar()
1194
1251
1195 @classmethod
1252 @classmethod
1196 def get(cls, user_group_id, cache=False):
1253 def get(cls, user_group_id, cache=False):
1197 user_group = cls.query()
1254 user_group = cls.query()
1198 if cache:
1255 if cache:
1199 user_group = user_group.options(FromCache("sql_cache_short",
1256 user_group = user_group.options(
1200 "get_users_group_%s" % user_group_id))
1257 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1201 return user_group.get(user_group_id)
1258 return user_group.get(user_group_id)
1202
1259
1203 def permissions(self, with_admins=True, with_owner=True):
1260 def permissions(self, with_admins=True, with_owner=True):
@@ -1454,6 +1511,11 b' class Repository(Base, BaseModel):'
1454 safe_unicode(self.repo_name))
1511 safe_unicode(self.repo_name))
1455
1512
1456 @hybrid_property
1513 @hybrid_property
1514 def description_safe(self):
1515 from rhodecode.lib import helpers as h
1516 return h.escape(self.description)
1517
1518 @hybrid_property
1457 def landing_rev(self):
1519 def landing_rev(self):
1458 # always should return [rev_type, rev]
1520 # always should return [rev_type, rev]
1459 if self._landing_revision:
1521 if self._landing_revision:
@@ -1538,9 +1600,9 b' class Repository(Base, BaseModel):'
1538 if val:
1600 if val:
1539 return val
1601 return val
1540 else:
1602 else:
1603 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1541 q = q.options(
1604 q = q.options(
1542 FromCache("sql_cache_short",
1605 FromCache("sql_cache_short", cache_key))
1543 "get_repo_by_name_%s" % _hash_key(repo_name)))
1544
1606
1545 return q.scalar()
1607 return q.scalar()
1546
1608
@@ -1750,6 +1812,7 b' class Repository(Base, BaseModel):'
1750 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1812 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1751 # move this methods on models level.
1813 # move this methods on models level.
1752 from rhodecode.model.settings import SettingsModel
1814 from rhodecode.model.settings import SettingsModel
1815 from rhodecode.model.repo import RepoModel
1753
1816
1754 repo = self
1817 repo = self
1755 _user_id, _time, _reason = self.locked
1818 _user_id, _time, _reason = self.locked
@@ -1759,13 +1822,14 b' class Repository(Base, BaseModel):'
1759 'repo_name': repo.repo_name,
1822 'repo_name': repo.repo_name,
1760 'repo_type': repo.repo_type,
1823 'repo_type': repo.repo_type,
1761 'clone_uri': repo.clone_uri or '',
1824 'clone_uri': repo.clone_uri or '',
1762 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1825 'url': RepoModel().get_url(self),
1763 'private': repo.private,
1826 'private': repo.private,
1764 'created_on': repo.created_on,
1827 'created_on': repo.created_on,
1765 'description': repo.description,
1828 'description': repo.description_safe,
1766 'landing_rev': repo.landing_rev,
1829 'landing_rev': repo.landing_rev,
1767 'owner': repo.user.username,
1830 'owner': repo.user.username,
1768 'fork_of': repo.fork.repo_name if repo.fork else None,
1831 'fork_of': repo.fork.repo_name if repo.fork else None,
1832 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1769 'enable_statistics': repo.enable_statistics,
1833 'enable_statistics': repo.enable_statistics,
1770 'enable_locking': repo.enable_locking,
1834 'enable_locking': repo.enable_locking,
1771 'enable_downloads': repo.enable_downloads,
1835 'enable_downloads': repo.enable_downloads,
@@ -1893,7 +1957,6 b' class Repository(Base, BaseModel):'
1893 return clone_uri
1957 return clone_uri
1894
1958
1895 def clone_url(self, **override):
1959 def clone_url(self, **override):
1896 qualified_home_url = url('home', qualified=True)
1897
1960
1898 uri_tmpl = None
1961 uri_tmpl = None
1899 if 'with_id' in override:
1962 if 'with_id' in override:
@@ -1915,8 +1978,9 b' class Repository(Base, BaseModel):'
1915 # ie, not having tmpl_context set up
1978 # ie, not having tmpl_context set up
1916 pass
1979 pass
1917
1980
1918 return get_clone_url(uri_tmpl=uri_tmpl,
1981 request = get_current_request()
1919 qualifed_home_url=qualified_home_url,
1982 return get_clone_url(request=request,
1983 uri_tmpl=uri_tmpl,
1920 repo_name=self.repo_name,
1984 repo_name=self.repo_name,
1921 repo_id=self.repo_id, **override)
1985 repo_id=self.repo_id, **override)
1922
1986
@@ -2160,8 +2224,13 b' class RepoGroup(Base, BaseModel):'
2160 self.parent_group = parent_group
2224 self.parent_group = parent_group
2161
2225
2162 def __unicode__(self):
2226 def __unicode__(self):
2163 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2227 return u"<%s('id:%s:%s')>" % (
2164 self.group_name)
2228 self.__class__.__name__, self.group_id, self.group_name)
2229
2230 @hybrid_property
2231 def description_safe(self):
2232 from rhodecode.lib import helpers as h
2233 return h.escape(self.group_description)
2165
2234
2166 @classmethod
2235 @classmethod
2167 def _generate_choice(cls, repo_group):
2236 def _generate_choice(cls, repo_group):
@@ -2176,7 +2245,7 b' class RepoGroup(Base, BaseModel):'
2176
2245
2177 repo_groups = []
2246 repo_groups = []
2178 if show_empty_group:
2247 if show_empty_group:
2179 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2248 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2180
2249
2181 repo_groups.extend([cls._generate_choice(x) for x in groups])
2250 repo_groups.extend([cls._generate_choice(x) for x in groups])
2182
2251
@@ -2196,16 +2265,19 b' class RepoGroup(Base, BaseModel):'
2196 else:
2265 else:
2197 gr = cls.query().filter(cls.group_name == group_name)
2266 gr = cls.query().filter(cls.group_name == group_name)
2198 if cache:
2267 if cache:
2199 gr = gr.options(FromCache(
2268 name_key = _hash_key(group_name)
2200 "sql_cache_short",
2269 gr = gr.options(
2201 "get_group_%s" % _hash_key(group_name)))
2270 FromCache("sql_cache_short", "get_group_%s" % name_key))
2202 return gr.scalar()
2271 return gr.scalar()
2203
2272
2204 @classmethod
2273 @classmethod
2205 def get_user_personal_repo_group(cls, user_id):
2274 def get_user_personal_repo_group(cls, user_id):
2206 user = User.get(user_id)
2275 user = User.get(user_id)
2276 if user.username == User.DEFAULT_USER:
2277 return None
2278
2207 return cls.query()\
2279 return cls.query()\
2208 .filter(cls.personal == true())\
2280 .filter(cls.personal == true()) \
2209 .filter(cls.user == user).scalar()
2281 .filter(cls.user == user).scalar()
2210
2282
2211 @classmethod
2283 @classmethod
@@ -2389,7 +2461,7 b' class RepoGroup(Base, BaseModel):'
2389 data = {
2461 data = {
2390 'group_id': group.group_id,
2462 'group_id': group.group_id,
2391 'group_name': group.group_name,
2463 'group_name': group.group_name,
2392 'group_description': group.group_description,
2464 'group_description': group.description_safe,
2393 'parent_group': group.parent_group.group_name if group.parent_group else None,
2465 'parent_group': group.parent_group.group_name if group.parent_group else None,
2394 'repositories': [x.repo_name for x in group.repositories],
2466 'repositories': [x.repo_name for x in group.repositories],
2395 'owner': group.user.username,
2467 'owner': group.user.username,
@@ -3088,20 +3160,39 b' class ChangesetComment(Base, BaseModel):'
3088 def is_todo(self):
3160 def is_todo(self):
3089 return self.comment_type == self.COMMENT_TYPE_TODO
3161 return self.comment_type == self.COMMENT_TYPE_TODO
3090
3162
3163 @property
3164 def is_inline(self):
3165 return self.line_no and self.f_path
3166
3091 def get_index_version(self, versions):
3167 def get_index_version(self, versions):
3092 return self.get_index_from_version(
3168 return self.get_index_from_version(
3093 self.pull_request_version_id, versions)
3169 self.pull_request_version_id, versions)
3094
3170
3095 def render(self, mentions=False):
3096 from rhodecode.lib import helpers as h
3097 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3098
3099 def __repr__(self):
3171 def __repr__(self):
3100 if self.comment_id:
3172 if self.comment_id:
3101 return '<DB:Comment #%s>' % self.comment_id
3173 return '<DB:Comment #%s>' % self.comment_id
3102 else:
3174 else:
3103 return '<DB:Comment at %#x>' % id(self)
3175 return '<DB:Comment at %#x>' % id(self)
3104
3176
3177 def get_api_data(self):
3178 comment = self
3179 data = {
3180 'comment_id': comment.comment_id,
3181 'comment_type': comment.comment_type,
3182 'comment_text': comment.text,
3183 'comment_status': comment.status_change,
3184 'comment_f_path': comment.f_path,
3185 'comment_lineno': comment.line_no,
3186 'comment_author': comment.author,
3187 'comment_created_on': comment.created_on
3188 }
3189 return data
3190
3191 def __json__(self):
3192 data = dict()
3193 data.update(self.get_api_data())
3194 return data
3195
3105
3196
3106 class ChangesetStatus(Base, BaseModel):
3197 class ChangesetStatus(Base, BaseModel):
3107 __tablename__ = 'changeset_statuses'
3198 __tablename__ = 'changeset_statuses'
@@ -3153,6 +3244,19 b' class ChangesetStatus(Base, BaseModel):'
3153 def status_lbl(self):
3244 def status_lbl(self):
3154 return ChangesetStatus.get_status_lbl(self.status)
3245 return ChangesetStatus.get_status_lbl(self.status)
3155
3246
3247 def get_api_data(self):
3248 status = self
3249 data = {
3250 'status_id': status.changeset_status_id,
3251 'status': status.status,
3252 }
3253 return data
3254
3255 def __json__(self):
3256 data = dict()
3257 data.update(self.get_api_data())
3258 return data
3259
3156
3260
3157 class _PullRequestBase(BaseModel):
3261 class _PullRequestBase(BaseModel):
3158 """
3262 """
@@ -3215,6 +3319,19 b' class _PullRequestBase(BaseModel):'
3215 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3319 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3216 merge_rev = Column('merge_rev', String(40), nullable=True)
3320 merge_rev = Column('merge_rev', String(40), nullable=True)
3217
3321
3322 reviewer_data = Column(
3323 'reviewer_data_json', MutationObj.as_mutable(
3324 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3325
3326 @property
3327 def reviewer_data_json(self):
3328 return json.dumps(self.reviewer_data)
3329
3330 @hybrid_property
3331 def description_safe(self):
3332 from rhodecode.lib import helpers as h
3333 return h.escape(self.description)
3334
3218 @hybrid_property
3335 @hybrid_property
3219 def revisions(self):
3336 def revisions(self):
3220 return self._revisions.split(':') if self._revisions else []
3337 return self._revisions.split(':') if self._revisions else []
@@ -3276,14 +3393,19 b' class _PullRequestBase(BaseModel):'
3276 else:
3393 else:
3277 return None
3394 return None
3278
3395
3279 def get_api_data(self):
3396 def get_api_data(self, with_merge_state=True):
3280 from rhodecode.model.pull_request import PullRequestModel
3397 from rhodecode.model.pull_request import PullRequestModel
3398
3281 pull_request = self
3399 pull_request = self
3282 merge_status = PullRequestModel().merge_status(pull_request)
3400 if with_merge_state:
3283
3401 merge_status = PullRequestModel().merge_status(pull_request)
3284 pull_request_url = url(
3402 merge_state = {
3285 'pullrequest_show', repo_name=self.target_repo.repo_name,
3403 'status': merge_status[0],
3286 pull_request_id=self.pull_request_id, qualified=True)
3404 'message': safe_unicode(merge_status[1]),
3405 }
3406 else:
3407 merge_state = {'status': 'not_available',
3408 'message': 'not_available'}
3287
3409
3288 merge_data = {
3410 merge_data = {
3289 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3411 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
@@ -3294,7 +3416,7 b' class _PullRequestBase(BaseModel):'
3294
3416
3295 data = {
3417 data = {
3296 'pull_request_id': pull_request.pull_request_id,
3418 'pull_request_id': pull_request.pull_request_id,
3297 'url': pull_request_url,
3419 'url': PullRequestModel().get_url(pull_request),
3298 'title': pull_request.title,
3420 'title': pull_request.title,
3299 'description': pull_request.description,
3421 'description': pull_request.description,
3300 'status': pull_request.status,
3422 'status': pull_request.status,
@@ -3302,10 +3424,7 b' class _PullRequestBase(BaseModel):'
3302 'updated_on': pull_request.updated_on,
3424 'updated_on': pull_request.updated_on,
3303 'commit_ids': pull_request.revisions,
3425 'commit_ids': pull_request.revisions,
3304 'review_status': pull_request.calculated_review_status(),
3426 'review_status': pull_request.calculated_review_status(),
3305 'mergeable': {
3427 'mergeable': merge_state,
3306 'status': merge_status[0],
3307 'message': unicode(merge_status[1]),
3308 },
3309 'source': {
3428 'source': {
3310 'clone_url': pull_request.source_repo.clone_url(),
3429 'clone_url': pull_request.source_repo.clone_url(),
3311 'repository': pull_request.source_repo.repo_name,
3430 'repository': pull_request.source_repo.repo_name,
@@ -3334,7 +3453,8 b' class _PullRequestBase(BaseModel):'
3334 'reasons': reasons,
3453 'reasons': reasons,
3335 'review_status': st[0][1].status if st else 'not_reviewed',
3454 'review_status': st[0][1].status if st else 'not_reviewed',
3336 }
3455 }
3337 for reviewer, reasons, st in pull_request.reviewers_statuses()
3456 for reviewer, reasons, mandatory, st in
3457 pull_request.reviewers_statuses()
3338 ]
3458 ]
3339 }
3459 }
3340
3460
@@ -3359,7 +3479,8 b' class PullRequest(Base, _PullRequestBase'
3359
3479
3360 reviewers = relationship('PullRequestReviewers',
3480 reviewers = relationship('PullRequestReviewers',
3361 cascade="all, delete, delete-orphan")
3481 cascade="all, delete, delete-orphan")
3362 statuses = relationship('ChangesetStatus')
3482 statuses = relationship('ChangesetStatus',
3483 cascade="all, delete, delete-orphan")
3363 comments = relationship('ChangesetComment',
3484 comments = relationship('ChangesetComment',
3364 cascade="all, delete, delete-orphan")
3485 cascade="all, delete, delete-orphan")
3365 versions = relationship('PullRequestVersion',
3486 versions = relationship('PullRequestVersion',
@@ -3424,6 +3545,8 b' class PullRequest(Base, _PullRequestBase'
3424 attrs.revisions = pull_request_obj.revisions
3545 attrs.revisions = pull_request_obj.revisions
3425
3546
3426 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3547 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3548 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3549 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3427
3550
3428 return PullRequestDisplay(attrs, internal=internal_methods)
3551 return PullRequestDisplay(attrs, internal=internal_methods)
3429
3552
@@ -3502,11 +3625,6 b' class PullRequestReviewers(Base, BaseMod'
3502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3625 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3503 )
3626 )
3504
3627
3505 def __init__(self, user=None, pull_request=None, reasons=None):
3506 self.user = user
3507 self.pull_request = pull_request
3508 self.reasons = reasons or []
3509
3510 @hybrid_property
3628 @hybrid_property
3511 def reasons(self):
3629 def reasons(self):
3512 if not self._reasons:
3630 if not self._reasons:
@@ -3531,7 +3649,7 b' class PullRequestReviewers(Base, BaseMod'
3531 _reasons = Column(
3649 _reasons = Column(
3532 'reason', MutationList.as_mutable(
3650 'reason', MutationList.as_mutable(
3533 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3651 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3534
3652 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3535 user = relationship('User')
3653 user = relationship('User')
3536 pull_request = relationship('PullRequest')
3654 pull_request = relationship('PullRequest')
3537
3655
@@ -3651,6 +3769,11 b' class Gist(Base, BaseModel):'
3651 def __repr__(self):
3769 def __repr__(self):
3652 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3770 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3653
3771
3772 @hybrid_property
3773 def description_safe(self):
3774 from rhodecode.lib import helpers as h
3775 return h.escape(self.gist_description)
3776
3654 @classmethod
3777 @classmethod
3655 def get_or_404(cls, id_, pyramid_exc=False):
3778 def get_or_404(cls, id_, pyramid_exc=False):
3656
3779
@@ -3670,6 +3793,8 b' class Gist(Base, BaseModel):'
3670
3793
3671 def gist_url(self):
3794 def gist_url(self):
3672 import rhodecode
3795 import rhodecode
3796 from pylons import url
3797
3673 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3798 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3674 if alias_url:
3799 if alias_url:
3675 return alias_url.replace('{gistid}', self.gist_access_id)
3800 return alias_url.replace('{gistid}', self.gist_access_id)
@@ -3835,14 +3960,17 b' class RepoReviewRuleUser(Base, BaseModel'
3835 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3836 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3961 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3837 )
3962 )
3838 repo_review_rule_user_id = Column(
3963 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3839 'repo_review_rule_user_id', Integer(), primary_key=True)
3964 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3840 repo_review_rule_id = Column("repo_review_rule_id",
3965 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3841 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3966 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3842 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3843 nullable=False)
3844 user = relationship('User')
3967 user = relationship('User')
3845
3968
3969 def rule_data(self):
3970 return {
3971 'mandatory': self.mandatory
3972 }
3973
3846
3974
3847 class RepoReviewRuleUserGroup(Base, BaseModel):
3975 class RepoReviewRuleUserGroup(Base, BaseModel):
3848 __tablename__ = 'repo_review_rules_users_groups'
3976 __tablename__ = 'repo_review_rules_users_groups'
@@ -3850,14 +3978,17 b' class RepoReviewRuleUserGroup(Base, Base'
3850 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3851 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3979 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3852 )
3980 )
3853 repo_review_rule_users_group_id = Column(
3981 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3854 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3982 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3855 repo_review_rule_id = Column("repo_review_rule_id",
3983 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3856 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3984 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3857 users_group_id = Column("users_group_id", Integer(),
3858 ForeignKey('users_groups.users_group_id'), nullable=False)
3859 users_group = relationship('UserGroup')
3985 users_group = relationship('UserGroup')
3860
3986
3987 def rule_data(self):
3988 return {
3989 'mandatory': self.mandatory
3990 }
3991
3861
3992
3862 class RepoReviewRule(Base, BaseModel):
3993 class RepoReviewRule(Base, BaseModel):
3863 __tablename__ = 'repo_review_rules'
3994 __tablename__ = 'repo_review_rules'
@@ -3872,13 +4003,14 b' class RepoReviewRule(Base, BaseModel):'
3872 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4003 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3873 repo = relationship('Repository', backref='review_rules')
4004 repo = relationship('Repository', backref='review_rules')
3874
4005
3875 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
4006 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3876 default=u'*') # glob
4007 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3877 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
4008
3878 default=u'*') # glob
4009 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
3879
4010 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
3880 use_authors_for_review = Column("use_authors_for_review", Boolean(),
4011 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
3881 nullable=False, default=False)
4012 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4013
3882 rule_users = relationship('RepoReviewRuleUser')
4014 rule_users = relationship('RepoReviewRuleUser')
3883 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4015 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3884
4016
@@ -3934,16 +4066,32 b' class RepoReviewRule(Base, BaseModel):'
3934 def review_users(self):
4066 def review_users(self):
3935 """ Returns the users which this rule applies to """
4067 """ Returns the users which this rule applies to """
3936
4068
3937 users = set()
4069 users = collections.OrderedDict()
3938 users |= set([
4070
3939 rule_user.user for rule_user in self.rule_users
4071 for rule_user in self.rule_users:
3940 if rule_user.user.active])
4072 if rule_user.user.active:
3941 users |= set(
4073 if rule_user.user not in users:
3942 member.user
4074 users[rule_user.user.username] = {
3943 for rule_user_group in self.rule_user_groups
4075 'user': rule_user.user,
3944 for member in rule_user_group.users_group.members
4076 'source': 'user',
3945 if member.user.active
4077 'source_data': {},
3946 )
4078 'data': rule_user.rule_data()
4079 }
4080
4081 for rule_user_group in self.rule_user_groups:
4082 source_data = {
4083 'name': rule_user_group.users_group.users_group_name,
4084 'members': len(rule_user_group.users_group.members)
4085 }
4086 for member in rule_user_group.users_group.members:
4087 if member.user.active:
4088 users[member.user.username] = {
4089 'user': member.user,
4090 'source': 'user_group',
4091 'source_data': source_data,
4092 'data': rule_user_group.rule_data()
4093 }
4094
3947 return users
4095 return users
3948
4096
3949 def __repr__(self):
4097 def __repr__(self):
@@ -242,7 +242,7 b' def RepoForm(edit=False, old_data=None, '
242 allow_extra_fields = True
242 allow_extra_fields = True
243 filter_extra_fields = False
243 filter_extra_fields = False
244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
245 v.SlugifyName())
245 v.SlugifyName(), v.CannotHaveGitSuffix())
246 repo_group = All(v.CanWriteGroup(old_data),
246 repo_group = All(v.CanWriteGroup(old_data),
247 v.OneOf(repo_groups, hideList=True))
247 v.OneOf(repo_groups, hideList=True))
248 repo_type = v.OneOf(supported_backends, required=False,
248 repo_type = v.OneOf(supported_backends, required=False,
@@ -384,6 +384,7 b' class _BaseVcsSettingsForm(formencode.Sc'
384
384
385 # hg
385 # hg
386 extensions_largefiles = v.StringBoolean(if_missing=False)
386 extensions_largefiles = v.StringBoolean(if_missing=False)
387 extensions_evolve = v.StringBoolean(if_missing=False)
387 phases_publish = v.StringBoolean(if_missing=False)
388 phases_publish = v.StringBoolean(if_missing=False)
388 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
389 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
389
390
@@ -534,12 +535,13 b' def PullRequestForm(repo_id):'
534 class ReviewerForm(formencode.Schema):
535 class ReviewerForm(formencode.Schema):
535 user_id = v.Int(not_empty=True)
536 user_id = v.Int(not_empty=True)
536 reasons = All()
537 reasons = All()
538 mandatory = v.StringBoolean()
537
539
538 class _PullRequestForm(formencode.Schema):
540 class _PullRequestForm(formencode.Schema):
539 allow_extra_fields = True
541 allow_extra_fields = True
540 filter_extra_fields = True
542 filter_extra_fields = True
541
543
542 user = v.UnicodeString(strip=True, required=True)
544 common_ancestor = v.UnicodeString(strip=True, required=True)
543 source_repo = v.UnicodeString(strip=True, required=True)
545 source_repo = v.UnicodeString(strip=True, required=True)
544 source_ref = v.UnicodeString(strip=True, required=True)
546 source_ref = v.UnicodeString(strip=True, required=True)
545 target_repo = v.UnicodeString(strip=True, required=True)
547 target_repo = v.UnicodeString(strip=True, required=True)
@@ -96,6 +96,9 b' class IntegrationModel(BaseModel):'
96 """ Send an event to an integration """
96 """ Send an event to an integration """
97 handler = self.get_integration_handler(integration)
97 handler = self.get_integration_handler(integration)
98 if handler:
98 if handler:
99 log.debug(
100 'events: sending event %s on integration %s using handler %s',
101 event, integration, handler)
99 handler.send_event(event)
102 handler.send_event(event)
100
103
101 def get_integrations(self, scope, IntegrationType=None):
104 def get_integrations(self, scope, IntegrationType=None):
@@ -200,14 +203,14 b' class IntegrationModel(BaseModel):'
200 query = query.filter(or_(*clauses))
203 query = query.filter(or_(*clauses))
201
204
202 if cache:
205 if cache:
203 query = query.options(FromCache(
206 cache_key = "get_enabled_repo_integrations_%i" % event.repo.repo_id
204 "sql_cache_short",
207 query = query.options(
205 "get_enabled_repo_integrations_%i" % event.repo.repo_id))
208 FromCache("sql_cache_short", cache_key))
206 else: # only global integrations
209 else: # only global integrations
207 query = query.filter(global_integrations_filter)
210 query = query.filter(global_integrations_filter)
208 if cache:
211 if cache:
209 query = query.options(FromCache(
212 query = query.options(
210 "sql_cache_short", "get_enabled_global_integrations"))
213 FromCache("sql_cache_short", "get_enabled_global_integrations"))
211
214
212 result = query.all()
215 result = query.all()
213 return result No newline at end of file
216 return result
@@ -332,14 +332,19 b' class EmailNotificationModel(BaseModel):'
332
332
333 :param kwargs:
333 :param kwargs:
334 """
334 """
335
335 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
336 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
336
337 instance_url = h.route_url('home')
337 _kwargs = {
338 _kwargs = {
338 'instance_url': h.url('home', qualified=True),
339 'instance_url': instance_url,
340 'whitespace_filter': self.whitespace_filter
339 }
341 }
340 _kwargs.update(kwargs)
342 _kwargs.update(kwargs)
341 return _kwargs
343 return _kwargs
342
344
345 def whitespace_filter(self, text):
346 return text.replace('\n', '').replace('\t', '')
347
343 def get_renderer(self, type_):
348 def get_renderer(self, type_):
344 template_name = self.email_types[type_]
349 template_name = self.email_types[type_]
345 return PartialRenderer(template_name)
350 return PartialRenderer(template_name)
@@ -31,14 +31,16 b' import urllib'
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.i18n.translation import lazy_ugettext
33 from pylons.i18n.translation import lazy_ugettext
34 from pyramid.threadlocal import get_current_request
34 from sqlalchemy import or_
35 from sqlalchemy import or_
35
36
37 from rhodecode import events
36 from rhodecode.lib import helpers as h, hooks_utils, diffs
38 from rhodecode.lib import helpers as h, hooks_utils, diffs
39 from rhodecode.lib import audit_logger
37 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
41 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
39 from rhodecode.lib.markup_renderer import (
42 from rhodecode.lib.markup_renderer import (
40 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
43 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
41 from rhodecode.lib.utils import action_logger
42 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe
44 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe
43 from rhodecode.lib.vcs.backends.base import (
45 from rhodecode.lib.vcs.backends.base import (
44 Reference, MergeResponse, MergeFailureReason, UpdateFailureReason)
46 Reference, MergeResponse, MergeFailureReason, UpdateFailureReason)
@@ -118,9 +120,9 b' class PullRequestModel(BaseModel):'
118 'Pull request update failed because of an unknown error.'),
120 'Pull request update failed because of an unknown error.'),
119 UpdateFailureReason.NO_CHANGE: lazy_ugettext(
121 UpdateFailureReason.NO_CHANGE: lazy_ugettext(
120 'No update needed because the source and target have not changed.'),
122 'No update needed because the source and target have not changed.'),
121 UpdateFailureReason.WRONG_REF_TPYE: lazy_ugettext(
123 UpdateFailureReason.WRONG_REF_TYPE: lazy_ugettext(
122 'Pull request cannot be updated because the reference type is '
124 'Pull request cannot be updated because the reference type is '
123 'not supported for an update.'),
125 'not supported for an update. Only Branch, Tag or Bookmark is allowed.'),
124 UpdateFailureReason.MISSING_TARGET_REF: lazy_ugettext(
126 UpdateFailureReason.MISSING_TARGET_REF: lazy_ugettext(
125 'This pull request cannot be updated because the target '
127 'This pull request cannot be updated because the target '
126 'reference is missing.'),
128 'reference is missing.'),
@@ -415,7 +417,9 b' class PullRequestModel(BaseModel):'
415 .all()
417 .all()
416
418
417 def create(self, created_by, source_repo, source_ref, target_repo,
419 def create(self, created_by, source_repo, source_ref, target_repo,
418 target_ref, revisions, reviewers, title, description=None):
420 target_ref, revisions, reviewers, title, description=None,
421 reviewer_data=None):
422
419 created_by_user = self._get_user(created_by)
423 created_by_user = self._get_user(created_by)
420 source_repo = self._get_repo(source_repo)
424 source_repo = self._get_repo(source_repo)
421 target_repo = self._get_repo(target_repo)
425 target_repo = self._get_repo(target_repo)
@@ -429,6 +433,7 b' class PullRequestModel(BaseModel):'
429 pull_request.title = title
433 pull_request.title = title
430 pull_request.description = description
434 pull_request.description = description
431 pull_request.author = created_by_user
435 pull_request.author = created_by_user
436 pull_request.reviewer_data = reviewer_data
432
437
433 Session().add(pull_request)
438 Session().add(pull_request)
434 Session().flush()
439 Session().flush()
@@ -436,15 +441,20 b' class PullRequestModel(BaseModel):'
436 reviewer_ids = set()
441 reviewer_ids = set()
437 # members / reviewers
442 # members / reviewers
438 for reviewer_object in reviewers:
443 for reviewer_object in reviewers:
439 if isinstance(reviewer_object, tuple):
444 user_id, reasons, mandatory = reviewer_object
440 user_id, reasons = reviewer_object
445 user = self._get_user(user_id)
441 else:
442 user_id, reasons = reviewer_object, []
443
446
444 user = self._get_user(user_id)
447 # skip duplicates
448 if user.user_id in reviewer_ids:
449 continue
450
445 reviewer_ids.add(user.user_id)
451 reviewer_ids.add(user.user_id)
446
452
447 reviewer = PullRequestReviewers(user, pull_request, reasons)
453 reviewer = PullRequestReviewers()
454 reviewer.user = user
455 reviewer.pull_request = pull_request
456 reviewer.reasons = reasons
457 reviewer.mandatory = mandatory
448 Session().add(reviewer)
458 Session().add(reviewer)
449
459
450 # Set approval status to "Under Review" for all commits which are
460 # Set approval status to "Under Review" for all commits which are
@@ -460,6 +470,11 b' class PullRequestModel(BaseModel):'
460 self._trigger_pull_request_hook(
470 self._trigger_pull_request_hook(
461 pull_request, created_by_user, 'create')
471 pull_request, created_by_user, 'create')
462
472
473 creation_data = pull_request.get_api_data(with_merge_state=False)
474 self._log_audit_action(
475 'repo.pull_request.create', {'data': creation_data},
476 created_by_user, pull_request)
477
463 return pull_request
478 return pull_request
464
479
465 def _trigger_pull_request_hook(self, pull_request, user, action):
480 def _trigger_pull_request_hook(self, pull_request, user, action):
@@ -510,7 +525,12 b' class PullRequestModel(BaseModel):'
510 log.debug(
525 log.debug(
511 "Merge was successful, updating the pull request comments.")
526 "Merge was successful, updating the pull request comments.")
512 self._comment_and_close_pr(pull_request, user, merge_state)
527 self._comment_and_close_pr(pull_request, user, merge_state)
513 self._log_action('user_merged_pull_request', user, pull_request)
528
529 self._log_audit_action(
530 'repo.pull_request.merge',
531 {'merge_state': merge_state.__dict__},
532 user, pull_request)
533
514 else:
534 else:
515 log.warn("Merge failed, not updating the pull request.")
535 log.warn("Merge failed, not updating the pull request.")
516 return merge_state
536 return merge_state
@@ -594,7 +614,7 b' class PullRequestModel(BaseModel):'
594 pull_request, source_ref_type)
614 pull_request, source_ref_type)
595 return UpdateResponse(
615 return UpdateResponse(
596 executed=False,
616 executed=False,
597 reason=UpdateFailureReason.WRONG_REF_TPYE,
617 reason=UpdateFailureReason.WRONG_REF_TYPE,
598 old=pull_request, new=None, changes=None,
618 old=pull_request, new=None, changes=None,
599 source_changed=False, target_changed=False)
619 source_changed=False, target_changed=False)
600
620
@@ -763,6 +783,7 b' class PullRequestModel(BaseModel):'
763 version._last_merge_status = pull_request._last_merge_status
783 version._last_merge_status = pull_request._last_merge_status
764 version.shadow_merge_ref = pull_request.shadow_merge_ref
784 version.shadow_merge_ref = pull_request.shadow_merge_ref
765 version.merge_rev = pull_request.merge_rev
785 version.merge_rev = pull_request.merge_rev
786 version.reviewer_data = pull_request.reviewer_data
766
787
767 version.revisions = pull_request.revisions
788 version.revisions = pull_request.revisions
768 version.pull_request = pull_request
789 version.pull_request = pull_request
@@ -806,13 +827,15 b' class PullRequestModel(BaseModel):'
806
827
807 """
828 """
808 pull_request = pull_request_version.pull_request
829 pull_request = pull_request_version.pull_request
809 comments = ChangesetComment.query().filter(
830 comments = ChangesetComment.query()\
810 # TODO: johbo: Should we query for the repo at all here?
831 .filter(
811 # Pending decision on how comments of PRs are to be related
832 # TODO: johbo: Should we query for the repo at all here?
812 # to either the source repo, the target repo or no repo at all.
833 # Pending decision on how comments of PRs are to be related
813 ChangesetComment.repo_id == pull_request.target_repo.repo_id,
834 # to either the source repo, the target repo or no repo at all.
814 ChangesetComment.pull_request == pull_request,
835 ChangesetComment.repo_id == pull_request.target_repo.repo_id,
815 ChangesetComment.pull_request_version == None)
836 ChangesetComment.pull_request == pull_request,
837 ChangesetComment.pull_request_version == None)\
838 .order_by(ChangesetComment.comment_id.asc())
816
839
817 # TODO: johbo: Find out why this breaks if it is done in a bulk
840 # TODO: johbo: Find out why this breaks if it is done in a bulk
818 # operation.
841 # operation.
@@ -886,8 +909,9 b' class PullRequestModel(BaseModel):'
886 renderer = RstTemplateRenderer()
909 renderer = RstTemplateRenderer()
887 return renderer.render('pull_request_update.mako', **params)
910 return renderer.render('pull_request_update.mako', **params)
888
911
889 def edit(self, pull_request, title, description):
912 def edit(self, pull_request, title, description, user):
890 pull_request = self.__get_pull_request(pull_request)
913 pull_request = self.__get_pull_request(pull_request)
914 old_data = pull_request.get_api_data(with_merge_state=False)
891 if pull_request.is_closed():
915 if pull_request.is_closed():
892 raise ValueError('This pull request is closed')
916 raise ValueError('This pull request is closed')
893 if title:
917 if title:
@@ -895,22 +919,27 b' class PullRequestModel(BaseModel):'
895 pull_request.description = description
919 pull_request.description = description
896 pull_request.updated_on = datetime.datetime.now()
920 pull_request.updated_on = datetime.datetime.now()
897 Session().add(pull_request)
921 Session().add(pull_request)
922 self._log_audit_action(
923 'repo.pull_request.edit', {'old_data': old_data},
924 user, pull_request)
898
925
899 def update_reviewers(self, pull_request, reviewer_data):
926 def update_reviewers(self, pull_request, reviewer_data, user):
900 """
927 """
901 Update the reviewers in the pull request
928 Update the reviewers in the pull request
902
929
903 :param pull_request: the pr to update
930 :param pull_request: the pr to update
904 :param reviewer_data: list of tuples [(user, ['reason1', 'reason2'])]
931 :param reviewer_data: list of tuples
932 [(user, ['reason1', 'reason2'], mandatory_flag)]
905 """
933 """
906
934
907 reviewers_reasons = {}
935 reviewers = {}
908 for user_id, reasons in reviewer_data:
936 for user_id, reasons, mandatory in reviewer_data:
909 if isinstance(user_id, (int, basestring)):
937 if isinstance(user_id, (int, basestring)):
910 user_id = self._get_user(user_id).user_id
938 user_id = self._get_user(user_id).user_id
911 reviewers_reasons[user_id] = reasons
939 reviewers[user_id] = {
940 'reasons': reasons, 'mandatory': mandatory}
912
941
913 reviewers_ids = set(reviewers_reasons.keys())
942 reviewers_ids = set(reviewers.keys())
914 pull_request = self.__get_pull_request(pull_request)
943 pull_request = self.__get_pull_request(pull_request)
915 current_reviewers = PullRequestReviewers.query()\
944 current_reviewers = PullRequestReviewers.query()\
916 .filter(PullRequestReviewers.pull_request ==
945 .filter(PullRequestReviewers.pull_request ==
@@ -926,9 +955,16 b' class PullRequestModel(BaseModel):'
926 for uid in ids_to_add:
955 for uid in ids_to_add:
927 changed = True
956 changed = True
928 _usr = self._get_user(uid)
957 _usr = self._get_user(uid)
929 reasons = reviewers_reasons[uid]
958 reviewer = PullRequestReviewers()
930 reviewer = PullRequestReviewers(_usr, pull_request, reasons)
959 reviewer.user = _usr
960 reviewer.pull_request = pull_request
961 reviewer.reasons = reviewers[uid]['reasons']
962 # NOTE(marcink): mandatory shouldn't be changed now
963 # reviewer.mandatory = reviewers[uid]['reasons']
931 Session().add(reviewer)
964 Session().add(reviewer)
965 self._log_audit_action(
966 'repo.pull_request.reviewer.add', {'data': reviewer.get_dict()},
967 user, pull_request)
932
968
933 for uid in ids_to_remove:
969 for uid in ids_to_remove:
934 changed = True
970 changed = True
@@ -939,7 +975,11 b' class PullRequestModel(BaseModel):'
939 # use .all() in case we accidentally added the same person twice
975 # use .all() in case we accidentally added the same person twice
940 # this CAN happen due to the lack of DB checks
976 # this CAN happen due to the lack of DB checks
941 for obj in reviewers:
977 for obj in reviewers:
978 old_data = obj.get_dict()
942 Session().delete(obj)
979 Session().delete(obj)
980 self._log_audit_action(
981 'repo.pull_request.reviewer.delete',
982 {'old_data': old_data}, user, pull_request)
943
983
944 if changed:
984 if changed:
945 pull_request.updated_on = datetime.datetime.now()
985 pull_request.updated_on = datetime.datetime.now()
@@ -948,11 +988,18 b' class PullRequestModel(BaseModel):'
948 self.notify_reviewers(pull_request, ids_to_add)
988 self.notify_reviewers(pull_request, ids_to_add)
949 return ids_to_add, ids_to_remove
989 return ids_to_add, ids_to_remove
950
990
951 def get_url(self, pull_request):
991 def get_url(self, pull_request, request=None, permalink=False):
952 return h.url('pullrequest_show',
992 if not request:
953 repo_name=safe_str(pull_request.target_repo.repo_name),
993 request = get_current_request()
954 pull_request_id=pull_request.pull_request_id,
994
955 qualified=True)
995 if permalink:
996 return request.route_url(
997 'pull_requests_global',
998 pull_request_id=pull_request.pull_request_id,)
999 else:
1000 return request.route_url('pullrequest_show',
1001 repo_name=safe_str(pull_request.target_repo.repo_name),
1002 pull_request_id=pull_request.pull_request_id,)
956
1003
957 def get_shadow_clone_url(self, pull_request):
1004 def get_shadow_clone_url(self, pull_request):
958 """
1005 """
@@ -979,22 +1026,16 b' class PullRequestModel(BaseModel):'
979 pr_source_repo = pull_request_obj.source_repo
1026 pr_source_repo = pull_request_obj.source_repo
980 pr_target_repo = pull_request_obj.target_repo
1027 pr_target_repo = pull_request_obj.target_repo
981
1028
982 pr_url = h.url(
1029 pr_url = h.route_url('pullrequest_show',
983 'pullrequest_show',
984 repo_name=pr_target_repo.repo_name,
1030 repo_name=pr_target_repo.repo_name,
985 pull_request_id=pull_request_obj.pull_request_id,
1031 pull_request_id=pull_request_obj.pull_request_id,)
986 qualified=True,)
987
1032
988 # set some variables for email notification
1033 # set some variables for email notification
989 pr_target_repo_url = h.url(
1034 pr_target_repo_url = h.route_url(
990 'summary_home',
1035 'repo_summary', repo_name=pr_target_repo.repo_name)
991 repo_name=pr_target_repo.repo_name,
992 qualified=True)
993
1036
994 pr_source_repo_url = h.url(
1037 pr_source_repo_url = h.route_url(
995 'summary_home',
1038 'repo_summary', repo_name=pr_source_repo.repo_name)
996 repo_name=pr_source_repo.repo_name,
997 qualified=True)
998
1039
999 # pull request specifics
1040 # pull request specifics
1000 pull_request_commits = [
1041 pull_request_commits = [
@@ -1031,9 +1072,13 b' class PullRequestModel(BaseModel):'
1031 email_kwargs=kwargs,
1072 email_kwargs=kwargs,
1032 )
1073 )
1033
1074
1034 def delete(self, pull_request):
1075 def delete(self, pull_request, user):
1035 pull_request = self.__get_pull_request(pull_request)
1076 pull_request = self.__get_pull_request(pull_request)
1077 old_data = pull_request.get_api_data(with_merge_state=False)
1036 self._cleanup_merge_workspace(pull_request)
1078 self._cleanup_merge_workspace(pull_request)
1079 self._log_audit_action(
1080 'repo.pull_request.delete', {'old_data': old_data},
1081 user, pull_request)
1037 Session().delete(pull_request)
1082 Session().delete(pull_request)
1038
1083
1039 def close_pull_request(self, pull_request, user):
1084 def close_pull_request(self, pull_request, user):
@@ -1044,44 +1089,64 b' class PullRequestModel(BaseModel):'
1044 Session().add(pull_request)
1089 Session().add(pull_request)
1045 self._trigger_pull_request_hook(
1090 self._trigger_pull_request_hook(
1046 pull_request, pull_request.author, 'close')
1091 pull_request, pull_request.author, 'close')
1047 self._log_action('user_closed_pull_request', user, pull_request)
1092 self._log_audit_action(
1093 'repo.pull_request.close', {}, user, pull_request)
1048
1094
1049 def close_pull_request_with_comment(self, pull_request, user, repo,
1095 def close_pull_request_with_comment(
1050 message=None):
1096 self, pull_request, user, repo, message=None):
1051 status = ChangesetStatus.STATUS_REJECTED
1097
1098 pull_request_review_status = pull_request.calculated_review_status()
1052
1099
1053 if not message:
1100 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
1054 message = (
1101 # approved only if we have voting consent
1055 _('Status change %(transition_icon)s %(status)s') % {
1102 status = ChangesetStatus.STATUS_APPROVED
1056 'transition_icon': '>',
1103 else:
1057 'status': ChangesetStatus.get_status_lbl(status)})
1104 status = ChangesetStatus.STATUS_REJECTED
1105 status_lbl = ChangesetStatus.get_status_lbl(status)
1058
1106
1059 internal_message = _('Closing with') + ' ' + message
1107 default_message = (
1108 _('Closing with status change {transition_icon} {status}.')
1109 ).format(transition_icon='>', status=status_lbl)
1110 text = message or default_message
1060
1111
1061 comm = CommentsModel().create(
1112 # create a comment, and link it to new status
1062 text=internal_message,
1113 comment = CommentsModel().create(
1114 text=text,
1063 repo=repo.repo_id,
1115 repo=repo.repo_id,
1064 user=user.user_id,
1116 user=user.user_id,
1065 pull_request=pull_request.pull_request_id,
1117 pull_request=pull_request.pull_request_id,
1066 f_path=None,
1118 status_change=status_lbl,
1067 line_no=None,
1068 status_change=ChangesetStatus.get_status_lbl(status),
1069 status_change_type=status,
1119 status_change_type=status,
1070 closing_pr=True
1120 closing_pr=True
1071 )
1121 )
1072
1122
1123 # calculate old status before we change it
1124 old_calculated_status = pull_request.calculated_review_status()
1073 ChangesetStatusModel().set_status(
1125 ChangesetStatusModel().set_status(
1074 repo.repo_id,
1126 repo.repo_id,
1075 status,
1127 status,
1076 user.user_id,
1128 user.user_id,
1077 comm,
1129 comment=comment,
1078 pull_request=pull_request.pull_request_id
1130 pull_request=pull_request.pull_request_id
1079 )
1131 )
1132
1080 Session().flush()
1133 Session().flush()
1134 events.trigger(events.PullRequestCommentEvent(pull_request, comment))
1135 # we now calculate the status of pull request again, and based on that
1136 # calculation trigger status change. This might happen in cases
1137 # that non-reviewer admin closes a pr, which means his vote doesn't
1138 # change the status, while if he's a reviewer this might change it.
1139 calculated_status = pull_request.calculated_review_status()
1140 if old_calculated_status != calculated_status:
1141 self._trigger_pull_request_hook(
1142 pull_request, user, 'review_status_change')
1081
1143
1144 # finally close the PR
1082 PullRequestModel().close_pull_request(
1145 PullRequestModel().close_pull_request(
1083 pull_request.pull_request_id, user)
1146 pull_request.pull_request_id, user)
1084
1147
1148 return comment, status
1149
1085 def merge_status(self, pull_request):
1150 def merge_status(self, pull_request):
1086 if not self._is_merge_enabled(pull_request):
1151 if not self._is_merge_enabled(pull_request):
1087 return False, _('Server-side pull request merging is disabled.')
1152 return False, _('Server-side pull request merging is disabled.')
@@ -1226,11 +1291,11 b' class PullRequestModel(BaseModel):'
1226 'user': {
1291 'user': {
1227 'user_id': repo.user.user_id,
1292 'user_id': repo.user.user_id,
1228 'username': repo.user.username,
1293 'username': repo.user.username,
1229 'firstname': repo.user.firstname,
1294 'firstname': repo.user.first_name,
1230 'lastname': repo.user.lastname,
1295 'lastname': repo.user.last_name,
1231 'gravatar_link': h.gravatar_url(repo.user.email, 14),
1296 'gravatar_link': h.gravatar_url(repo.user.email, 14),
1232 },
1297 },
1233 'description': h.chop_at_smart(repo.description, '\n'),
1298 'description': h.chop_at_smart(repo.description_safe, '\n'),
1234 'refs': {
1299 'refs': {
1235 'all_refs': all_refs,
1300 'all_refs': all_refs,
1236 'selected_ref': selected_ref,
1301 'selected_ref': selected_ref,
@@ -1360,12 +1425,29 b' class PullRequestModel(BaseModel):'
1360 settings = settings_model.get_general_settings()
1425 settings = settings_model.get_general_settings()
1361 return settings.get('rhodecode_hg_use_rebase_for_merging', False)
1426 return settings.get('rhodecode_hg_use_rebase_for_merging', False)
1362
1427
1363 def _log_action(self, action, user, pull_request):
1428 def _log_audit_action(self, action, action_data, user, pull_request):
1364 action_logger(
1429 audit_logger.store(
1365 user,
1430 action=action,
1366 '{action}:{pr_id}'.format(
1431 action_data=action_data,
1367 action=action, pr_id=pull_request.pull_request_id),
1432 user=user,
1368 pull_request.target_repo)
1433 repo=pull_request.target_repo)
1434
1435 def get_reviewer_functions(self):
1436 """
1437 Fetches functions for validation and fetching default reviewers.
1438 If available we use the EE package, else we fallback to CE
1439 package functions
1440 """
1441 try:
1442 from rc_reviewers.utils import get_default_reviewers_data
1443 from rc_reviewers.utils import validate_default_reviewers
1444 except ImportError:
1445 from rhodecode.apps.repository.utils import \
1446 get_default_reviewers_data
1447 from rhodecode.apps.repository.utils import \
1448 validate_default_reviewers
1449
1450 return get_default_reviewers_data, validate_default_reviewers
1369
1451
1370
1452
1371 class MergeCheck(object):
1453 class MergeCheck(object):
@@ -30,8 +30,7 b' import time'
30 import traceback
30 import traceback
31 from datetime import datetime, timedelta
31 from datetime import datetime, timedelta
32
32
33 from sqlalchemy.sql import func
33 from pyramid.threadlocal import get_current_request
34 from sqlalchemy.sql.expression import true, or_
35 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
36
35
37 from rhodecode import events
36 from rhodecode import events
@@ -40,19 +39,17 b' from rhodecode.lib.auth import HasUserGr'
40 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.exceptions import AttachedForksError
40 from rhodecode.lib.exceptions import AttachedForksError
42 from rhodecode.lib.hooks_base import log_delete_repository
41 from rhodecode.lib.hooks_base import log_delete_repository
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.utils import make_db_config
42 from rhodecode.lib.utils import make_db_config
45 from rhodecode.lib.utils2 import (
43 from rhodecode.lib.utils2 import (
46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
48 from rhodecode.lib.vcs.backends import get_backend
46 from rhodecode.lib.vcs.backends import get_backend
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
50 from rhodecode.model import BaseModel
47 from rhodecode.model import BaseModel
51 from rhodecode.model.db import (
48 from rhodecode.model.db import (_hash_key,
52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
54 RepoGroup, RepositoryField)
51 RepoGroup, RepositoryField)
55 from rhodecode.model.scm import UserGroupList
52
56 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
57
54
58
55
@@ -103,8 +100,8 b' class RepoModel(BaseModel):'
103 .filter(Repository.repo_id == repo_id)
100 .filter(Repository.repo_id == repo_id)
104
101
105 if cache:
102 if cache:
106 repo = repo.options(FromCache("sql_cache_short",
103 repo = repo.options(
107 "get_repo_%s" % repo_id))
104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
108 return repo.scalar()
105 return repo.scalar()
109
106
110 def get_repo(self, repository):
107 def get_repo(self, repository):
@@ -115,8 +112,9 b' class RepoModel(BaseModel):'
115 .filter(Repository.repo_name == repo_name)
112 .filter(Repository.repo_name == repo_name)
116
113
117 if cache:
114 if cache:
118 repo = repo.options(FromCache("sql_cache_short",
115 name_key = _hash_key(repo_name)
119 "get_repo_%s" % repo_name))
116 repo = repo.options(
117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
120 return repo.scalar()
118 return repo.scalar()
121
119
122 def _extract_id_from_repo_name(self, repo_name):
120 def _extract_id_from_repo_name(self, repo_name):
@@ -134,6 +132,7 b' class RepoModel(BaseModel):'
134 :param repo_name:
132 :param repo_name:
135 :return: repo object if matched else None
133 :return: repo object if matched else None
136 """
134 """
135
137 try:
136 try:
138 _repo_id = self._extract_id_from_repo_name(repo_name)
137 _repo_id = self._extract_id_from_repo_name(repo_name)
139 if _repo_id:
138 if _repo_id:
@@ -156,86 +155,36 b' class RepoModel(BaseModel):'
156 repos = Repository.query().filter(Repository.group == root).all()
155 repos = Repository.query().filter(Repository.group == root).all()
157 return repos
156 return repos
158
157
159 def get_url(self, repo):
158 def get_url(self, repo, request=None, permalink=False):
160 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
159 if not request:
161 qualified=True)
160 request = get_current_request()
162
161
163 def get_users(self, name_contains=None, limit=20, only_active=True):
162 if not request:
164
163 return
165 # TODO: mikhail: move this method to the UserModel.
166 query = self.sa.query(User)
167 if only_active:
168 query = query.filter(User.active == true())
169
164
170 if name_contains:
165 if permalink:
171 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
166 return request.route_url(
172 query = query.filter(
167 'repo_summary', repo_name=safe_str(repo.repo_id))
173 or_(
168 else:
174 User.name.ilike(ilike_expression),
169 return request.route_url(
175 User.lastname.ilike(ilike_expression),
170 'repo_summary', repo_name=safe_str(repo.repo_name))
176 User.username.ilike(ilike_expression)
177 )
178 )
179 query = query.limit(limit)
180 users = query.all()
181
182 _users = [
183 {
184 'id': user.user_id,
185 'first_name': user.name,
186 'last_name': user.lastname,
187 'username': user.username,
188 'email': user.email,
189 'icon_link': h.gravatar_url(user.email, 30),
190 'value_display': h.person(user),
191 'value': user.username,
192 'value_type': 'user',
193 'active': user.active,
194 }
195 for user in users
196 ]
197 return _users
198
171
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
200
173 if not request:
201 # TODO: mikhail: move this method to the UserGroupModel.
174 request = get_current_request()
202 query = self.sa.query(UserGroup)
203 if only_active:
204 query = query.filter(UserGroup.users_group_active == true())
205
175
206 if name_contains:
176 if not request:
207 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
177 return
208 query = query.filter(
209 UserGroup.users_group_name.ilike(ilike_expression))\
210 .order_by(func.length(UserGroup.users_group_name))\
211 .order_by(UserGroup.users_group_name)
212
213 query = query.limit(limit)
214 user_groups = query.all()
215 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
216 user_groups = UserGroupList(user_groups, perm_set=perm_set)
217
178
218 _groups = [
179 if permalink:
219 {
180 return request.route_url(
220 'id': group.users_group_id,
181 'repo_commit', repo_name=safe_str(repo.repo_id),
221 # TODO: marcink figure out a way to generate the url for the
182 commit_id=commit_id)
222 # icon
223 'icon_link': '',
224 'value_display': 'Group: %s (%d members)' % (
225 group.users_group_name, len(group.members),),
226 'value': group.users_group_name,
227 'description': group.user_group_description,
228 'owner': group.user.username,
229
183
230 'owner_icon': h.gravatar_url(group.user.email, 30),
184 else:
231 'value_display_owner': h.person(group.user.email),
185 return request.route_url(
232
186 'repo_commit', repo_name=safe_str(repo.repo_name),
233 'value_type': 'user_group',
187 commit_id=commit_id)
234 'active': group.users_group_active,
235 }
236 for group in user_groups
237 ]
238 return _groups
239
188
240 @classmethod
189 @classmethod
241 def update_repoinfo(cls, repositories=None):
190 def update_repoinfo(cls, repositories=None):
@@ -308,7 +257,7 b' class RepoModel(BaseModel):'
308 "last_changeset": last_rev(repo.repo_name, cs_cache),
257 "last_changeset": last_rev(repo.repo_name, cs_cache),
309 "last_changeset_raw": cs_cache.get('revision'),
258 "last_changeset_raw": cs_cache.get('revision'),
310
259
311 "desc": desc(repo.description),
260 "desc": desc(repo.description_safe),
312 "owner": user_profile(repo.user.username),
261 "owner": user_profile(repo.user.username),
313
262
314 "state": state(repo.repo_state),
263 "state": state(repo.repo_state),
@@ -340,7 +289,7 b' class RepoModel(BaseModel):'
340 defaults = repo_info.get_dict()
289 defaults = repo_info.get_dict()
341 defaults['repo_name'] = repo_info.just_name
290 defaults['repo_name'] = repo_info.just_name
342
291
343 groups = repo_info.groups_with_parents
292 groups = repo_info.groups_with_parents
344 parent_group = groups[-1] if groups else None
293 parent_group = groups[-1] if groups else None
345
294
346 # we use -1 as this is how in HTML, we mark an empty group
295 # we use -1 as this is how in HTML, we mark an empty group
@@ -376,16 +325,6 b' class RepoModel(BaseModel):'
376 replacement_user = User.get_first_super_admin().username
325 replacement_user = User.get_first_super_admin().username
377 defaults.update({'user': replacement_user})
326 defaults.update({'user': replacement_user})
378
327
379 # fill repository users
380 for p in repo_info.repo_to_perm:
381 defaults.update({'u_perm_%s' % p.user.user_id:
382 p.permission.permission_name})
383
384 # fill repository groups
385 for p in repo_info.users_group_to_perm:
386 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
387 p.permission.permission_name})
388
389 return defaults
328 return defaults
390
329
391 def update(self, repo, **kwargs):
330 def update(self, repo, **kwargs):
@@ -414,12 +353,6 b' class RepoModel(BaseModel):'
414 val = kwargs[k]
353 val = kwargs[k]
415 if strip:
354 if strip:
416 k = remove_prefix(k, 'repo_')
355 k = remove_prefix(k, 'repo_')
417 if k == 'clone_uri':
418 from rhodecode.model.validators import Missing
419 _change = kwargs.get('clone_uri_change')
420 if _change in [Missing, 'OLD']:
421 # we don't change the value, so use original one
422 val = cur_repo.clone_uri
423
356
424 setattr(cur_repo, k, val)
357 setattr(cur_repo, k, val)
425
358
@@ -594,10 +527,16 b' class RepoModel(BaseModel):'
594
527
595 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
528 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
596
529
530 changes = {
531 'added': [],
532 'updated': [],
533 'deleted': []
534 }
597 # update permissions
535 # update permissions
598 for member_id, perm, member_type in perm_updates:
536 for member_id, perm, member_type in perm_updates:
599 member_id = int(member_id)
537 member_id = int(member_id)
600 if member_type == 'user':
538 if member_type == 'user':
539 member_name = User.get(member_id).username
601 # this updates also current one if found
540 # this updates also current one if found
602 self.grant_user_permission(
541 self.grant_user_permission(
603 repo=repo, user=member_id, perm=perm)
542 repo=repo, user=member_id, perm=perm)
@@ -609,10 +548,14 b' class RepoModel(BaseModel):'
609 self.grant_user_group_permission(
548 self.grant_user_group_permission(
610 repo=repo, group_name=member_id, perm=perm)
549 repo=repo, group_name=member_id, perm=perm)
611
550
551 changes['updated'].append({'type': member_type, 'id': member_id,
552 'name': member_name, 'new_perm': perm})
553
612 # set new permissions
554 # set new permissions
613 for member_id, perm, member_type in perm_additions:
555 for member_id, perm, member_type in perm_additions:
614 member_id = int(member_id)
556 member_id = int(member_id)
615 if member_type == 'user':
557 if member_type == 'user':
558 member_name = User.get(member_id).username
616 self.grant_user_permission(
559 self.grant_user_permission(
617 repo=repo, user=member_id, perm=perm)
560 repo=repo, user=member_id, perm=perm)
618 else: # set for user group
561 else: # set for user group
@@ -622,11 +565,13 b' class RepoModel(BaseModel):'
622 *req_perms)(member_name, user=cur_user):
565 *req_perms)(member_name, user=cur_user):
623 self.grant_user_group_permission(
566 self.grant_user_group_permission(
624 repo=repo, group_name=member_id, perm=perm)
567 repo=repo, group_name=member_id, perm=perm)
625
568 changes['added'].append({'type': member_type, 'id': member_id,
569 'name': member_name, 'new_perm': perm})
626 # delete permissions
570 # delete permissions
627 for member_id, perm, member_type in perm_deletions:
571 for member_id, perm, member_type in perm_deletions:
628 member_id = int(member_id)
572 member_id = int(member_id)
629 if member_type == 'user':
573 if member_type == 'user':
574 member_name = User.get(member_id).username
630 self.revoke_user_permission(repo=repo, user=member_id)
575 self.revoke_user_permission(repo=repo, user=member_id)
631 else: # set for user group
576 else: # set for user group
632 # check if we have permissions to alter this usergroup
577 # check if we have permissions to alter this usergroup
@@ -636,6 +581,10 b' class RepoModel(BaseModel):'
636 self.revoke_user_group_permission(
581 self.revoke_user_group_permission(
637 repo=repo, group_name=member_id)
582 repo=repo, group_name=member_id)
638
583
584 changes['deleted'].append({'type': member_type, 'id': member_id,
585 'name': member_name, 'new_perm': perm})
586 return changes
587
639 def create_fork(self, form_data, cur_user):
588 def create_fork(self, form_data, cur_user):
640 """
589 """
641 Simple wrapper into executing celery task for fork creation
590 Simple wrapper into executing celery task for fork creation
@@ -35,7 +35,7 b' from zope.cachedescriptors.property impo'
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (_hash_key,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 UserGroup, Repository)
40 UserGroup, Repository)
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
@@ -73,8 +73,9 b' class RepoGroupModel(BaseModel):'
73 .filter(RepoGroup.group_name == repo_group_name)
73 .filter(RepoGroup.group_name == repo_group_name)
74
74
75 if cache:
75 if cache:
76 repo = repo.options(FromCache(
76 name_key = _hash_key(repo_group_name)
77 "sql_cache_short", "get_repo_group_%s" % repo_group_name))
77 repo = repo.options(
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
78 return repo.scalar()
79 return repo.scalar()
79
80
80 def get_default_create_personal_repo_group(self):
81 def get_default_create_personal_repo_group(self):
@@ -339,6 +340,12 b' class RepoGroupModel(BaseModel):'
339
340
340 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
341
342
343 changes = {
344 'added': [],
345 'updated': [],
346 'deleted': []
347 }
348
342 def _set_perm_user(obj, user, perm):
349 def _set_perm_user(obj, user, perm):
343 if isinstance(obj, RepoGroup):
350 if isinstance(obj, RepoGroup):
344 self.grant_user_permission(
351 self.grant_user_permission(
@@ -381,7 +388,6 b' class RepoGroupModel(BaseModel):'
381 repo=obj, group_name=user_group)
388 repo=obj, group_name=user_group)
382
389
383 # start updates
390 # start updates
384 updates = []
385 log.debug('Now updating permissions for %s in recursive mode:%s',
391 log.debug('Now updating permissions for %s in recursive mode:%s',
386 repo_group, recursive)
392 repo_group, recursive)
387
393
@@ -407,10 +413,13 b' class RepoGroupModel(BaseModel):'
407 # in recursive mode
413 # in recursive mode
408 obj = repo_group
414 obj = repo_group
409
415
416 change_obj = obj.get_api_data()
417
410 # update permissions
418 # update permissions
411 for member_id, perm, member_type in perm_updates:
419 for member_id, perm, member_type in perm_updates:
412 member_id = int(member_id)
420 member_id = int(member_id)
413 if member_type == 'user':
421 if member_type == 'user':
422 member_name = User.get(member_id).username
414 # this updates also current one if found
423 # this updates also current one if found
415 _set_perm_user(obj, user=member_id, perm=perm)
424 _set_perm_user(obj, user=member_id, perm=perm)
416 else: # set for user group
425 else: # set for user group
@@ -419,10 +428,15 b' class RepoGroupModel(BaseModel):'
419 user=cur_user):
428 user=cur_user):
420 _set_perm_group(obj, users_group=member_id, perm=perm)
429 _set_perm_group(obj, users_group=member_id, perm=perm)
421
430
431 changes['updated'].append(
432 {'change_obj': change_obj, 'type': member_type,
433 'id': member_id, 'name': member_name, 'new_perm': perm})
434
422 # set new permissions
435 # set new permissions
423 for member_id, perm, member_type in perm_additions:
436 for member_id, perm, member_type in perm_additions:
424 member_id = int(member_id)
437 member_id = int(member_id)
425 if member_type == 'user':
438 if member_type == 'user':
439 member_name = User.get(member_id).username
426 _set_perm_user(obj, user=member_id, perm=perm)
440 _set_perm_user(obj, user=member_id, perm=perm)
427 else: # set for user group
441 else: # set for user group
428 # check if we have permissions to alter this usergroup
442 # check if we have permissions to alter this usergroup
@@ -431,10 +445,15 b' class RepoGroupModel(BaseModel):'
431 user=cur_user):
445 user=cur_user):
432 _set_perm_group(obj, users_group=member_id, perm=perm)
446 _set_perm_group(obj, users_group=member_id, perm=perm)
433
447
448 changes['added'].append(
449 {'change_obj': change_obj, 'type': member_type,
450 'id': member_id, 'name': member_name, 'new_perm': perm})
451
434 # delete permissions
452 # delete permissions
435 for member_id, perm, member_type in perm_deletions:
453 for member_id, perm, member_type in perm_deletions:
436 member_id = int(member_id)
454 member_id = int(member_id)
437 if member_type == 'user':
455 if member_type == 'user':
456 member_name = User.get(member_id).username
438 _revoke_perm_user(obj, user=member_id)
457 _revoke_perm_user(obj, user=member_id)
439 else: # set for user group
458 else: # set for user group
440 # check if we have permissions to alter this usergroup
459 # check if we have permissions to alter this usergroup
@@ -443,13 +462,16 b' class RepoGroupModel(BaseModel):'
443 user=cur_user):
462 user=cur_user):
444 _revoke_perm_group(obj, user_group=member_id)
463 _revoke_perm_group(obj, user_group=member_id)
445
464
446 updates.append(obj)
465 changes['deleted'].append(
466 {'change_obj': change_obj, 'type': member_type,
467 'id': member_id, 'name': member_name, 'new_perm': perm})
468
447 # if it's not recursive call for all,repos,groups
469 # if it's not recursive call for all,repos,groups
448 # break the loop and don't proceed with other changes
470 # break the loop and don't proceed with other changes
449 if recursive not in ['all', 'repos', 'groups']:
471 if recursive not in ['all', 'repos', 'groups']:
450 break
472 break
451
473
452 return updates
474 return changes
453
475
454 def update(self, repo_group, form_data):
476 def update(self, repo_group, form_data):
455 try:
477 try:
@@ -689,7 +711,7 b' class RepoGroupModel(BaseModel):'
689 "menu": quick_menu(group.group_name),
711 "menu": quick_menu(group.group_name),
690 "name": repo_group_lnk(group.group_name),
712 "name": repo_group_lnk(group.group_name),
691 "name_raw": group.group_name,
713 "name_raw": group.group_name,
692 "desc": desc(group.group_description, group.personal),
714 "desc": desc(group.description_safe, group.personal),
693 "top_level_repos": 0,
715 "top_level_repos": 0,
694 "owner": user_profile(group.user.username)
716 "owner": user_profile(group.user.username)
695 }
717 }
@@ -47,7 +47,7 b' from rhodecode.lib.auth import ('
47 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
47 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
48 from rhodecode.lib import hooks_utils, caches
48 from rhodecode.lib import hooks_utils, caches
49 from rhodecode.lib.utils import (
49 from rhodecode.lib.utils import (
50 get_filesystem_repos, action_logger, make_db_config)
50 get_filesystem_repos, make_db_config)
51 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
51 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
52 from rhodecode.lib.system_info import get_system_info
52 from rhodecode.lib.system_info import get_system_info
53 from rhodecode.model import BaseModel
53 from rhodecode.model import BaseModel
@@ -289,9 +289,6 b' class ScmModel(BaseModel):'
289 if f is not None:
289 if f is not None:
290 try:
290 try:
291 self.sa.delete(f)
291 self.sa.delete(f)
292 action_logger(UserTemp(user_id),
293 'stopped_following_repo',
294 RepoTemp(follow_repo_id))
295 return
292 return
296 except Exception:
293 except Exception:
297 log.error(traceback.format_exc())
294 log.error(traceback.format_exc())
@@ -302,10 +299,6 b' class ScmModel(BaseModel):'
302 f.user_id = user_id
299 f.user_id = user_id
303 f.follows_repo_id = follow_repo_id
300 f.follows_repo_id = follow_repo_id
304 self.sa.add(f)
301 self.sa.add(f)
305
306 action_logger(UserTemp(user_id),
307 'started_following_repo',
308 RepoTemp(follow_repo_id))
309 except Exception:
302 except Exception:
310 log.error(traceback.format_exc())
303 log.error(traceback.format_exc())
311 raise
304 raise
@@ -503,7 +496,7 b' class ScmModel(BaseModel):'
503
496
504 if not flat:
497 if not flat:
505 _data = {
498 _data = {
506 "name": f.unicode_path,
499 "name": h.escape(f.unicode_path),
507 "type": "file",
500 "type": "file",
508 }
501 }
509 if extended_info:
502 if extended_info:
@@ -529,7 +522,7 b' class ScmModel(BaseModel):'
529 _data = d.unicode_path
522 _data = d.unicode_path
530 if not flat:
523 if not flat:
531 _data = {
524 _data = {
532 "name": d.unicode_path,
525 "name": h.escape(d.unicode_path),
533 "type": "dir",
526 "type": "dir",
534 }
527 }
535 if extended_info:
528 if extended_info:
@@ -18,6 +18,7 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import hashlib
22 import hashlib
22 import logging
23 import logging
23 from collections import namedtuple
24 from collections import namedtuple
@@ -51,7 +52,8 b' class SettingsModel(BaseModel):'
51 BUILTIN_HOOKS = (
52 BUILTIN_HOOKS = (
52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
54 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
54 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL)
55 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
56 RhodeCodeUi.HOOK_PUSH_KEY,)
55 HOOKS_SECTION = 'hooks'
57 HOOKS_SECTION = 'hooks'
56
58
57 def __init__(self, sa=None, repo=None):
59 def __init__(self, sa=None, repo=None):
@@ -207,6 +209,7 b' class SettingsModel(BaseModel):'
207 caches.clear_cache_manager(cache_manager)
209 caches.clear_cache_manager(cache_manager)
208
210
209 def get_all_settings(self, cache=False):
211 def get_all_settings(self, cache=False):
212
210 def _compute():
213 def _compute():
211 q = self._get_settings_query()
214 q = self._get_settings_query()
212 if not q:
215 if not q:
@@ -413,15 +416,16 b' class VcsSettingsModel(object):'
413 ('hooks', 'outgoing.pull_logger'),)
416 ('hooks', 'outgoing.pull_logger'),)
414 HG_SETTINGS = (
417 HG_SETTINGS = (
415 ('extensions', 'largefiles'),
418 ('extensions', 'largefiles'),
416 ('phases', 'publish'),)
419 ('phases', 'publish'),
420 ('extensions', 'evolve'),)
417 GIT_SETTINGS = (
421 GIT_SETTINGS = (
418 ('vcs_git_lfs', 'enabled'),)
422 ('vcs_git_lfs', 'enabled'),)
419
420 GLOBAL_HG_SETTINGS = (
423 GLOBAL_HG_SETTINGS = (
421 ('extensions', 'largefiles'),
424 ('extensions', 'largefiles'),
422 ('largefiles', 'usercache'),
425 ('largefiles', 'usercache'),
423 ('phases', 'publish'),
426 ('phases', 'publish'),
424 ('extensions', 'hgsubversion'))
427 ('extensions', 'hgsubversion'),
428 ('extensions', 'evolve'),)
425 GLOBAL_GIT_SETTINGS = (
429 GLOBAL_GIT_SETTINGS = (
426 ('vcs_git_lfs', 'enabled'),
430 ('vcs_git_lfs', 'enabled'),
427 ('vcs_git_lfs', 'store_location'))
431 ('vcs_git_lfs', 'store_location'))
@@ -544,22 +548,26 b' class VcsSettingsModel(object):'
544
548
545 @assert_repo_settings
549 @assert_repo_settings
546 def create_or_update_repo_hg_settings(self, data):
550 def create_or_update_repo_hg_settings(self, data):
547 largefiles, phases = \
551 largefiles, phases, evolve = \
548 self.HG_SETTINGS
552 self.HG_SETTINGS
549 largefiles_key, phases_key = \
553 largefiles_key, phases_key, evolve_key = \
550 self._get_settings_keys(self.HG_SETTINGS, data)
554 self._get_settings_keys(self.HG_SETTINGS, data)
551
555
552 self._create_or_update_ui(
556 self._create_or_update_ui(
553 self.repo_settings, *largefiles, value='',
557 self.repo_settings, *largefiles, value='',
554 active=data[largefiles_key])
558 active=data[largefiles_key])
555 self._create_or_update_ui(
559 self._create_or_update_ui(
560 self.repo_settings, *evolve, value='',
561 active=data[evolve_key])
562 self._create_or_update_ui(
556 self.repo_settings, *phases, value=safe_str(data[phases_key]))
563 self.repo_settings, *phases, value=safe_str(data[phases_key]))
557
564
558 def create_or_update_global_hg_settings(self, data):
565 def create_or_update_global_hg_settings(self, data):
559 largefiles, largefiles_store, phases, hgsubversion \
566 largefiles, largefiles_store, phases, hgsubversion, evolve \
560 = self.GLOBAL_HG_SETTINGS
567 = self.GLOBAL_HG_SETTINGS
561 largefiles_key, largefiles_store_key, phases_key, subversion_key \
568 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
562 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
569 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
570
563 self._create_or_update_ui(
571 self._create_or_update_ui(
564 self.global_settings, *largefiles, value='',
572 self.global_settings, *largefiles, value='',
565 active=data[largefiles_key])
573 active=data[largefiles_key])
@@ -570,6 +578,9 b' class VcsSettingsModel(object):'
570 self.global_settings, *phases, value=safe_str(data[phases_key]))
578 self.global_settings, *phases, value=safe_str(data[phases_key]))
571 self._create_or_update_ui(
579 self._create_or_update_ui(
572 self.global_settings, *hgsubversion, active=data[subversion_key])
580 self.global_settings, *hgsubversion, active=data[subversion_key])
581 self._create_or_update_ui(
582 self.global_settings, *evolve, value='',
583 active=data[evolve_key])
573
584
574 def create_or_update_repo_git_settings(self, data):
585 def create_or_update_repo_git_settings(self, data):
575 # NOTE(marcink): # comma make unpack work properly
586 # NOTE(marcink): # comma make unpack work properly
@@ -774,3 +785,27 b' class VcsSettingsModel(object):'
774 raise ValueError(
785 raise ValueError(
775 'The given data does not contain {} key'.format(data_key))
786 'The given data does not contain {} key'.format(data_key))
776 return data_keys
787 return data_keys
788
789 def create_largeobjects_dirs_if_needed(self, repo_store_path):
790 """
791 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
792 does a repository scan if enabled in the settings.
793 """
794
795 from rhodecode.lib.vcs.backends.hg import largefiles_store
796 from rhodecode.lib.vcs.backends.git import lfs_store
797
798 paths = [
799 largefiles_store(repo_store_path),
800 lfs_store(repo_store_path)]
801
802 for path in paths:
803 if os.path.isdir(path):
804 continue
805 if os.path.isfile(path):
806 continue
807 # not a file nor dir, we try to create it
808 try:
809 os.makedirs(path)
810 except Exception:
811 log.warning('Failed to create largefiles dir:%s', path)
@@ -30,21 +30,21 b' from pylons.i18n.translation import _'
30
30
31 import ipaddress
31 import ipaddress
32 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.sql.expression import true, false
34
33
35 from rhodecode import events
34 from rhodecode import events
36 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.utils2 import (
36 from rhodecode.lib.utils2 import (
38 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
39 AttributeDict, str2bool)
38 AttributeDict, str2bool)
39 from rhodecode.lib.exceptions import (
40 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
40 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
42 from rhodecode.model.auth_token import AuthTokenModel
44 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.db import (
45 from rhodecode.model.db import (
44 or_, joinedload, User, UserToPerm, UserEmailMap, UserIpMap, UserLog)
46 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 from rhodecode.lib.exceptions import (
47 UserEmailMap, UserIpMap, UserLog)
46 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
47 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.repo_group import RepoGroupModel
50
50
@@ -58,13 +58,52 b' class UserModel(BaseModel):'
58 def get(self, user_id, cache=False):
58 def get(self, user_id, cache=False):
59 user = self.sa.query(User)
59 user = self.sa.query(User)
60 if cache:
60 if cache:
61 user = user.options(FromCache("sql_cache_short",
61 user = user.options(
62 "get_user_%s" % user_id))
62 FromCache("sql_cache_short", "get_user_%s" % user_id))
63 return user.get(user_id)
63 return user.get(user_id)
64
64
65 def get_user(self, user):
65 def get_user(self, user):
66 return self._get_user(user)
66 return self._get_user(user)
67
67
68 def _serialize_user(self, user):
69 import rhodecode.lib.helpers as h
70
71 return {
72 'id': user.user_id,
73 'first_name': user.first_name,
74 'last_name': user.last_name,
75 'username': user.username,
76 'email': user.email,
77 'icon_link': h.gravatar_url(user.email, 30),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
80 'value_type': 'user',
81 'active': user.active,
82 }
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
86 query = self.sa.query(User)
87 if only_active:
88 query = query.filter(User.active == true())
89
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
93 or_(
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
97 )
98 )
99 query = query.limit(limit)
100 users = query.all()
101
102 _users = [
103 self._serialize_user(user) for user in users
104 ]
105 return _users
106
68 def get_by_username(self, username, cache=False, case_insensitive=False):
107 def get_by_username(self, username, cache=False, case_insensitive=False):
69
108
70 if case_insensitive:
109 if case_insensitive:
@@ -73,8 +112,9 b' class UserModel(BaseModel):'
73 user = self.sa.query(User)\
112 user = self.sa.query(User)\
74 .filter(User.username == username)
113 .filter(User.username == username)
75 if cache:
114 if cache:
76 user = user.options(FromCache("sql_cache_short",
115 name_key = _hash_key(username)
77 "get_user_%s" % username))
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
78 return user.scalar()
118 return user.scalar()
79
119
80 def get_by_email(self, email, cache=False, case_insensitive=False):
120 def get_by_email(self, email, cache=False, case_insensitive=False):
@@ -630,7 +670,8 b' class UserModel(BaseModel):'
630 user_id, api_key, username)
670 user_id, api_key, username)
631 return False
671 return False
632 if not dbuser.active:
672 if not dbuser.active:
633 log.debug('User `%s` is inactive, skipping fill data', username)
673 log.debug('User `%s:%s` is inactive, skipping fill data',
674 username, user_id)
634 return False
675 return False
635
676
636 log.debug('filling user:%s data', dbuser)
677 log.debug('filling user:%s data', dbuser)
@@ -638,6 +679,11 b' class UserModel(BaseModel):'
638 # TODO: johbo: Think about this and find a clean solution
679 # TODO: johbo: Think about this and find a clean solution
639 user_data = dbuser.get_dict()
680 user_data = dbuser.get_dict()
640 user_data.update(dbuser.get_api_data(include_secrets=True))
681 user_data.update(dbuser.get_api_data(include_secrets=True))
682 user_data.update({
683 # set explicit the safe escaped values
684 'first_name': dbuser.first_name,
685 'last_name': dbuser.last_name,
686 })
641
687
642 for k, v in user_data.iteritems():
688 for k, v in user_data.iteritems():
643 # properties of auth user we dont update
689 # properties of auth user we dont update
@@ -726,7 +772,7 b' class UserModel(BaseModel):'
726 """
772 """
727 user = self._get_user(user)
773 user = self._get_user(user)
728 obj = UserEmailMap.query().get(email_id)
774 obj = UserEmailMap.query().get(email_id)
729 if obj:
775 if obj and obj.user_id == user.user_id:
730 self.sa.delete(obj)
776 self.sa.delete(obj)
731
777
732 def parse_ip_range(self, ip_range):
778 def parse_ip_range(self, ip_range):
@@ -783,7 +829,7 b' class UserModel(BaseModel):'
783 """
829 """
784 user = self._get_user(user)
830 user = self._get_user(user)
785 obj = UserIpMap.query().get(ip_id)
831 obj = UserIpMap.query().get(ip_id)
786 if obj:
832 if obj and obj.user_id == user.user_id:
787 self.sa.delete(obj)
833 self.sa.delete(obj)
788
834
789 def get_accounts_in_creation_order(self, current_user=None):
835 def get_accounts_in_creation_order(self, current_user=None):
@@ -27,14 +27,18 b' user group model for RhodeCode'
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str, safe_unicode
31 from rhodecode.lib.exceptions import (
32 UserGroupAssignedException, RepoGroupAssignmentError)
33 from rhodecode.lib.utils2 import (
34 get_current_rhodecode_user, action_logger_generic)
31 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
36 from rhodecode.model.scm import UserGroupList
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
37 from rhodecode.model.db import (
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
38 true, func, User, UserGroupMember, UserGroup,
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
39 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
36 RepoGroupAssignmentError
40 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
41
38
42
39 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
40
44
@@ -112,7 +116,7 b' class UserGroupModel(BaseModel):'
112 if member_type == 'user':
116 if member_type == 'user':
113 self.revoke_user_permission(user_group=user_group, user=member_id)
117 self.revoke_user_permission(user_group=user_group, user=member_id)
114 else:
118 else:
115 #check if we have permissions to alter this usergroup
119 # check if we have permissions to alter this usergroup
116 member_name = UserGroup.get(member_id).users_group_name
120 member_name = UserGroup.get(member_id).users_group_name
117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
121 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
118 self.revoke_user_group_permission(
122 self.revoke_user_group_permission(
@@ -171,7 +175,7 b' class UserGroupModel(BaseModel):'
171 user_id for user_id in current_members_ids
175 user_id for user_id in current_members_ids
172 if user_id not in user_id_list]
176 if user_id not in user_id_list]
173
177
174 return (added_members, deleted_members)
178 return added_members, deleted_members
175
179
176 def _set_users_as_members(self, user_group, user_ids):
180 def _set_users_as_members(self, user_group, user_ids):
177 user_group.members = []
181 user_group.members = []
@@ -187,6 +191,7 b' class UserGroupModel(BaseModel):'
187 self._set_users_as_members(user_group, user_ids)
191 self._set_users_as_members(user_group, user_ids)
188 self._log_user_changes('added to', user_group, added)
192 self._log_user_changes('added to', user_group, added)
189 self._log_user_changes('removed from', user_group, removed)
193 self._log_user_changes('removed from', user_group, removed)
194 return added, removed
190
195
191 def _clean_members_data(self, members_data):
196 def _clean_members_data(self, members_data):
192 if not members_data:
197 if not members_data:
@@ -221,12 +226,16 b' class UserGroupModel(BaseModel):'
221
226
222 user_group.user = owner
227 user_group.user = owner
223
228
229 added_user_ids = []
230 removed_user_ids = []
224 if 'users_group_members' in form_data:
231 if 'users_group_members' in form_data:
225 members_id_list = self._clean_members_data(
232 members_id_list = self._clean_members_data(
226 form_data['users_group_members'])
233 form_data['users_group_members'])
227 self._update_members_from_user_ids(user_group, members_id_list)
234 added_user_ids, removed_user_ids = \
235 self._update_members_from_user_ids(user_group, members_id_list)
228
236
229 self.sa.add(user_group)
237 self.sa.add(user_group)
238 return user_group, added_user_ids, removed_user_ids
230
239
231 def delete(self, user_group, force=False):
240 def delete(self, user_group, force=False):
232 """
241 """
@@ -539,6 +548,59 b' class UserGroupModel(BaseModel):'
539 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
548 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
540 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
549 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
541
550
551 def _serialize_user_group(self, user_group):
552 import rhodecode.lib.helpers as h
553 return {
554 'id': user_group.users_group_id,
555 # TODO: marcink figure out a way to generate the url for the
556 # icon
557 'icon_link': '',
558 'value_display': 'Group: %s (%d members)' % (
559 user_group.users_group_name, len(user_group.members),),
560 'value': user_group.users_group_name,
561 'description': user_group.user_group_description,
562 'owner': user_group.user.username,
563
564 'owner_icon': h.gravatar_url(user_group.user.email, 30),
565 'value_display_owner': h.person(user_group.user.email),
566
567 'value_type': 'user_group',
568 'active': user_group.users_group_active,
569 }
570
571 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
572 expand_groups=False):
573 query = self.sa.query(UserGroup)
574 if only_active:
575 query = query.filter(UserGroup.users_group_active == true())
576
577 if name_contains:
578 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
579 query = query.filter(
580 UserGroup.users_group_name.ilike(ilike_expression))\
581 .order_by(func.length(UserGroup.users_group_name))\
582 .order_by(UserGroup.users_group_name)
583
584 query = query.limit(limit)
585 user_groups = query.all()
586 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
587 user_groups = UserGroupList(user_groups, perm_set=perm_set)
588
589 # store same serialize method to extract data from User
590 from rhodecode.model.user import UserModel
591 serialize_user = UserModel()._serialize_user
592
593 _groups = []
594 for group in user_groups:
595 entry = self._serialize_user_group(group)
596 if expand_groups:
597 expanded_members = []
598 for member in group.members:
599 expanded_members.append(serialize_user(member.user))
600 entry['members'] = expanded_members
601 _groups.append(entry)
602 return _groups
603
542 @staticmethod
604 @staticmethod
543 def get_user_groups_as_dict(user_group):
605 def get_user_groups_as_dict(user_group):
544 import rhodecode.lib.helpers as h
606 import rhodecode.lib.helpers as h
@@ -550,7 +612,9 b' class UserGroupModel(BaseModel):'
550 'active': user_group.users_group_active,
612 'active': user_group.users_group_active,
551 "owner": user_group.user.username,
613 "owner": user_group.user.username,
552 'owner_icon': h.gravatar_url(user_group.user.email, 30),
614 'owner_icon': h.gravatar_url(user_group.user.email, 30),
553 "owner_data": {'owner': user_group.user.username, 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
615 "owner_data": {
616 'owner': user_group.user.username,
617 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
554 }
618 }
555 return data
619 return data
556
620
@@ -19,8 +19,10 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 import deform.widget
22
23
23 from rhodecode.translation import _
24 from rhodecode.translation import _
25 from rhodecode.model.validation_schema.utils import convert_to_optgroup
24 from rhodecode.model.validation_schema import validators, preparers, types
26 from rhodecode.model.validation_schema import validators, preparers, types
25
27
26 DEFAULT_LANDING_REF = 'rev:tip'
28 DEFAULT_LANDING_REF = 'rev:tip'
@@ -32,6 +34,11 b' def get_group_and_repo(repo_name):'
32 repo_name, get_object=True)
34 repo_name, get_object=True)
33
35
34
36
37 def get_repo_group(repo_group_id):
38 from rhodecode.model.repo_group import RepoGroup
39 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
40
41
35 @colander.deferred
42 @colander.deferred
36 def deferred_repo_type_validator(node, kw):
43 def deferred_repo_type_validator(node, kw):
37 options = kw.get('repo_type_options', [])
44 options = kw.get('repo_type_options', [])
@@ -53,11 +60,27 b' def deferred_repo_owner_validator(node, '
53
60
54 @colander.deferred
61 @colander.deferred
55 def deferred_landing_ref_validator(node, kw):
62 def deferred_landing_ref_validator(node, kw):
56 options = kw.get('repo_ref_options', [DEFAULT_LANDING_REF])
63 options = kw.get(
64 'repo_ref_options', [DEFAULT_LANDING_REF])
57 return colander.OneOf([x for x in options])
65 return colander.OneOf([x for x in options])
58
66
59
67
60 @colander.deferred
68 @colander.deferred
69 def deferred_clone_uri_validator(node, kw):
70 repo_type = kw.get('repo_type')
71 validator = validators.CloneUriValidator(repo_type)
72 return validator
73
74
75 @colander.deferred
76 def deferred_landing_ref_widget(node, kw):
77 items = kw.get(
78 'repo_ref_items', [(DEFAULT_LANDING_REF, DEFAULT_LANDING_REF)])
79 items = convert_to_optgroup(items)
80 return deform.widget.Select2Widget(values=items)
81
82
83 @colander.deferred
61 def deferred_fork_of_validator(node, kw):
84 def deferred_fork_of_validator(node, kw):
62 old_values = kw.get('old_values') or {}
85 old_values = kw.get('old_values') or {}
63
86
@@ -191,7 +214,25 b' def deferred_unique_name_validator(node,'
191
214
192 @colander.deferred
215 @colander.deferred
193 def deferred_repo_name_validator(node, kw):
216 def deferred_repo_name_validator(node, kw):
194 return validators.valid_name_validator
217 def no_git_suffix_validator(node, value):
218 if value.endswith('.git'):
219 msg = _('Repository name cannot end with .git')
220 raise colander.Invalid(node, msg)
221 return colander.All(
222 no_git_suffix_validator, validators.valid_name_validator)
223
224
225 @colander.deferred
226 def deferred_repo_group_validator(node, kw):
227 options = kw.get(
228 'repo_repo_group_options')
229 return colander.OneOf([x for x in options])
230
231
232 @colander.deferred
233 def deferred_repo_group_widget(node, kw):
234 items = kw.get('repo_repo_group_items')
235 return deform.widget.Select2Widget(values=items)
195
236
196
237
197 class GroupType(colander.Mapping):
238 class GroupType(colander.Mapping):
@@ -215,8 +256,10 b' class GroupType(colander.Mapping):'
215 parent_group_name,
256 parent_group_name,
216 parent_group) = get_group_and_repo(validated_name)
257 parent_group) = get_group_and_repo(validated_name)
217
258
259 appstruct['repo_name_with_group'] = validated_name
218 appstruct['repo_name_without_group'] = repo_name_without_group
260 appstruct['repo_name_without_group'] = repo_name_without_group
219 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
261 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
262
220 if parent_group:
263 if parent_group:
221 appstruct['repo_group_id'] = parent_group.group_id
264 appstruct['repo_group_id'] = parent_group.group_id
222
265
@@ -260,16 +303,19 b' class RepoSchema(colander.MappingSchema)'
260
303
261 repo_owner = colander.SchemaNode(
304 repo_owner = colander.SchemaNode(
262 colander.String(),
305 colander.String(),
263 validator=deferred_repo_owner_validator)
306 validator=deferred_repo_owner_validator,
307 widget=deform.widget.TextInputWidget())
264
308
265 repo_description = colander.SchemaNode(
309 repo_description = colander.SchemaNode(
266 colander.String(), missing='')
310 colander.String(), missing='',
311 widget=deform.widget.TextAreaWidget())
267
312
268 repo_landing_commit_ref = colander.SchemaNode(
313 repo_landing_commit_ref = colander.SchemaNode(
269 colander.String(),
314 colander.String(),
270 validator=deferred_landing_ref_validator,
315 validator=deferred_landing_ref_validator,
271 preparers=[preparers.strip_preparer],
316 preparers=[preparers.strip_preparer],
272 missing=DEFAULT_LANDING_REF)
317 missing=DEFAULT_LANDING_REF,
318 widget=deferred_landing_ref_widget)
273
319
274 repo_clone_uri = colander.SchemaNode(
320 repo_clone_uri = colander.SchemaNode(
275 colander.String(),
321 colander.String(),
@@ -284,19 +330,19 b' class RepoSchema(colander.MappingSchema)'
284
330
285 repo_private = colander.SchemaNode(
331 repo_private = colander.SchemaNode(
286 types.StringBooleanType(),
332 types.StringBooleanType(),
287 missing=False)
333 missing=False, widget=deform.widget.CheckboxWidget())
288 repo_copy_permissions = colander.SchemaNode(
334 repo_copy_permissions = colander.SchemaNode(
289 types.StringBooleanType(),
335 types.StringBooleanType(),
290 missing=False)
336 missing=False, widget=deform.widget.CheckboxWidget())
291 repo_enable_statistics = colander.SchemaNode(
337 repo_enable_statistics = colander.SchemaNode(
292 types.StringBooleanType(),
338 types.StringBooleanType(),
293 missing=False)
339 missing=False, widget=deform.widget.CheckboxWidget())
294 repo_enable_downloads = colander.SchemaNode(
340 repo_enable_downloads = colander.SchemaNode(
295 types.StringBooleanType(),
341 types.StringBooleanType(),
296 missing=False)
342 missing=False, widget=deform.widget.CheckboxWidget())
297 repo_enable_locking = colander.SchemaNode(
343 repo_enable_locking = colander.SchemaNode(
298 types.StringBooleanType(),
344 types.StringBooleanType(),
299 missing=False)
345 missing=False, widget=deform.widget.CheckboxWidget())
300
346
301 def deserialize(self, cstruct):
347 def deserialize(self, cstruct):
302 """
348 """
@@ -319,3 +365,50 b' class RepoSchema(colander.MappingSchema)'
319 third.deserialize({'unique_repo_name': validated_name})
365 third.deserialize({'unique_repo_name': validated_name})
320
366
321 return appstruct
367 return appstruct
368
369
370 class RepoSettingsSchema(RepoSchema):
371 repo_group = colander.SchemaNode(
372 colander.Integer(),
373 validator=deferred_repo_group_validator,
374 widget=deferred_repo_group_widget,
375 missing='')
376
377 repo_clone_uri_change = colander.SchemaNode(
378 colander.String(),
379 missing='NEW')
380
381 repo_clone_uri = colander.SchemaNode(
382 colander.String(),
383 preparers=[preparers.strip_preparer],
384 validator=deferred_clone_uri_validator,
385 missing='')
386
387 def deserialize(self, cstruct):
388 """
389 Custom deserialize that allows to chain validation, and verify
390 permissions, and as last step uniqueness
391 """
392
393 # first pass, to validate given data
394 appstruct = super(RepoSchema, self).deserialize(cstruct)
395 validated_name = appstruct['repo_name']
396 # because of repoSchema adds repo-group as an ID, we inject it as
397 # full name here because validators require it, it's unwrapped later
398 # so it's safe to use and final name is going to be without group anyway
399
400 group, separator = get_repo_group(appstruct['repo_group'])
401 if group:
402 validated_name = separator.join([group.group_name, validated_name])
403
404 # second pass to validate permissions to repo_group
405 second = RepoGroupAccessSchema().bind(**self.bindings)
406 appstruct_second = second.deserialize({'repo_group': validated_name})
407 # save result
408 appstruct['repo_group'] = appstruct_second['repo_group']
409
410 # thirds to validate uniqueness
411 third = RepoNameUniqueSchema().bind(**self.bindings)
412 third.deserialize({'unique_repo_name': validated_name})
413
414 return appstruct
@@ -18,10 +18,12 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import colander
22 import colander
22
23
23 from rhodecode import forms
24 from rhodecode import forms
24 from rhodecode.model.db import User
25 from rhodecode.model.db import User
26 from rhodecode.model.validation_schema import types, validators
25 from rhodecode.translation import _
27 from rhodecode.translation import _
26 from rhodecode.lib.auth import check_password
28 from rhodecode.lib.auth import check_password
27
29
@@ -52,10 +54,72 b' class ChangePasswordSchema(colander.Sche'
52 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
54 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
53 validator=colander.Length(min=6))
55 validator=colander.Length(min=6))
54
56
55
56 def validator(self, form, values):
57 def validator(self, form, values):
57 if values['current_password'] == values['new_password']:
58 if values['current_password'] == values['new_password']:
58 exc = colander.Invalid(form)
59 exc = colander.Invalid(form)
59 exc['new_password'] = _('New password must be different '
60 exc['new_password'] = _('New password must be different '
60 'to old password')
61 'to old password')
61 raise exc
62 raise exc
63
64
65 @colander.deferred
66 def deferred_username_validator(node, kw):
67
68 def name_validator(node, value):
69 msg = _(
70 u'Username may only contain alphanumeric characters '
71 u'underscores, periods or dashes and must begin with '
72 u'alphanumeric character or underscore')
73
74 if not re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value):
75 raise colander.Invalid(node, msg)
76
77 return name_validator
78
79
80 @colander.deferred
81 def deferred_email_validator(node, kw):
82 # NOTE(marcink): we might provide uniqueness validation later here...
83 return colander.Email()
84
85
86 class UserSchema(colander.Schema):
87 username = colander.SchemaNode(
88 colander.String(),
89 validator=deferred_username_validator)
90
91 email = colander.SchemaNode(
92 colander.String(),
93 validator=deferred_email_validator)
94
95 password = colander.SchemaNode(
96 colander.String(), missing='')
97
98 first_name = colander.SchemaNode(
99 colander.String(), missing='')
100
101 last_name = colander.SchemaNode(
102 colander.String(), missing='')
103
104 active = colander.SchemaNode(
105 types.StringBooleanType(),
106 missing=False)
107
108 admin = colander.SchemaNode(
109 types.StringBooleanType(),
110 missing=False)
111
112 extern_name = colander.SchemaNode(
113 colander.String(), missing='')
114
115 extern_type = colander.SchemaNode(
116 colander.String(), missing='')
117
118 def deserialize(self, cstruct):
119 """
120 Custom deserialize that allows to chain validation, and verify
121 permissions, and as last step uniqueness
122 """
123
124 appstruct = super(UserSchema, self).deserialize(cstruct)
125 return appstruct
@@ -190,7 +190,7 b' class UserGroupType(UserOrUserGroupType)'
190
190
191 class StrOrIntType(colander.String):
191 class StrOrIntType(colander.String):
192 def deserialize(self, node, cstruct):
192 def deserialize(self, node, cstruct):
193 if isinstance(node, basestring):
193 if isinstance(cstruct, basestring):
194 return super(StrOrIntType, self).deserialize(node, cstruct)
194 return super(StrOrIntType, self).deserialize(node, cstruct)
195 else:
195 else:
196 return colander.Integer().deserialize(node, cstruct)
196 return colander.Integer().deserialize(node, cstruct)
@@ -1,5 +1,27 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
1 import os
21 import os
2 import re
22 import re
23 import logging
24
3
25
4 import ipaddress
26 import ipaddress
5 import colander
27 import colander
@@ -7,6 +29,8 b' import colander'
7 from rhodecode.translation import _
29 from rhodecode.translation import _
8 from rhodecode.lib.utils2 import glob2re
30 from rhodecode.lib.utils2 import glob2re
9
31
32 log = logging.getLogger(__name__)
33
10
34
11 def ip_addr_validator(node, value):
35 def ip_addr_validator(node, value):
12 try:
36 try:
@@ -46,3 +70,71 b' def valid_name_validator(node, value):'
46 msg = _('Name must start with a letter or number. Got `{}`').format(value)
70 msg = _('Name must start with a letter or number. Got `{}`').format(value)
47 if not re.match(r'^[a-zA-z0-9]{1,}', value):
71 if not re.match(r'^[a-zA-z0-9]{1,}', value):
48 raise colander.Invalid(node, msg)
72 raise colander.Invalid(node, msg)
73
74
75 class InvalidCloneUrl(Exception):
76 allowed_prefixes = ()
77
78
79 def url_validator(url, repo_type, config):
80 from rhodecode.lib.vcs.backends.hg import MercurialRepository
81 from rhodecode.lib.vcs.backends.git import GitRepository
82 from rhodecode.lib.vcs.backends.svn import SubversionRepository
83
84 if repo_type == 'hg':
85 allowed_prefixes = ('http', 'svn+http', 'git+http')
86
87 if 'http' in url[:4]:
88 # initially check if it's at least the proper URL
89 # or does it pass basic auth
90
91 MercurialRepository.check_url(url, config)
92 elif 'svn+http' in url[:8]: # svn->hg import
93 SubversionRepository.check_url(url, config)
94 elif 'git+http' in url[:8]: # git->hg import
95 raise NotImplementedError()
96 else:
97 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
98 'Allowed url must start with one of %s'
99 % (url, ','.join(allowed_prefixes)))
100 exc.allowed_prefixes = allowed_prefixes
101 raise exc
102
103 elif repo_type == 'git':
104 allowed_prefixes = ('http', 'svn+http', 'hg+http')
105 if 'http' in url[:4]:
106 # initially check if it's at least the proper URL
107 # or does it pass basic auth
108 GitRepository.check_url(url, config)
109 elif 'svn+http' in url[:8]: # svn->git import
110 raise NotImplementedError()
111 elif 'hg+http' in url[:8]: # hg->git import
112 raise NotImplementedError()
113 else:
114 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
115 'Allowed url must start with one of %s'
116 % (url, ','.join(allowed_prefixes)))
117 exc.allowed_prefixes = allowed_prefixes
118 raise exc
119
120
121 class CloneUriValidator(object):
122 def __init__(self, repo_type):
123 self.repo_type = repo_type
124
125 def __call__(self, node, value):
126 from rhodecode.lib.utils import make_db_config
127 try:
128 config = make_db_config(clear_session=False)
129 url_validator(value, self.repo_type, config)
130 except InvalidCloneUrl as e:
131 log.warning(e)
132 msg = _(u'Invalid clone url, provide a valid clone '
133 u'url starting with one of {allowed_prefixes}').format(
134 allowed_prefixes=e.allowed_prefixes)
135 raise colander.Invalid(node, msg)
136 except Exception:
137 log.exception('Url validation failed')
138 msg = _(u'invalid clone url for {repo_type} repository').format(
139 repo_type=self.repo_type)
140 raise colander.Invalid(node, msg)
@@ -574,6 +574,26 b' def SlugifyName():'
574 return _validator
574 return _validator
575
575
576
576
577 def CannotHaveGitSuffix():
578 class _validator(formencode.validators.FancyValidator):
579 messages = {
580 'has_git_suffix':
581 _(u'Repository name cannot end with .git'),
582 }
583
584 def _to_python(self, value, state):
585 return value
586
587 def validate_python(self, value, state):
588 if value and value.endswith('.git'):
589 msg = M(
590 self, 'has_git_suffix', state)
591 raise formencode.Invalid(
592 msg, value, state, error_dict={'repo_name': msg})
593
594 return _validator
595
596
577 def ValidCloneUri():
597 def ValidCloneUri():
578 class InvalidCloneUrl(Exception):
598 class InvalidCloneUrl(Exception):
579 allowed_prefixes = ()
599 allowed_prefixes = ()
@@ -764,7 +784,8 b" def ValidPerms(type_='repo'):"
764 del_member = perm_dict.get('id')
784 del_member = perm_dict.get('id')
765 del_type = perm_dict.get('type')
785 del_type = perm_dict.get('type')
766 if del_member and del_type:
786 if del_member and del_type:
767 perm_deletions.add((del_member, None, del_type))
787 perm_deletions.add(
788 (del_member, None, del_type))
768
789
769 # store additions in order of how they were added in web form
790 # store additions in order of how they were added in web form
770 for k in sorted(new_perms_group.keys()):
791 for k in sorted(new_perms_group.keys()):
@@ -773,36 +794,48 b" def ValidPerms(type_='repo'):"
773 new_type = perm_dict.get('type')
794 new_type = perm_dict.get('type')
774 new_perm = perm_dict.get('perm')
795 new_perm = perm_dict.get('perm')
775 if new_member and new_perm and new_type:
796 if new_member and new_perm and new_type:
776 perm_additions.add((new_member, new_perm, new_type))
797 perm_additions.add(
798 (new_member, new_perm, new_type))
777
799
778 # get updates of permissions
800 # get updates of permissions
779 # (read the existing radio button states)
801 # (read the existing radio button states)
802 default_user_id = User.get_default_user().user_id
780 for k, update_value in value.iteritems():
803 for k, update_value in value.iteritems():
781 if k.startswith('u_perm_') or k.startswith('g_perm_'):
804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
782 member = k[7:]
805 member = k[7:]
783 update_type = {'u': 'user',
806 update_type = {'u': 'user',
784 'g': 'users_group'}[k[0]]
807 'g': 'users_group'}[k[0]]
785 if member == User.DEFAULT_USER:
808
809 if safe_int(member) == default_user_id:
786 if str2bool(value.get('repo_private')):
810 if str2bool(value.get('repo_private')):
787 # set none for default when updating to
811 # prevent from updating default user permissions
788 # private repo protects agains form manipulation
812 # when this repository is marked as private
789 update_value = EMPTY_PERM
813 update_value = EMPTY_PERM
790 perm_updates.add((member, update_value, update_type))
791 # check the deletes
792
814
793 value['perm_additions'] = list(perm_additions)
815 perm_updates.add(
816 (member, update_value, update_type))
817
818 value['perm_additions'] = [] # propagated later
794 value['perm_updates'] = list(perm_updates)
819 value['perm_updates'] = list(perm_updates)
795 value['perm_deletions'] = list(perm_deletions)
820 value['perm_deletions'] = list(perm_deletions)
796
821
797 # validate users they exist and they are active !
822 updates_map = dict(
798 for member_id, _perm, member_type in perm_additions:
823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
824 # make sure Additions don't override updates.
825 for member_id, perm, member_type in list(perm_additions):
826 if member_id in updates_map:
827 perm = updates_map[member_id][0]
828 value['perm_additions'].append((member_id, perm, member_type))
829
830 # on new entries validate users they exist and they are active !
831 # this leaves feedback to the form
799 try:
832 try:
800 if member_type == 'user':
833 if member_type == 'user':
801 self.user_db = User.query()\
834 User.query()\
802 .filter(User.active == true())\
835 .filter(User.active == true())\
803 .filter(User.user_id == member_id).one()
836 .filter(User.user_id == member_id).one()
804 if member_type == 'users_group':
837 if member_type == 'users_group':
805 self.user_db = UserGroup.query()\
838 UserGroup.query()\
806 .filter(UserGroup.users_group_active == true())\
839 .filter(UserGroup.users_group_active == true())\
807 .filter(UserGroup.users_group_id == member_id)\
840 .filter(UserGroup.users_group_id == member_id)\
808 .one()
841 .one()
@@ -62,6 +62,10 b' input[type="button"] {'
62 text-shadow: none;
62 text-shadow: none;
63 }
63 }
64
64
65 &.no-margin {
66 margin: 0 0 0 0;
67 }
68
65 }
69 }
66
70
67
71
@@ -270,11 +274,17 b' input[type="button"] {'
270 font-size: inherit;
274 font-size: inherit;
271 color: @rcblue;
275 color: @rcblue;
272 border: none;
276 border: none;
273 .border-radius (0);
277 border-radius: 0;
274 background-color: transparent;
278 background-color: transparent;
275
279
280 &.last-item {
281 border: none;
282 padding: 0 0 0 0;
283 }
284
276 &:last-child {
285 &:last-child {
277 border: none;
286 border: none;
287 padding: 0 0 0 0;
278 }
288 }
279
289
280 &:hover {
290 &:hover {
@@ -12,7 +12,7 b''
12
12
13 .control-label {
13 .control-label {
14 width: 200px;
14 width: 200px;
15 padding: 10px;
15 padding: 10px 0px;
16 float: left;
16 float: left;
17 }
17 }
18 .control-inputs {
18 .control-inputs {
@@ -165,6 +165,7 b' div.markdown-block h6 {'
165 div.markdown-block hr {
165 div.markdown-block hr {
166 border: 0;
166 border: 0;
167 color: #e6e5e5;
167 color: #e6e5e5;
168 background-color: #e6e5e5;
168 height: 3px;
169 height: 3px;
169 margin-bottom: 13px;
170 margin-bottom: 13px;
170 }
171 }
@@ -200,6 +201,12 b' div.markdown-block img {'
200 background-color: #fff;
201 background-color: #fff;
201 }
202 }
202
203
204
205 div.markdown-block strong {
206 font-weight: 600;
207 margin: 0;
208 }
209
203 div.markdown-block ul,
210 div.markdown-block ul,
204 div.markdown-block ol {
211 div.markdown-block ol {
205 padding-left: 30px !important;
212 padding-left: 30px !important;
@@ -210,7 +217,7 b' div.markdown-block ol {'
210 div.markdown-block ul li,
217 div.markdown-block ul li,
211 div.markdown-block ol li {
218 div.markdown-block ol li {
212 list-style: disc !important;
219 list-style: disc !important;
213 margin: 13px !important;
220 margin: 6px !important;
214 padding: 0 !important;
221 padding: 0 !important;
215 }
222 }
216
223
@@ -11,6 +11,7 b''
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'readme-box';
14 @import 'progress-bar';
15 @import 'progress-bar';
15
16
16 @import 'type';
17 @import 'type';
@@ -358,9 +359,26 b' ul.auth_plugins {'
358 }
359 }
359 }
360 }
360
361
362 .pr-mergeinfo {
363 clear: both;
364 margin: .5em 0;
365
366 input {
367 min-width: 100% !important;
368 padding: 0 !important;
369 border: 0;
370 }
371 }
372
361 .pr-pullinfo {
373 .pr-pullinfo {
362 clear: both;
374 clear: both;
363 margin: .5em 0;
375 margin: .5em 0;
376
377 input {
378 min-width: 100% !important;
379 padding: 0 !important;
380 border: 0;
381 }
364 }
382 }
365
383
366 #pr-title-input {
384 #pr-title-input {
@@ -1297,6 +1315,11 b' table.integrations {'
1297 width: 100%;
1315 width: 100%;
1298 margin-bottom: 8px;
1316 margin-bottom: 8px;
1299 }
1317 }
1318
1319 .reviewer_entry {
1320 min-height: 55px;
1321 }
1322
1300 .reviewers_member {
1323 .reviewers_member {
1301 width: 100%;
1324 width: 100%;
1302 overflow: auto;
1325 overflow: auto;
@@ -1331,6 +1354,8 b' table.integrations {'
1331 }
1354 }
1332 }
1355 }
1333
1356
1357 .reviewer_member_mandatory,
1358 .reviewer_member_mandatory_remove,
1334 .reviewer_member_remove {
1359 .reviewer_member_remove {
1335 position: absolute;
1360 position: absolute;
1336 right: 0;
1361 right: 0;
@@ -1340,6 +1365,15 b' table.integrations {'
1340 padding: 0;
1365 padding: 0;
1341 color: black;
1366 color: black;
1342 }
1367 }
1368
1369 .reviewer_member_mandatory_remove {
1370 color: @grey4;
1371 }
1372
1373 .reviewer_member_mandatory {
1374 padding-top:20px;
1375 }
1376
1343 .reviewer_member_status {
1377 .reviewer_member_status {
1344 margin-top: 5px;
1378 margin-top: 5px;
1345 }
1379 }
@@ -1369,6 +1403,11 b' table.integrations {'
1369 .pr-description {
1403 .pr-description {
1370 white-space:pre-wrap;
1404 white-space:pre-wrap;
1371 }
1405 }
1406
1407 .pr-reviewer-rules {
1408 padding: 10px 0px 20px 0px;
1409 }
1410
1372 .group_members {
1411 .group_members {
1373 margin-top: 0;
1412 margin-top: 0;
1374 padding: 0;
1413 padding: 0;
@@ -2008,6 +2047,10 b' BIN_FILENODE = 7'
2008 input {
2047 input {
2009 display: none;
2048 display: none;
2010 }
2049 }
2050 margin-top: 10px;
2051 }
2052 .file-upload-label {
2053 margin-top: 10px;
2011 }
2054 }
2012 p {
2055 p {
2013 margin-top: 5px;
2056 margin-top: 5px;
@@ -558,6 +558,8 b' ul#context-pages {'
558 .dataTables_processing {
558 .dataTables_processing {
559 text-align: center;
559 text-align: center;
560 font-size: 1.1em;
560 font-size: 1.1em;
561 position: relative;
562 top: 95px;
561 }
563 }
562
564
563 .dataTables_paginate, .pagination-wh {
565 .dataTables_paginate, .pagination-wh {
@@ -42,6 +42,10 b''
42
42
43 .panel-body {
43 .panel-body {
44 padding: @panel-padding;
44 padding: @panel-padding;
45
46 &.panel-body-min-height {
47 min-height: 150px
48 }
45 }
49 }
46
50
47 .panel-footer {
51 .panel-footer {
@@ -57,9 +57,13 b''
57 white-space: pre-wrap;
57 white-space: pre-wrap;
58 }
58 }
59
59
60 #clone_url,
60 #clone_url {
61 width: ~"calc(100% - 96px)";
62 padding: @padding/4;
63 }
64
61 #clone_url_id {
65 #clone_url_id {
62 min-width: 29em;
66 width: ~"calc(100% - 118px)";
63 padding: @padding/4;
67 padding: @padding/4;
64 }
68 }
65
69
@@ -211,6 +215,7 b''
211 float: left;
215 float: left;
212 display: block;
216 display: block;
213 position: relative;
217 position: relative;
218 width: 100%;
214
219
215 // adds some space to make copy and paste easier
220 // adds some space to make copy and paste easier
216 .left-label,
221 .left-label,
@@ -108,6 +108,11 b' table.dataTable {'
108 &.td-hash {
108 &.td-hash {
109 min-width: 80px;
109 min-width: 80px;
110 width: 200px;
110 width: 200px;
111
112 .obsolete {
113 text-decoration: line-through;
114 color: lighten(@grey2,25%);
115 }
111 }
116 }
112
117
113 &.td-time {
118 &.td-time {
@@ -98,4 +98,12 b''
98 &.admin {
98 &.admin {
99 &:extend(.tag5);
99 &:extend(.tag5);
100 }
100 }
101 } No newline at end of file
101 }
102
103 .phase-draft {
104 color: @color3
105 }
106
107 .phase-secret {
108 color:@grey3
109 }
@@ -62,7 +62,7 b' function setRCMouseBindings(repoName, re'
62 // nav in repo context
62 // nav in repo context
63 Mousetrap.bind(['g s'], function(e) {
63 Mousetrap.bind(['g s'], function(e) {
64 window.location = pyroutes.url(
64 window.location = pyroutes.url(
65 'summary_home', {'repo_name': repoName});
65 'repo_summary', {'repo_name': repoName});
66 });
66 });
67 Mousetrap.bind(['g c'], function(e) {
67 Mousetrap.bind(['g c'], function(e) {
68 window.location = pyroutes.url(
68 window.location = pyroutes.url(
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Add another comment',
9 'Add another comment': 'Add another comment',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Follow',
20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Selection link',
50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Unfollow',
67 'Unfollow': 'Unfollow',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'just now',
80 'just now': 'just now',
73 'loading...': 'loading...',
81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Add another comment',
9 'Add another comment': 'Add another comment',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Follow',
20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Bitte {0} Zeichen löschen',
44 'Please delete {0} characters': 'Bitte {0} Zeichen löschen',
39 'Please enter {0} or more character': 'Bitte {0} oder mehr Zeichen eingeben',
45 'Please enter {0} or more character': 'Bitte {0} oder mehr Zeichen eingeben',
40 'Please enter {0} or more characters': 'Bitte {0} oder mehr Zeichen eingeben',
46 'Please enter {0} or more characters': 'Bitte {0} oder mehr Zeichen eingeben',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Selection link',
50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Unfollow',
67 'Unfollow': 'Unfollow',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'jetzt gerade',
80 'just now': 'jetzt gerade',
73 'loading...': 'loading...',
81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Add another comment',
9 'Add another comment': 'Add another comment',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Follow',
20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Selection link',
50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Unfollow',
67 'Unfollow': 'Unfollow',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'just now',
80 'just now': 'just now',
73 'loading...': 'loading...',
81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Add another comment',
9 'Add another comment': 'Add another comment',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Follow',
20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Selection link',
50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Unfollow',
67 'Unfollow': 'Unfollow',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'just now',
80 'just now': 'just now',
73 'loading...': 'loading...',
81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Add another comment',
9 'Add another comment': 'Add another comment',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Follow',
20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Lien vers la sélection',
50 'Selection link': 'Lien vers la sélection',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Unfollow',
67 'Unfollow': 'Unfollow',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'à l’instant',
80 'just now': 'à l’instant',
73 'loading...': 'Chargement…',
81 'loading...': 'Chargement…',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Aggiungi un altro commento',
9 'Add another comment': 'Aggiungi un altro commento',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Segui',
20 'Follow': 'Segui',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Collegamento selezione',
50 'Selection link': 'Collegamento selezione',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'Al momento non ci sono richieste di PULL che richiedono il tuo intervento',
66 'There are currently no open pull requests requiring your participation.': 'Al momento non ci sono richieste di PULL che richiedono il tuo intervento',
60 'Unfollow': 'Smetti di seguire',
67 'Unfollow': 'Smetti di seguire',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'proprio ora',
80 'just now': 'proprio ora',
73 'loading...': 'loading...',
81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': '別のコメントを追加',
9 'Add another comment': '別のコメントを追加',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': '選択したステータス ({0}) を元にコメントが自動的に設定されます...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': '選択したステータス ({0}) を元にコメントが自動的に設定されます...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'フォロー',
20 'Follow': 'フォロー',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': '{0} 文字削除してください',
44 'Please delete {0} characters': '{0} 文字削除してください',
39 'Please enter {0} or more character': '{0} 文字以上入力してください',
45 'Please enter {0} or more character': '{0} 文字以上入力してください',
40 'Please enter {0} or more characters': '{0} 文字以上入力してください',
46 'Please enter {0} or more characters': '{0} 文字以上入力してください',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': '検索中...',
49 'Searching...': '検索中...',
43 'Selection link': 'セレクション・リンク',
50 'Selection link': 'セレクション・リンク',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'アンフォロー',
67 'Unfollow': 'アンフォロー',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': '{0} 件のみ選択できます',
70 'You can only select {0} item': '{0} 件のみ選択できます',
63 'You can only select {0} items': '{0} 件のみ選択できます',
71 'You can only select {0} items': '{0} 件のみ選択できます',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'たったいま',
80 'just now': 'たったいま',
73 'loading...': '読み込み中...',
81 'loading...': '読み込み中...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -1,8 +1,14 b''
1 // AUTO GENERATED FILE FOR Babel JS-GETTEXT EXTRACTORS, DO NOT CHANGE
1 // AUTO GENERATED FILE FOR Babel JS-GETTEXT EXTRACTORS, DO NOT CHANGE
2 _gettext('(from usergroup {0})');
2 _gettext('(from usergroup {0})');
3 _gettext('Add another comment');
3 _gettext('Add another comment');
4 _gettext('Adding new reviewers is forbidden.');
5 _gettext('All reviewers must vote.');
6 _gettext('At least {0} reviewer must vote.');
7 _gettext('At least {0} reviewers must vote.');
8 _gettext('Author is not allowed to be a reviewer.');
4 _gettext('Close');
9 _gettext('Close');
5 _gettext('Comment text will be set automatically based on currently selected status ({0}) ...');
10 _gettext('Comment text will be set automatically based on currently selected status ({0}) ...');
11 _gettext('Commit Authors are not allowed to be a reviewer.');
6 _gettext('Delete this comment?');
12 _gettext('Delete this comment?');
7 _gettext('Diff to Commit ');
13 _gettext('Diff to Commit ');
8 _gettext('Follow');
14 _gettext('Follow');
@@ -32,6 +38,7 b''
32 _gettext('Please delete {0} characters');
38 _gettext('Please delete {0} characters');
33 _gettext('Please enter {0} or more character');
39 _gettext('Please enter {0} or more character');
34 _gettext('Please enter {0} or more characters');
40 _gettext('Please enter {0} or more characters');
41 _gettext('Reviewers picked from source code changes.');
35 _gettext('Saving...');
42 _gettext('Saving...');
36 _gettext('Searching...');
43 _gettext('Searching...');
37 _gettext('Selection link');
44 _gettext('Selection link');
@@ -53,6 +60,7 b''
53 _gettext('There are currently no open pull requests requiring your participation.');
60 _gettext('There are currently no open pull requests requiring your participation.');
54 _gettext('Unfollow');
61 _gettext('Unfollow');
55 _gettext('Updating...');
62 _gettext('Updating...');
63 _gettext('User `{0}` not allowed to be a reviewer');
56 _gettext('You can only select {0} item');
64 _gettext('You can only select {0} item');
57 _gettext('You can only select {0} items');
65 _gettext('You can only select {0} items');
58 _gettext('added manually by "{0}"');
66 _gettext('added manually by "{0}"');
@@ -65,6 +73,7 b''
65 _gettext('in {0}, {1}');
73 _gettext('in {0}, {1}');
66 _gettext('just now');
74 _gettext('just now');
67 _gettext('loading...');
75 _gettext('loading...');
76 _gettext('member of "{0}"');
68 _gettext('resolve comment');
77 _gettext('resolve comment');
69 _gettext('showing {0} out of {1} commit');
78 _gettext('showing {0} out of {1} commit');
70 _gettext('showing {0} out of {1} commits');
79 _gettext('showing {0} out of {1} commits');
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Dodaj kolejny komentarz',
9 'Add another comment': 'Dodaj kolejny komentarz',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Zamknij',
15 'Close': 'Zamknij',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Obserwuj',
20 'Follow': 'Obserwuj',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Wybór linku',
50 'Selection link': 'Wybór linku',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Nie obserwuj',
67 'Unfollow': 'Nie obserwuj',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'przed chwilą',
80 'just now': 'przed chwilą',
73 'loading...': 'wczytywanie...',
81 'loading...': 'wczytywanie...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Adicionar outro comentário',
9 'Add another comment': 'Adicionar outro comentário',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Seguir',
20 'Follow': 'Seguir',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Link da seleção',
50 'Selection link': 'Link da seleção',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Parar de seguir',
67 'Unfollow': 'Parar de seguir',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'agora há pouco',
80 'just now': 'agora há pouco',
73 'loading...': 'loading...',
81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Добавить другой комментарий',
9 'Add another comment': 'Добавить другой комментарий',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Наблюдать',
20 'Follow': 'Наблюдать',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': 'Ссылка выбора',
50 'Selection link': 'Ссылка выбора',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Не наблюдать',
67 'Unfollow': 'Не наблюдать',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': 'прямо сейчас',
80 'just now': 'прямо сейчас',
73 'loading...': 'загрузка...',
81 'loading...': 'загрузка...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 var _TM = {
7 var _TM = {
8 '(from usergroup {0})': '(from usergroup {0})',
8 '(from usergroup {0})': '(from usergroup {0})',
9 'Add another comment': 'Add another comment',
9 'Add another comment': 'Add another comment',
10 'Adding new reviewers is forbidden.': 'Adding new reviewers is forbidden.',
11 'All reviewers must vote.': 'All reviewers must vote.',
12 'At least {0} reviewer must vote.': 'At least {0} reviewer must vote.',
13 'At least {0} reviewers must vote.': 'At least {0} reviewers must vote.',
14 'Author is not allowed to be a reviewer.': 'Author is not allowed to be a reviewer.',
10 'Close': 'Close',
15 'Close': 'Close',
11 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
16 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
17 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
12 'Delete this comment?': 'Delete this comment?',
18 'Delete this comment?': 'Delete this comment?',
13 'Diff to Commit ': 'Diff to Commit ',
19 'Diff to Commit ': 'Diff to Commit ',
14 'Follow': 'Follow',
20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 'Please delete {0} characters': 'Please delete {0} characters',
44 'Please delete {0} characters': 'Please delete {0} characters',
39 'Please enter {0} or more character': 'Please enter {0} or more character',
45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 'Please enter {0} or more characters': 'Please enter {0} or more characters',
46 'Please enter {0} or more characters': 'Please enter {0} or more characters',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 'Saving...': 'Saving...',
48 'Saving...': 'Saving...',
42 'Searching...': 'Searching...',
49 'Searching...': 'Searching...',
43 'Selection link': '选择链接',
50 'Selection link': '选择链接',
@@ -59,6 +66,7 b' var _TM = {'
59 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 'Unfollow': 'Unfollow',
67 'Unfollow': 'Unfollow',
61 'Updating...': 'Updating...',
68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 'You can only select {0} item': 'You can only select {0} item',
70 'You can only select {0} item': 'You can only select {0} item',
63 'You can only select {0} items': 'You can only select {0} items',
71 'You can only select {0} items': 'You can only select {0} items',
64 'added manually by "{0}"': 'added manually by "{0}"',
72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 'in {0}, {1}': 'in {0}, {1}',
79 'in {0}, {1}': 'in {0}, {1}',
72 'just now': '刚才',
80 'just now': '刚才',
73 'loading...': 'loading...',
81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 'resolve comment': 'resolve comment',
83 'resolve comment': 'resolve comment',
75 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -12,22 +12,13 b''
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('home', '/', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
17 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
18 pyroutes.register('new_repo', '/_admin/create_repository', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
19 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
20 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
21 pyroutes.register('gists', '/_admin/gists', []);
18 pyroutes.register('gists', '/_admin/gists', []);
22 pyroutes.register('new_gist', '/_admin/gists/new', []);
19 pyroutes.register('new_gist', '/_admin/gists/new', []);
23 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
20 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
24 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
25 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
26 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
27 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
28 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
21 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
29 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
30 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
31 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
22 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
32 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
23 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
33 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
24 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
@@ -39,7 +30,6 b' function registerRCRoutes() {'
39 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
30 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
40 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
31 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
41 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
32 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
42 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
43 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
33 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
44 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
34 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
45 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
35 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
@@ -53,8 +43,6 b' function registerRCRoutes() {'
53 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
43 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
54 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
55 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
45 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
56 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
57 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
58 pyroutes.register('favicon', '/favicon.ico', []);
46 pyroutes.register('favicon', '/favicon.ico', []);
59 pyroutes.register('robots', '/robots.txt', []);
47 pyroutes.register('robots', '/robots.txt', []);
60 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
48 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
@@ -73,17 +61,30 b' function registerRCRoutes() {'
73 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
61 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
74 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
62 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
75 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
63 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
64 pyroutes.register('ops_ping', '_admin/ops/ping', []);
65 pyroutes.register('admin_home', '/_admin', []);
66 pyroutes.register('admin_audit_logs', '_admin/audit_logs', []);
67 pyroutes.register('pull_requests_global_0', '_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
68 pyroutes.register('pull_requests_global_1', '_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
69 pyroutes.register('pull_requests_global', '_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
76 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
70 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
77 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
71 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
78 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
72 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
79 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
73 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
80 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
74 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
81 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
75 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
76 pyroutes.register('admin_permissions_ips', '_admin/permissions/ips', []);
82 pyroutes.register('users', '_admin/users', []);
77 pyroutes.register('users', '_admin/users', []);
83 pyroutes.register('users_data', '_admin/users_data', []);
78 pyroutes.register('users_data', '_admin/users_data', []);
84 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
79 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
85 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
80 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
86 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
81 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
82 pyroutes.register('edit_user_emails', '_admin/users/%(user_id)s/edit/emails', ['user_id']);
83 pyroutes.register('edit_user_emails_add', '_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
84 pyroutes.register('edit_user_emails_delete', '_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
85 pyroutes.register('edit_user_ips', '_admin/users/%(user_id)s/edit/ips', ['user_id']);
86 pyroutes.register('edit_user_ips_add', '_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
87 pyroutes.register('edit_user_ips_delete', '_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
87 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
88 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
88 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
89 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
89 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
90 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
@@ -95,11 +96,44 b' function registerRCRoutes() {'
95 pyroutes.register('register', '/_admin/register', []);
96 pyroutes.register('register', '/_admin/register', []);
96 pyroutes.register('reset_password', '/_admin/password_reset', []);
97 pyroutes.register('reset_password', '/_admin/password_reset', []);
97 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
98 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
98 pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']);
99 pyroutes.register('home', '/', []);
99 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']);
100 pyroutes.register('user_autocomplete_data', '/_users', []);
100 pyroutes.register('strip', '/%(repo_name)s/strip', ['repo_name']);
101 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
101 pyroutes.register('strip_check', '/%(repo_name)s/strip_check', ['repo_name']);
102 pyroutes.register('repo_list_data', '/_repos', []);
102 pyroutes.register('strip_execute', '/%(repo_name)s/strip_execute', ['repo_name']);
103 pyroutes.register('goto_switcher_data', '/_goto_data', []);
104 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
105 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
106 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
108 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
109 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
110 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
111 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
112 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
113 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
114 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
115 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
116 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
117 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
118 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
119 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
120 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
121 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
122 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
123 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
124 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
125 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
126 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
127 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
128 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
129 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
130 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
131 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
132 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
133 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
134 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
135 pyroutes.register('search', '/_admin/search', []);
136 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
103 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
137 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
104 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
138 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
105 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
139 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
@@ -107,5 +141,14 b' function registerRCRoutes() {'
107 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
141 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
108 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
142 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
109 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
143 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
144 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
145 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
146 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
147 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
148 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
149 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
150 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
151 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
152 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
110 pyroutes.register('apiv2', '/_admin/api', []);
153 pyroutes.register('apiv2', '/_admin/api', []);
111 }
154 }
@@ -16,77 +16,342 b''
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
20 var prButtonLockChecks = {
21 'compare': false,
22 'reviewers': false
23 };
24
19 /**
25 /**
20 * Pull request reviewers
26 * lock button until all checks and loads are made. E.g reviewer calculation
27 * should prevent from submitting a PR
28 * @param lockEnabled
29 * @param msg
30 * @param scope
21 */
31 */
22 var removeReviewMember = function(reviewer_id, mark_delete){
32 var prButtonLock = function(lockEnabled, msg, scope) {
23 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
33 scope = scope || 'all';
34 if (scope == 'all'){
35 prButtonLockChecks['compare'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 } else if (scope == 'compare') {
38 prButtonLockChecks['compare'] = !lockEnabled;
39 } else if (scope == 'reviewers'){
40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 }
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 if (lockEnabled) {
44 $('#save').attr('disabled', 'disabled');
45 }
46 else if (checksMeet) {
47 $('#save').removeAttr('disabled');
48 }
24
49
25 if(typeof(mark_delete) === undefined){
50 if (msg) {
26 mark_delete = false;
51 $('#pr_open_message').html(msg);
27 }
52 }
53 };
28
54
29 if(mark_delete === true){
55
30 if (reviewer){
56 /**
31 // now delete the input
57 Generate Title and Description for a PullRequest.
32 $('#reviewer_{0} input'.format(reviewer_id)).remove();
58 In case of 1 commits, the title and description is that one commit
33 // mark as to-delete
59 in case of multiple commits, we iterate on them with max N number of commits,
34 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
60 and build description in a form
35 obj.addClass('to-delete');
61 - commitN
36 obj.css({"text-decoration":"line-through", "opacity": 0.5});
62 - commitN+1
37 }
63 ...
38 }
64
39 else{
65 Title is then constructed from branch names, or other references,
40 $('#reviewer_{0}'.format(reviewer_id)).remove();
66 replacing '-' and '_' into spaces
41 }
67
68 * @param sourceRef
69 * @param elements
70 * @param limit
71 * @returns {*[]}
72 */
73 var getTitleAndDescription = function(sourceRef, elements, limit) {
74 var title = '';
75 var desc = '';
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
84 }
85 else {
86 // use reference name
87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
88 }
89
90 return [title, desc]
42 };
91 };
43
92
44 var addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons) {
93
45 var members = $('#review_members').get(0);
94
46 var reasons_html = '';
95 ReviewersController = function () {
47 var reasons_inputs = '';
96 var self = this;
48 var reasons = reasons || [];
97 this.$reviewRulesContainer = $('#review_rules');
49 if (reasons) {
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
50 for (var i = 0; i < reasons.length; i++) {
99 this.forbidReviewUsers = undefined;
51 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]);
100 this.$reviewMembers = $('#review_members');
52 reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">';
101 this.currentRequest = null;
102
103 this.defaultForbidReviewUsers = function() {
104 return [
105 {'username': 'default',
106 'user_id': templateContext.default_user.user_id}
107 ];
108 };
109
110 this.hideReviewRules = function() {
111 self.$reviewRulesContainer.hide();
112 };
113
114 this.showReviewRules = function() {
115 self.$reviewRulesContainer.show();
116 };
117
118 this.addRule = function(ruleText) {
119 self.showReviewRules();
120 return '<div>- {0}</div>'.format(ruleText)
121 };
122
123 this.loadReviewRules = function(data) {
124 // reset forbidden Users
125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
126
127 // reset state of review rules
128 self.$rulesList.html('');
129
130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
131 // default rule, case for older repo that don't have any rules stored
132 self.$rulesList.append(
133 self.addRule(
134 _gettext('All reviewers must vote.'))
135 );
136 return self.forbidReviewUsers
137 }
138
139 if (data.rules.voting !== undefined) {
140 if (data.rules.voting < 0){
141 self.$rulesList.append(
142 self.addRule(
143 _gettext('All reviewers must vote.'))
144 )
145 } else if (data.rules.voting === 1) {
146 self.$rulesList.append(
147 self.addRule(
148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
149 )
150
151 } else {
152 self.$rulesList.append(
153 self.addRule(
154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
155 )
156 }
157 }
158 if (data.rules.use_code_authors_for_review) {
159 self.$rulesList.append(
160 self.addRule(
161 _gettext('Reviewers picked from source code changes.'))
162 )
163 }
164 if (data.rules.forbid_adding_reviewers) {
165 $('#add_reviewer_input').remove();
166 self.$rulesList.append(
167 self.addRule(
168 _gettext('Adding new reviewers is forbidden.'))
169 )
170 }
171 if (data.rules.forbid_author_to_review) {
172 self.forbidReviewUsers.push(data.rules_data.pr_author);
173 self.$rulesList.append(
174 self.addRule(
175 _gettext('Author is not allowed to be a reviewer.'))
176 )
177 }
178 if (data.rules.forbid_commit_author_to_review) {
179
180 if (data.rules_data.forbidden_users) {
181 $.each(data.rules_data.forbidden_users, function(index, member_data) {
182 self.forbidReviewUsers.push(member_data)
183 });
184
185 }
186
187 self.$rulesList.append(
188 self.addRule(
189 _gettext('Commit Authors are not allowed to be a reviewer.'))
190 )
191 }
192
193 return self.forbidReviewUsers
194 };
195
196 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
197
198 if (self.currentRequest) {
199 // make sure we cleanup old running requests before triggering this
200 // again
201 self.currentRequest.abort();
53 }
202 }
54 }
203
55 var tmpl = '<li id="reviewer_{2}">'+
204 $('.calculate-reviewers').show();
56 '<input type="hidden" name="__start__" value="reviewer:mapping">'+
205 // reset reviewer members
57 '<div class="reviewer_status">'+
206 self.$reviewMembers.empty();
58 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
207
59 '</div>'+
208 prButtonLock(true, null, 'reviewers');
60 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
209 $('#user').hide(); // hide user autocomplete before load
61 '<span class="reviewer_name user">{1}</span>'+
210
62 reasons_html +
211 var url = pyroutes.url('repo_default_reviewers_data',
63 '<input type="hidden" name="user_id" value="{2}">'+
212 {
64 '<input type="hidden" name="__start__" value="reasons:sequence">'+
213 'repo_name': templateContext.repo_name,
65 '{3}'+
214 'source_repo': sourceRepo,
66 '<input type="hidden" name="__end__" value="reasons:sequence">'+
215 'source_ref': sourceRef[2],
67 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
216 'target_repo': targetRepo,
68 '<i class="icon-remove-sign"></i>'+
217 'target_ref': targetRef[2]
69 '</div>'+
218 });
70 '</div>'+
219
71 '<input type="hidden" name="__end__" value="reviewer:mapping">'+
220 self.currentRequest = $.get(url)
72 '</li>' ;
221 .done(function(data) {
222 self.currentRequest = null;
223
224 // review rules
225 self.loadReviewRules(data);
226
227 for (var i = 0; i < data.reviewers.length; i++) {
228 var reviewer = data.reviewers[i];
229 self.addReviewMember(
230 reviewer.user_id, reviewer.first_name,
231 reviewer.last_name, reviewer.username,
232 reviewer.gravatar_link, reviewer.reasons,
233 reviewer.mandatory);
234 }
235 $('.calculate-reviewers').hide();
236 prButtonLock(false, null, 'reviewers');
237 $('#user').show(); // show user autocomplete after load
238 });
239 };
240
241 // check those, refactor
242 this.removeReviewMember = function(reviewer_id, mark_delete) {
243 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
244
245 if(typeof(mark_delete) === undefined){
246 mark_delete = false;
247 }
248
249 if(mark_delete === true){
250 if (reviewer){
251 // now delete the input
252 $('#reviewer_{0} input'.format(reviewer_id)).remove();
253 // mark as to-delete
254 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
255 obj.addClass('to-delete');
256 obj.css({"text-decoration":"line-through", "opacity": 0.5});
257 }
258 }
259 else{
260 $('#reviewer_{0}'.format(reviewer_id)).remove();
261 }
262 };
263
264 this.addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons, mandatory) {
265 var members = self.$reviewMembers.get(0);
266 var reasons_html = '';
267 var reasons_inputs = '';
268 var reasons = reasons || [];
269 var mandatory = mandatory || false;
73
270
74 var displayname = "{0} ({1} {2})".format(
271 if (reasons) {
75 nname, escapeHtml(fname), escapeHtml(lname));
272 for (var i = 0; i < reasons.length; i++) {
76 var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs);
273 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]);
77 // check if we don't have this ID already in
274 reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">';
78 var ids = [];
275 }
79 var _els = $('#review_members li').toArray();
276 }
80 for (el in _els){
277 var tmpl = '' +
81 ids.push(_els[el].id)
278 '<li id="reviewer_{2}" class="reviewer_entry">'+
82 }
279 '<input type="hidden" name="__start__" value="reviewer:mapping">'+
83 if(ids.indexOf('reviewer_'+id) == -1){
280 '<div class="reviewer_status">'+
84 // only add if it's not there
281 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
85 members.innerHTML += element;
282 '</div>'+
86 }
283 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
284 '<span class="reviewer_name user">{1}</span>'+
285 reasons_html +
286 '<input type="hidden" name="user_id" value="{2}">'+
287 '<input type="hidden" name="__start__" value="reasons:sequence">'+
288 '{3}'+
289 '<input type="hidden" name="__end__" value="reasons:sequence">';
290
291 if (mandatory) {
292 tmpl += ''+
293 '<div class="reviewer_member_mandatory_remove">' +
294 '<i class="icon-remove-sign"></i>'+
295 '</div>' +
296 '<input type="hidden" name="mandatory" value="true">'+
297 '<div class="reviewer_member_mandatory">' +
298 '<i class="icon-lock" title="Mandatory reviewer"></i>'+
299 '</div>';
300
301 } else {
302 tmpl += ''+
303 '<input type="hidden" name="mandatory" value="false">'+
304 '<div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember({2})">' +
305 '<i class="icon-remove-sign"></i>'+
306 '</div>';
307 }
308 // continue template
309 tmpl += ''+
310 '<input type="hidden" name="__end__" value="reviewer:mapping">'+
311 '</li>' ;
312
313 var displayname = "{0} ({1} {2})".format(
314 nname, escapeHtml(fname), escapeHtml(lname));
315 var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs);
316 // check if we don't have this ID already in
317 var ids = [];
318 var _els = self.$reviewMembers.find('li').toArray();
319 for (el in _els){
320 ids.push(_els[el].id)
321 }
322
323 var userAllowedReview = function(userId) {
324 var allowed = true;
325 $.each(self.forbidReviewUsers, function(index, member_data) {
326 if (parseInt(userId) === member_data['user_id']) {
327 allowed = false;
328 return false // breaks the loop
329 }
330 });
331 return allowed
332 };
333
334 var userAllowed = userAllowedReview(id);
335 if (!userAllowed){
336 alert(_gettext('User `{0}` not allowed to be a reviewer').format(nname));
337 }
338 var shouldAdd = userAllowed && ids.indexOf('reviewer_'+id) == -1;
339
340 if(shouldAdd) {
341 // only add if it's not there
342 members.innerHTML += element;
343 }
344
345 };
346
347 this.updateReviewers = function(repo_name, pull_request_id){
348 var postData = '_method=put&' + $('#reviewers input').serialize();
349 _updatePullRequest(repo_name, pull_request_id, postData);
350 };
87
351
88 };
352 };
89
353
354
90 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
355 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
91 var url = pyroutes.url(
356 var url = pyroutes.url(
92 'pullrequest_update',
357 'pullrequest_update',
@@ -102,23 +367,6 b' var _updatePullRequest = function(repo_n'
102 ajaxPOST(url, postData, success);
367 ajaxPOST(url, postData, success);
103 };
368 };
104
369
105 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
106 if (reviewers_ids === undefined){
107 var postData = '_method=put&' + $('#reviewers input').serialize();
108 _updatePullRequest(repo_name, pull_request_id, postData);
109 }
110 };
111
112 /**
113 * PULL REQUEST reject & close
114 */
115 var closePullRequest = function(repo_name, pull_request_id) {
116 var postData = {
117 '_method': 'put',
118 'close_pull_request': true};
119 _updatePullRequest(repo_name, pull_request_id, postData);
120 };
121
122 /**
370 /**
123 * PULL REQUEST update commits
371 * PULL REQUEST update commits
124 */
372 */
@@ -198,8 +446,8 b' var initPullRequestsCodeMirror = functio'
198 /**
446 /**
199 * Reviewer autocomplete
447 * Reviewer autocomplete
200 */
448 */
201 var ReviewerAutoComplete = function(input_id) {
449 var ReviewerAutoComplete = function(inputId) {
202 $('#'+input_id).autocomplete({
450 $(inputId).autocomplete({
203 serviceUrl: pyroutes.url('user_autocomplete_data'),
451 serviceUrl: pyroutes.url('user_autocomplete_data'),
204 minChars:2,
452 minChars:2,
205 maxHeight:400,
453 maxHeight:400,
@@ -207,14 +455,28 b' var ReviewerAutoComplete = function(inpu'
207 showNoSuggestionNotice: true,
455 showNoSuggestionNotice: true,
208 tabDisabled: true,
456 tabDisabled: true,
209 autoSelectFirst: true,
457 autoSelectFirst: true,
458 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
210 formatResult: autocompleteFormatResult,
459 formatResult: autocompleteFormatResult,
211 lookupFilter: autocompleteFilterResult,
460 lookupFilter: autocompleteFilterResult,
212 onSelect: function(suggestion, data){
461 onSelect: function(element, data) {
213 var msg = _gettext('added manually by "{0}"');
462
214 var reasons = [msg.format(templateContext.rhodecode_user.username)];
463 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
215 addReviewMember(data.id, data.first_name, data.last_name,
464 if (data.value_type == 'user_group') {
216 data.username, data.icon_link, reasons);
465 reasons.push(_gettext('member of "{0}"').format(data.value_display));
217 $('#'+input_id).val('');
466
467 $.each(data.members, function(index, member_data) {
468 reviewersController.addReviewMember(
469 member_data.id, member_data.first_name, member_data.last_name,
470 member_data.username, member_data.icon_link, reasons);
471 })
472
473 } else {
474 reviewersController.addReviewMember(
475 data.id, data.first_name, data.last_name,
476 data.username, data.icon_link, reasons);
477 }
478
479 $(inputId).val('');
218 }
480 }
219 });
481 });
220 };
482 };
@@ -121,7 +121,7 b' def add_pylons_context(event):'
121 # Setup the pylons context object ('c')
121 # Setup the pylons context object ('c')
122 context = ContextObj()
122 context = ContextObj()
123 context.rhodecode_user = auth_user
123 context.rhodecode_user = auth_user
124 attach_context_attributes(context, request)
124 attach_context_attributes(context, request, request.user.user_id)
125 pylons.tmpl_context._push_object(context)
125 pylons.tmpl_context._push_object(context)
126
126
127
127
@@ -130,12 +130,12 b' def scan_repositories_if_enabled(event):'
130 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
130 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
131 does a repository scan if enabled in the settings.
131 does a repository scan if enabled in the settings.
132 """
132 """
133 from rhodecode.model.scm import ScmModel
134 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
135 settings = event.app.registry.settings
133 settings = event.app.registry.settings
136 vcs_server_enabled = settings['vcs.server.enable']
134 vcs_server_enabled = settings['vcs.server.enable']
137 import_on_startup = settings['startup.import_repos']
135 import_on_startup = settings['startup.import_repos']
138 if vcs_server_enabled and import_on_startup:
136 if vcs_server_enabled and import_on_startup:
137 from rhodecode.model.scm import ScmModel
138 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
139 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
139 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
140 repo2db_mapper(repositories, remove_obsolete=False)
140 repo2db_mapper(repositories, remove_obsolete=False)
141
141
@@ -172,6 +172,10 b' def write_metadata_if_needed(event):'
172 with open(metadata_destination, 'wb') as f:
172 with open(metadata_destination, 'wb') as f:
173 f.write(ext_json.json.dumps(metadata))
173 f.write(ext_json.json.dumps(metadata))
174
174
175 settings = event.app.registry.settings
176 if settings.get('metadata.skip'):
177 return
178
175 try:
179 try:
176 write()
180 write()
177 except Exception:
181 except Exception:
@@ -2,7 +2,7 b''
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Admin journal')}
5 ${_('Admin audit logs')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
@@ -10,9 +10,9 b''
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.form(None, id_="filter_form", method="get")}
12 ${h.form(None, id_="filter_form", method="get")}
13 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or ''}" placeholder="${_('journal filter...')}"/>
13 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or ''}" placeholder="${_('filter...')}"/>
14 <input type='submit' value="${_('filter')}" class="btn" />
14 <input type='submit' value="${_('filter')}" class="btn" />
15 ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
15 ${_('Audit logs')} - ${_ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
16 ${h.end_form()}
16 ${h.end_form()}
17 <p class="tooltip filterexample" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
17 <p class="tooltip filterexample" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
18 </%def>
18 </%def>
@@ -29,7 +29,7 b''
29 <!-- end box / title -->
29 <!-- end box / title -->
30 <div class="table">
30 <div class="table">
31 <div id="user_log">
31 <div id="user_log">
32 ${c.log_data}
32 <%include file="/admin/admin_log_base.mako" />
33 </div>
33 </div>
34 </div>
34 </div>
35 </div>
35 </div>
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${_('Authentication Plugins')}
14 ${_('Authentication Plugins')}
15 </%def>
15 </%def>
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
15 &raquo;
15 &raquo;
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${_('Repositories defaults')}
14 ${_('Repositories defaults')}
15 </%def>
15 </%def>
@@ -73,7 +73,7 b''
73 </div>
73 </div>
74
74
75 <div class="author">
75 <div class="author">
76 <div title="${c.file_last_commit.author}">
76 <div title="${h.tooltip(c.file_last_commit.author)}">
77 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
77 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
78 </div>
78 </div>
79
79
@@ -18,7 +18,7 b''
18 </%def>
18 </%def>
19
19
20 <%def name="breadcrumbs_links()">
20 <%def name="breadcrumbs_links()">
21 ${h.link_to(_('Admin'),h.url('admin_home'))}
21 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
22 &raquo;
22 &raquo;
23 ${_('Integrations')}
23 ${_('Integrations')}
24 </%def>
24 </%def>
@@ -3,7 +3,7 b''
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 &raquo;
7 &raquo;
8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 &raquo;
9 &raquo;
@@ -12,7 +12,7 b''
12 repo_name=c.repo.repo_name,
12 repo_name=c.repo.repo_name,
13 integration=current_IntegrationType.key))}
13 integration=current_IntegrationType.key))}
14 %elif c.repo_group:
14 %elif c.repo_group:
15 ${h.link_to(_('Admin'),h.url('admin_home'))}
15 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
16 &raquo;
16 &raquo;
17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
18 &raquo;
18 &raquo;
@@ -25,7 +25,7 b''
25 repo_group_name=c.repo_group.group_name,
25 repo_group_name=c.repo_group.group_name,
26 integration=current_IntegrationType.key))}
26 integration=current_IntegrationType.key))}
27 %else:
27 %else:
28 ${h.link_to(_('Admin'),h.url('admin_home'))}
28 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
29 &raquo;
29 &raquo;
30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
31 &raquo;
31 &raquo;
@@ -3,15 +3,15 b''
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 %elif c.repo_group:
7 %elif c.repo_group:
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
13 %else:
13 %else:
14 ${h.link_to(_('Admin'),h.url('admin_home'))}
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 &raquo;
15 &raquo;
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 %endif
17 %endif
@@ -160,11 +160,11 b''
160 </td>
160 </td>
161 <td class="td-scope">
161 <td class="td-scope">
162 %if integration.repo:
162 %if integration.repo:
163 <a href="${h.url('summary_home', repo_name=integration.repo.repo_name)}">
163 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
164 ${_('repo')}:${integration.repo.repo_name}
164 ${_('repo')}:${integration.repo.repo_name}
165 </a>
165 </a>
166 %elif integration.repo_group:
166 %elif integration.repo_group:
167 <a href="${h.url('repo_group_home', group_name=integration.repo_group.group_name)}">
167 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
168 ${_('repogroup')}:${integration.repo_group.group_name}
168 ${_('repogroup')}:${integration.repo_group.group_name}
169 %if integration.child_repos_only:
169 %if integration.child_repos_only:
170 ${_('child repos only')}
170 ${_('child repos only')}
@@ -4,11 +4,11 b''
4
4
5 <%def name="breadcrumbs_links()">
5 <%def name="breadcrumbs_links()">
6 %if c.repo:
6 %if c.repo:
7 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
8 &raquo;
8 &raquo;
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 %elif c.repo_group:
10 %elif c.repo_group:
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 &raquo;
14 &raquo;
@@ -16,7 +16,7 b''
16 &raquo;
16 &raquo;
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 %else:
18 %else:
19 ${h.link_to(_('Admin'),h.url('admin_home'))}
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 &raquo;
20 &raquo;
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 &raquo;
22 &raquo;
@@ -34,12 +34,12 b''
34 % if my_account_oauth_url:
34 % if my_account_oauth_url:
35 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
35 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
36 % endif
36 % endif
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('Emails')}</a></li>
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Repositories')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li>
40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Permissions')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('my_account_notifications')}">${_('Live Notifications')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
43 </ul>
43 </ul>
44 </div>
44 </div>
45
45
@@ -43,9 +43,9 b''
43 </td>
43 </td>
44 <td class="td-action">
44 <td class="td-action">
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
46 ${h.hidden('del_auth_token',auth_token.api_key)}
46 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
47 <button class="btn btn-link btn-danger" type="submit"
47 <button class="btn btn-link btn-danger" type="submit"
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
49 ${_('Delete')}
49 ${_('Delete')}
50 </button>
50 </button>
51 ${h.end_form()}
51 ${h.end_form()}
@@ -139,7 +139,7 b' var repoFilter = function(data) {'
139 query.callback({results: cachedData.results});
139 query.callback({results: cachedData.results});
140 } else {
140 } else {
141 $.ajax({
141 $.ajax({
142 url: "${h.url('repo_list_data')}",
142 url: pyroutes.url('repo_list_data'),
143 data: {'query': query.term},
143 data: {'query': query.term},
144 dataType: 'json',
144 dataType: 'json',
145 type: 'GET',
145 type: 'GET',
@@ -25,10 +25,10 b''
25 <span class="user email">${em.email}</span>
25 <span class="user email">${em.email}</span>
26 </td>
26 </td>
27 <td class="td-action">
27 <td class="td-action">
28 ${h.secure_form(url('my_account_emails'),method='delete')}
28 ${h.secure_form(h.route_path('my_account_emails_delete'), method='POST')}
29 ${h.hidden('del_email_id',em.email_id)}
29 ${h.hidden('del_email_id',em.email_id)}
30 <button class="btn btn-link btn-danger" type="submit" id="remove_email_%s" % em.email_id
30 <button class="btn btn-link btn-danger" type="submit" id="${'remove_email_%s'.format(em.email_id)}"
31 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
31 onclick="return confirm('${_('Confirm to delete this email: {}').format(em.email)}');">
32 ${_('Delete')}
32 ${_('Delete')}
33 </button>
33 </button>
34 ${h.end_form()}
34 ${h.end_form()}
@@ -48,7 +48,7 b''
48 </div>
48 </div>
49
49
50 <div>
50 <div>
51 ${h.secure_form(url('my_account_emails'), method='post')}
51 ${h.secure_form(h.route_path('my_account_emails_add'), method='POST')}
52 <div class="form">
52 <div class="form">
53 <!-- fields -->
53 <!-- fields -->
54 <div class="fields">
54 <div class="fields">
@@ -1,7 +1,7 b''
1 <template is="dom-bind" id="notificationsPage">
1 <template is="dom-bind" id="notificationsPage">
2 <iron-ajax id="toggleNotifications"
2 <iron-ajax id="toggleNotifications"
3 method="post"
3 method="post"
4 url="${url('my_account_notifications_toggle_visibility')}"
4 url="${h.route_path('my_account_notifications_toggle_visibility')}"
5 content-type="application/json"
5 content-type="application/json"
6 loading="{{changeNotificationsLoading}}"
6 loading="{{changeNotificationsLoading}}"
7 on-response="handleNotifications"
7 on-response="handleNotifications"
@@ -10,7 +10,7 b''
10
10
11 <iron-ajax id="sendTestNotification"
11 <iron-ajax id="sendTestNotification"
12 method="post"
12 method="post"
13 url="${url('my_account_notifications_test_channelstream')}"
13 url="${h.route_path('my_account_notifications_test_channelstream')}"
14 content-type="application/json"
14 content-type="application/json"
15 on-response="handleTestNotification"
15 on-response="handleTestNotification"
16 handle-as="json">
16 handle-as="json">
@@ -32,7 +32,7 b''
32 ${_('First Name')}:
32 ${_('First Name')}:
33 </div>
33 </div>
34 <div class="right-content">
34 <div class="right-content">
35 ${c.user.firstname}
35 ${c.user.first_name}
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="fieldset">
38 <div class="fieldset">
@@ -40,7 +40,7 b''
40 ${_('Last Name')}:
40 ${_('Last Name')}:
41 </div>
41 </div>
42 <div class="right-content">
42 <div class="right-content">
43 ${c.user.lastname}
43 ${c.user.last_name}
44 </div>
44 </div>
45 </div>
45 </div>
46 <div class="fieldset">
46 <div class="fieldset">
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Notifications'),h.url('notifications'))}
12 ${h.link_to(_('Notifications'), h.url('notifications'))}
13 &raquo;
13 &raquo;
14 ${_('Show notification')}
14 ${_('Show notification')}
15 </%def>
15 </%def>
@@ -23,11 +23,6 b''
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 ##<ul class="links">
27 ## <li>
28 ## <span ><a href="#">${_('Compose message')}</a></span>
29 ## </li>
30 ##</ul>
31 </div>
26 </div>
32 <div class="table">
27 <div class="table">
33 <div id="notification_${c.notification.notification_id}" class="main-content-full">
28 <div id="notification_${c.notification.notification_id}" class="main-content-full">
@@ -41,7 +36,9 b''
41 </div>
36 </div>
42 </div>
37 </div>
43 <div class="notification-body">
38 <div class="notification-body">
44 <div class="notification-subject">${h.literal(c.notification.subject)}</div>
39 <div class="notification-subject">
40 <h3>${_('Subject')}: ${c.notification.subject}</h3>
41 </div>
45 %if c.notification.body:
42 %if c.notification.body:
46 ${h.render(c.notification.body, renderer=c.visual.default_renderer, mentions=True)}
43 ${h.render(c.notification.body, renderer=c.visual.default_renderer, mentions=True)}
47 %endif
44 %endif
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${_('Permissions')}
14 ${_('Permissions')}
15 </%def>
15 </%def>
@@ -20,7 +20,7 b''
20 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
20 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
21 <td class="td-description"><div class="ip">${ip.description}</div></td>
21 <td class="td-description"><div class="ip">${ip.description}</div></td>
22 <td class="td-action">
22 <td class="td-action">
23 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='delete')}
23 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST')}
24 ${h.hidden('del_ip_id',ip.ip_id)}
24 ${h.hidden('del_ip_id',ip.ip_id)}
25 ${h.hidden('default_user', 'True')}
25 ${h.hidden('default_user', 'True')}
26 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
26 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
@@ -40,7 +40,7 b''
40 </table>
40 </table>
41 </div>
41 </div>
42
42
43 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='put')}
43 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST')}
44 <div class="form">
44 <div class="form">
45 <!-- fields -->
45 <!-- fields -->
46 <div class="fields">
46 <div class="fields">
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
15 &raquo;
15 &raquo;
@@ -9,11 +9,11 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
15 %if c.repo_group.parent_group:
15 %if c.repo_group.parent_group:
16 &raquo; ${h.link_to(c.repo_group.parent_group.name,h.url('repo_group_home',group_name=c.repo_group.parent_group.group_name))}
16 &raquo; ${h.link_to(c.repo_group.parent_group.name, h.route_path('repo_group_home', repo_group_name=c.repo_group.parent_group.group_name))}
17 %endif
17 %endif
18 &raquo; ${c.repo_group.name}
18 &raquo; ${c.repo_group.name}
19 </%def>
19 </%def>
@@ -10,7 +10,7 b''
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
@@ -10,7 +10,7 b''
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 %if c.rhodecode_user.is_admin:
12 %if c.rhodecode_user.is_admin:
13 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 &raquo;
14 &raquo;
15 ${h.link_to(_('Repositories'),h.url('repos'))}
15 ${h.link_to(_('Repositories'),h.url('repos'))}
16 %else:
16 %else:
@@ -48,12 +48,12 b''
48 if (jsonResponse === undefined) {
48 if (jsonResponse === undefined) {
49 setTimeout(function () {
49 setTimeout(function () {
50 // we might have a backend problem, try dashboard again
50 // we might have a backend problem, try dashboard again
51 window.location = "${h.url('summary_home', repo_name = c.repo)}";
51 window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
52 }, 3000);
52 }, 3000);
53 } else {
53 } else {
54 if (skipCheck || jsonResponse.result === true) {
54 if (skipCheck || jsonResponse.result === true) {
55 // success, means go to dashboard
55 // success, means go to dashboard
56 window.location = "${h.url('summary_home', repo_name = c.repo)}";
56 window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
57 } else {
57 } else {
58 // Schedule the next request when the current one's complete
58 // Schedule the next request when the current one's complete
59 setTimeout(worker, 1000);
59 setTimeout(worker, 1000);
@@ -61,7 +61,7 b''
61 }
61 }
62 }
62 }
63 else {
63 else {
64 window.location = "${h.url('home')}";
64 window.location = "${h.route_path('home')}";
65 }
65 }
66 }
66 }
67 });
67 });
@@ -24,7 +24,11 b''
24 </%def>
24 </%def>
25
25
26 <%def name="main_content()">
26 <%def name="main_content()">
27 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
27 % if hasattr(c, 'repo_edit_template'):
28 <%include file="${c.repo_edit_template}"/>
29 % else:
30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
31 % endif
28 </%def>
32 </%def>
29
33
30
34
@@ -36,17 +40,16 b''
36 </div>
40 </div>
37
41
38 <div class="sidebar-col-wrapper scw-small">
42 <div class="sidebar-col-wrapper scw-small">
39 ##main
40 <div class="sidebar">
43 <div class="sidebar">
41 <ul class="nav nav-pills nav-stacked">
44 <ul class="nav nav-pills nav-stacked">
42 <li class="${'active' if c.active=='settings' else ''}">
45 <li class="${'active' if c.active=='settings' else ''}">
43 <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
44 </li>
47 </li>
45 <li class="${'active' if c.active=='permissions' else ''}">
48 <li class="${'active' if c.active=='permissions' else ''}">
46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
47 </li>
50 </li>
48 <li class="${'active' if c.active=='advanced' else ''}">
51 <li class="${'active' if c.active=='advanced' else ''}">
49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
52 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
50 </li>
53 </li>
51 <li class="${'active' if c.active=='vcs' else ''}">
54 <li class="${'active' if c.active=='vcs' else ''}">
52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
55 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
@@ -58,7 +61,7 b''
58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
61 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
59 </li>
62 </li>
60 <li class="${'active' if c.active=='caches' else ''}">
63 <li class="${'active' if c.active=='caches' else ''}">
61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
64 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
62 </li>
65 </li>
63 %if c.repo_info.repo_type != 'svn':
66 %if c.repo_info.repo_type != 'svn':
64 <li class="${'active' if c.active=='remote' else ''}">
67 <li class="${'active' if c.active=='remote' else ''}">
@@ -71,28 +74,18 b''
71 <li class="${'active' if c.active=='integrations' else ''}">
74 <li class="${'active' if c.active=='integrations' else ''}">
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
73 </li>
76 </li>
77 %if c.repo_info.repo_type != 'svn':
78 <li class="${'active' if c.active=='reviewers' else ''}">
79 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
80 </li>
81 %endif
74 <li class="${'active' if c.active=='maintenance' else ''}">
82 <li class="${'active' if c.active=='maintenance' else ''}">
75 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
83 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
76 </li>
84 </li>
77 <li class="${'active' if c.active=='strip' else ''}">
85 <li class="${'active' if c.active=='strip' else ''}">
78 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
86 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
79 </li>
87 </li>
80 ## TODO: dan: replace repo navigation with navlist registry like with
88
81 ## admin menu. First must find way to allow runtime configuration
82 ## it to account for the c.repo_info.repo_type != 'svn' call above
83 <%
84 reviewer_settings = False
85 try:
86 import rc_reviewers
87 reviewer_settings = True
88 except ImportError:
89 pass
90 %>
91 %if reviewer_settings:
92 <li class="${'active' if c.active=='reviewers' else ''}">
93 <a href="${h.route_path('repo_reviewers_home', repo_name=c.repo_name)}">${_('Reviewers')}</a>
94 </li>
95 %endif
96 </ul>
89 </ul>
97 </div>
90 </div>
98
91
@@ -10,8 +10,8 b''
10 %>
10 %>
11
11
12 <div class="panel panel-default">
12 <div class="panel panel-default">
13 <div class="panel-heading">
13 <div class="panel-heading" id="advanced-info" >
14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name}</h3>
14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name} <a class="permalink" href="#advanced-info"></a></h3>
15 </div>
15 </div>
16 <div class="panel-body">
16 <div class="panel-body">
17 ${base.dt_info_panel(elems)}
17 ${base.dt_info_panel(elems)}
@@ -20,14 +20,14 b''
20
20
21
21
22 <div class="panel panel-default">
22 <div class="panel panel-default">
23 <div class="panel-heading">
23 <div class="panel-heading" id="advanced-fork">
24 <h3 class="panel-title">${_('Fork Reference')}</h3>
24 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"></a></h3>
25 </div>
25 </div>
26 <div class="panel-body">
26 <div class="panel-body">
27 ${h.secure_form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='put')}
27 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='POST')}
28
28
29 % if c.repo_info.fork:
29 % if c.repo_info.fork:
30 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.repo_info.fork.repo_name, h.url('summary_home', repo_name=c.repo_info.fork.repo_name))})}
30 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.repo_info.fork.repo_name, h.route_path('repo_summary', repo_name=c.repo_info.fork.repo_name))})}
31 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
31 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
32 % endif
32 % endif
33
33
@@ -44,15 +44,14 b''
44
44
45
45
46 <div class="panel panel-default">
46 <div class="panel panel-default">
47 <div class="panel-heading">
47 <div class="panel-heading" id="advanced-journal">
48 <h3 class="panel-title">${_('Public Journal Visibility')}</h3>
48 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"></a></h3>
49 </div>
49 </div>
50 <div class="panel-body">
50 <div class="panel-body">
51 ${h.secure_form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='put')}
51 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='POST')}
52 <div class="field">
52 <div class="field">
53 %if c.in_public_journal:
53 %if c.in_public_journal:
54 <button class="btn btn-small" type="submit">
54 <button class="btn btn-small" type="submit">
55 <i class="icon-minus"></i>
56 ${_('Remove from Public Journal')}
55 ${_('Remove from Public Journal')}
57 </button>
56 </button>
58 %else:
57 %else:
@@ -70,11 +69,11 b''
70
69
71
70
72 <div class="panel panel-default">
71 <div class="panel panel-default">
73 <div class="panel-heading">
72 <div class="panel-heading" id="advanced-locking">
74 <h3 class="panel-title">${_('Locking state')}</h3>
73 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"></a></h3>
75 </div>
74 </div>
76 <div class="panel-body">
75 <div class="panel-body">
77 ${h.secure_form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')}
76 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='POST')}
78
77
79 %if c.repo_info.locked[0]:
78 %if c.repo_info.locked[0]:
80 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.repo_info.locked[0]),
79 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.repo_info.locked[0]),
@@ -110,15 +109,15 b''
110 </div>
109 </div>
111
110
112 <div class="panel panel-danger">
111 <div class="panel panel-danger">
113 <div class="panel-heading">
112 <div class="panel-heading" id="advanced-delete">
114 <h3 class="panel-title">${_('Delete repository')}</h3>
113 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"></a></h3>
115 </div>
114 </div>
116 <div class="panel-body">
115 <div class="panel-body">
117 ${h.secure_form(url('repo', repo_name=c.repo_name),method='delete')}
116 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), method='POST')}
118 <table class="display">
117 <table class="display">
119 <tr>
118 <tr>
120 <td>
119 <td>
121 ${ungettext('This repository has %s fork.', 'This repository has %s forks.', c.repo_info. forks.count()) % c.repo_info.forks.count()}
120 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.repo_info.forks.count()) % c.repo_info.forks.count()}
122 </td>
121 </td>
123 <td>
122 <td>
124 %if c.repo_info.forks.count():
123 %if c.repo_info.forks.count():
@@ -143,7 +142,7 b''
143 </div>
142 </div>
144 <div class="field">
143 <div class="field">
145 <span class="help-block">
144 <span class="help-block">
146 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command.')}
145 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
147 </span>
146 </span>
148 </div>
147 </div>
149
148
@@ -191,7 +190,7 b' var repoTypeFilter = function(data) {'
191 query.callback({results: cachedData.results});
190 query.callback({results: cachedData.results});
192 } else {
191 } else {
193 $.ajax({
192 $.ajax({
194 url: "${h.url('repo_list_data')}",
193 url: pyroutes.url('repo_list_data'),
195 data: {'query': query.term, repo_type: '${c.repo_info.repo_type}'},
194 data: {'query': query.term, repo_type: '${c.repo_info.repo_type}'},
196 dataType: 'json',
195 dataType: 'json',
197 type: 'GET',
196 type: 'GET',
@@ -3,21 +3,25 b''
3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 ${h.secure_form(url('edit_repo_caches', repo_name=c.repo_name), method='put')}
6
7 <div>
7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
8
9 <p>
10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
11 <br/>
12 <code>
13 ${h.api_call_example(method='invalidate_cache', args={"repoid": c.repo_info.repo_name})}
14 </code>
15 </p>
16
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), method='POST')}
18 <div class="form">
8 <div class="fields">
19 <div class="fields">
9 <p>
20 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
10 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
11 </p>
12 <div class="field" >
13 <span class="help-block">
14 ${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}
15 </span>
16 </div>
17
18 </div>
21 </div>
19 </div>
22 </div>
20 ${h.end_form()}
23 ${h.end_form()}
24
21 </div>
25 </div>
22 </div>
26 </div>
23
27
@@ -25,7 +29,7 b''
25 <div class="panel panel-default">
29 <div class="panel panel-default">
26 <div class="panel-heading">
30 <div class="panel-heading">
27 <h3 class="panel-title">
31 <h3 class="panel-title">
28 ${(ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.repo_info.cache_keys)) % {'count': len(c.repo_info.cache_keys)})}
32 ${(_ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.repo_info.cache_keys)) % {'count': len(c.repo_info.cache_keys)})}
29 </h3>
33 </h3>
30 </div>
34 </div>
31 <div class="panel-body">
35 <div class="panel-body">
@@ -47,5 +51,3 b''
47 </div>
51 </div>
48 </div>
52 </div>
49 </div>
53 </div>
50
51
@@ -4,19 +4,27 b''
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6
6
7 % if c.executable_tasks:
8 <h4>${_('Perform maintenance tasks for this repo')}</h4>
9
10 <span>${_('Following tasks will be performed')}:</span>
11 <ol>
12 % for task in c.executable_tasks:
13 <li>${task}</li>
14 % endfor
15 </ol>
7 <p>
16 <p>
8 % if c.executable_tasks:
17 ${_('Maintenance can be automated by such api call. Can be called periodically in crontab etc.')}
9 ${_('Perform maintenance tasks for this repo, following tasks will be performed')}:
18 <br/>
10 <ol>
19 <code>
11 % for task in c.executable_tasks:
20 ${h.api_call_example(method='maintenance', args={"repoid": c.repo_info.repo_name})}
12 <li>${task}</li>
21 </code>
13 % endfor
14 </ol>
15 % else:
16 ${_('No maintenance tasks for this repo available')}
17 % endif
18 </p>
22 </p>
19
23
24 % else:
25 <h4>${_('No maintenance tasks for this repo available')}</h4>
26 % endif
27
20 <div id="results" style="display:none; padding: 10px 0px;"></div>
28 <div id="results" style="display:none; padding: 10px 0px;"></div>
21
29
22 % if c.executable_tasks:
30 % if c.executable_tasks:
@@ -5,8 +5,7 b''
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST')}
9 ${h.hidden('repo_private')}
10 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
11 <tr>
10 <tr>
12 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
@@ -40,7 +39,7 b''
40 <tr>
39 <tr>
41 <td colspan="4">
40 <td colspan="4">
42 <span class="private_repo_msg">
41 <span class="private_repo_msg">
43 <strong>${_('private repository')}</strong>
42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
44 </span>
43 </span>
45 </td>
44 </td>
46 <td class="private_repo_msg">
45 <td class="private_repo_msg">
@@ -50,10 +49,10 b''
50 </tr>
49 </tr>
51 %else:
50 %else:
52 <tr>
51 <tr>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none')}</td>
52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read')}</td>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
57 <td class="td-user">
56 <td class="td-user">
58 ${base.gravatar(_user.email, 16)}
57 ${base.gravatar(_user.email, 16)}
59 <span class="user">
58 <span class="user">
@@ -79,10 +78,10 b''
79 ## USER GROUPS
78 ## USER GROUPS
80 %for _user_group in c.repo_info.permission_user_groups():
79 %for _user_group in c.repo_info.permission_user_groups():
81 <tr>
80 <tr>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none')}</td>
81 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read')}</td>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write')}</td>
83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
85 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin')}</td>
84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
86 <td class="td-componentname">
85 <td class="td-componentname">
87 <i class="icon-group" ></i>
86 <i class="icon-group" ></i>
88 %if h.HasPermissionAny('hg.admin')():
87 %if h.HasPermissionAny('hg.admin')():
@@ -4,17 +4,20 b''
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6
6
7 <h4>${_('Manually pull changes from external repository.')}</h4>
8
7 %if c.repo_info.clone_uri:
9 %if c.repo_info.clone_uri:
8
10
9 <div class="panel-body-title-text">${_('Remote mirror url')}:
11 ${_('Remote mirror url')}:
10 <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a>
12 <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a>
11 <p>
13
12 ${_('Pull can be automated by such api call called periodically (in crontab etc)')}
14 <p>
13 </p>
15 ${_('Pull can be automated by such api call. Can be called periodically in crontab etc.')}
16 <br/>
14 <code>
17 <code>
15 curl ${h.route_url('apiv2')} -X POST -H 'content-type:text/plain' --data-binary '{"id":1, "auth_token":"SECRET","method":"pull", "args":{"repoid":"${c.repo_info.repo_name}"}}'
18 ${h.api_call_example(method='pull', args={"repoid": c.repo_info.repo_name})}
16 </code>
19 </code>
17 </div>
20 </p>
18
21
19 ${h.secure_form(url('edit_repo_remote', repo_name=c.repo_name), method='put')}
22 ${h.secure_form(url('edit_repo_remote', repo_name=c.repo_name), method='put')}
20 <div class="form">
23 <div class="form">
@@ -24,14 +27,14 b''
24 </div>
27 </div>
25 ${h.end_form()}
28 ${h.end_form()}
26 %else:
29 %else:
27 <div class="panel-body-title-text">${_('This repository does not have any remote mirror url set.')}</div>
28
30
31 ${_('This repository does not have any remote mirror url set.')}
32 <a href="${h.route_path('edit_repo', repo_name=c.repo_info.repo_name)}">${_('Set remote url.')}</a>
33 <br/>
34 <br/>
29 <button class="btn disabled" type="submit" disabled="disabled">
35 <button class="btn disabled" type="submit" disabled="disabled">
30 ${_('Pull changes from remote location')}
36 ${_('Pull changes from remote location')}
31 </button>
37 </button>
32 %endif
38 %endif
33 </div>
39 </div>
34 </div>
40 </div>
35
36
37
@@ -3,92 +3,121 b''
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.repo_info.repo_name}</h3>
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST')}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13
13 <div class="field">
14 <div class="field">
14 <div class="label">
15 <div class="label">
15 <label for="repo_name">${_('Name')}:</label>
16 <label for="repo_name">${_('Name')}:</label>
16 </div>
17 </div>
17 <div class="input">
18 <div class="input">
18 ${h.text('repo_name',class_="medium")}
19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
19 <p class="help-block">${_('Non-changeable id')}: `_${c.repo_info.repo_id}` <span><a id="show_more_clone_id" href="#">${_('what is that ?')}</a></span></p>
20 ${c.form.render_error(request, c.form['repo_name'])|n}
21
22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
20 <p id="clone_id" style="display:none;">
23 <p id="clone_id" style="display:none;">
21 ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}` </br>
24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
22 ${_('''In case this repository is renamed or moved into another group the repository url changes.
25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
23 Using above url guarantees that this repository will always be accessible under such url.
26 Using above url guarantees that this repository will always be accessible under such url.
24 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
25 </div>
28 </div>
26 </div>
29 </div>
27 % if c.repo_info.repo_type != 'svn':
30
31 <div class="field">
32 <div class="label">
33 <label for="repo_group">${_('Repository group')}:</label>
34 </div>
35 <div class="select">
36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
37 ${c.form.render_error(request, c.form['repo_group'])|n}
38
39 % if c.personal_repo_group:
40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
42 </a>
43 % endif
44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
45 </div>
46 </div>
47
48 % if c.rhodecode_db_repo.repo_type != 'svn':
28 <div class="field">
49 <div class="field">
29 <div class="label">
50 <div class="label">
30 <label for="clone_uri">${_('Remote uri')}:</label>
51 <label for="clone_uri">${_('Remote uri')}:</label>
31 </div>
52 </div>
32 <div class="input">
53 <div class="input">
33 %if c.repo_info.clone_uri:
54 %if c.rhodecode_db_repo.clone_uri:
55 ## display, if we don't have any errors
56 % if not c.form['repo_clone_uri'].error:
34 <div id="clone_uri_hidden" class='text-as-placeholder'>
57 <div id="clone_uri_hidden" class='text-as-placeholder'>
35 <span id="clone_uri_hidden_value">${c.repo_info.clone_uri_hidden}</span>
58 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
36 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
37 </div>
60 </div>
38 <div id="alter_clone_uri" style="display: none">
61 % endif
39 ${h.text('clone_uri',class_="medium", placeholder=_('new value, leave empty to remove'))}
62
40 ${h.hidden('clone_uri_change', 'OLD')}
63 ## alter field
64 <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}">
65 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
66 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
67 % if c.form['repo_clone_uri'].error:
68 ## we got error from form subit, means we modify the url
69 ${h.hidden('repo_clone_uri_change', 'MOD')}
70 % else:
71 ${h.hidden('repo_clone_uri_change', 'OLD')}
72 % endif
73
74 % if not c.form['repo_clone_uri'].error:
41 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
76 % endif
77
42 </div>
78 </div>
43 %else:
79 %else:
44 ## not set yet, display form to set it
80 ## not set yet, display form to set it
45 ${h.text('clone_uri',class_="medium")}
81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
46 ${h.hidden('clone_uri_change', 'NEW')}
82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
83 ${h.hidden('repo_clone_uri_change', 'NEW')}
47 %endif
84 %endif
48 <p id="alter_clone_uri_help_block" class="help-block">${_('http[s] url where from repository was imported, also used for doing remote pulls.')}</p>
85 <p id="alter_clone_uri_help_block" class="help-block">
86 <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %>
87 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
88 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
89 </p>
49 </div>
90 </div>
50 </div>
91 </div>
51 % else:
92 % else:
52 ${h.hidden('clone_uri', '')}
93 ${h.hidden('repo_clone_uri', '')}
53 % endif
94 % endif
95
54 <div class="field">
96 <div class="field">
55 <div class="label">
97 <div class="label">
56 <label for="repo_group">${_('Repository group')}:</label>
98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
57 </div>
99 </div>
58 <div class="select">
100 <div class="select">
59 ${h.select('repo_group','',c.repo_groups,class_="medium")}
101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
60 % if c.personal_repo_group:
102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
61 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
62 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
63 </a>
64 % endif
65 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
66 </div>
67 </div>
68 <div class="field">
69 <div class="label">
70 <label for="repo_landing_rev">${_('Landing commit')}:</label>
71 </div>
72 <div class="select">
73 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
74 <p class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</p>
75 </div>
104 </div>
76 </div>
105 </div>
77
106
78 <div class="field badged-field">
107 <div class="field badged-field">
79 <div class="label">
108 <div class="label">
80 <label for="user">${_('Owner')}:</label>
109 <label for="repo_owner">${_('Owner')}:</label>
81 </div>
110 </div>
82 <div class="input">
111 <div class="input">
83 <div class="badge-input-container">
112 <div class="badge-input-container">
84 <div class="user-badge">
113 <div class="user-badge">
85 ${base.gravatar_with_user(c.repo_info.user.email, show_disabled=not c.repo_info.user.active)}
114 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email or c.rhodecode_db_repo.user.username, show_disabled=not c.rhodecode_db_repo.user.active)}
86 </div>
115 </div>
87 <div class="badge-input-wrap">
116 <div class="badge-input-wrap">
88 ${h.text('user', class_="medium", autocomplete="off")}
117 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
89 </div>
118 </div>
90 </div>
119 </div>
91 <form:error name="user"/>
120 ${c.form.render_error(request, c.form['repo_owner'])|n}
92 <p class="help-block">${_('Change owner of this repository.')}</p>
121 <p class="help-block">${_('Change owner of this repository.')}</p>
93 </div>
122 </div>
94 </div>
123 </div>
@@ -98,44 +127,49 b''
98 <label for="repo_description">${_('Description')}:</label>
127 <label for="repo_description">${_('Description')}:</label>
99 </div>
128 </div>
100 <div class="textarea text-area editor">
129 <div class="textarea text-area editor">
101 ${h.textarea('repo_description', )}
130 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
131 ${c.form.render_error(request, c.form['repo_description'])|n}
102 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
132 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
103 </div>
133 </div>
104 </div>
134 </div>
105
135
106 <div class="field">
136 <div class="field">
107 <div class="label label-checkbox">
137 <div class="label label-checkbox">
108 <label for="repo_private">${_('Private repository')}:</label>
138 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
109 </div>
139 </div>
110 <div class="checkboxes">
140 <div class="checkboxes">
111 ${h.checkbox('repo_private',value="True")}
141 ${c.form['repo_private'].render(css_class='medium')|n}
142 ${c.form.render_error(request, c.form['repo_private'])|n}
112 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
143 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
113 </div>
144 </div>
114 </div>
145 </div>
115 <div class="field">
146 <div class="field">
116 <div class="label label-checkbox">
147 <div class="label label-checkbox">
117 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
148 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
118 </div>
149 </div>
119 <div class="checkboxes">
150 <div class="checkboxes">
120 ${h.checkbox('repo_enable_statistics',value="True")}
151 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
152 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
121 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
153 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
122 </div>
154 </div>
123 </div>
155 </div>
124 <div class="field">
156 <div class="field">
125 <div class="label label-checkbox">
157 <div class="label label-checkbox">
126 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
158 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
127 </div>
159 </div>
128 <div class="checkboxes">
160 <div class="checkboxes">
129 ${h.checkbox('repo_enable_downloads',value="True")}
161 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
162 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
130 <span class="help-block">${_('Enable download menu on summary page.')}</span>
163 <span class="help-block">${_('Enable download menu on summary page.')}</span>
131 </div>
164 </div>
132 </div>
165 </div>
133 <div class="field">
166 <div class="field">
134 <div class="label label-checkbox">
167 <div class="label label-checkbox">
135 <label for="repo_enable_locking">${_('Enable automatic locking')}:</label>
168 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
136 </div>
169 </div>
137 <div class="checkboxes">
170 <div class="checkboxes">
138 ${h.checkbox('repo_enable_locking',value="True")}
171 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
172 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
139 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
173 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
140 </div>
174 </div>
141 </div>
175 </div>
@@ -168,12 +202,6 b''
168
202
169 <script>
203 <script>
170 $(document).ready(function(){
204 $(document).ready(function(){
171 var select2Options = {
172 'containerCssClass': "drop-menu",
173 'dropdownCssClass': "drop-menu-dropdown",
174 'dropdownAutoWidth': true
175 };
176
177 var cloneUrl = function() {
205 var cloneUrl = function() {
178 var alterButton = $('#alter_clone_uri');
206 var alterButton = $('#alter_clone_uri');
179 var editButton = $('#edit_clone_uri');
207 var editButton = $('#edit_clone_uri');
@@ -182,7 +210,7 b''
182 var hiddenUrlValue = $('#clone_uri_hidden_value');
210 var hiddenUrlValue = $('#clone_uri_hidden_value');
183 var input = $('#clone_uri');
211 var input = $('#clone_uri');
184 var helpBlock = $('#alter_clone_uri_help_block');
212 var helpBlock = $('#alter_clone_uri_help_block');
185 var changedFlag = $('#clone_uri_change');
213 var changedFlag = $('#repo_clone_uri_change');
186 var originalText = helpBlock.html();
214 var originalText = helpBlock.html();
187 var obfuscatedUrl = hiddenUrlValue.html();
215 var obfuscatedUrl = hiddenUrlValue.html();
188
216
@@ -223,18 +251,10 b''
223 initEvents();
251 initEvents();
224 }();
252 }();
225
253
226 $('#show_more_clone_id').on('click', function(e){
254 selectMyGroup = function(element) {
227 $('#clone_id').show();
255 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
228 e.preventDefault();
256 };
229 });
230
257
231 $('#repo_landing_rev').select2(select2Options);
258 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
232 $('#repo_group').select2(select2Options);
233
234 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
235 $('#select_my_group').on('click', function(e){
236 e.preventDefault();
237 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
238 });
239 });
259 });
240 </script>
260 </script>
@@ -23,7 +23,7 b''
23 <div id="results" style="display:none; padding: 10px 0px;"></div>
23 <div id="results" style="display:none; padding: 10px 0px;"></div>
24
24
25 <div class="buttons">
25 <div class="buttons">
26 <button class="btn btn-small btn-primary" onclick="checkCommits();return false">
26 <button id="strip_action" class="btn btn-small btn-primary" onclick="checkCommits();return false">
27 ${_('Check commits')}
27 ${_('Check commits')}
28 </button>
28 </button>
29 </div>
29 </div>
@@ -104,34 +104,60 b' delOld = function(number){'
104
104
105 };
105 };
106
106
107 var result_data;
107 var resultData = {
108 'csrf_token': CSRF_TOKEN
109 };
108
110
109 checkCommits = function() {
111 checkCommits = function() {
110 var postData = $('form').serialize();
112 var postData = $('form').serialize();
111 $('#results').show();
113 $('#results').show();
112 $('#results').html('<h4>${_('Checking commits')}...</h4>');
114 $('#results').html('<h4>${_('Checking commits')}...</h4>');
113 var url = "${h.route_path('strip_check', repo_name=c.repo_info.repo_name)}";
115 var url = "${h.route_path('strip_check', repo_name=c.repo_info.repo_name)}";
114 var btn = $('button');
116 var btn = $('#strip_action');
115 btn.attr('disabled', 'disabled');
117 btn.attr('disabled', 'disabled');
116 btn.addClass('disabled');
118 btn.addClass('disabled');
117
119
118 var success = function (data) {
120 var success = function (data) {
119 result_data = {};
121 resultData = {
122 'csrf_token': CSRF_TOKEN
123 };
120 var i = 0;
124 var i = 0;
121 result ='';
125 var result = '<ol>';
122 $.each(data, function(index, value){
126 $.each(data, function(index, value){
123 i= index;
127 i= index;
124 var box = $('#box-'+index);
128 var box = $('#box-'+index);
125 if (value.rev){
129 if (value.rev){
126 result_data[index] = JSON.stringify(value);
130 resultData[index] = JSON.stringify(value);
127 msg = '${_("author")}: ' + value.author + ' ${_("comment")}: ' + value.comment;
131
128 result += '<h4><code>' +value.rev+ '</code>${_(' commit verified positive')}</br> '+ msg + '</h4>';
132 var verifiedHtml = (
133 '<li style="line-height:1.2em">' +
134 '<code>{0}</code>' +
135 '{1}' +
136 '<div style="white-space:pre">' +
137 'author: {2}\n' +
138 'description: {3}' +
139 '</div>' +
140 '</li>').format(
141 value.rev,
142 "${_(' commit verified positive')}",
143 value.author, value.comment
144 );
145 result += verifiedHtml;
129 }
146 }
130 else{
147 else {
131 result += '<h4><code>' +value.commit+ '</code>${_(' commit verified negative')}' + '</h4>';
148 var verifiedHtml = (
149 '<li style="line-height:1.2em">' +
150 '<code><strike>{0}</strike></code>' +
151 '{1}' +
152 '</li>').format(
153 value.commit,
154 "${_(' commit verified negative')}"
155 );
156 result += verifiedHtml;
132 }
157 }
133 box.remove();
158 box.remove();
134 });
159 });
160 result += '</ol>';
135 var box = $('#box-'+(parseInt(i)+1));
161 var box = $('#box-'+(parseInt(i)+1));
136 box.remove();
162 box.remove();
137 $('#results').html(result);
163 $('#results').html(result);
@@ -144,25 +170,28 b' checkCommits = function() {'
144 ajaxPOST(url, postData, success, null);
170 ajaxPOST(url, postData, success, null);
145 };
171 };
146
172
147 strip = function(){
173 strip = function() {
148 var url = "${h.route_path('strip_execute', repo_name=c.repo_info.repo_name)}";
174 var url = "${h.route_path('strip_execute', repo_name=c.repo_info.repo_name)}";
149 var success = function(data){
175 var success = function(data) {
150 result = '';
176 var result = '<h4>Strip executed</h4><ol>';
151 $.each(data, function(index, value){
177 $.each(data, function(index, value){
152 if(data[index]){
178 if(data[index]) {
153 result += '<h4>' +index+ '${_(' commit striped successful')}' + '</h4>';
179 result += '<li><code>' +index+ '</code> ${_(' commit striped successfully')}' + '</li>';
154 }
180 }
155 else{
181 else {
156 result += '<h4>' +index+ '${_(' commit striped failed')}' + '</h4>';
182 result += '<li><code>' +index+ '</code> ${_(' commit strip failed')}' + '</li>';
157 }
183 }
158 });
184 });
185 if ($.isEmptyObject(data)) {
186 result += '<li>Nothing done...</li>'
187 }
188 result += '</ol>';
159 $('#results').html(result);
189 $('#results').html(result);
160
190
161 };
191 };
162 ajaxPOST(url, result_data, success, null);
192 ajaxPOST(url, resultData, success, null);
163 var btn = $('button');
193 var btn = $('#strip_action');
164 btn.attr('disabled', 'disabled');
194 btn.remove();
165 btn.addClass('disabled');
166
195
167 };
196 };
168 </script>
197 </script>
@@ -10,7 +10,7 b''
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${_('Settings')}
14 ${_('Settings')}
15 </%def>
15 </%def>
@@ -23,7 +23,7 b''
23 <dl class="dl-horizontal settings">
23 <dl class="dl-horizontal settings">
24 %for dt, dd, tt in elems:
24 %for dt, dd, tt in elems:
25 <dt >${dt}:</dt>
25 <dt >${dt}:</dt>
26 <dd title="${tt}">${dd}</dd>
26 <dd title="${h.tooltip(tt)}">${dd}</dd>
27 %endfor
27 %endfor
28 </dl>
28 </dl>
29 </div>
29 </div>
@@ -16,7 +16,7 b''
16 <dl class="dl-horizontal settings">
16 <dl class="dl-horizontal settings">
17 %for dt, dd, tt in elems:
17 %for dt, dd, tt in elems:
18 <dt>${dt}:</dt>
18 <dt>${dt}:</dt>
19 <dd title="${tt}">${dd}</dd>
19 <dd title="${h.tooltip(tt)}">${dd}</dd>
20 %endfor
20 %endfor
21 </dl>
21 </dl>
22 </div>
22 </div>
@@ -52,6 +52,6 b''
52 <script>
52 <script>
53 $('#check_for_update').click(function(e){
53 $('#check_for_update').click(function(e){
54 $('#update_notice').show();
54 $('#update_notice').show();
55 $('#update_notice').load("${h.route_path('admin_settings_system_update',version=c.rhodecode_version, platform=c.platform)}");
55 $('#update_notice').load("${h.route_path('admin_settings_system_update')}");
56 })
56 })
57 </script>
57 </script>
@@ -8,7 +8,7 b''
8 %endif
8 %endif
9 </%def>
9 </%def>
10 <%def name="breadcrumbs_links()">
10 <%def name="breadcrumbs_links()">
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('User groups'),h.url('users_groups'))}
13 ${h.link_to(_('User groups'),h.url('users_groups'))}
14 &raquo;
14 &raquo;
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
15 &raquo;
15 &raquo;
@@ -34,7 +34,6 b''
34 % if sync_type:
34 % if sync_type:
35 <p>
35 <p>
36 ${_('This group is set to be automatically synchronised.')}<br/>
36 ${_('This group is set to be automatically synchronised.')}<br/>
37 ${_('Each member will be added or removed from this groups once they interact with RhodeCode system.')}<br/>
38 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
37 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
39 </p>
38 </p>
40 % else:
39 % else:
@@ -56,11 +55,8 b''
56 </div>
55 </div>
57 <div class="field">
56 <div class="field">
58 <span class="help-block">
57 <span class="help-block">
59 %if sync_type:
58 ${_('Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. '
60 ${_('User group will no longer synchronize membership')}
59 'This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)')}
61 %else:
62 ${_('User group will start to synchronize membership')}
63 %endif
64 </span>
60 </span>
65 </div>
61 </div>
66 ${h.end_form()}
62 ${h.end_form()}
@@ -10,7 +10,7 b''
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
@@ -8,7 +8,7 b''
8 %endif
8 %endif
9 </%def>
9 </%def>
10 <%def name="breadcrumbs_links()">
10 <%def name="breadcrumbs_links()">
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('Users'),h.route_path('users'))}
13 ${h.link_to(_('Users'),h.route_path('users'))}
14 &raquo;
14 &raquo;
@@ -9,11 +9,16 b''
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Users'),h.route_path('users'))}
14 ${h.link_to(_('Users'),h.route_path('users'))}
15 &raquo;
15 &raquo;
16 % if c.user.active:
16 ${c.user.username}
17 ${c.user.username}
18 % else:
19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
20 % endif
21
17 </%def>
22 </%def>
18
23
19 <%def name="menu_bar_nav()">
24 <%def name="menu_bar_nav()">
@@ -35,8 +40,8 b''
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
40 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
41 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
42 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
43 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
39 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
44 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.route_path('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
40 <li class="${'active' if c.active=='groups' else ''}"><a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a></li>
45 <li class="${'active' if c.active=='groups' else ''}"><a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a></li>
41 <li class="${'active' if c.active=='audit' else ''}"><a href="${h.route_path('edit_user_audit_logs', user_id=c.user.user_id)}">${_('User audit')}</a></li>
46 <li class="${'active' if c.active=='audit' else ''}"><a href="${h.route_path('edit_user_audit_logs', user_id=c.user.user_id)}">${_('User audit')}</a></li>
42 </ul>
47 </ul>
@@ -59,7 +59,7 b''
59 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
59 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
60
60
61 %if c.personal_repo_group:
61 %if c.personal_repo_group:
62 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, url('repo_group_home', group_name=c.personal_repo_group.group_name))}</div>
62 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, h.route_path('repo_group_home', repo_group_name=c.personal_repo_group.group_name))}</div>
63 %else:
63 %else:
64 <div class="panel-body-title-text">
64 <div class="panel-body-title-text">
65 ${_('This user currently does not have a personal repository group')}
65 ${_('This user currently does not have a personal repository group')}
@@ -5,7 +5,7 b''
5 <div class="panel panel-default">
5 <div class="panel panel-default">
6 <div class="panel-heading">
6 <div class="panel-heading">
7 <h3 class="panel-title">${_('User Audit Logs')} -
7 <h3 class="panel-title">${_('User Audit Logs')} -
8 ${_ungettext('%s entry', '%s entries', c.user_log.item_count) % (c.user_log.item_count)}
8 ${_ungettext('%s entry', '%s entries', c.audit_logs.item_count) % (c.audit_logs.item_count)}
9 </h3>
9 </h3>
10 </div>
10 </div>
11 <div class="panel-body">
11 <div class="panel-body">
@@ -16,50 +16,7 b''
16 ${h.end_form()}
16 ${h.end_form()}
17 <p class="tooltip filterexample" style="position: inherit" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
17 <p class="tooltip filterexample" style="position: inherit" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
18
18
19 % if c.user_log:
19 <%include file="/admin/admin_log_base.mako" />
20 <table class="rctable admin_log">
21 <tr>
22 <th>${_('Username')}</th>
23 <th>${_('Action')}</th>
24 <th>${_('Repository')}</th>
25 <th>${_('Date')}</th>
26 <th>${_('From IP')}</th>
27 </tr>
28
29 %for cnt,l in enumerate(c.user_log):
30 <tr class="parity${cnt%2}">
31 <td class="td-user">
32 %if l.user is not None:
33 ${base.gravatar_with_user(l.user.email)}
34 %else:
35 ${l.username}
36 %endif
37 </td>
38 <td class="td-journalaction">${h.action_parser(l)[0]()}
39 <div class="journal_action_params">
40 ${h.literal(h.action_parser(l)[1]())}
41 </div>
42 </td>
43 <td class="td-componentname">
44 %if l.repository is not None:
45 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
46 %else:
47 ${l.repository_name}
48 %endif
49 </td>
50
51 <td class="td-time">${h.format_date(l.action_date)}</td>
52 <td class="td-ip">${l.user_ip}</td>
53 </tr>
54 %endfor
55 </table>
56
57 <div class="pagination-wh pagination-left">
58 ${c.user_log.pager('$link_previous ~2~ $link_next')}
59 </div>
60 % else:
61 ${_('No actions yet')}
62 % endif
63
20
64 </div>
21 </div>
65 </div>
22 </div>
@@ -38,10 +38,10 b''
38 %endif
38 %endif
39 </td>
39 </td>
40 <td class="td-action">
40 <td class="td-action">
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='post')}
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='POST')}
42 ${h.hidden('del_auth_token',auth_token.api_key)}
42 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
43 <button class="btn btn-link btn-danger" type="submit"
43 <button class="btn btn-link btn-danger" type="submit"
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
45 ${_('Delete')}
45 ${_('Delete')}
46 </button>
46 </button>
47 ${h.end_form()}
47 ${h.end_form()}
@@ -55,7 +55,7 b''
55 </div>
55 </div>
56
56
57 <div class="user_auth_tokens">
57 <div class="user_auth_tokens">
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='post')}
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='POST')}
59 <div class="form form-vertical">
59 <div class="form form-vertical">
60 <!-- fields -->
60 <!-- fields -->
61 <div class="fields">
61 <div class="fields">
@@ -136,7 +136,7 b' var repoFilter = function(data) {'
136 query.callback({results: cachedData.results});
136 query.callback({results: cachedData.results});
137 } else {
137 } else {
138 $.ajax({
138 $.ajax({
139 url: "${h.url('repo_list_data')}",
139 url: pyroutes.url('repo_list_data'),
140 data: {'query': query.term},
140 data: {'query': query.term},
141 dataType: 'json',
141 dataType: 'json',
142 type: 'GET',
142 type: 'GET',
@@ -24,8 +24,8 b''
24 <span class="user email">${em.email}</span>
24 <span class="user email">${em.email}</span>
25 </td>
25 </td>
26 <td class="td-action">
26 <td class="td-action">
27 ${h.secure_form(url('edit_user_emails', user_id=c.user.user_id),method='delete')}
27 ${h.secure_form(h.route_path('edit_user_emails_delete', user_id=c.user.user_id), method='POST')}
28 ${h.hidden('del_email_id',em.email_id)}
28 ${h.hidden('del_email_id', em.email_id)}
29 <button class="btn btn-link btn-danger" type="submit"
29 <button class="btn btn-link btn-danger" type="submit"
30 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
30 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
31 ${_('Delete')}
31 ${_('Delete')}
@@ -46,7 +46,7 b''
46 </table>
46 </table>
47 </div>
47 </div>
48
48
49 ${h.secure_form(url('edit_user_emails', user_id=c.user.user_id),method='put')}
49 ${h.secure_form(h.route_path('edit_user_emails_add', user_id=c.user.user_id), method='POST')}
50 <div class="form">
50 <div class="form">
51 <!-- fields -->
51 <!-- fields -->
52 <div class="fields">
52 <div class="fields">
@@ -4,11 +4,12 b''
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="ips_wrap">
6 <div class="ips_wrap">
7 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
7 <table class="rctable ip-whitelist">
8 <table class="rctable ip-whitelist">
8 <tr>
9 <tr>
9 <th>IP Address</th>
10 <th>${_('IP Address')}</th>
10 <th>IP Range</th>
11 <th>${_('IP Range')}</th>
11 <th>Description</th>
12 <th>${_('Description')}</th>
12 <th></th>
13 <th></th>
13 </tr>
14 </tr>
14 %if c.default_user_ip_map and c.inherit_default_ips:
15 %if c.default_user_ip_map and c.inherit_default_ips:
@@ -29,9 +30,9 b''
29 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
30 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
30 <td class="td-description"><div class="ip">${ip.description}</div></td>
31 <td class="td-description"><div class="ip">${ip.description}</div></td>
31 <td class="td-action">
32 <td class="td-action">
32 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='delete')}
33 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST')}
33 ${h.hidden('del_ip_id',ip.ip_id)}
34 ${h.hidden('del_ip_id', ip.ip_id)}
34 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
35 ${h.submit('remove_', _('Delete'),id="remove_ip_%s" % ip.ip_id,
35 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
36 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
36 ${h.end_form()}
37 ${h.end_form()}
37 </td>
38 </td>
@@ -50,7 +51,7 b''
50 </div>
51 </div>
51
52
52 <div>
53 <div>
53 ${h.secure_form(url('edit_user_ips', user_id=c.user.user_id),method='put')}
54 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST')}
54 <div class="form">
55 <div class="form">
55 <!-- fields -->
56 <!-- fields -->
56 <div class="fields">
57 <div class="fields">
@@ -127,7 +127,7 b''
127 ## allowed_languages is defined in the users.py
127 ## allowed_languages is defined in the users.py
128 ## c.language comes from base.py as a default language
128 ## c.language comes from base.py as a default language
129 ${h.select('language', c.language, c.allowed_languages)}
129 ${h.select('language', c.language, c.allowed_languages)}
130 <p class="help-block">${h.literal(_('Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.url('rhodecode_translations'))})}</p>
130 <p class="help-block">${h.literal(_('Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
131 </div>
131 </div>
132 </div>
132 </div>
133 <div class="buttons">
133 <div class="buttons">
@@ -10,7 +10,7 b''
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_count">0</span>
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
@@ -38,9 +38,10 b''
38 <script type="text/javascript">
38 <script type="text/javascript">
39
39
40 $(document).ready(function() {
40 $(document).ready(function() {
41 var $userListTable = $('#user_list_table');
41
42
42 var getDatatableCount = function(){
43 var getDatatableCount = function(){
43 var table = $('#user_list_table').dataTable();
44 var table = $userListTable.dataTable();
44 var page = table.api().page.info();
45 var page = table.api().page.info();
45 var active = page.recordsDisplay;
46 var active = page.recordsDisplay;
46 var total = page.recordsTotal;
47 var total = page.recordsTotal;
@@ -50,7 +51,7 b''
50 };
51 };
51
52
52 // user list
53 // user list
53 $('#user_list_table').DataTable({
54 $userListTable.DataTable({
54 processing: true,
55 processing: true,
55 serverSide: true,
56 serverSide: true,
56 ajax: "${h.route_path('users_data')}",
57 ajax: "${h.route_path('users_data')}",
@@ -76,7 +77,7 b''
76 { data: {"_": "extern_type",
77 { data: {"_": "extern_type",
77 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
78 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
78 { data: {"_": "action",
79 { data: {"_": "action",
79 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
80 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
80 ],
81 ],
81 language: {
82 language: {
82 paginate: DEFAULT_GRID_PAGINATION,
83 paginate: DEFAULT_GRID_PAGINATION,
@@ -91,23 +92,23 b''
91 }
92 }
92 });
93 });
93
94
94 $('#user_list_table').on('xhr.dt', function(e, settings, json, xhr){
95 $userListTable.on('xhr.dt', function(e, settings, json, xhr){
95 $('#user_list_table').css('opacity', 1);
96 $userListTable.css('opacity', 1);
96 });
97 });
97
98
98 $('#user_list_table').on('preXhr.dt', function(e, settings, data){
99 $userListTable.on('preXhr.dt', function(e, settings, data){
99 $('#user_list_table').css('opacity', 0.3);
100 $userListTable.css('opacity', 0.3);
100 });
101 });
101
102
102 // refresh counters on draw
103 // refresh counters on draw
103 $('#user_list_table').on('draw.dt', function(){
104 $userListTable.on('draw.dt', function(){
104 getDatatableCount();
105 getDatatableCount();
105 });
106 });
106
107
107 // filter
108 // filter
108 $('#q_filter').on('keyup',
109 $('#q_filter').on('keyup',
109 $.debounce(250, function() {
110 $.debounce(250, function() {
110 $('#user_list_table').DataTable().search(
111 $userListTable.DataTable().search(
111 $('#q_filter').val()
112 $('#q_filter').val()
112 ).draw();
113 ).draw();
113 })
114 })
@@ -7,7 +7,7 b''
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
@@ -41,7 +41,7 b''
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
@@ -72,7 +72,7 b''
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
@@ -90,7 +90,7 b''
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${title}">
93 <dd title="${h.tooltip(title)}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
@@ -134,7 +134,7 b''
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.author_string(email)}">
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
@@ -186,7 +186,7 b''
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
@@ -225,7 +225,7 b''
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}">
231 <li class="${is_active('compare')}">
@@ -234,7 +234,7 b''
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
236 <li class="${is_active('showpullrequest')}">
237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 %if c.repository_pull_requests:
238 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
240 %endif
@@ -243,17 +243,19 b''
243 </li>
243 </li>
244 %endif
244 %endif
245 <li class="${is_active('options')}">
245 <li class="${is_active('options')}">
246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
246 <a class="menulink dropdown">
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 </a>
247 <ul class="submenu">
249 <ul class="submenu">
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 %endif
252 %endif
251 %if c.rhodecode_db_repo.fork:
253 %if c.rhodecode_db_repo.fork:
252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 ${_('Compare fork')}</a></li>
255 ${_('Compare fork')}</a></li>
254 %endif
256 %endif
255
257
256 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
257
259
258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 %if c.rhodecode_db_repo.locked[0]:
261 %if c.rhodecode_db_repo.locked[0]:
@@ -322,8 +324,9 b''
322 <div class="buttons">
324 <div class="buttons">
323 <div class="register">
325 <div class="register">
324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
326 %endif
328 %endif
329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
327 </div>
330 </div>
328 <div class="submit">
331 <div class="submit">
329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
@@ -342,7 +345,7 b''
342 <ol class="links">
345 <ol class="links">
343 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
344 % if c.rhodecode_user.personal_repo_group:
347 % if c.rhodecode_user.personal_repo_group:
345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
348 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
346 % endif
349 % endif
347 <li class="logout">
350 <li class="logout">
348 ${h.secure_form(h.route_path('logout'))}
351 ${h.secure_form(h.route_path('logout'))}
@@ -399,7 +402,7 b''
399 </a>
402 </a>
400 </li>
403 </li>
401 <li class="${is_active('search')}">
404 <li class="${is_active('search')}">
402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
405 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
403 <div class="menulabel">${_('Search')}</div>
406 <div class="menulabel">${_('Search')}</div>
404 </a>
407 </a>
405 </li>
408 </li>
@@ -503,7 +506,7 b''
503 query.callback({results: cachedData.results});
506 query.callback({results: cachedData.results});
504 } else {
507 } else {
505 $.ajax({
508 $.ajax({
506 url: "${h.url('goto_switcher_data')}",
509 url: pyroutes.url('goto_switcher_data'),
507 data: {'query': query.term},
510 data: {'query': query.term},
508 dataType: 'json',
511 dataType: 'json',
509 type: 'GET',
512 type: 'GET',
@@ -18,7 +18,7 b''
18 <td class="td-regex issue-tracker-example">${'(?:#)(?P<issue_id>\d+)'}</td>
18 <td class="td-regex issue-tracker-example">${'(?:#)(?P<issue_id>\d+)'}</td>
19 <td class="td-url issue-tracker-example">${'https://myissueserver.com/${repo}/issue/${issue_id}'}</td>
19 <td class="td-url issue-tracker-example">${'https://myissueserver.com/${repo}/issue/${issue_id}'}</td>
20 <td class="td-prefix issue-tracker-example">#</td>
20 <td class="td-prefix issue-tracker-example">#</td>
21 <td class="issue-tracker-example"><a href="https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html" target="_blank">${_('Read more')}</a></td>
21 <td class="issue-tracker-example"><a href="${h.route_url('enterprise_issue_tracker_settings')}" target="_blank">${_('Read more')}</a></td>
22 </tr>
22 </tr>
23 %for uid, entry in patterns:
23 %for uid, entry in patterns:
24 <tr id="entry_${uid}">
24 <tr id="entry_${uid}">
@@ -124,9 +124,9 b''
124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
125 <td class="td-componentname">
125 <td class="td-componentname">
126 %if section == 'repositories':
126 %if section == 'repositories':
127 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
127 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
128 %elif section == 'repositories_groups':
128 %elif section == 'repositories_groups':
129 <a href="${h.url('repo_group_home',group_name=k)}">${k}</a>
129 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
130 %elif section == 'user_groups':
130 %elif section == 'user_groups':
131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
132 ${k}
132 ${k}
@@ -146,7 +146,7 b''
146 %if actions:
146 %if actions:
147 <td class="td-action">
147 <td class="td-action">
148 %if section == 'repositories':
148 %if section == 'repositories':
149 <a href="${h.url('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
149 <a href="${h.route_path('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
150 %elif section == 'repositories_groups':
150 %elif section == 'repositories_groups':
151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 %elif section == 'user_groups':
152 %elif section == 'user_groups':
@@ -12,10 +12,15 b" if getattr(c, 'rhodecode_user', None) an"
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
14 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
14 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
15 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.name
15 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
16 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.lastname
16 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
17
17
18 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
18 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
19 c.template_context['default_user'] = {
20 'username': h.DEFAULT_USER,
21 'user_id': 1
22 }
23
19 %>
24 %>
20 <html xmlns="http://www.w3.org/1999/xhtml">
25 <html xmlns="http://www.w3.org/1999/xhtml">
21 <head>
26 <head>
@@ -79,7 +84,7 b" c.template_context['visual']['default_re"
79 // register templateContext to pass template variables to JS
84 // register templateContext to pass template variables to JS
80 var templateContext = ${h.json.dumps(c.template_context)|n};
85 var templateContext = ${h.json.dumps(c.template_context)|n};
81
86
82 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
87 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
83 var ASSET_URL = "${h.asset('')}";
88 var ASSET_URL = "${h.asset('')}";
84 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
89 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
85 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
90 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
@@ -130,6 +130,19 b''
130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
131 </div>
131 </div>
132 % endif
132 % endif
133
134 <div class="checkbox">
135 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
136 <label for="extensions_evolve${suffix}">${_('Enable evolve extension')}</label>
137 </div>
138 <div class="label">
139 % if display_globals:
140 <span class="help-block">${_('Enable evolve extension for all repositories.')}</span>
141 % else:
142 <span class="help-block">${_('Enable evolve extension for this repository.')}</span>
143 % endif
144 </div>
145
133 </div>
146 </div>
134 </div>
147 </div>
135 ## LABS for HG
148 ## LABS for HG
@@ -201,7 +214,7 b''
201 <div class="label">
214 <div class="label">
202 <span class="help-block">
215 <span class="help-block">
203 ${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}
216 ${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}
204 <a href="${h.url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
217 <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
205 </span>
218 </span>
206 </div>
219 </div>
207 </div>
220 </div>
@@ -10,7 +10,7 b''
10
10
11
11
12 <%def name="name(name, files_url)">
12 <%def name="name(name, files_url)">
13 <span class="tag booktag" title="${_('Bookmark %s') % (name,)}">
13 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % (name,))}">
14 <a href="${files_url}">
14 <a href="${files_url}">
15 <i class="icon-bookmark"></i>
15 <i class="icon-bookmark"></i>
16 ${name}
16 ${name}
@@ -23,11 +23,11 b''
23 </%def>
23 </%def>
24
24
25 <%def name="author(author)">
25 <%def name="author(author)">
26 <span class="tooltip" title="${author}">${h.link_to_user(author)}</span>
26 <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span>
27 </%def>
27 </%def>
28
28
29 <%def name="commit(message, commit_id, commit_idx)">
29 <%def name="commit(message, commit_id, commit_idx)">
30 <div>
30 <div>
31 <pre><a title="${message}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
31 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
32 </div>
32 </div>
33 </%def>
33 </%def>
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="name(name, files_url)">
11 <%def name="name(name, files_url)">
12 <span class="tag branchtag" title="${_('Branch %s') % (name,)}">
12 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % (name,))}">
13 <a href="${files_url}"><i class="icon-code-fork"></i>${name}
13 <a href="${files_url}"><i class="icon-code-fork"></i>${name}
14 %if name in c.closed_branches:
14 %if name in c.closed_branches:
15 [closed]
15 [closed]
@@ -23,11 +23,11 b''
23 </%def>
23 </%def>
24
24
25 <%def name="author(author)">
25 <%def name="author(author)">
26 <span class="tooltip" title="${author}">${h.link_to_user(author)}</span>
26 <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span>
27 </%def>
27 </%def>
28
28
29 <%def name="commit(message, commit_id, commit_idx)">
29 <%def name="commit(message, commit_id, commit_idx)">
30 <div>
30 <div>
31 <pre><a title="${message}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
31 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
32 </div>
32 </div>
33 </%def>
33 </%def>
@@ -37,7 +37,7 b''
37 %if c.rhodecode_db_repo.fork:
37 %if c.rhodecode_db_repo.fork:
38 <span>
38 <span>
39 <a id="compare_fork_button"
39 <a id="compare_fork_button"
40 title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}"
40 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
41 class="btn btn-small"
41 class="btn btn-small"
42 href="${h.url('compare_url',
42 href="${h.url('compare_url',
43 repo_name=c.rhodecode_db_repo.fork.repo_name,
43 repo_name=c.rhodecode_db_repo.fork.repo_name,
@@ -23,7 +23,7 b''
23 %if c.statuses.get(commit.raw_id):
23 %if c.statuses.get(commit.raw_id):
24 <div class="changeset-status-ico">
24 <div class="changeset-status-ico">
25 %if c.statuses.get(commit.raw_id)[2]:
25 %if c.statuses.get(commit.raw_id)[2]:
26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
28 </a>
28 </a>
29 %else:
29 %else:
@@ -46,8 +46,28 b''
46 <td class="td-hash">
46 <td class="td-hash">
47 <code>
47 <code>
48 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
48 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
49 <span class="commit_hash">${h.show_id(commit)}</span>
49 <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span>
50 </a>
50 </a>
51 % if hasattr(commit, 'phase'):
52 % if commit.phase != 'public':
53 <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span>
54 % endif
55 % endif
56
57 ## obsolete commits
58 % if hasattr(commit, 'obsolete'):
59 % if commit.obsolete:
60 <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
61 % endif
62 % endif
63
64 ## hidden commits
65 % if hasattr(commit, 'hidden'):
66 % if commit.hidden:
67 <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
68 % endif
69 % endif
70
51 </code>
71 </code>
52 </td>
72 </td>
53 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
73 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
@@ -80,7 +100,7 b''
80
100
81 ## branch
101 ## branch
82 %if commit.branch:
102 %if commit.branch:
83 <span class="tag branchtag" title="${_('Branch %s') % commit.branch}">
103 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
84 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
104 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
85 </span>
105 </span>
86 %endif
106 %endif
@@ -88,7 +108,7 b''
88 ## bookmarks
108 ## bookmarks
89 %if h.is_hg(c.rhodecode_repo):
109 %if h.is_hg(c.rhodecode_repo):
90 %for book in commit.bookmarks:
110 %for book in commit.bookmarks:
91 <span class="tag booktag" title="${_('Bookmark %s') % book}">
111 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
92 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
112 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
93 </span>
113 </span>
94 %endfor
114 %endfor
@@ -96,7 +116,7 b''
96
116
97 ## tags
117 ## tags
98 %for tag in commit.tags:
118 %for tag in commit.tags:
99 <span class="tag tagtag" title="${_('Tag %s') % tag}">
119 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
100 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
120 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
101 </span>
121 </span>
102 %endfor
122 %endfor
@@ -14,7 +14,7 b''
14 </td>
14 </td>
15 <td class="td-message">
15 <td class="td-message">
16 <div class="log-container">
16 <div class="log-container">
17 <div class="message_history" title="${cs.message}">
17 <div class="message_history" title="${h.tooltip(cs.message)}">
18 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
18 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
19 ${h.shorter(cs.message, 75)}
19 ${h.shorter(cs.message, 75)}
20 </a>
20 </a>
@@ -36,16 +36,36 b''
36 <h4>${_('Commit')}
36 <h4>${_('Commit')}
37 <code>
37 <code>
38 ${h.show_id(c.commit)}
38 ${h.show_id(c.commit)}
39 % if hasattr(c.commit, 'phase'):
40 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span>
41 % endif
42
43 ## obsolete commits
44 % if hasattr(c.commit, 'obsolete'):
45 % if c.commit.obsolete:
46 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
47 % endif
48 % endif
49
50 ## hidden commits
51 % if hasattr(c.commit, 'hidden'):
52 % if c.commit.hidden:
53 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
54 % endif
55 % endif
56
39 </code>
57 </code>
40 </h4>
58 </h4>
41 </span>
59 </span>
42 <span id="parent_link">
60 <div class="pull-right">
43 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
61 <span id="parent_link">
44 </span>
62 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
45 |
63 </span>
46 <span id="child_link">
64 |
47 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
65 <span id="child_link">
48 </span>
66 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
67 </span>
68 </div>
49 </div>
69 </div>
50
70
51 <div class="fieldset">
71 <div class="fieldset">
@@ -89,20 +109,20 b''
89
109
90 %if h.is_hg(c.rhodecode_repo):
110 %if h.is_hg(c.rhodecode_repo):
91 %for book in c.commit.bookmarks:
111 %for book in c.commit.bookmarks:
92 <span class="booktag tag" title="${_('Bookmark %s') % book}">
112 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
93 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
113 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
94 </span>
114 </span>
95 %endfor
115 %endfor
96 %endif
116 %endif
97
117
98 %for tag in c.commit.tags:
118 %for tag in c.commit.tags:
99 <span class="tagtag tag" title="${_('Tag %s') % tag}">
119 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
100 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
120 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
101 </span>
121 </span>
102 %endfor
122 %endfor
103
123
104 %if c.commit.branch:
124 %if c.commit.branch:
105 <span class="branchtag tag" title="${_('Branch %s') % c.commit.branch}">
125 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}">
106 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
126 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
107 </span>
127 </span>
108 %endif
128 %endif
@@ -1,4 +1,4 b''
1 ## this is a dummy html file for partial rendering on server and sending
1 ## this is a dummy html file for partial rendering on server and sending
2 ## generated output via ajax after comment submit
2 ## generated output via ajax after comment submit
3 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ${comment.comment_block(c.co, inline=c.inline_comment)}
4 ${comment.comment_block(c.co, inline=c.co.is_inline)}
@@ -61,7 +61,7 b''
61 % else:
61 % else:
62 <div class="status-change">
62 <div class="status-change">
63 % if comment.pull_request:
63 % if comment.pull_request:
64 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
64 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
65 % if comment.status_change:
65 % if comment.status_change:
66 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
66 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
67 % else:
67 % else:
@@ -92,7 +92,12 b''
92 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
92 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
93
93
94 <div class="comment-links-block">
94 <div class="comment-links-block">
95
95 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
96 <span class="tag authortag tooltip" title="${_('Pull request author')}">
97 ${_('author')}
98 </span>
99 |
100 % endif
96 % if inline:
101 % if inline:
97 <div class="pr-version-inline">
102 <div class="pr-version-inline">
98 <a href="${h.url.current(version=comment.pull_request_version_id, anchor='comment-{}'.format(comment.comment_id))}">
103 <a href="${h.url.current(version=comment.pull_request_version_id, anchor='comment-{}'.format(comment.comment_id))}">
@@ -117,7 +122,7 b''
117 </a>
122 </a>
118 % else:
123 % else:
119 <div title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
124 <div title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
120 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
125 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
121 <code class="pr-version-num">
126 <code class="pr-version-num">
122 ${'v{}'.format(pr_index_ver)}
127 ${'v{}'.format(pr_index_ver)}
123 </code>
128 </code>
@@ -153,7 +158,7 b''
153 </div>
158 </div>
154 </div>
159 </div>
155 <div class="text">
160 <div class="text">
156 ${comment.render(mentions=True)|n}
161 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
157 </div>
162 </div>
158
163
159 </div>
164 </div>
@@ -341,7 +346,7 b''
341 <div class="toolbar">
346 <div class="toolbar">
342 <div class="toolbar-text">
347 <div class="toolbar-text">
343 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
348 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
344 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
349 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
345 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
350 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
346 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
351 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
347 )
352 )
@@ -114,7 +114,7 b' collapse_all = len(diffset.files) > coll'
114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
115 %if commit:
115 %if commit:
116 <div class="pull-right">
116 <div class="pull-right">
117 <a class="btn tooltip" title="${_('Browse Files at revision {}').format(commit.raw_id)}" href="${h.url('files_home',repo_name=diffset.repo_name, revision=commit.raw_id, f_path='')}">
117 <a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h.url('files_home',repo_name=diffset.repo_name, revision=commit.raw_id, f_path='')}">
118 ${_('Browse Files')}
118 ${_('Browse Files')}
119 </a>
119 </a>
120 </div>
120 </div>
@@ -147,14 +147,14 b' collapse_all = len(diffset.files) > coll'
147 %for i, filediff in enumerate(diffset.files):
147 %for i, filediff in enumerate(diffset.files):
148
148
149 <%
149 <%
150 lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted']
150 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
151 over_lines_changed_limit = lines_changed > lines_changed_limit
151 over_lines_changed_limit = lines_changed > lines_changed_limit
152 %>
152 %>
153 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
153 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
154 <div
154 <div
155 class="filediff"
155 class="filediff"
156 data-f-path="${filediff['patch']['filename']}"
156 data-f-path="${filediff.patch['filename']}"
157 id="a_${h.FID('', filediff['patch']['filename'])}">
157 id="a_${h.FID('', filediff.patch['filename'])}">
158 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
158 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
159 <div class="filediff-collapse-indicator"></div>
159 <div class="filediff-collapse-indicator"></div>
160 ${diff_ops(filediff)}
160 ${diff_ops(filediff)}
@@ -162,7 +162,7 b' collapse_all = len(diffset.files) > coll'
162 ${diff_menu(filediff, use_comments=use_comments)}
162 ${diff_menu(filediff, use_comments=use_comments)}
163 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
163 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
164 %if not filediff.hunks:
164 %if not filediff.hunks:
165 %for op_id, op_text in filediff['patch']['stats']['ops'].items():
165 %for op_id, op_text in filediff.patch['stats']['ops'].items():
166 <tr>
166 <tr>
167 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
167 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
168 %if op_id == DEL_FILENODE:
168 %if op_id == DEL_FILENODE:
@@ -176,7 +176,7 b' collapse_all = len(diffset.files) > coll'
176 </tr>
176 </tr>
177 %endfor
177 %endfor
178 %endif
178 %endif
179 %if filediff.patch['is_limited_diff']:
179 %if filediff.limited_diff:
180 <tr class="cb-warning cb-collapser">
180 <tr class="cb-warning cb-collapser">
181 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
181 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
182 ${_('The requested commit is too big and content was truncated.')} <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
182 ${_('The requested commit is too big and content was truncated.')} <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
@@ -322,7 +322,6 b' collapse_all = len(diffset.files) > coll'
322
322
323 <%def name="diff_ops(filediff)">
323 <%def name="diff_ops(filediff)">
324 <%
324 <%
325 stats = filediff['patch']['stats']
326 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
325 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
327 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
326 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
328 %>
327 %>
@@ -330,9 +329,9 b' from rhodecode.lib.diffs import NEW_FILE'
330 %if filediff.source_file_path and filediff.target_file_path:
329 %if filediff.source_file_path and filediff.target_file_path:
331 %if filediff.source_file_path != filediff.target_file_path:
330 %if filediff.source_file_path != filediff.target_file_path:
332 ## file was renamed, or copied
331 ## file was renamed, or copied
333 %if RENAMED_FILENODE in stats['ops']:
332 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
334 <strong>${filediff.target_file_path}</strong><del>${filediff.source_file_path}</del>
333 <strong>${filediff.target_file_path}</strong><del>${filediff.source_file_path}</del>
335 %elif COPIED_FILENODE in stats['ops']:
334 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
336 <strong>${filediff.target_file_path}</strong>${filediff.source_file_path}
335 <strong>${filediff.target_file_path}</strong>${filediff.source_file_path}
337 %endif
336 %endif
338 %else:
337 %else:
@@ -350,19 +349,19 b' from rhodecode.lib.diffs import NEW_FILE'
350 %endif
349 %endif
351 </span>
350 </span>
352 <span class="pill-group" style="float: left">
351 <span class="pill-group" style="float: left">
353 %if filediff.patch['is_limited_diff']:
352 %if filediff.limited_diff:
354 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
353 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
355 %endif
354 %endif
356
355
357 %if RENAMED_FILENODE in stats['ops']:
356 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
358 <span class="pill" op="renamed">renamed</span>
357 <span class="pill" op="renamed">renamed</span>
359 %endif
358 %endif
360
359
361 %if COPIED_FILENODE in stats['ops']:
360 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
362 <span class="pill" op="copied">copied</span>
361 <span class="pill" op="copied">copied</span>
363 %endif
362 %endif
364
363
365 %if NEW_FILENODE in stats['ops']:
364 %if NEW_FILENODE in filediff.patch['stats']['ops']:
366 <span class="pill" op="created">created</span>
365 <span class="pill" op="created">created</span>
367 %if filediff['target_mode'].startswith('120'):
366 %if filediff['target_mode'].startswith('120'):
368 <span class="pill" op="symlink">symlink</span>
367 <span class="pill" op="symlink">symlink</span>
@@ -371,11 +370,11 b' from rhodecode.lib.diffs import NEW_FILE'
371 %endif
370 %endif
372 %endif
371 %endif
373
372
374 %if DEL_FILENODE in stats['ops']:
373 %if DEL_FILENODE in filediff.patch['stats']['ops']:
375 <span class="pill" op="removed">removed</span>
374 <span class="pill" op="removed">removed</span>
376 %endif
375 %endif
377
376
378 %if CHMOD_FILENODE in stats['ops']:
377 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
379 <span class="pill" op="mode">
378 <span class="pill" op="mode">
380 ${nice_mode(filediff['source_mode'])}${nice_mode(filediff['target_mode'])}
379 ${nice_mode(filediff['source_mode'])}${nice_mode(filediff['target_mode'])}
381 </span>
380 </span>
@@ -385,17 +384,17 b' from rhodecode.lib.diffs import NEW_FILE'
385 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}"></a>
384 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}"></a>
386
385
387 <span class="pill-group" style="float: right">
386 <span class="pill-group" style="float: right">
388 %if BIN_FILENODE in stats['ops']:
387 %if BIN_FILENODE in filediff.patch['stats']['ops']:
389 <span class="pill" op="binary">binary</span>
388 <span class="pill" op="binary">binary</span>
390 %if MOD_FILENODE in stats['ops']:
389 %if MOD_FILENODE in filediff.patch['stats']['ops']:
391 <span class="pill" op="modified">modified</span>
390 <span class="pill" op="modified">modified</span>
392 %endif
391 %endif
393 %endif
392 %endif
394 %if stats['added']:
393 %if filediff.patch['stats']['added']:
395 <span class="pill" op="added">+${stats['added']}</span>
394 <span class="pill" op="added">+${filediff.patch['stats']['added']}</span>
396 %endif
395 %endif
397 %if stats['deleted']:
396 %if filediff.patch['stats']['deleted']:
398 <span class="pill" op="deleted">-${stats['deleted']}</span>
397 <span class="pill" op="deleted">-${filediff.patch['stats']['deleted']}</span>
399 %endif
398 %endif
400 </span>
399 </span>
401
400
@@ -408,7 +407,7 b' from rhodecode.lib.diffs import NEW_FILE'
408 <%def name="diff_menu(filediff, use_comments=False)">
407 <%def name="diff_menu(filediff, use_comments=False)">
409 <div class="filediff-menu">
408 <div class="filediff-menu">
410 %if filediff.diffset.source_ref:
409 %if filediff.diffset.source_ref:
411 %if filediff.patch['operation'] in ['D', 'M']:
410 %if filediff.operation in ['D', 'M']:
412 <a
411 <a
413 class="tooltip"
412 class="tooltip"
414 href="${h.url('files_home',repo_name=filediff.diffset.repo_name,f_path=filediff.source_file_path,revision=filediff.diffset.source_ref)}"
413 href="${h.url('files_home',repo_name=filediff.diffset.repo_name,f_path=filediff.source_file_path,revision=filediff.diffset.source_ref)}"
@@ -424,7 +423,7 b' from rhodecode.lib.diffs import NEW_FILE'
424 ${_('Show file before')}
423 ${_('Show file before')}
425 </span> |
424 </span> |
426 %endif
425 %endif
427 %if filediff.patch['operation'] in ['A', 'M']:
426 %if filediff.operation in ['A', 'M']:
428 <a
427 <a
429 class="tooltip"
428 class="tooltip"
430 href="${h.url('files_home',repo_name=filediff.diffset.source_repo_name,f_path=filediff.target_file_path,revision=filediff.diffset.target_ref)}"
429 href="${h.url('files_home',repo_name=filediff.diffset.source_repo_name,f_path=filediff.target_file_path,revision=filediff.diffset.target_ref)}"
@@ -460,10 +459,10 b' from rhodecode.lib.diffs import NEW_FILE'
460
459
461 ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks)
460 ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks)
462 %if hasattr(c, 'ignorews_url'):
461 %if hasattr(c, 'ignorews_url'):
463 ${c.ignorews_url(request.GET, h.FID('', filediff['patch']['filename']))}
462 ${c.ignorews_url(request.GET, h.FID('', filediff.patch['filename']))}
464 %endif
463 %endif
465 %if hasattr(c, 'context_url'):
464 %if hasattr(c, 'context_url'):
466 ${c.context_url(request.GET, h.FID('', filediff['patch']['filename']))}
465 ${c.context_url(request.GET, h.FID('', filediff.patch['filename']))}
467 %endif
466 %endif
468
467
469 %if use_comments:
468 %if use_comments:
@@ -503,9 +502,9 b' from rhodecode.lib.diffs import NEW_FILE'
503 <%
502 <%
504 old_line_anchor, new_line_anchor = None, None
503 old_line_anchor, new_line_anchor = None, None
505 if line.original.lineno:
504 if line.original.lineno:
506 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, line.original.lineno, 'o')
505 old_line_anchor = diff_line_anchor(hunk.source_file_path, line.original.lineno, 'o')
507 if line.modified.lineno:
506 if line.modified.lineno:
508 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, line.modified.lineno, 'n')
507 new_line_anchor = diff_line_anchor(hunk.target_file_path, line.modified.lineno, 'n')
509 %>
508 %>
510
509
511 <tr class="cb-line">
510 <tr class="cb-line">
@@ -579,9 +578,9 b' from rhodecode.lib.diffs import NEW_FILE'
579 <%
578 <%
580 old_line_anchor, new_line_anchor = None, None
579 old_line_anchor, new_line_anchor = None, None
581 if old_line_no:
580 if old_line_no:
582 old_line_anchor = diff_line_anchor(hunk.filediff.source_file_path, old_line_no, 'o')
581 old_line_anchor = diff_line_anchor(hunk.source_file_path, old_line_no, 'o')
583 if new_line_no:
582 if new_line_no:
584 new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, new_line_no, 'n')
583 new_line_anchor = diff_line_anchor(hunk.target_file_path, new_line_no, 'n')
585 %>
584 %>
586 <tr class="cb-line">
585 <tr class="cb-line">
587 <td class="cb-data ${action_class(action)}">
586 <td class="cb-data ${action_class(action)}">
@@ -640,13 +639,13 b' from rhodecode.lib.diffs import NEW_FILE'
640
639
641 <a
640 <a
642 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
641 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
643 title="${_('View side by side')}"
642 title="${h.tooltip(_('View side by side'))}"
644 href="${h.url_replace(diffmode='sideside')}">
643 href="${h.url_replace(diffmode='sideside')}">
645 <span>${_('Side by Side')}</span>
644 <span>${_('Side by Side')}</span>
646 </a>
645 </a>
647 <a
646 <a
648 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
647 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
649 title="${_('View unified')}" href="${h.url_replace(diffmode='unified')}">
648 title="${h.tooltip(_('View unified'))}" href="${h.url_replace(diffmode='unified')}">
650 <span>${_('Unified')}</span>
649 <span>${_('Unified')}</span>
651 </a>
650 </a>
652 </div>
651 </div>
@@ -5,6 +5,7 b''
5 from rhodecode.lib.codeblocks import render_tokenstream
5 from rhodecode.lib.codeblocks import render_tokenstream
6 # avoid module lookup for performance
6 # avoid module lookup for performance
7 html_escape = h.html_escape
7 html_escape = h.html_escape
8 tooltip = h.tooltip
8 %>
9 %>
9 <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}"
10 <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}"
10 %if annotation:
11 %if annotation:
@@ -15,13 +16,13 b''
15 % if annotation:
16 % if annotation:
16 % if show_annotation:
17 % if show_annotation:
17 <td class="cb-annotate-info tooltip"
18 <td class="cb-annotate-info tooltip"
18 title="Author: ${annotation.author | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}"
19 title="Author: ${tooltip(annotation.author) | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}"
19 >
20 >
20 ${h.gravatar_with_user(annotation.author, 16) | n}
21 ${h.gravatar_with_user(annotation.author, 16) | n}
21 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
22 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
22 </td>
23 </td>
23 <td class="cb-annotate-message-spacer">
24 <td class="cb-annotate-message-spacer">
24 <a class="tooltip" href="#show-previous-annotation" onclick="return annotationController.previousAnnotation('${annotation.raw_id}', '${c.f_path}')" title="${_('view annotation from before this change')}">
25 <a class="tooltip" href="#show-previous-annotation" onclick="return annotationController.previousAnnotation('${annotation.raw_id}', '${c.f_path}')" title="${tooltip(_('view annotation from before this change'))}">
25 <i class="icon-left"></i>
26 <i class="icon-left"></i>
26 </a>
27 </a>
27 </td>
28 </td>
@@ -6,6 +6,7 b''
6 <a href="${h.url('changeset_home', repo_name=c.repo_name, revision=c.ancestor)}">
6 <a href="${h.url('changeset_home', repo_name=c.repo_name, revision=c.ancestor)}">
7 ${h.short_id(c.ancestor)}
7 ${h.short_id(c.ancestor)}
8 </a>. ${_('Compare was calculated based on this shared commit.')}
8 </a>. ${_('Compare was calculated based on this shared commit.')}
9 <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}">
9 </div>
10 </div>
10 %endif
11 %endif
11
12
@@ -9,7 +9,7 b''
9 <div class="menu_items_container hidden">
9 <div class="menu_items_container hidden">
10 <ul class="menu_items">
10 <ul class="menu_items">
11 <li>
11 <li>
12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
13 <span>${_('Summary')}</span>
13 <span>${_('Summary')}</span>
14 </a>
14 </a>
15 </li>
15 </li>
@@ -42,7 +42,7 b''
42 %>
42 %>
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 ##NAME
44 ##NAME
45 <a href="${h.url('edit_repo' if admin else 'summary_home',repo_name=name)}">
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
46
46
47 ##TYPE OF REPO
47 ##TYPE OF REPO
48 %if h.is_hg(rtype):
48 %if h.is_hg(rtype):
@@ -64,7 +64,7 b''
64 ${get_name(name)}
64 ${get_name(name)}
65 </a>
65 </a>
66 %if fork_of:
66 %if fork_of:
67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 %endif
68 %endif
69 %if rstate == 'repo_state_pending':
69 %if rstate == 'repo_state_pending':
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
@@ -92,22 +92,22 b''
92
92
93 <%def name="rss(name)">
93 <%def name="rss(name)">
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
95 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
96 %else:
96 %else:
97 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 %endif
98 %endif
99 </%def>
99 </%def>
100
100
101 <%def name="atom(name)">
101 <%def name="atom(name)">
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
103 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
104 %else:
104 %else:
105 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 %endif
106 %endif
107 </%def>
107 </%def>
108
108
109 <%def name="user_gravatar(email, size=16)">
109 <%def name="user_gravatar(email, size=16)">
110 <div class="rc-user tooltip" title="${h.author_string(email)}">
110 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
111 ${base.gravatar(email, 16)}
111 ${base.gravatar(email, 16)}
112 </div>
112 </div>
113 </%def>
113 </%def>
@@ -115,11 +115,11 b''
115 <%def name="repo_actions(repo_name, super_user=True)">
115 <%def name="repo_actions(repo_name, super_user=True)">
116 <div>
116 <div>
117 <div class="grid_edit">
117 <div class="grid_edit">
118 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
118 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 <i class="icon-pencil"></i>Edit</a>
119 <i class="icon-pencil"></i>Edit</a>
120 </div>
120 </div>
121 <div class="grid_delete">
121 <div class="grid_delete">
122 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST')}
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 ${h.end_form()}
125 ${h.end_form()}
@@ -134,7 +134,7 b''
134 %elif repo_state == 'repo_state_created':
134 %elif repo_state == 'repo_state_created':
135 <div class="tag tag1">${_('Created')}</div>
135 <div class="tag tag1">${_('Created')}</div>
136 %else:
136 %else:
137 <div class="tag alert2" title="${repo_state}">invalid</div>
137 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
138 %endif
138 %endif
139 </div>
139 </div>
140 </%def>
140 </%def>
@@ -146,7 +146,7 b''
146 <div class="menu_items_container hidden">
146 <div class="menu_items_container hidden">
147 <ul class="menu_items">
147 <ul class="menu_items">
148 <li>
148 <li>
149 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
149 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
150 <span class="icon">
150 <span class="icon">
151 <i class="icon-file-text"></i>
151 <i class="icon-file-text"></i>
152 </span>
152 </span>
@@ -160,7 +160,7 b''
160
160
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 <div>
162 <div>
163 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
163 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 %if children_groups:
165 %if children_groups:
166 ${h.literal(' &raquo; '.join(children_groups))}
166 ${h.literal(' &raquo; '.join(children_groups))}
@@ -282,7 +282,7 b''
282
282
283 <%def name="pullrequest_target_repo(repo_name)">
283 <%def name="pullrequest_target_repo(repo_name)">
284 <div class="truncate">
284 <div class="truncate">
285 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
285 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
286 </div>
286 </div>
287 </%def>
287 </%def>
288 <%def name="pullrequest_status(status)">
288 <%def name="pullrequest_status(status)">
@@ -299,7 +299,7 b''
299 </%def>
299 </%def>
300
300
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
302 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 % if short:
303 % if short:
304 #${pull_request_id}
304 #${pull_request_id}
305 % else:
305 % else:
@@ -886,7 +886,7 b''
886 AJAX_COMMENT_DELETE_URL = "/rhodecode-momentum/pull-request-comment/__COMMENT_ID__/delete";
886 AJAX_COMMENT_DELETE_URL = "/rhodecode-momentum/pull-request-comment/__COMMENT_ID__/delete";
887
887
888 $(function(){
888 $(function(){
889 ReviewerAutoComplete('user');
889 ReviewerAutoComplete('#user');
890
890
891 $('#open_edit_reviewers').on('click', function(e){
891 $('#open_edit_reviewers').on('click', function(e){
892 $('#open_edit_reviewers').hide();
892 $('#open_edit_reviewers').hide();
@@ -949,9 +949,6 b''
949 updateCommits("rhodecode-momentum", "720");
949 updateCommits("rhodecode-momentum", "720");
950 });
950 });
951
951
952 $('#close_pull_request').on('click', function(e){
953 closePullRequest("rhodecode-momentum", "720");
954 });
955 })
952 })
956 </script>
953 </script>
957
954
@@ -3,7 +3,7 b''
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 ## EMAIL SUBJECT
5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 <%
7 <%
8 data = {
8 data = {
9 'user': h.person(user),
9 'user': h.person(user),
@@ -1,7 +1,7 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode test email: ${h.format_date(date)}
5 RhodeCode test email: ${h.format_date(date)}
6 </%def>
6 </%def>
7
7
@@ -1,7 +1,7 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 </%def>
5 </%def>
6
6
7
7
@@ -1,7 +1,7 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode Password reset
5 RhodeCode Password reset
6 </%def>
6 </%def>
7
7
@@ -1,7 +1,7 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 Your new RhodeCode password
5 Your new RhodeCode password
6 </%def>
6 </%def>
7
7
@@ -3,7 +3,7 b''
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 ## EMAIL SUBJECT
5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 <%
7 <%
8 data = {
8 data = {
9 'user': h.person(user),
9 'user': h.person(user),
@@ -2,7 +2,7 b''
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
4
5 <%def name="subject()" filter="n,trim">
5 <%def name="subject()" filter="n,trim,whitespace_filter">
6 <%
6 <%
7 data = {
7 data = {
8 'user': h.person(user),
8 'user': h.person(user),
@@ -1,7 +1,7 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 Test "Subject" ${_('hello "world"')|n}
5 Test "Subject" ${_('hello "world"')|n}
6 </%def>
6 </%def>
7
7
@@ -1,7 +1,7 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 RhodeCode new user registration: ${user.username}
5 RhodeCode new user registration: ${user.username}
6 </%def>
6 </%def>
7
7
@@ -10,18 +10,18 b' RhodeCode new user registration: ${user.'
10 A new user `${user.username}` has registered on ${h.format_date(date)}
10 A new user `${user.username}` has registered on ${h.format_date(date)}
11
11
12 - Username: ${user.username}
12 - Username: ${user.username}
13 - Full Name: ${user.firstname} ${user.lastname}
13 - Full Name: ${user.first_name} ${user.last_name}
14 - Email: ${user.email}
14 - Email: ${user.email}
15 - Profile link: ${h.route_path('user_profile', username=user.username, qualified=True)}
15 - Profile link: ${h.route_url('user_profile', username=user.username)}
16
16
17 ${self.plaintext_footer()}
17 ${self.plaintext_footer()}
18 </%def>
18 </%def>
19
19
20 ## BODY GOES BELOW
20 ## BODY GOES BELOW
21 <table style="text-align:left;vertical-align:middle;">
21 <table style="text-align:left;vertical-align:middle;">
22 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.route_path('user_profile', username=user.username, qualified=True)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr>
22 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.route_url('user_profile', username=user.username)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr>
23 <tr><td style="padding-right:20px;padding-top:20px;">${_('Username')}</td><td style="line-height:1;padding-top:20px;"><img style="margin-bottom:-5px;text-align:left;border:1px solid #dbd9da" src="${h.gravatar_url(user.email, 16)}" height="16" width="16">&nbsp;${user.username}</td></tr>
23 <tr><td style="padding-right:20px;padding-top:20px;">${_('Username')}</td><td style="line-height:1;padding-top:20px;"><img style="margin-bottom:-5px;text-align:left;border:1px solid #dbd9da" src="${h.gravatar_url(user.email, 16)}" height="16" width="16">&nbsp;${user.username}</td></tr>
24 <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.firstname} ${user.lastname}</td></tr>
24 <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.first_name} ${user.last_name}</td></tr>
25 <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr>
25 <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr>
26 <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.route_path('user_profile', username=user.username, qualified=True)}">${h.route_path('user_profile', username=user.username, qualified=True)}</a></td></tr>
26 <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.route_url('user_profile', username=user.username)}">${h.route_url('user_profile', username=user.username)}</a></td></tr>
27 </table> No newline at end of file
27 </table>
@@ -24,11 +24,10 b''
24 <script type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
24 <script type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
25 </head>
25 </head>
26 <body>
26 <body>
27 <% messages = h.flash.pop_messages() %>
28
27
29 <div class="wrapper error_page">
28 <div class="wrapper error_page">
30 <div class="sidebar">
29 <div class="sidebar">
31 <a href="${h.url('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
30 <a href="${h.route_path('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
32 </div>
31 </div>
33 <div class="main-content">
32 <div class="main-content">
34 <h1>
33 <h1>
@@ -37,8 +36,8 b''
37 </span><br/>
36 </span><br/>
38 ${c.error_message} | <span class="error_message">${c.error_explanation}</span>
37 ${c.error_message} | <span class="error_message">${c.error_explanation}</span>
39 </h1>
38 </h1>
40 % if messages:
39 % if c.messages:
41 % for message in messages:
40 % for message in c.messages:
42 <div class="alert alert-${message.category}">${message}</div>
41 <div class="alert alert-${message.category}">${message}</div>
43 % endfor
42 % endfor
44 % endif
43 % endif
@@ -62,12 +61,12 b''
62 <div class="inner-column">
61 <div class="inner-column">
63 <h4>Support</h4>
62 <h4>Support</h4>
64 <p>For support, go to <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>.
63 <p>For support, go to <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>.
65 It may be useful to include your log file; see the log file locations <a href="${h.url('enterprise_log_file_locations')}">here</a>.
64 It may be useful to include your log file; see the log file locations <a href="${h.route_url('enterprise_log_file_locations')}">here</a>.
66 </p>
65 </p>
67 </div>
66 </div>
68 <div class="inner-column">
67 <div class="inner-column">
69 <h4>Documentation</h4>
68 <h4>Documentation</h4>
70 <p>For more information, see <a href="${h.url('enterprise_docs')}">docs.rhodecode.com</a>.</p>
69 <p>For more information, see <a href="${h.route_url('enterprise_docs')}">docs.rhodecode.com</a>.</p>
71 </div>
70 </div>
72 </div>
71 </div>
73 </div>
72 </div>
@@ -7,20 +7,20 b''
7
7
8 %if h.is_hg(c.rhodecode_repo):
8 %if h.is_hg(c.rhodecode_repo):
9 %for book in commit.bookmarks:
9 %for book in commit.bookmarks:
10 <span class="booktag tag" title="${_('Bookmark %s') % book}">
10 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
11 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
11 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
12 </span>
12 </span>
13 %endfor
13 %endfor
14 %endif
14 %endif
15
15
16 %for tag in commit.tags:
16 %for tag in commit.tags:
17 <span class="tagtag tag" title="${_('Tag %s') % tag}">
17 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
18 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
18 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
19 </span>
19 </span>
20 %endfor
20 %endfor
21
21
22 %if commit.branch:
22 %if commit.branch:
23 <span class="branchtag tag" title="${_('Branch %s') % commit.branch}">
23 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
24 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
24 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
25 </span>
25 </span>
26 %endif
26 %endif
@@ -15,7 +15,7 b''
15 <ul class="sidebar-right-content">
15 <ul class="sidebar-right-content">
16 % for email, user in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]):
16 % for email, user in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]):
17 <li class="file_author">
17 <li class="file_author">
18 <div class="rc-user tooltip" title="${h.author_string(email)}">
18 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
19 ${base.gravatar(email, 16)}
19 ${base.gravatar(email, 16)}
20 <span class="user">${h.link_to_user(user)}</span>
20 <span class="user">${h.link_to_user(user)}</span>
21 </div>
21 </div>
@@ -56,11 +56,17 b''
56 </div>
56 </div>
57 <div id="upload_file_container" class="fieldset" style="display: none;">
57 <div id="upload_file_container" class="fieldset" style="display: none;">
58 <div class="filename-label left-label">
58 <div class="filename-label left-label">
59 ${_('Filename')}:
60 </div>
61 <div class="right-content">
62 <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}">
63 </div>
64 <div class="filename-label left-label file-upload-label">
59 ${_('Upload file')}:
65 ${_('Upload file')}:
60 </div>
66 </div>
61 <div class="right-content file-upload-input">
67 <div class="right-content file-upload-input">
62 <label for="upload_file" class="btn btn-default">Browse</label>
68 <label for="upload_file" class="btn btn-default">Browse</label>
63 <span id="selected-file">${_('No file selected')}</span>
69
64 <input type="file" name="upload_file" id="upload_file">
70 <input type="file" name="upload_file" id="upload_file">
65 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
71 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
66 </div>
72 </div>
@@ -155,10 +161,8 b''
155 });
161 });
156
162
157 $('#upload_file').on('change', function() {
163 $('#upload_file').on('change', function() {
158 if (detectIE() && detectIE() <= 9) {
164 if (this.files && this.files[0]) {
159 if (this.files && this.files[0]) {
165 $('#filename_upload').val(this.files[0].name);
160 $('#selected-file').html(this.files[0].name);
161 }
162 }
166 }
163 });
167 });
164
168
@@ -58,14 +58,14 b''
58 </td>
58 </td>
59 <td class="td-hash" data-attr-name="commit_id">
59 <td class="td-hash" data-attr-name="commit_id">
60 % if c.full_load:
60 % if c.full_load:
61 <div class="tooltip" title="${node.last_commit.message}">
61 <div class="tooltip" title="${h.tooltip(node.last_commit.message)}">
62 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre>
62 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre>
63 </div>
63 </div>
64 % endif
64 % endif
65 </td>
65 </td>
66 <td class="td-user" data-attr-name="author">
66 <td class="td-user" data-attr-name="author">
67 % if c.full_load:
67 % if c.full_load:
68 <span data-author="${node.last_commit.author}" title="${node.last_commit.author}">${h.gravatar_with_user(node.last_commit.author)|n}</span>
68 <span data-author="${node.last_commit.author}" title="${h.tooltip(node.last_commit.author)}">${h.gravatar_with_user(node.last_commit.author)|n}</span>
69 % endif
69 % endif
70 </td>
70 </td>
71 %else:
71 %else:
@@ -16,7 +16,7 b''
16 ${base.gravatar_with_user(f.user.email, 16)}
16 ${base.gravatar_with_user(f.user.email, 16)}
17 </td>
17 </td>
18 <td class="td-componentname">
18 <td class="td-componentname">
19 ${h.link_to(f.repo_name,h.url('summary_home',repo_name=f.repo_name))}
19 ${h.link_to(f.repo_name,h.route_path('repo_summary',repo_name=f.repo_name))}
20 </td>
20 </td>
21 <td class="td-description">
21 <td class="td-description">
22 <div class="truncate">${f.description}</div>
22 <div class="truncate">${f.description}</div>
@@ -25,7 +25,7 b''
25 ${h.age_component(f.created_on, time_is_local=True)}
25 ${h.age_component(f.created_on, time_is_local=True)}
26 </td>
26 </td>
27 <td class="td-compare">
27 <td class="td-compare">
28 <a title="${_('Compare fork with %s' % c.repo_name)}"
28 <a title="${h.tooltip(_('Compare fork with %s' % c.repo_name))}"
29 href="${h.url('compare_url',repo_name=c.repo_name, source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1],target_repo=f.repo_name,target_ref_type=c.rhodecode_db_repo.landing_rev[0],target_ref=c.rhodecode_db_repo.landing_rev[1], merge=1)}"
29 href="${h.url('compare_url',repo_name=c.repo_name, source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1],target_repo=f.repo_name,target_ref_type=c.rhodecode_db_repo.landing_rev[0],target_ref=c.rhodecode_db_repo.landing_rev[1], merge=1)}"
30 class="btn-link"><i class="icon-loop"></i> ${_('Compare fork')}</a>
30 class="btn-link"><i class="icon-loop"></i> ${_('Compare fork')}</a>
31 </td>
31 </td>
@@ -21,7 +21,7 b''
21 tal:attributes="label item.label">
21 tal:attributes="label item.label">
22 <option tal:repeat="(value, description) item.options"
22 <option tal:repeat="(value, description) item.options"
23 tal:attributes="
23 tal:attributes="
24 selected (multiple and value in list(map(unicode, cstruct)) or value == list(map(unicode, cstruct))) and 'selected';
24 selected python:field.widget.get_select_value(cstruct, value);
25 class css_class;
25 class css_class;
26 label field.widget.long_label_generator and description;
26 label field.widget.long_label_generator and description;
27 value value"
27 value value"
@@ -29,7 +29,7 b''
29 </optgroup>
29 </optgroup>
30 <option tal:condition="not isinstance(item, optgroup_class)"
30 <option tal:condition="not isinstance(item, optgroup_class)"
31 tal:attributes="
31 tal:attributes="
32 selected (multiple and item[0] in list(map(unicode, cstruct)) or item[0] == unicode(cstruct)) and 'selected';
32 selected python:field.widget.get_select_value(cstruct, item[0]);
33 class css_class;
33 class css_class;
34 value item[0]">${item[1]}</option>
34 value item[0]">${item[1]}</option>
35 </tal:loop>
35 </tal:loop>
@@ -37,7 +37,7 b''
37
37
38 <script type="text/javascript">
38 <script type="text/javascript">
39 deform.addCallback(
39 deform.addCallback(
40 '${field.oid}',
40 '${oid}',
41 function(oid) {
41 function(oid) {
42 $('#' + oid).select2({
42 $('#' + oid).select2({
43 containerCssClass: 'form-control drop-menu',
43 containerCssClass: 'form-control drop-menu',
@@ -32,21 +32,21 b''
32 tal:attributes="prototype prototype"/>
32 tal:attributes="prototype prototype"/>
33
33
34 <div class="panel panel-default">
34 <div class="panel panel-default">
35 <div class="panel-heading">${title}</div>
36 <div class="panel-body">
35 <div class="panel-body">
37
36
38 <div class="deform-seq-container"
37 <div class="deform-seq-container"
39 id="${oid}-orderable">
38 id="${oid}-orderable">
40 <div tal:define="subfields [ x[1] for x in subfields ]"
39 <div tal:define="subfields [ x[1] for x in subfields ]"
41 tal:repeat="subfield subfields"
40 tal:repeat="subfield subfields"
42 tal:replace="structure subfield.render_template(item_tmpl,
41 tal:replace="structure subfield.render_template(item_tmpl,parent=field)" />
43 parent=field)" />
44 <span class="deform-insert-before"
42 <span class="deform-insert-before"
45 tal:attributes="
43 tal:attributes="
46 min_len min_len;
44 min_len min_len;
47 max_len max_len;
45 max_len max_len;
48 now_len now_len;
46 now_len now_len;
49 orderable orderable;"></span>
47 orderable orderable;">
48
49 </span>
50 </div>
50 </div>
51
51
52 <div style="clear: both"></div>
52 <div style="clear: both"></div>
@@ -78,12 +78,12 b''
78 placeholder: '<span class="glyphicon glyphicon-arrow-right placeholder"></span>',
78 placeholder: '<span class="glyphicon glyphicon-arrow-right placeholder"></span>',
79 onDragStart: function ($item, container, _super) {
79 onDragStart: function ($item, container, _super) {
80 var offset = $item.offset(),
80 var offset = $item.offset(),
81 pointer = container.rootGroup.pointer
81 pointer = container.rootGroup.pointer;
82
82
83 adjustment = {
83 adjustment = {
84 left: pointer.left - offset.left,
84 left: pointer.left - offset.left,
85 top: pointer.top - offset.top
85 top: pointer.top - offset.top
86 }
86 };
87
87
88 _super($item, container)
88 _super($item, container)
89 },
89 },
@@ -6,6 +6,7 b''
6 oid oid|field.oid"
6 oid oid|field.oid"
7 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
7 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
8 i18n:domain="deform">
8 i18n:domain="deform">
9
9 <div class="deform-seq-item-group">
10 <div class="deform-seq-item-group">
10 <span tal:replace="structure field.serialize(cstruct)"/>
11 <span tal:replace="structure field.serialize(cstruct)"/>
11 <tal:errors condition="field.error and not hidden"
12 <tal:errors condition="field.error and not hidden"
@@ -17,13 +18,8 b''
17 i18n:translate="">${msg}</p>
18 i18n:translate="">${msg}</p>
18 </tal:errors>
19 </tal:errors>
19 </div>
20 </div>
21
20 <div class="deform-seq-item-handle" style="padding:0">
22 <div class="deform-seq-item-handle" style="padding:0">
21 <!-- sequence_item -->
22 <span class="deform-order-button close glyphicon glyphicon-resize-vertical"
23 id="${oid}-order"
24 tal:condition="not hidden"
25 title="Reorder (via drag and drop)"
26 i18n:attributes="title"></span>
27 <a class="deform-close-button close"
23 <a class="deform-close-button close"
28 id="${oid}-close"
24 id="${oid}-close"
29 tal:condition="not field.widget.hidden"
25 tal:condition="not field.widget.hidden"
@@ -31,5 +27,5 b''
31 i18n:attributes="title"
27 i18n:attributes="title"
32 onclick="javascript:deform.removeSequenceItem(this);">&times;</a>
28 onclick="javascript:deform.removeSequenceItem(this);">&times;</a>
33 </div>
29 </div>
34 <!-- /sequence_item -->
30
35 </div>
31 </div>
@@ -10,9 +10,9 b''
10
10
11 <%def name="breadcrumbs()">
11 <%def name="breadcrumbs()">
12 <span class="groups_breadcrumbs">
12 <span class="groups_breadcrumbs">
13 ${h.link_to(_(u'Home'),h.url('/'))}
13 ${h.link_to(_(u'Home'), h.route_path('home'))}
14 %if c.repo_group.parent_group:
14 %if c.repo_group.parent_group:
15 &raquo; ${h.link_to(c.repo_group.parent_group.name,h.url('repo_group_home',group_name=c.repo_group.parent_group.group_name))}
15 &raquo; ${h.link_to(c.repo_group.parent_group.name, h.route_path('repo_group_home', repo_group_name=c.repo_group.parent_group.group_name))}
16 %endif
16 %endif
17 &raquo; ${c.repo_group.name}
17 &raquo; ${c.repo_group.name}
18 </span>
18 </span>
@@ -20,7 +20,7 b''
20 <span class="journal_repo_name">
20 <span class="journal_repo_name">
21 %if entry.repository is not None:
21 %if entry.repository is not None:
22 ${h.link_to(entry.repository.repo_name,
22 ${h.link_to(entry.repository.repo_name,
23 h.url('summary_home',repo_name=entry.repository.repo_name))}
23 h.route_path('repo_summary',repo_name=entry.repository.repo_name))}
24 %else:
24 %else:
25 ${entry.repository_name}
25 ${entry.repository_name}
26 %endif
26 %endif
@@ -14,7 +14,7 b''
14 <div id="header-inner" class="title">
14 <div id="header-inner" class="title">
15 <div id="logo">
15 <div id="logo">
16 <div class="logo-wrapper">
16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 </div>
18 </div>
19 %if c.rhodecode_name:
19 %if c.rhodecode_name:
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -14,7 +14,7 b''
14 <div id="header-inner" class="title">
14 <div id="header-inner" class="title">
15 <div id="logo">
15 <div id="logo">
16 <div class="logo-wrapper">
16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 </div>
18 </div>
19 %if c.rhodecode_name:
19 %if c.rhodecode_name:
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -20,15 +20,22 b''
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.repo_page_title(c.rhodecode_db_repo)}
22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 ${self.breadcrumbs()}
24 </div>
23 </div>
25
24
26 ${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
25 ${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
26
27 ${self.breadcrumbs()}
28
27 <div class="box pr-summary">
29 <div class="box pr-summary">
28
30
29 <div class="summary-details block-left">
31 <div class="summary-details block-left">
30
32
31 <div class="form">
33
34 <div class="pr-details-title">
35 ${_('Pull request summary')}
36 </div>
37
38 <div class="form" style="padding-top: 10px">
32 <!-- fields -->
39 <!-- fields -->
33
40
34 <div class="fields" >
41 <div class="fields" >
@@ -62,7 +69,7 b''
62
69
63 ##ORG
70 ##ORG
64 <div class="content">
71 <div class="content">
65 <strong>${_('Origin repository')}:</strong>
72 <strong>${_('Source repository')}:</strong>
66 ${c.rhodecode_db_repo.description}
73 ${c.rhodecode_db_repo.description}
67 </div>
74 </div>
68 <div class="content">
75 <div class="content">
@@ -102,6 +109,31 b''
102 </div>
109 </div>
103 </div>
110 </div>
104 <div>
111 <div>
112 ## AUTHOR
113 <div class="reviewers-title block-right">
114 <div class="pr-details-title">
115 ${_('Author of this pull request')}
116 </div>
117 </div>
118 <div class="block-right pr-details-content reviewers">
119 <ul class="group_members">
120 <li>
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 </li>
123 </ul>
124 </div>
125
126 ## REVIEW RULES
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 <div class="pr-details-title">
129 ${_('Reviewer rules')}
130 </div>
131 <div class="pr-reviewer-rules">
132 ## review rules will be appended here, by default reviewers logic
133 </div>
134 </div>
135
136 ## REVIEWERS
105 <div class="reviewers-title block-right">
137 <div class="reviewers-title block-right">
106 <div class="pr-details-title">
138 <div class="pr-details-title">
107 ${_('Pull request reviewers')}
139 ${_('Pull request reviewers')}
@@ -115,7 +147,7 b''
115 <input type="hidden" name="__end__" value="review_members:sequence">
147 <input type="hidden" name="__end__" value="review_members:sequence">
116 <div id="add_reviewer_input" class='ac'>
148 <div id="add_reviewer_input" class='ac'>
117 <div class="reviewer_ac">
149 <div class="reviewer_ac">
118 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
119 <div id="reviewers_container"></div>
151 <div id="reviewers_container"></div>
120 </div>
152 </div>
121 </div>
153 </div>
@@ -137,7 +169,6 b''
137 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
138 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
139 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
140 var targetRepoName = '${c.repo_name}';
141
172
142 var $pullRequestForm = $('#pull_request_form');
173 var $pullRequestForm = $('#pull_request_form');
143 var $sourceRepo = $('#source_repo', $pullRequestForm);
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
@@ -145,6 +176,12 b''
145 var $sourceRef = $('#source_ref', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
146 var $targetRef = $('#target_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
147
178
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
181
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
184
148 var calculateContainerWidth = function() {
185 var calculateContainerWidth = function() {
149 var maxWidth = 0;
186 var maxWidth = 0;
150 var repoSelect2Containers = ['#source_repo', '#target_repo'];
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
@@ -204,6 +241,8 b''
204 // custom code mirror
241 // custom code mirror
205 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
242 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
206
243
244 reviewersController = new ReviewersController();
245
207 var queryTargetRepo = function(self, query) {
246 var queryTargetRepo = function(self, query) {
208 // cache ALL results if query is empty
247 // cache ALL results if query is empty
209 var cacheKey = query.term || '__';
248 var cacheKey = query.term || '__';
@@ -213,7 +252,7 b''
213 query.callback({results: cachedData.results});
252 query.callback({results: cachedData.results});
214 } else {
253 } else {
215 $.ajax({
254 $.ajax({
216 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': targetRepoName}),
255 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
217 data: {query: query.term},
256 data: {query: query.term},
218 dataType: 'json',
257 dataType: 'json',
219 type: 'GET',
258 type: 'GET',
@@ -246,55 +285,21 b''
246 query.callback({results: data.results});
285 query.callback({results: data.results});
247 };
286 };
248
287
249
250 var prButtonLockChecks = {
251 'compare': false,
252 'reviewers': false
253 };
254
255 var prButtonLock = function(lockEnabled, msg, scope) {
256 scope = scope || 'all';
257 if (scope == 'all'){
258 prButtonLockChecks['compare'] = !lockEnabled;
259 prButtonLockChecks['reviewers'] = !lockEnabled;
260 } else if (scope == 'compare') {
261 prButtonLockChecks['compare'] = !lockEnabled;
262 } else if (scope == 'reviewers'){
263 prButtonLockChecks['reviewers'] = !lockEnabled;
264 }
265 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
266 if (lockEnabled) {
267 $('#save').attr('disabled', 'disabled');
268 }
269 else if (checksMeet) {
270 $('#save').removeAttr('disabled');
271 }
272
273 if (msg) {
274 $('#pr_open_message').html(msg);
275 }
276 };
277
278 var loadRepoRefDiffPreview = function() {
288 var loadRepoRefDiffPreview = function() {
279 var sourceRepo = $sourceRepo.eq(0).val();
280 var sourceRef = $sourceRef.eq(0).val().split(':');
281
282 var targetRepo = $targetRepo.eq(0).val();
283 var targetRef = $targetRef.eq(0).val().split(':');
284
289
285 var url_data = {
290 var url_data = {
286 'repo_name': targetRepo,
291 'repo_name': targetRepo(),
287 'target_repo': sourceRepo,
292 'target_repo': sourceRepo(),
288 'source_ref': targetRef[2],
293 'source_ref': targetRef()[2],
289 'source_ref_type': 'rev',
294 'source_ref_type': 'rev',
290 'target_ref': sourceRef[2],
295 'target_ref': sourceRef()[2],
291 'target_ref_type': 'rev',
296 'target_ref_type': 'rev',
292 'merge': true,
297 'merge': true,
293 '_': Date.now() // bypass browser caching
298 '_': Date.now() // bypass browser caching
294 }; // gather the source/target ref and repo here
299 }; // gather the source/target ref and repo here
295
300
296 if (sourceRef.length !== 3 || targetRef.length !== 3) {
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
297 prButtonLock(true, "${_('Please select origin and destination')}");
302 prButtonLock(true, "${_('Please select source and target')}");
298 return;
303 return;
299 }
304 }
300 var url = pyroutes.url('compare_url', url_data);
305 var url = pyroutes.url('compare_url', url_data);
@@ -315,10 +320,11 b''
315 .done(function(data) {
320 .done(function(data) {
316 loadRepoRefDiffPreview._currentRequest = null;
321 loadRepoRefDiffPreview._currentRequest = null;
317 $('#pull_request_overview').html(data);
322 $('#pull_request_overview').html(data);
323
318 var commitElements = $(data).find('tr[commit_id]');
324 var commitElements = $(data).find('tr[commit_id]');
319
325
320 var prTitleAndDesc = getTitleAndDescription(sourceRef[1],
326 var prTitleAndDesc = getTitleAndDescription(
321 commitElements, 5);
327 sourceRef()[1], commitElements, 5);
322
328
323 var title = prTitleAndDesc[0];
329 var title = prTitleAndDesc[0];
324 var proposedDescription = prTitleAndDesc[1];
330 var proposedDescription = prTitleAndDesc[1];
@@ -366,43 +372,6 b''
366 });
372 });
367 };
373 };
368
374
369 /**
370 Generate Title and Description for a PullRequest.
371 In case of 1 commits, the title and description is that one commit
372 in case of multiple commits, we iterate on them with max N number of commits,
373 and build description in a form
374 - commitN
375 - commitN+1
376 ...
377
378 Title is then constructed from branch names, or other references,
379 replacing '-' and '_' into spaces
380
381 * @param sourceRef
382 * @param elements
383 * @param limit
384 * @returns {*[]}
385 */
386 var getTitleAndDescription = function(sourceRef, elements, limit) {
387 var title = '';
388 var desc = '';
389
390 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
391 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
392 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
393 });
394 // only 1 commit, use commit message as title
395 if (elements.length == 1) {
396 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
397 }
398 else {
399 // use reference name
400 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
401 }
402
403 return [title, desc]
404 };
405
406 var Select2Box = function(element, overrides) {
375 var Select2Box = function(element, overrides) {
407 var globalDefaults = {
376 var globalDefaults = {
408 dropdownAutoWidth: true,
377 dropdownAutoWidth: true,
@@ -459,7 +428,7 b''
459 var targetRepoChanged = function(repoData) {
428 var targetRepoChanged = function(repoData) {
460 // generate new DESC of target repo displayed next to select
429 // generate new DESC of target repo displayed next to select
461 $('#target_repo_desc').html(
430 $('#target_repo_desc').html(
462 "<strong>${_('Destination repository')}</strong>: {0}".format(repoData['description'])
431 "<strong>${_('Target repository')}</strong>: {0}".format(repoData['description'])
463 );
432 );
464
433
465 // generate dynamic select2 for refs.
434 // generate dynamic select2 for refs.
@@ -468,8 +437,7 b''
468
437
469 };
438 };
470
439
471 var sourceRefSelect2 = Select2Box(
440 var sourceRefSelect2 = Select2Box($sourceRef, {
472 $sourceRef, {
473 placeholder: "${_('Select commit reference')}",
441 placeholder: "${_('Select commit reference')}",
474 query: function(query) {
442 query: function(query) {
475 var initialData = defaultSourceRepoData['refs']['select2_refs'];
443 var initialData = defaultSourceRepoData['refs']['select2_refs'];
@@ -499,12 +467,14 b''
499
467
500 $sourceRef.on('change', function(e){
468 $sourceRef.on('change', function(e){
501 loadRepoRefDiffPreview();
469 loadRepoRefDiffPreview();
502 loadDefaultReviewers();
470 reviewersController.loadDefaultReviewers(
471 sourceRepo(), sourceRef(), targetRepo(), targetRef());
503 });
472 });
504
473
505 $targetRef.on('change', function(e){
474 $targetRef.on('change', function(e){
506 loadRepoRefDiffPreview();
475 loadRepoRefDiffPreview();
507 loadDefaultReviewers();
476 reviewersController.loadDefaultReviewers(
477 sourceRepo(), sourceRef(), targetRepo(), targetRef());
508 });
478 });
509
479
510 $targetRepo.on('change', function(e){
480 $targetRepo.on('change', function(e){
@@ -515,7 +485,7 b''
515
485
516 $.ajax({
486 $.ajax({
517 url: pyroutes.url('pullrequest_repo_refs',
487 url: pyroutes.url('pullrequest_repo_refs',
518 {'repo_name': targetRepoName, 'target_repo_name':repoName}),
488 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
519 data: {},
489 data: {},
520 dataType: 'json',
490 dataType: 'json',
521 type: 'GET',
491 type: 'GET',
@@ -531,43 +501,7 b''
531
501
532 });
502 });
533
503
534 var loadDefaultReviewers = function() {
504 prButtonLock(true, "${_('Please select source and target')}", 'all');
535 if (loadDefaultReviewers._currentRequest) {
536 loadDefaultReviewers._currentRequest.abort();
537 }
538 $('.calculate-reviewers').show();
539 prButtonLock(true, null, 'reviewers');
540
541 var url = pyroutes.url('repo_default_reviewers_data', {'repo_name': targetRepoName});
542
543 var sourceRepo = $sourceRepo.eq(0).val();
544 var sourceRef = $sourceRef.eq(0).val().split(':');
545 var targetRepo = $targetRepo.eq(0).val();
546 var targetRef = $targetRef.eq(0).val().split(':');
547 url += '?source_repo=' + sourceRepo;
548 url += '&source_ref=' + sourceRef[2];
549 url += '&target_repo=' + targetRepo;
550 url += '&target_ref=' + targetRef[2];
551
552 loadDefaultReviewers._currentRequest = $.get(url)
553 .done(function(data) {
554 loadDefaultReviewers._currentRequest = null;
555
556 // reset && add the reviewer based on selected repo
557 $('#review_members').html('');
558 for (var i = 0; i < data.reviewers.length; i++) {
559 var reviewer = data.reviewers[i];
560 addReviewMember(
561 reviewer.user_id, reviewer.firstname,
562 reviewer.lastname, reviewer.username,
563 reviewer.gravatar_link, reviewer.reasons);
564 }
565 $('.calculate-reviewers').hide();
566 prButtonLock(false, null, 'reviewers');
567 });
568 };
569
570 prButtonLock(true, "${_('Please select origin and destination')}", 'all');
571
505
572 // auto-load on init, the target refs select2
506 // auto-load on init, the target refs select2
573 calculateContainerWidth();
507 calculateContainerWidth();
@@ -578,13 +512,14 b''
578 });
512 });
579
513
580 % if c.default_source_ref:
514 % if c.default_source_ref:
581 // in case we have a pre-selected value, use it now
515 // in case we have a pre-selected value, use it now
582 $sourceRef.select2('val', '${c.default_source_ref}');
516 $sourceRef.select2('val', '${c.default_source_ref}');
583 loadRepoRefDiffPreview();
517 loadRepoRefDiffPreview();
584 loadDefaultReviewers();
518 reviewersController.loadDefaultReviewers(
519 sourceRepo(), sourceRef(), targetRepo(), targetRef());
585 % endif
520 % endif
586
521
587 ReviewerAutoComplete('user');
522 ReviewerAutoComplete('#user');
588 });
523 });
589 </script>
524 </script>
590
525
@@ -48,13 +48,13 b''
48 <div class="summary-details block-left">
48 <div class="summary-details block-left">
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 <div class="pr-details-title">
50 <div class="pr-details-title">
51 <a href="${h.url('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
52 %if c.allowed_to_update:
52 %if c.allowed_to_update:
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 % if c.allowed_to_delete:
54 % if c.allowed_to_delete:
55 ${h.secure_form(url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
55 ${h.secure_form(url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 ${h.end_form()}
58 ${h.end_form()}
59 % else:
59 % else:
60 ${_('Delete')}
60 ${_('Delete')}
@@ -68,7 +68,7 b''
68 <div id="summary" class="fields pr-details-content">
68 <div id="summary" class="fields pr-details-content">
69 <div class="field">
69 <div class="field">
70 <div class="label-summary">
70 <div class="label-summary">
71 <label>${_('Origin')}:</label>
71 <label>${_('Source')}:</label>
72 </div>
72 </div>
73 <div class="input">
73 <div class="input">
74 <div class="pr-origininfo">
74 <div class="pr-origininfo">
@@ -81,7 +81,7 b''
81 %endif
81 %endif
82 </span>
82 </span>
83 <span class="clone-url">
83 <span class="clone-url">
84 <a href="${h.url('summary_home', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
85 </span>
85 </span>
86 <br/>
86 <br/>
87 % if c.ancestor_commit:
87 % if c.ancestor_commit:
@@ -113,7 +113,7 b''
113 %endif
113 %endif
114 </span>
114 </span>
115 <span class="clone-url">
115 <span class="clone-url">
116 <a href="${h.url('summary_home', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
116 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 </span>
117 </span>
118 </div>
118 </div>
119 </div>
119 </div>
@@ -297,7 +297,7 b''
297 <div id="pr-save" class="field" style="display: none;">
297 <div id="pr-save" class="field" style="display: none;">
298 <div class="label-summary"></div>
298 <div class="label-summary"></div>
299 <div class="input">
299 <div class="input">
300 <span id="edit_pull_request" class="btn btn-small">${_('Save Changes')}</span>
300 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
301 </div>
301 </div>
302 </div>
302 </div>
303 </div>
303 </div>
@@ -306,7 +306,7 b''
306 ## AUTHOR
306 ## AUTHOR
307 <div class="reviewers-title block-right">
307 <div class="reviewers-title block-right">
308 <div class="pr-details-title">
308 <div class="pr-details-title">
309 ${_('Author')}
309 ${_('Author of this pull request')}
310 </div>
310 </div>
311 </div>
311 </div>
312 <div class="block-right pr-details-content reviewers">
312 <div class="block-right pr-details-content reviewers">
@@ -316,13 +316,27 b''
316 </li>
316 </li>
317 </ul>
317 </ul>
318 </div>
318 </div>
319
320 ## REVIEW RULES
321 <div id="review_rules" style="display: none" class="reviewers-title block-right">
322 <div class="pr-details-title">
323 ${_('Reviewer rules')}
324 %if c.allowed_to_update:
325 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
326 %endif
327 </div>
328 <div class="pr-reviewer-rules">
329 ## review rules will be appended here, by default reviewers logic
330 </div>
331 <input id="review_data" type="hidden" name="review_data" value="">
332 </div>
333
319 ## REVIEWERS
334 ## REVIEWERS
320 <div class="reviewers-title block-right">
335 <div class="reviewers-title block-right">
321 <div class="pr-details-title">
336 <div class="pr-details-title">
322 ${_('Pull request reviewers')}
337 ${_('Pull request reviewers')}
323 %if c.allowed_to_update:
338 %if c.allowed_to_update:
324 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
339 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
325 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
326 %endif
340 %endif
327 </div>
341 </div>
328 </div>
342 </div>
@@ -330,8 +344,8 b''
330 ## members goes here !
344 ## members goes here !
331 <input type="hidden" name="__start__" value="review_members:sequence">
345 <input type="hidden" name="__start__" value="review_members:sequence">
332 <ul id="review_members" class="group_members">
346 <ul id="review_members" class="group_members">
333 %for member,reasons,status in c.pull_request_reviewers:
347 %for member,reasons,mandatory,status in c.pull_request_reviewers:
334 <li id="reviewer_${member.user_id}">
348 <li id="reviewer_${member.user_id}" class="reviewer_entry">
335 <div class="reviewers_member">
349 <div class="reviewers_member">
336 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
350 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
337 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
351 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
@@ -348,30 +362,43 b''
348 %endfor
362 %endfor
349 <input type="hidden" name="__end__" value="reasons:sequence">
363 <input type="hidden" name="__end__" value="reasons:sequence">
350 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
364 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
365 <input type="hidden" name="mandatory" value="${mandatory}"/>
351 <input type="hidden" name="__end__" value="reviewer:mapping">
366 <input type="hidden" name="__end__" value="reviewer:mapping">
352 %if c.allowed_to_update:
367 % if mandatory:
353 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
368 <div class="reviewer_member_mandatory_remove">
354 <i class="icon-remove-sign" ></i>
369 <i class="icon-remove-sign"></i>
355 </div>
370 </div>
356 %endif
371 <div class="reviewer_member_mandatory">
372 <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i>
373 </div>
374 % else:
375 %if c.allowed_to_update:
376 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
377 <i class="icon-remove-sign" ></i>
378 </div>
379 %endif
380 % endif
357 </div>
381 </div>
358 </li>
382 </li>
359 %endfor
383 %endfor
360 </ul>
384 </ul>
361 <input type="hidden" name="__end__" value="review_members:sequence">
385 <input type="hidden" name="__end__" value="review_members:sequence">
362 %if not c.pull_request.is_closed():
386
363 <div id="add_reviewer_input" class='ac' style="display: none;">
387 %if not c.pull_request.is_closed():
364 %if c.allowed_to_update:
388 <div id="add_reviewer" class="ac" style="display: none;">
365 <div class="reviewer_ac">
389 %if c.allowed_to_update:
366 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
390 % if not c.forbid_adding_reviewers:
367 <div id="reviewers_container"></div>
391 <div id="add_reviewer_input" class="reviewer_ac">
368 </div>
392 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
369 <div>
393 <div id="reviewers_container"></div>
370 <button id="update_pull_request" class="btn btn-small">${_('Save Changes')}</button>
394 </div>
371 </div>
395 % endif
396 <div class="pull-right">
397 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
398 </div>
399 %endif
400 </div>
372 %endif
401 %endif
373 </div>
374 %endif
375 </div>
402 </div>
376 </div>
403 </div>
377 </div>
404 </div>
@@ -429,7 +456,7 b''
429
456
430 <div class="pull-right">
457 <div class="pull-right">
431 % if c.allowed_to_update and not c.pull_request.is_closed():
458 % if c.allowed_to_update and not c.pull_request.is_closed():
432 <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a>
459 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
433 % else:
460 % else:
434 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
461 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
435 % endif
462 % endif
@@ -473,7 +500,7 b''
473 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
500 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
474 <td>
501 <td>
475 <div class="commit-change-indicator color-${c_type}-border">
502 <div class="commit-change-indicator color-${c_type}-border">
476 <div class="commit-change-content color-${c_type} tooltip" title="${cc_title}">
503 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
477 ${c_type.upper()}
504 ${c_type.upper()}
478 </div>
505 </div>
479 </div>
506 </div>
@@ -615,9 +642,10 b''
615 versionController = new VersionController();
642 versionController = new VersionController();
616 versionController.init();
643 versionController.init();
617
644
645 reviewersController = new ReviewersController();
618
646
619 $(function(){
647 $(function(){
620 ReviewerAutoComplete('user');
648
621 // custom code mirror
649 // custom code mirror
622 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
650 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
623
651
@@ -655,13 +683,13 b''
655 var ReviewersPanel = {
683 var ReviewersPanel = {
656 editButton: $('#open_edit_reviewers'),
684 editButton: $('#open_edit_reviewers'),
657 closeButton: $('#close_edit_reviewers'),
685 closeButton: $('#close_edit_reviewers'),
658 addButton: $('#add_reviewer_input'),
686 addButton: $('#add_reviewer'),
659 removeButtons: $('.reviewer_member_remove'),
687 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
660
688
661 init: function() {
689 init: function() {
662 var that = this;
690 var self = this;
663 this.editButton.on('click', function(e) { that.edit(); });
691 this.editButton.on('click', function(e) { self.edit(); });
664 this.closeButton.on('click', function(e) { that.close(); });
692 this.closeButton.on('click', function(e) { self.close(); });
665 },
693 },
666
694
667 edit: function(event) {
695 edit: function(event) {
@@ -669,6 +697,9 b''
669 this.closeButton.show();
697 this.closeButton.show();
670 this.addButton.show();
698 this.addButton.show();
671 this.removeButtons.css('visibility', 'visible');
699 this.removeButtons.css('visibility', 'visible');
700 // review rules
701 reviewersController.loadReviewRules(
702 ${c.pull_request.reviewer_data_json | n});
672 },
703 },
673
704
674 close: function(event) {
705 close: function(event) {
@@ -676,6 +707,8 b''
676 this.closeButton.hide();
707 this.closeButton.hide();
677 this.addButton.hide();
708 this.addButton.hide();
678 this.removeButtons.css('visibility', 'hidden');
709 this.removeButtons.css('visibility', 'hidden');
710 // hide review rules
711 reviewersController.hideReviewRules()
679 }
712 }
680 };
713 };
681
714
@@ -774,7 +807,8 b''
774 $(this).attr('disabled', 'disabled');
807 $(this).attr('disabled', 'disabled');
775 $(this).addClass('disabled');
808 $(this).addClass('disabled');
776 $(this).html(_gettext('Saving...'));
809 $(this).html(_gettext('Saving...'));
777 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
810 reviewersController.updateReviewers(
811 "${c.repo_name}", "${c.pull_request.pull_request_id}");
778 });
812 });
779
813
780 $('#update_commits').on('click', function(e){
814 $('#update_commits').on('click', function(e){
@@ -784,16 +818,13 b''
784 $(e.currentTarget).removeClass('btn-primary');
818 $(e.currentTarget).removeClass('btn-primary');
785 $(e.currentTarget).text(_gettext('Updating...'));
819 $(e.currentTarget).text(_gettext('Updating...'));
786 if(isDisabled){
820 if(isDisabled){
787 updateCommits("${c.repo_name}", "${c.pull_request.pull_request_id}");
821 updateCommits(
822 "${c.repo_name}", "${c.pull_request.pull_request_id}");
788 }
823 }
789 });
824 });
790 // fixing issue with caches on firefox
825 // fixing issue with caches on firefox
791 $('#update_commits').removeAttr("disabled");
826 $('#update_commits').removeAttr("disabled");
792
827
793 $('#close_pull_request').on('click', function(e){
794 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
795 });
796
797 $('.show-inline-comments').on('click', function(e){
828 $('.show-inline-comments').on('click', function(e){
798 var boxid = $(this).attr('data-comment-id');
829 var boxid = $(this).attr('data-comment-id');
799 var button = $(this);
830 var button = $(this);
@@ -818,6 +849,8 b''
818 // initial injection
849 // initial injection
819 injectCloseAction();
850 injectCloseAction();
820
851
852 ReviewerAutoComplete('#user');
853
821 })
854 })
822 </script>
855 </script>
823
856
@@ -45,12 +45,12 b''
45 ##main
45 ##main
46 <div class="sidebar">
46 <div class="sidebar">
47 <ul class="nav nav-pills nav-stacked">
47 <ul class="nav nav-pills nav-stacked">
48 <li class="${'active' if c.active=='open' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0)}">${_('Opened')}</a></li>
48 <li class="${'active' if c.active=='open' else ''}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0})}">${_('Opened')}</a></li>
49 <li class="${'active' if c.active=='my' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,my=1)}">${_('Opened by me')}</a></li>
49 <li class="${'active' if c.active=='my' else ''}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'my':1})}">${_('Opened by me')}</a></li>
50 <li class="${'active' if c.active=='awaiting' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,awaiting_review=1)}">${_('Awaiting review')}</a></li>
50 <li class="${'active' if c.active=='awaiting' else ''}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_review':1})}">${_('Awaiting review')}</a></li>
51 <li class="${'active' if c.active=='awaiting_my' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,awaiting_my_review=1)}">${_('Awaiting my review')}</a></li>
51 <li class="${'active' if c.active=='awaiting_my' else ''}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_my_review':1})}">${_('Awaiting my review')}</a></li>
52 <li class="${'active' if c.active=='closed' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=0,closed=1)}">${_('Closed')}</a></li>
52 <li class="${'active' if c.active=='closed' else ''}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'closed':1})}">${_('Closed')}</a></li>
53 <li class="${'active' if c.active=='source' else ''}"><a href="${h.url('pullrequest_show_all',repo_name=c.repo_name,source=1)}">${_('From this repo')}</a></li>
53 <li class="${'active' if c.active=='source' else ''}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':1})}">${_('From this repo')}</a></li>
54 </ul>
54 </ul>
55 </div>
55 </div>
56
56
@@ -73,7 +73,7 b''
73 %endif
73 %endif
74 </h3>
74 </h3>
75 </div>
75 </div>
76 <div class="panel-body">
76 <div class="panel-body panel-body-min-height">
77 <table id="pull_request_list_table" class="display"></table>
77 <table id="pull_request_list_table" class="display"></table>
78 </div>
78 </div>
79 </div>
79 </div>
@@ -83,14 +83,24 b''
83
83
84 <script type="text/javascript">
84 <script type="text/javascript">
85 $(document).ready(function() {
85 $(document).ready(function() {
86
87 var $pullRequestListTable = $('#pull_request_list_table');
88
86 // object list
89 // object list
87 $('#pull_request_list_table').DataTable({
90 $pullRequestListTable.DataTable({
88 data: ${c.data|n},
91 processing: true,
89 processing: false,
90 serverSide: true,
92 serverSide: true,
91 deferLoading: ${c.records_total},
93 ajax: {
92 ajax: "",
94 "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}",
93 dom: 'tp',
95 "data": function (d) {
96 d.source = "${c.source}";
97 d.closed = "${c.closed}";
98 d.my = "${c.my}";
99 d.awaiting_review = "${c.awaiting_review}";
100 d.awaiting_my_review = "${c.awaiting_my_review}";
101 }
102 },
103 dom: 'rtp',
94 pageLength: ${c.visual.dashboard_items},
104 pageLength: ${c.visual.dashboard_items},
95 order: [[ 1, "desc" ]],
105 order: [[ 1, "desc" ]],
96 columns: [
106 columns: [
@@ -109,6 +119,7 b''
109 ],
119 ],
110 language: {
120 language: {
111 paginate: DEFAULT_GRID_PAGINATION,
121 paginate: DEFAULT_GRID_PAGINATION,
122 sProcessing: _gettext('loading...'),
112 emptyTable: _gettext("No pull requests available yet.")
123 emptyTable: _gettext("No pull requests available yet.")
113 },
124 },
114 "drawCallback": function( settings, json ) {
125 "drawCallback": function( settings, json ) {
@@ -120,13 +131,16 b''
120 }
131 }
121 }
132 }
122 });
133 });
123 });
134
124 $('#pull_request_list_table').on('xhr.dt', function(e, settings, json, xhr){
135 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
125 $('#pull_request_list_table').css('opacity', 1);
136 $pullRequestListTable.css('opacity', 1);
137 });
138
139 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
140 $pullRequestListTable.css('opacity', 0.3);
141 });
142
126 });
143 });
127
144
128 $('#pull_request_list_table').on('preXhr.dt', function(e, settings, data){
129 $('#pull_request_list_table').css('opacity', 0.3);
130 });
131 </script>
145 </script>
132 </%def>
146 </%def>
@@ -14,7 +14,7 b''
14 <div id="header-inner" class="title">
14 <div id="header-inner" class="title">
15 <div id="logo">
15 <div id="logo">
16 <div class="logo-wrapper">
16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 </div>
18 </div>
19 %if c.rhodecode_name:
19 %if c.rhodecode_name:
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -45,7 +45,7 b''
45 <div class="title">
45 <div class="title">
46 ${self.repo_page_title(c.rhodecode_db_repo)}
46 ${self.repo_page_title(c.rhodecode_db_repo)}
47 </div>
47 </div>
48 ${h.form(h.url('search_repo_home',repo_name=c.repo_name),method='get')}
48 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
49 %else:
49 %else:
50 <!-- box / title -->
50 <!-- box / title -->
51 <div class="title">
51 <div class="title">
@@ -53,7 +53,7 b''
53 <ul class="links">&nbsp;</ul>
53 <ul class="links">&nbsp;</ul>
54 </div>
54 </div>
55 <!-- end box / title -->
55 <!-- end box / title -->
56 ${h.form(h.url('search'),method='get')}
56 ${h.form(h.route_path('search'), method='get')}
57 %endif
57 %endif
58 <div class="form search-form">
58 <div class="form search-form">
59 <div class="fields">
59 <div class="fields">
@@ -63,6 +63,7 b''
63 ${h.select('type',c.search_type,[('content',_('File contents')), ('commit',_('Commit messages')), ('path',_('File names')),],id='id_search_type')}
63 ${h.select('type',c.search_type,[('content',_('File contents')), ('commit',_('Commit messages')), ('path',_('File names')),],id='id_search_type')}
64 <input type="submit" value="${_('Search')}" class="btn"/>
64 <input type="submit" value="${_('Search')}" class="btn"/>
65 <br/>
65 <br/>
66
66 <div class="search-feedback-items">
67 <div class="search-feedback-items">
67 % for error in c.errors:
68 % for error in c.errors:
68 <span class="error-message">
69 <span class="error-message">
@@ -71,10 +72,16 b''
71 % endfor
72 % endfor
72 </span>
73 </span>
73 % endfor
74 % endfor
75 <div class="field">
76 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Example Queries')}</p>
77 <pre id="search-help" style="display: none">${h.tooltip(h.search_filter_help(c.searcher))}</pre>
78 </div>
79
74 <div class="field">${c.runtime}</div>
80 <div class="field">${c.runtime}</div>
75 </div>
81 </div>
76 </div>
82 </div>
77 </div>
83 </div>
84
78 ${h.end_form()}
85 ${h.end_form()}
79 <div class="search">
86 <div class="search">
80 % if c.search_type == 'content':
87 % if c.search_type == 'content':
@@ -27,7 +27,7 b''
27 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
27 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
28 <i class="icon-svn"></i>
28 <i class="icon-svn"></i>
29 %endif
29 %endif
30 ${h.link_to(entry['repository'], h.url('summary_home',repo_name=entry['repository']))}
30 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
31 </td>
31 </td>
32 <td class="td-commit">
32 <td class="td-commit">
33 ${h.link_to(h._shorten_commit_id(entry['commit_id']),
33 ${h.link_to(h._shorten_commit_id(entry['commit_id']),
@@ -49,12 +49,12 b' for line_number in matching_lines:'
49 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
49 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
50 <i class="icon-svn"></i>
50 <i class="icon-svn"></i>
51 %endif
51 %endif
52 ${h.link_to(entry['repository'], h.url('summary_home',repo_name=entry['repository']))}
52 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
53 </h2>
53 </h2>
54 <div class="stats">
54 <div class="stats">
55 ${h.link_to(h.literal(entry['f_path']), h.url('files_home',repo_name=entry['repository'],revision=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
55 ${h.link_to(h.literal(entry['f_path']), h.url('files_home',repo_name=entry['repository'],revision=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
56 %if entry.get('lines'):
56 %if entry.get('lines'):
57 | ${entry.get('lines', 0.)} ${ungettext('line', 'lines', entry.get('lines', 0.))}
57 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
58 %endif
58 %endif
59 %if entry.get('size'):
59 %if entry.get('size'):
60 | ${h.format_byte_size_binary(entry['size'])}
60 | ${h.format_byte_size_binary(entry['size'])}
@@ -16,7 +16,7 b''
16 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
16 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
17 <i class="icon-svn"></i>
17 <i class="icon-svn"></i>
18 %endif
18 %endif
19 ${h.link_to(entry['repository'], h.url('summary_home',repo_name=entry['repository']))}
19 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
20 </td>
20 </td>
21 <td class="td-componentname">
21 <td class="td-componentname">
22 ${h.link_to(h.literal(entry['f_path']),
22 ${h.link_to(h.literal(entry['f_path']),
@@ -10,8 +10,8 b''
10
10
11
11
12 <%def name="head_extra()">
12 <%def name="head_extra()">
13 <link href="${h.url('atom_feed_home',repo_name=c.rhodecode_db_repo.repo_name,auth_token=c.rhodecode_user.feed_token)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
13 <link href="${h.url('atom_feed_home',repo_name=c.rhodecode_db_repo.repo_name,auth_token=c.rhodecode_user.feed_token)}" rel="alternate" title="${h.tooltip(_('%s ATOM feed') % c.repo_name)}" type="application/atom+xml" />
14 <link href="${h.url('rss_feed_home',repo_name=c.rhodecode_db_repo.repo_name,auth_token=c.rhodecode_user.feed_token)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
14 <link href="${h.url('rss_feed_home',repo_name=c.rhodecode_db_repo.repo_name,auth_token=c.rhodecode_user.feed_token)}" rel="alternate" title="${h.tooltip(_('%s RSS feed') % c.repo_name)}" type="application/rss+xml" />
15 </%def>
15 </%def>
16
16
17
17
@@ -1,28 +1,28 b''
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 <span class="branchtag tag">
2 <span class="branchtag tag">
3 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${ungettext(
4 <i class="icon-branch"></i>${_ungettext(
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 </span>
6 </span>
7
7
8 %if closed_branches:
8 %if closed_branches:
9 <span class="branchtag tag">
9 <span class="branchtag tag">
10 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${ungettext(
11 <i class="icon-branch"></i>${_ungettext(
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 </span>
13 </span>
14 %endif
14 %endif
15
15
16 <span class="tagtag tag">
16 <span class="tagtag tag">
17 <a href="${h.url('tags_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${ungettext(
18 <i class="icon-tag"></i>${_ungettext(
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 </span>
20 </span>
21
21
22 %if bookmarks:
22 %if bookmarks:
23 <span class="booktag tag">
23 <span class="booktag tag">
24 <a href="${h.url('bookmarks_home',repo_name=c.repo_name)}" class="childs">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${ungettext(
25 <i class="icon-bookmark"></i>${_ungettext(
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 </span>
27 </span>
28 %endif
28 %endif
@@ -53,7 +53,7 b''
53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
55 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
55 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
57 </div>
57 </div>
58 %else:
58 %else:
59 <div class="left-label">
59 <div class="left-label">
@@ -92,15 +92,15 b''
92
92
93 ## commits
93 ## commits
94 % if commit_rev == -1:
94 % if commit_rev == -1:
95 ${ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
95 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
96 % else:
96 % else:
97 <a href="${h.url('changelog_home', repo_name=c.repo_name)}">
97 <a href="${h.url('changelog_home', repo_name=c.repo_name)}">
98 ${ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
98 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
99 % endif
99 % endif
100
100
101 ## forks
101 ## forks
102 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
102 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
103 ${c.repository_forks} ${ungettext('Fork', 'Forks', c.repository_forks)}</a>,
103 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
104
104
105 ## repo size
105 ## repo size
106 % if commit_rev == -1:
106 % if commit_rev == -1:
@@ -145,7 +145,7 b''
145 ${_('Statistics are disabled for this repository')}
145 ${_('Statistics are disabled for this repository')}
146 </span>
146 </span>
147 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
147 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
148 , ${h.link_to(_('enable statistics'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
148 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
149 % endif
149 % endif
150 % endif
150 % endif
151 </div>
151 </div>
@@ -169,7 +169,7 b''
169 ${_('Downloads are disabled for this repository')}
169 ${_('Downloads are disabled for this repository')}
170 </span>
170 </span>
171 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
171 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
172 , ${h.link_to(_('enable downloads'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
172 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
173 % endif
173 % endif
174 % else:
174 % else:
175 <span class="enabled">
175 <span class="enabled">
@@ -14,8 +14,14 b''
14
14
15 <div class="alert alert-dismissable alert-warning">
15 <div class="alert alert-dismissable alert-warning">
16 <strong>Missing requirements</strong>
16 <strong>Missing requirements</strong>
17 These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.
17 Commits cannot be displayed, because this repository uses one or more extensions, which was not enabled. <br/>
18 Please <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">enable this extension in settings</a>, or contact the repository owner for help.
18 Please <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">enable extension in settings</a>, or contact the repository owner for help.
19 Missing extensions could be:
20 <pre>
21
22 - Mercurial largefiles
23 - Git LFS
24 </pre>
19 </div>
25 </div>
20
26
21 </%def>
27 </%def>
@@ -36,7 +36,7 b''
36 %endif
36 %endif
37 <div class="table">
37 <div class="table">
38 <div id="shortlog_data">
38 <div id="shortlog_data">
39 <%include file='../changelog/changelog_summary_data.mako'/>
39 <%include file='summary_commits.mako'/>
40 </div>
40 </div>
41 </div>
41 </div>
42 </div>
42 </div>
@@ -44,13 +44,13 b''
44 %if c.readme_data:
44 %if c.readme_data:
45 <div id="readme" class="anchor">
45 <div id="readme" class="anchor">
46 <div class="box" >
46 <div class="box" >
47 <div class="title" title="${_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1])}">
47 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
48 <h3 class="breadcrumbs">
48 <h3 class="breadcrumbs">
49 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
49 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="readme codeblock">
52 <div class="readme codeblock">
53 <div class="readme_box markdown-block">
53 <div class="readme_box">
54 ${c.readme_data|n}
54 ${c.readme_data|n}
55 </div>
55 </div>
56 </div>
56 </div>
@@ -110,8 +110,7 b''
110
110
111 var callback = function (data) {
111 var callback = function (data) {
112 % if c.show_stats:
112 % if c.show_stats:
113 showRepoStats(
113 showRepoStats('lang_stats', data);
114 'lang_stats', data);
115 % endif
114 % endif
116 };
115 };
117
116
@@ -9,7 +9,7 b''
9 </%def>
9 </%def>
10
10
11 <%def name="name(name, files_url)">
11 <%def name="name(name, files_url)">
12 <span class="tagtag tag" title="${_('Tag %s') % (name,)}">
12 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % (name,))}">
13 <a href="${files_url}"><i class="icon-tag"></i>${name}</a>
13 <a href="${files_url}"><i class="icon-tag"></i>${name}</a>
14 </span>
14 </span>
15 </%def>
15 </%def>
@@ -19,11 +19,11 b''
19 </%def>
19 </%def>
20
20
21 <%def name="author(author)">
21 <%def name="author(author)">
22 <span class="tooltip" title="${author}">${h.link_to_user(author)}</span>
22 <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span>
23 </%def>
23 </%def>
24
24
25 <%def name="commit(message, commit_id, commit_idx)">
25 <%def name="commit(message, commit_id, commit_idx)">
26 <div>
26 <div>
27 <pre><a title="${message}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
27 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
28 </div>
28 </div>
29 </%def>
29 </%def>
@@ -35,7 +35,7 b''
35 ${_('First name')}:
35 ${_('First name')}:
36 </div>
36 </div>
37 <div class="right-content">
37 <div class="right-content">
38 ${c.user.firstname}
38 ${c.user.first_name}
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="fieldset">
41 <div class="fieldset">
@@ -43,7 +43,7 b''
43 ${_('Last name')}:
43 ${_('Last name')}:
44 </div>
44 </div>
45 <div class="right-content">
45 <div class="right-content">
46 ${c.user.lastname}
46 ${c.user.last_name}
47 </div>
47 </div>
48 </div>
48 </div>
49 <div class="fieldset">
49 <div class="fieldset">
@@ -48,7 +48,6 b' from rhodecode.model.db import User'
48 from rhodecode.lib import auth
48 from rhodecode.lib import auth
49 from rhodecode.lib.helpers import flash, link_to
49 from rhodecode.lib.helpers import flash, link_to
50 from rhodecode.lib.utils2 import safe_unicode, safe_str
50 from rhodecode.lib.utils2 import safe_unicode, safe_str
51 from rhodecode.tests.utils import get_session_from_response
52
51
53
52
54 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
@@ -56,7 +55,7 b' log = logging.getLogger(__name__)'
56 __all__ = [
55 __all__ = [
57 'get_new_dir', 'TestController', 'SkipTest',
56 'get_new_dir', 'TestController', 'SkipTest',
58 'url', 'link_to', 'ldap_lib_installed', 'clear_all_caches',
57 'url', 'link_to', 'ldap_lib_installed', 'clear_all_caches',
59 'assert_session_flash', 'login_user',
58 'assert_session_flash', 'login_user', 'no_newline_id_generator',
60 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
59 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
61 'NEW_HG_REPO', 'NEW_GIT_REPO',
60 'NEW_HG_REPO', 'NEW_GIT_REPO',
62 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
61 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
@@ -178,7 +177,7 b' def login_user_session('
178 response = response.follow()
177 response = response.follow()
179 assert response.status == '200 OK'
178 assert response.status == '200 OK'
180
179
181 session = get_session_from_response(response)
180 session = response.get_session_from_response()
182 assert 'rhodecode_user' in session
181 assert 'rhodecode_user' in session
183 rc_user = session['rhodecode_user']
182 rc_user = session['rhodecode_user']
184 assert rc_user.get('username') == username
183 assert rc_user.get('username') == username
@@ -249,3 +248,17 b' def assert_session_flash_is_empty(respon'
249 msg = 'flash messages are present in session:%s' % \
248 msg = 'flash messages are present in session:%s' % \
250 response.session['flash'][0]
249 response.session['flash'][0]
251 pytest.fail(safe_str(msg))
250 pytest.fail(safe_str(msg))
251
252
253 def no_newline_id_generator(test_name):
254 """
255 Generates a test name without spaces or newlines characters. Used for
256 nicer output of progress of test
257 """
258 org_name = test_name
259 test_name = test_name\
260 .replace('\n', '_N') \
261 .replace('\t', '_T') \
262 .replace(' ', '_S')
263
264 return test_name or 'test-with-empty-name'
@@ -18,23 +18,28 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import pytest
22
22 import requests
23 from rhodecode.config import routing_links
23 from rhodecode.config import routing_links
24
24
25
25
26 def test_connect_redirection_links():
26 def check_connection():
27 link_config = [
27 try:
28 {"name": "example_link",
28 response = requests.get('https://rhodecode.com')
29 "external_target": "http://example.com",
29 return response.status_code == 200
30 "target": "https://rhodecode.com/r/v1/enterprise/example",
30 except Exception as e:
31 },
31 print(e)
32 ]
32
33 return False
34
33
35
34 rmap = mock.Mock()
36 connection_available = pytest.mark.skipif(
35 with mock.patch.object(routing_links, 'link_config', link_config):
37 not check_connection(), reason="No outside internet connection available")
36 routing_links.connect_redirection_links(rmap)
38
37
39
38 rmap.connect.assert_called_with(
40 @connection_available
39 link_config[0]['name'], link_config[0]['target'],
41 def test_connect_redirection_links():
40 _static=True)
42
43 for link_data in routing_links.link_config:
44 response = requests.get(link_data['target'])
45 assert response.url == link_data['external_target']
@@ -21,6 +21,7 b''
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.tests import no_newline_id_generator
24 from rhodecode.config.middleware import (
25 from rhodecode.config.middleware import (
25 _sanitize_vcs_settings, _bool_setting, _string_setting, _list_setting,
26 _sanitize_vcs_settings, _bool_setting, _string_setting, _list_setting,
26 _int_setting)
27 _int_setting)
@@ -70,7 +71,7 b' class TestHelperFunctions(object):'
70 (' hg\n git\n svn ', ['hg', 'git', 'svn']),
71 (' hg\n git\n svn ', ['hg', 'git', 'svn']),
71 (', hg , git , svn , ', ['', 'hg', 'git', 'svn', '']),
72 (', hg , git , svn , ', ['', 'hg', 'git', 'svn', '']),
72 ('cheese,free node,other', ['cheese', 'free node', 'other']),
73 ('cheese,free node,other', ['cheese', 'free node', 'other']),
73 ])
74 ], ids=no_newline_id_generator)
74 def test_list_setting_helper(self, raw, expected):
75 def test_list_setting_helper(self, raw, expected):
75 key = 'dummy-key'
76 key = 'dummy-key'
76 settings = {key: raw}
77 settings = {key: raw}
@@ -64,7 +64,7 b' def test_repo_groups_load_defaults('
64 personal_group = personal_group_with_parent
64 personal_group = personal_group_with_parent
65 controller._RepoGroupsController__load_defaults(True, personal_group)
65 controller._RepoGroupsController__load_defaults(True, personal_group)
66
66
67 expected_list = ['-1', personal_group.parent_group.group_id]
67 expected_list = [-1, personal_group.parent_group.group_id]
68 returned_group_ids = [group[0] for group in c.repo_groups]
68 returned_group_ids = [group[0] for group in c.repo_groups]
69 assert returned_group_ids == expected_list
69 assert returned_group_ids == expected_list
70
70
@@ -74,6 +74,6 b' def test_repo_groups_load_defaults_with_'
74 personal_group = personal_group_with_parent
74 personal_group = personal_group_with_parent
75 controller._RepoGroupsController__load_defaults(True)
75 controller._RepoGroupsController__load_defaults(True)
76
76
77 expected_list = sorted(['-1', personal_group.group_id])
77 expected_list = sorted([-1, personal_group.group_id])
78 returned_group_ids = sorted([group[0] for group in c.repo_groups])
78 returned_group_ids = sorted([group[0] for group in c.repo_groups])
79 assert returned_group_ids == expected_list
79 assert returned_group_ids == expected_list
@@ -42,7 +42,7 b' from rhodecode.events import ('
42 PullRequestMergeEvent,
42 PullRequestMergeEvent,
43 PullRequestCloseEvent,
43 PullRequestCloseEvent,
44 ])
44 ])
45 def test_pullrequest_events_serialized(pr_util, EventClass):
45 def test_pullrequest_events_serialized(EventClass, pr_util, config_stub):
46 pr = pr_util.create_pull_request()
46 pr = pr_util.create_pull_request()
47 event = EventClass(pr)
47 event = EventClass(pr)
48 data = event.as_dict()
48 data = event.as_dict()
@@ -50,16 +50,19 b' def test_pullrequest_events_serialized(p'
50 assert data['repo']['repo_name'] == pr.target_repo.repo_name
50 assert data['repo']['repo_name'] == pr.target_repo.repo_name
51 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
51 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
52 assert data['pullrequest']['url']
52 assert data['pullrequest']['url']
53 assert data['pullrequest']['permalink_url']
54
53
55
54 @pytest.mark.backends("git", "hg")
56 @pytest.mark.backends("git", "hg")
55 def test_create_pull_request_events(pr_util):
57 def test_create_pull_request_events(pr_util, config_stub):
56 with EventCatcher() as event_catcher:
58 with EventCatcher() as event_catcher:
57 pr_util.create_pull_request()
59 pr_util.create_pull_request()
58
60
59 assert PullRequestCreateEvent in event_catcher.events_types
61 assert PullRequestCreateEvent in event_catcher.events_types
60
62
63
61 @pytest.mark.backends("git", "hg")
64 @pytest.mark.backends("git", "hg")
62 def test_pullrequest_comment_events_serialized(pr_util):
65 def test_pullrequest_comment_events_serialized(pr_util, config_stub):
63 pr = pr_util.create_pull_request()
66 pr = pr_util.create_pull_request()
64 comment = CommentsModel().get_comments(
67 comment = CommentsModel().get_comments(
65 pr.target_repo.repo_id, pull_request=pr)[0]
68 pr.target_repo.repo_id, pull_request=pr)[0]
@@ -69,11 +72,12 b' def test_pullrequest_comment_events_seri'
69 assert data['repo']['repo_name'] == pr.target_repo.repo_name
72 assert data['repo']['repo_name'] == pr.target_repo.repo_name
70 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
73 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
71 assert data['pullrequest']['url']
74 assert data['pullrequest']['url']
75 assert data['pullrequest']['permalink_url']
72 assert data['comment']['text'] == comment.text
76 assert data['comment']['text'] == comment.text
73
77
74
78
75 @pytest.mark.backends("git", "hg")
79 @pytest.mark.backends("git", "hg")
76 def test_close_pull_request_events(pr_util, user_admin):
80 def test_close_pull_request_events(pr_util, user_admin, config_stub):
77 pr = pr_util.create_pull_request()
81 pr = pr_util.create_pull_request()
78
82
79 with EventCatcher() as event_catcher:
83 with EventCatcher() as event_catcher:
@@ -83,7 +87,7 b' def test_close_pull_request_events(pr_ut'
83
87
84
88
85 @pytest.mark.backends("git", "hg")
89 @pytest.mark.backends("git", "hg")
86 def test_close_pull_request_with_comment_events(pr_util, user_admin):
90 def test_close_pull_request_with_comment_events(pr_util, user_admin, config_stub):
87 pr = pr_util.create_pull_request()
91 pr = pr_util.create_pull_request()
88
92
89 with EventCatcher() as event_catcher:
93 with EventCatcher() as event_catcher:
@@ -43,6 +43,7 b' def scm_extras(user_regular, repo_stub):'
43 'config': '',
43 'config': '',
44 'server_url': 'http://example.com',
44 'server_url': 'http://example.com',
45 'make_lock': None,
45 'make_lock': None,
46 'user-agent': 'some-client',
46 'locked_by': [None],
47 'locked_by': [None],
47 'commit_ids': ['a' * 40] * 3,
48 'commit_ids': ['a' * 40] * 3,
48 'is_shadow_repo': False,
49 'is_shadow_repo': False,
@@ -55,28 +56,29 b' def scm_extras(user_regular, repo_stub):'
55 RepoPreCreateEvent, RepoCreateEvent,
56 RepoPreCreateEvent, RepoCreateEvent,
56 RepoPreDeleteEvent, RepoDeleteEvent,
57 RepoPreDeleteEvent, RepoDeleteEvent,
57 ])
58 ])
58 def test_repo_events_serialized(repo_stub, EventClass):
59 def test_repo_events_serialized(config_stub, repo_stub, EventClass):
59 event = EventClass(repo_stub)
60 event = EventClass(repo_stub)
60 data = event.as_dict()
61 data = event.as_dict()
61 assert data['name'] == EventClass.name
62 assert data['name'] == EventClass.name
62 assert data['repo']['repo_name'] == repo_stub.repo_name
63 assert data['repo']['repo_name'] == repo_stub.repo_name
63 assert data['repo']['url']
64 assert data['repo']['url']
65 assert data['repo']['permalink_url']
64
66
65
67
66 @pytest.mark.parametrize('EventClass', [
68 @pytest.mark.parametrize('EventClass', [
67 RepoPrePullEvent, RepoPullEvent, RepoPrePushEvent
69 RepoPrePullEvent, RepoPullEvent, RepoPrePushEvent
68 ])
70 ])
69 def test_vcs_repo_events_serialize(repo_stub, scm_extras, EventClass):
71 def test_vcs_repo_events_serialize(config_stub, repo_stub, scm_extras, EventClass):
70 event = EventClass(repo_name=repo_stub.repo_name, extras=scm_extras)
72 event = EventClass(repo_name=repo_stub.repo_name, extras=scm_extras)
71 data = event.as_dict()
73 data = event.as_dict()
72 assert data['name'] == EventClass.name
74 assert data['name'] == EventClass.name
73 assert data['repo']['repo_name'] == repo_stub.repo_name
75 assert data['repo']['repo_name'] == repo_stub.repo_name
74 assert data['repo']['url']
76 assert data['repo']['url']
75
77 assert data['repo']['permalink_url']
76
78
77
79
78 @pytest.mark.parametrize('EventClass', [RepoPushEvent])
80 @pytest.mark.parametrize('EventClass', [RepoPushEvent])
79 def test_vcs_repo_push_event_serialize(repo_stub, scm_extras, EventClass):
81 def test_vcs_repo_push_event_serialize(config_stub, repo_stub, scm_extras, EventClass):
80 event = EventClass(repo_name=repo_stub.repo_name,
82 event = EventClass(repo_name=repo_stub.repo_name,
81 pushed_commit_ids=scm_extras['commit_ids'],
83 pushed_commit_ids=scm_extras['commit_ids'],
82 extras=scm_extras)
84 extras=scm_extras)
@@ -84,6 +86,7 b' def test_vcs_repo_push_event_serialize(r'
84 assert data['name'] == EventClass.name
86 assert data['name'] == EventClass.name
85 assert data['repo']['repo_name'] == repo_stub.repo_name
87 assert data['repo']['repo_name'] == repo_stub.repo_name
86 assert data['repo']['url']
88 assert data['repo']['url']
89 assert data['repo']['permalink_url']
87
90
88
91
89 def test_create_delete_repo_fires_events(backend):
92 def test_create_delete_repo_fires_events(backend):
@@ -286,6 +286,10 b' class Fixture(object):'
286 gr = UserGroup.get_by_group_name(group_name=name)
286 gr = UserGroup.get_by_group_name(group_name=name)
287 if gr:
287 if gr:
288 return gr
288 return gr
289 # map active flag to the real attribute. For API consistency of fixtures
290 if 'active' in kwargs:
291 kwargs['users_group_active'] = kwargs['active']
292 del kwargs['active']
289 form_data = self._get_user_group_create_params(name, **kwargs)
293 form_data = self._get_user_group_create_params(name, **kwargs)
290 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
294 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
291 user_group = UserGroupModel().create(
295 user_group = UserGroupModel().create(
@@ -28,7 +28,6 b' from rhodecode.model.meta import Session'
28 from rhodecode.tests import (
28 from rhodecode.tests import (
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
30 TestController, assert_session_flash, url)
30 TestController, assert_session_flash, url)
31 from rhodecode.tests.utils import AssertResponse
32
31
33
32
34 class GistUtility(object):
33 class GistUtility(object):
@@ -273,7 +272,7 b' class TestGistsController(TestController'
273
272
274 response.mustcontain('added file: gist-show-me<')
273 response.mustcontain('added file: gist-show-me<')
275
274
276 assert_response = AssertResponse(response)
275 assert_response = response.assert_response()
277 assert_response.element_equals_to(
276 assert_response.element_equals_to(
278 'div.rc-user span.user',
277 'div.rc-user span.user',
279 '<a href="/_profiles/test_admin">test_admin</a></span>')
278 '<a href="/_profiles/test_admin">test_admin</a></span>')
@@ -296,7 +295,7 b' class TestGistsController(TestController'
296 response = self.app.get(url('gist', gist_id=gist.gist_access_id))
295 response = self.app.get(url('gist', gist_id=gist.gist_access_id))
297 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
296 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
298
297
299 assert_response = AssertResponse(response)
298 assert_response = response.assert_response()
300 assert_response.element_equals_to(
299 assert_response.element_equals_to(
301 'div.rc-user span.user',
300 'div.rc-user span.user',
302 '<a href="/_profiles/test_admin">test_admin</a></span>')
301 '<a href="/_profiles/test_admin">test_admin</a></span>')
@@ -337,9 +336,7 b' class TestGistsController(TestController'
337
336
338 def test_user_first_name_is_escaped(self, user_util, create_gist):
337 def test_user_first_name_is_escaped(self, user_util, create_gist):
339 xss_atack_string = '"><script>alert(\'First Name\')</script>'
338 xss_atack_string = '"><script>alert(\'First Name\')</script>'
340 xss_escaped_string = (
339 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
341 '&#34;&gt;&lt;script&gt;alert(&#39;First Name&#39;)&lt;/script'
342 '&gt;')
343 password = 'test'
340 password = 'test'
344 user = user_util.create_user(
341 user = user_util.create_user(
345 firstname=xss_atack_string, password=password)
342 firstname=xss_atack_string, password=password)
@@ -349,8 +346,7 b' class TestGistsController(TestController'
349
346
350 def test_user_last_name_is_escaped(self, user_util, create_gist):
347 def test_user_last_name_is_escaped(self, user_util, create_gist):
351 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
348 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
352 xss_escaped_string = (
349 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
353 '&#34;&gt;&lt;script&gt;alert(&#39;Last Name&#39;)&lt;/script&gt;')
354 password = 'test'
350 password = 'test'
355 user = user_util.create_user(
351 user = user_util.create_user(
356 lastname=xss_atack_string, password=password)
352 lastname=xss_atack_string, password=password)
@@ -23,14 +23,19 b' import pytest'
23 from rhodecode.lib import helpers as h
23 from rhodecode.lib import helpers as h
24 from rhodecode.model.db import User, UserFollowing, Repository
24 from rhodecode.model.db import User, UserFollowing, Repository
25 from rhodecode.tests import (
25 from rhodecode.tests import (
26 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
26 TestController, url, TEST_USER_ADMIN_LOGIN, assert_session_flash)
27 assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import AssertResponse
28 from rhodecode.tests.utils import AssertResponse
30
29
31 fixture = Fixture()
30 fixture = Fixture()
32
31
33
32
33 def route_path(name, **kwargs):
34 return {
35 'home': '/',
36 }[name].format(**kwargs)
37
38
34 class TestMyAccountController(TestController):
39 class TestMyAccountController(TestController):
35 test_user_1 = 'testme'
40 test_user_1 = 'testme'
36 test_user_1_password = '0jd83nHNS/d23n'
41 test_user_1_password = '0jd83nHNS/d23n'
@@ -41,7 +46,7 b' class TestMyAccountController(TestContro'
41 fixture.destroy_users(cls.destroy_users)
46 fixture.destroy_users(cls.destroy_users)
42
47
43 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
48 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
44 response = self.app.get(url('home'))
49 response = self.app.get(route_path('home'))
45 assert_response = AssertResponse(response)
50 assert_response = AssertResponse(response)
46 element = assert_response.get_element('.logout #csrf_token')
51 element = assert_response.get_element('.logout #csrf_token')
47 assert element.value == csrf_token
52 assert element.value == csrf_token
@@ -52,26 +57,6 b' class TestMyAccountController(TestContro'
52
57
53 response.mustcontain('value="test_admin')
58 response.mustcontain('value="test_admin')
54
59
55 def test_my_account_my_repos(self):
56 self.log_user()
57 response = self.app.get(url('my_account_repos'))
58 repos = Repository.query().filter(
59 Repository.user == User.get_by_username(
60 TEST_USER_ADMIN_LOGIN)).all()
61 for repo in repos:
62 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
63
64 def test_my_account_my_watched(self):
65 self.log_user()
66 response = self.app.get(url('my_account_watched'))
67
68 repos = UserFollowing.query().filter(
69 UserFollowing.user == User.get_by_username(
70 TEST_USER_ADMIN_LOGIN)).all()
71 for repo in repos:
72 response.mustcontain(
73 '"name_raw": "%s"' % repo.follows_repository.repo_name)
74
75 @pytest.mark.backends("git", "hg")
60 @pytest.mark.backends("git", "hg")
76 def test_my_account_my_pullrequests(self, pr_util):
61 def test_my_account_my_pullrequests(self, pr_util):
77 self.log_user()
62 self.log_user()
@@ -84,57 +69,6 b' class TestMyAccountController(TestContro'
84 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
69 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
85 response.mustcontain('TestMyAccountPR')
70 response.mustcontain('TestMyAccountPR')
86
71
87 def test_my_account_my_emails(self):
88 self.log_user()
89 response = self.app.get(url('my_account_emails'))
90 response.mustcontain('No additional emails specified')
91
92 def test_my_account_my_emails_add_existing_email(self):
93 self.log_user()
94 response = self.app.get(url('my_account_emails'))
95 response.mustcontain('No additional emails specified')
96 response = self.app.post(url('my_account_emails'),
97 {'new_email': TEST_USER_REGULAR_EMAIL,
98 'csrf_token': self.csrf_token})
99 assert_session_flash(response, 'This e-mail address is already taken')
100
101 def test_my_account_my_emails_add_mising_email_in_form(self):
102 self.log_user()
103 response = self.app.get(url('my_account_emails'))
104 response.mustcontain('No additional emails specified')
105 response = self.app.post(url('my_account_emails'),
106 {'csrf_token': self.csrf_token})
107 assert_session_flash(response, 'Please enter an email address')
108
109 def test_my_account_my_emails_add_remove(self):
110 self.log_user()
111 response = self.app.get(url('my_account_emails'))
112 response.mustcontain('No additional emails specified')
113
114 response = self.app.post(url('my_account_emails'),
115 {'new_email': 'foo@barz.com',
116 'csrf_token': self.csrf_token})
117
118 response = self.app.get(url('my_account_emails'))
119
120 from rhodecode.model.db import UserEmailMap
121 email_id = UserEmailMap.query().filter(
122 UserEmailMap.user == User.get_by_username(
123 TEST_USER_ADMIN_LOGIN)).filter(
124 UserEmailMap.email == 'foo@barz.com').one().email_id
125
126 response.mustcontain('foo@barz.com')
127 response.mustcontain('<input id="del_email_id" name="del_email_id" '
128 'type="hidden" value="%s" />' % email_id)
129
130 response = self.app.post(
131 url('my_account_emails'), {
132 'del_email_id': email_id, '_method': 'delete',
133 'csrf_token': self.csrf_token})
134 assert_session_flash(response, 'Removed email address from user account')
135 response = self.app.get(url('my_account_emails'))
136 response.mustcontain('No additional emails specified')
137
138 @pytest.mark.parametrize(
72 @pytest.mark.parametrize(
139 "name, attrs", [
73 "name, attrs", [
140 ('firstname', {'firstname': 'new_username'}),
74 ('firstname', {'firstname': 'new_username'}),
@@ -25,6 +25,24 b' from rhodecode.tests import ('
25 TestController, url, clear_all_caches, assert_session_flash)
25 TestController, url, clear_all_caches, assert_session_flash)
26
26
27
27
28 def route_path(name, params=None, **kwargs):
29 import urllib
30 from rhodecode.apps._base import ADMIN_PREFIX
31
32 base_url = {
33 'edit_user_ips':
34 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
35 'edit_user_ips_add':
36 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
37 'edit_user_ips_delete':
38 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
39 }[name].format(**kwargs)
40
41 if params:
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
43 return base_url
44
45
28 class TestAdminPermissionsController(TestController):
46 class TestAdminPermissionsController(TestController):
29
47
30 @pytest.fixture(scope='class', autouse=True)
48 @pytest.fixture(scope='class', autouse=True)
@@ -181,10 +199,9 b' class TestAdminPermissionsController(Tes'
181
199
182 # ADD
200 # ADD
183 default_user_id = User.get_default_user().user_id
201 default_user_id = User.get_default_user().user_id
184 response = self.app.post(
202 self.app.post(
185 url('edit_user_ips', user_id=default_user_id),
203 route_path('edit_user_ips_add', user_id=default_user_id),
186 params={'new_ip': '127.0.0.0/24', '_method': 'put',
204 params={'new_ip': '127.0.0.0/24', 'csrf_token': self.csrf_token})
187 'csrf_token': self.csrf_token})
188
205
189 response = self.app.get(url('admin_permissions_ips'))
206 response = self.app.get(url('admin_permissions_ips'))
190 response.mustcontain('127.0.0.0/24')
207 response.mustcontain('127.0.0.0/24')
@@ -196,9 +213,11 b' class TestAdminPermissionsController(Tes'
196 default_user_id).first().ip_id
213 default_user_id).first().ip_id
197
214
198 response = self.app.post(
215 response = self.app.post(
199 url('edit_user_ips', user_id=default_user_id),
216 route_path('edit_user_ips_delete', user_id=default_user_id),
200 params={'_method': 'delete', 'del_ip_id': del_ip_id,
217 params={'del_ip_id': del_ip_id, 'csrf_token': self.csrf_token})
201 'csrf_token': self.csrf_token})
218
219 assert_session_flash(response, 'Removed ip address from user whitelist')
220
202 clear_all_caches()
221 clear_all_caches()
203 response = self.app.get(url('admin_permissions_ips'))
222 response = self.app.get(url('admin_permissions_ips'))
204 response.mustcontain('All IP addresses are allowed')
223 response.mustcontain('All IP addresses are allowed')
@@ -31,6 +31,7 b' from rhodecode.tests.fixture import Fixt'
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34
34 def test_update(app, csrf_token, autologin_user, user_util):
35 def test_update(app, csrf_token, autologin_user, user_util):
35 repo_group = user_util.create_repo_group()
36 repo_group = user_util.create_repo_group()
36 description = 'description for newly created repo group'
37 description = 'description for newly created repo group'
@@ -123,11 +124,13 b' class _BaseTest(TestController):'
123 # run the check page that triggers the flash message
124 # run the check page that triggers the flash message
124 # response = self.app.get(url('repo_check_home', repo_name=repo_name))
125 # response = self.app.get(url('repo_check_home', repo_name=repo_name))
125 # assert response.json == {u'result': True}
126 # assert response.json == {u'result': True}
127 repo_gr_url = h.route_path(
128 'repo_group_home', repo_group_name=repo_group_name)
129
126 assert_session_flash(
130 assert_session_flash(
127 response,
131 response,
128 u'Created repository group <a href="%s">%s</a>' % (
132 'Created repository group <a href="%s">%s</a>' % (
129 h.url('repo_group_home', group_name=repo_group_name),
133 repo_gr_url, repo_group_name_unicode))
130 repo_group_name_unicode))
131
134
132 # # test if the repo group was created in the database
135 # # test if the repo group was created in the database
133 new_repo_group = RepoGroupModel()._get_repo_group(
136 new_repo_group = RepoGroupModel()._get_repo_group(
@@ -138,8 +141,7 b' class _BaseTest(TestController):'
138 assert new_repo_group.group_description == description
141 assert new_repo_group.group_description == description
139
142
140 # test if the repository is visible in the list ?
143 # test if the repository is visible in the list ?
141 response = self.app.get(
144 response = self.app.get(repo_gr_url)
142 url('repo_group_home', group_name=repo_group_name))
143 response.mustcontain(repo_group_name)
145 response.mustcontain(repo_group_name)
144
146
145 # test if the repository group was created on filesystem
147 # test if the repository group was created on filesystem
@@ -173,7 +175,7 b' class _BaseTest(TestController):'
173 assert_session_flash(
175 assert_session_flash(
174 response,
176 response,
175 u'Created repository group <a href="%s">%s</a>' % (
177 u'Created repository group <a href="%s">%s</a>' % (
176 h.url('repo_group_home', group_name=expected_group_name),
178 h.route_path('repo_group_home', repo_group_name=expected_group_name),
177 expected_group_name_unicode))
179 expected_group_name_unicode))
178 finally:
180 finally:
179 RepoGroupModel().delete(expected_group_name_unicode)
181 RepoGroupModel().delete(expected_group_name_unicode)
@@ -199,6 +201,13 b' class TestRepoGroupsControllerGIT(_BaseT'
199 REPO_TYPE = 'git'
201 REPO_TYPE = 'git'
200
202
201
203
204 class TestRepoGroupsControllerNonAsciiGit(_BaseTest):
205 REPO_GROUP = None
206 NEW_REPO_GROUP = 'git_repo_ąć'
207 REPO = GIT_REPO
208 REPO_TYPE = 'git'
209
210
202 class TestRepoGroupsControllerHG(_BaseTest):
211 class TestRepoGroupsControllerHG(_BaseTest):
203 REPO_GROUP = None
212 REPO_GROUP = None
204 NEW_REPO_GROUP = 'hg_repo'
213 NEW_REPO_GROUP = 'hg_repo'
@@ -25,9 +25,9 b' import pytest'
25
25
26 from rhodecode.lib import auth
26 from rhodecode.lib import auth
27 from rhodecode.lib.utils2 import safe_str, str2bool
27 from rhodecode.lib.utils2 import safe_str, str2bool
28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
28 from rhodecode.lib import helpers as h
29 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
29 from rhodecode.model.db import (
30 Permission
30 Repository, RepoGroup, UserRepoToPerm, User, Permission)
31 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
32 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo_group import RepoGroupModel
33 from rhodecode.model.repo_group import RepoGroupModel
@@ -44,7 +44,7 b' fixture = Fixture()'
44
44
45
45
46 @pytest.mark.usefixtures("app")
46 @pytest.mark.usefixtures("app")
47 class TestAdminRepos:
47 class TestAdminRepos(object):
48
48
49 def test_index(self):
49 def test_index(self):
50 self.app.get(url('repos'))
50 self.app.get(url('repos'))
@@ -63,13 +63,14 b' class TestAdminRepos:'
63 assert_response.element_contains('#repo_type', 'svn')
63 assert_response.element_contains('#repo_type', 'svn')
64 assert_response.element_contains('#repo_type', 'hg')
64 assert_response.element_contains('#repo_type', 'hg')
65
65
66 @pytest.mark.parametrize("suffix", [u'', u''], ids=['', 'non-ascii'])
66 @pytest.mark.parametrize("suffix",
67 [u'', u'xxa'], ids=['', 'non-ascii'])
67 def test_create(self, autologin_user, backend, suffix, csrf_token):
68 def test_create(self, autologin_user, backend, suffix, csrf_token):
68 repo_name_unicode = backend.new_repo_name(suffix=suffix)
69 repo_name_unicode = backend.new_repo_name(suffix=suffix)
69 repo_name = repo_name_unicode.encode('utf8')
70 repo_name = repo_name_unicode.encode('utf8')
70 description_unicode = u'description for newly created repo' + suffix
71 description_unicode = u'description for newly created repo' + suffix
71 description = description_unicode.encode('utf8')
72 description = description_unicode.encode('utf8')
72 self.app.post(
73 response = self.app.post(
73 url('repos'),
74 url('repos'),
74 fixture._get_repo_create_params(
75 fixture._get_repo_create_params(
75 repo_private=False,
76 repo_private=False,
@@ -77,8 +78,7 b' class TestAdminRepos:'
77 repo_type=backend.alias,
78 repo_type=backend.alias,
78 repo_description=description,
79 repo_description=description,
79 csrf_token=csrf_token),
80 csrf_token=csrf_token),
80 status=302
81 status=302)
81 )
82
82
83 self.assert_repository_is_created_correctly(
83 self.assert_repository_is_created_correctly(
84 repo_name, description, backend)
84 repo_name, description, backend)
@@ -368,85 +368,23 b' class TestAdminRepos:'
368 csrf_token=csrf_token))
368 csrf_token=csrf_token))
369 response.mustcontain('invalid clone url')
369 response.mustcontain('invalid clone url')
370
370
371 @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii'])
371 def test_create_with_git_suffix(
372 def test_delete(self, autologin_user, backend, suffix, csrf_token):
372 self, autologin_user, backend, csrf_token):
373 repo = backend.create_repo(name_suffix=suffix)
373 repo_name = backend.new_repo_name() + ".git"
374 repo_name = repo.repo_name
374 description = 'description for newly created repo'
375
375 response = self.app.post(
376 response = self.app.post(url('repo', repo_name=repo_name),
376 url('repos'),
377 params={'_method': 'delete',
377 fixture._get_repo_create_params(
378 'csrf_token': csrf_token})
378 repo_private=False,
379 assert_session_flash(response, 'Deleted repository %s' % (repo_name))
379 repo_name=repo_name,
380 response.follow()
380 repo_type=backend.alias,
381
381 repo_description=description,
382 # check if repo was deleted from db
382 csrf_token=csrf_token))
383 assert RepoModel().get_by_repo_name(repo_name) is None
383 response.mustcontain('Repository name cannot end with .git')
384 assert not repo_on_filesystem(repo_name)
385
384
386 def test_show(self, autologin_user, backend):
385 def test_show(self, autologin_user, backend):
387 self.app.get(url('repo', repo_name=backend.repo_name))
386 self.app.get(url('repo', repo_name=backend.repo_name))
388
387
389 def test_edit(self, backend, autologin_user):
390 self.app.get(url('edit_repo', repo_name=backend.repo_name))
391
392 def test_edit_accessible_when_missing_requirements(
393 self, backend_hg, autologin_user):
394 scm_patcher = mock.patch.object(
395 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
396 with scm_patcher:
397 self.app.get(url('edit_repo', repo_name=backend_hg.repo_name))
398
399 def test_set_private_flag_sets_default_to_none(
400 self, autologin_user, backend, csrf_token):
401 # initially repository perm should be read
402 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
403 assert len(perm) == 1
404 assert perm[0].permission.permission_name == 'repository.read'
405 assert not backend.repo.private
406
407 response = self.app.post(
408 url('repo', repo_name=backend.repo_name),
409 fixture._get_repo_create_params(
410 repo_private=1,
411 repo_name=backend.repo_name,
412 repo_type=backend.alias,
413 user=TEST_USER_ADMIN_LOGIN,
414 _method='put',
415 csrf_token=csrf_token))
416 assert_session_flash(
417 response,
418 msg='Repository %s updated successfully' % (backend.repo_name))
419 assert backend.repo.private
420
421 # now the repo default permission should be None
422 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
423 assert len(perm) == 1
424 assert perm[0].permission.permission_name == 'repository.none'
425
426 response = self.app.post(
427 url('repo', repo_name=backend.repo_name),
428 fixture._get_repo_create_params(
429 repo_private=False,
430 repo_name=backend.repo_name,
431 repo_type=backend.alias,
432 user=TEST_USER_ADMIN_LOGIN,
433 _method='put',
434 csrf_token=csrf_token))
435 assert_session_flash(
436 response,
437 msg='Repository %s updated successfully' % (backend.repo_name))
438 assert not backend.repo.private
439
440 # we turn off private now the repo default permission should stay None
441 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
442 assert len(perm) == 1
443 assert perm[0].permission.permission_name == 'repository.none'
444
445 # update this permission back
446 perm[0].permission = Permission.get_by_key('repository.read')
447 Session().add(perm[0])
448 Session().commit()
449
450 def test_default_user_cannot_access_private_repo_in_a_group(
388 def test_default_user_cannot_access_private_repo_in_a_group(
451 self, autologin_user, user_util, backend, csrf_token):
389 self, autologin_user, user_util, backend, csrf_token):
452
390
@@ -461,82 +399,6 b' class TestAdminRepos:'
461 assert permissions[0].permission.permission_name == 'repository.none'
399 assert permissions[0].permission.permission_name == 'repository.none'
462 assert permissions[0].repository.private is True
400 assert permissions[0].repository.private is True
463
401
464 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
465 repo = backend.repo
466 response = self.app.get(
467 url('edit_repo_advanced', repo_name=backend.repo_name))
468 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
469 response.mustcontain(no=[opt])
470
471 def test_set_fork_of_target_repo(
472 self, autologin_user, backend, csrf_token):
473 target_repo = 'target_%s' % backend.alias
474 fixture.create_repo(target_repo, repo_type=backend.alias)
475 repo2 = Repository.get_by_repo_name(target_repo)
476 response = self.app.post(
477 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
478 params={'id_fork_of': repo2.repo_id, '_method': 'put',
479 'csrf_token': csrf_token})
480 repo = Repository.get_by_repo_name(backend.repo_name)
481 repo2 = Repository.get_by_repo_name(target_repo)
482 assert_session_flash(
483 response,
484 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
485
486 assert repo.fork == repo2
487 response = response.follow()
488 # check if given repo is selected
489
490 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
491 url('summary_home', repo_name=repo2.repo_name), repo2.repo_name)
492
493 response.mustcontain(opt)
494
495 fixture.destroy_repo(target_repo, forks='detach')
496
497 @pytest.mark.backends("hg", "git")
498 def test_set_fork_of_other_type_repo(self, autologin_user, backend,
499 csrf_token):
500 TARGET_REPO_MAP = {
501 'git': {
502 'type': 'hg',
503 'repo_name': HG_REPO},
504 'hg': {
505 'type': 'git',
506 'repo_name': GIT_REPO},
507 }
508 target_repo = TARGET_REPO_MAP[backend.alias]
509
510 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
511 response = self.app.post(
512 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
513 params={'id_fork_of': repo2.repo_id, '_method': 'put',
514 'csrf_token': csrf_token})
515 assert_session_flash(
516 response,
517 'Cannot set repository as fork of repository with other type')
518
519 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
520 # mark it as None
521 response = self.app.post(
522 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
523 params={'id_fork_of': None, '_method': 'put',
524 'csrf_token': csrf_token})
525 assert_session_flash(
526 response,
527 'Marked repo %s as fork of %s'
528 % (backend.repo_name, "Nothing"))
529 assert backend.repo.fork is None
530
531 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
532 repo = Repository.get_by_repo_name(backend.repo_name)
533 response = self.app.post(
534 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
535 params={'id_fork_of': repo.repo_id, '_method': 'put',
536 'csrf_token': csrf_token})
537 assert_session_flash(
538 response, 'An error occurred during this operation')
539
540 def test_create_on_top_level_without_permissions(self, backend):
402 def test_create_on_top_level_without_permissions(self, backend):
541 session = login_user_session(
403 session = login_user_session(
542 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
404 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
@@ -596,15 +458,15 b' class TestAdminRepos:'
596
458
597 def assert_repository_is_created_correctly(
459 def assert_repository_is_created_correctly(
598 self, repo_name, description, backend):
460 self, repo_name, description, backend):
599 repo_name_utf8 = repo_name.encode('utf-8')
461 repo_name_utf8 = safe_str(repo_name)
600
462
601 # run the check page that triggers the flash message
463 # run the check page that triggers the flash message
602 response = self.app.get(url('repo_check_home', repo_name=repo_name))
464 response = self.app.get(url('repo_check_home', repo_name=repo_name))
603 assert response.json == {u'result': True}
465 assert response.json == {u'result': True}
604 assert_session_flash(
466
605 response,
467 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
606 u'Created repository <a href="/%s">%s</a>'
468 urllib.quote(repo_name_utf8), repo_name)
607 % (urllib.quote(repo_name_utf8), repo_name))
469 assert_session_flash(response, flash_msg)
608
470
609 # test if the repo was created in the database
471 # test if the repo was created in the database
610 new_repo = RepoModel().get_by_repo_name(repo_name)
472 new_repo = RepoModel().get_by_repo_name(repo_name)
@@ -613,7 +475,7 b' class TestAdminRepos:'
613 assert new_repo.description == description
475 assert new_repo.description == description
614
476
615 # test if the repository is visible in the list ?
477 # test if the repository is visible in the list ?
616 response = self.app.get(url('summary_home', repo_name=repo_name))
478 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
617 response.mustcontain(repo_name)
479 response.mustcontain(repo_name)
618 response.mustcontain(backend.alias)
480 response.mustcontain(backend.alias)
619
481
@@ -628,6 +490,7 b' class TestVcsSettings(object):'
628 'hooks_changegroup_push_logger': False,
490 'hooks_changegroup_push_logger': False,
629 'hooks_outgoing_pull_logger': False,
491 'hooks_outgoing_pull_logger': False,
630 'extensions_largefiles': False,
492 'extensions_largefiles': False,
493 'extensions_evolve': False,
631 'phases_publish': 'False',
494 'phases_publish': 'False',
632 'rhodecode_pr_merge_enabled': False,
495 'rhodecode_pr_merge_enabled': False,
633 'rhodecode_use_outdated_comments': False,
496 'rhodecode_use_outdated_comments': False,
@@ -783,13 +646,14 b' class TestVcsSettings(object):'
783 self, backend, user_util, settings_util, csrf_token):
646 self, backend, user_util, settings_util, csrf_token):
784 repo = backend.create_repo()
647 repo = backend.create_repo()
785 repo_name = repo.repo_name
648 repo_name = repo.repo_name
786 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
787
649
788 logout_user_session(self.app, csrf_token)
650 logout_user_session(self.app, csrf_token)
789 session = login_user_session(
651 session = login_user_session(
790 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
652 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
791 new_csrf_token = auth.get_csrf_token(session)
653 new_csrf_token = auth.get_csrf_token(session)
792
654
655 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
656 repo = Repository.get_by_repo_name(repo_name)
793 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
657 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
794 data = self.FORM_DATA.copy()
658 data = self.FORM_DATA.copy()
795 data['csrf_token'] = new_csrf_token
659 data['csrf_token'] = new_csrf_token
@@ -1165,11 +1029,14 b' class TestVcsSettings(object):'
1165 self, backend_svn, user_util, settings_util, csrf_token):
1029 self, backend_svn, user_util, settings_util, csrf_token):
1166 repo = backend_svn.create_repo()
1030 repo = backend_svn.create_repo()
1167 repo_name = repo.repo_name
1031 repo_name = repo.repo_name
1168 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1032
1169 logout_user_session(self.app, csrf_token)
1033 logout_user_session(self.app, csrf_token)
1170 session = login_user_session(
1034 session = login_user_session(
1171 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1035 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1172 csrf_token = auth.get_csrf_token(session)
1036 csrf_token = auth.get_csrf_token(session)
1037
1038 repo = Repository.get_by_repo_name(repo_name)
1039 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1173 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1040 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1174 branch = settings_util.create_repo_rhodecode_ui(
1041 branch = settings_util.create_repo_rhodecode_ui(
1175 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1042 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
@@ -275,6 +275,23 b' class TestAdminSettingsVcs(object):'
275 'value="True" checked="checked" />')
275 'value="True" checked="checked" />')
276 response.mustcontain(extensions_input)
276 response.mustcontain(extensions_input)
277
277
278 def test_extensions_hgevolve(self, app, form_defaults, csrf_token):
279 form_defaults.update({
280 'csrf_token': csrf_token,
281 'extensions_evolve': 'True',
282 })
283 response = app.post(
284 url('admin_settings_vcs'),
285 params=form_defaults,
286 status=302)
287
288 response = response.follow()
289 extensions_input = (
290 '<input id="extensions_evolve" '
291 'name="extensions_evolve" type="checkbox" '
292 'value="True" checked="checked" />')
293 response.mustcontain(extensions_input)
294
278 def test_has_a_section_for_pull_request_settings(self, app):
295 def test_has_a_section_for_pull_request_settings(self, app):
279 response = app.get(url('admin_settings_vcs'))
296 response = app.get(url('admin_settings_vcs'))
280 response.mustcontain('Pull Request Settings')
297 response.mustcontain('Pull Request Settings')
@@ -445,7 +462,7 b' class TestOpenSourceLicenses(object):'
445 '.panel-heading', 'Licenses of Third Party Packages')
462 '.panel-heading', 'Licenses of Third Party Packages')
446
463
447 def test_forbidden_when_normal_user(self, autologin_regular_user):
464 def test_forbidden_when_normal_user(self, autologin_regular_user):
448 self.app.get(self._get_url(), status=403)
465 self.app.get(self._get_url(), status=404)
449
466
450
467
451 @pytest.mark.usefixtures('app')
468 @pytest.mark.usefixtures('app')
@@ -458,7 +475,7 b' class TestUserSessions(object):'
458 }[name]
475 }[name]
459
476
460 def test_forbidden_when_normal_user(self, autologin_regular_user):
477 def test_forbidden_when_normal_user(self, autologin_regular_user):
461 self.app.get(self._get_url(), status=403)
478 self.app.get(self._get_url(), status=404)
462
479
463 def test_show_sessions_page(self, autologin_user):
480 def test_show_sessions_page(self, autologin_user):
464 response = self.app.get(self._get_url(), status=200)
481 response = self.app.get(self._get_url(), status=200)
@@ -485,7 +502,7 b' class TestAdminSystemInfo(object):'
485 }[name]
502 }[name]
486
503
487 def test_forbidden_when_normal_user(self, autologin_regular_user):
504 def test_forbidden_when_normal_user(self, autologin_regular_user):
488 self.app.get(self._get_url(), status=403)
505 self.app.get(self._get_url(), status=404)
489
506
490 def test_system_info_page(self, autologin_user):
507 def test_system_info_page(self, autologin_user):
491 response = self.app.get(self._get_url())
508 response = self.app.get(self._get_url())
@@ -510,66 +510,3 b' class TestAdminUsersController(TestContr'
510 element = assert_response.get_element(css_selector)
510 element = assert_response.get_element(css_selector)
511 assert element.value == default_permissions[permission]
511 assert element.value == default_permissions[permission]
512
512
513 def test_ips(self):
514 self.log_user()
515 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
516 response = self.app.get(url('edit_user_ips', user_id=user.user_id))
517 response.mustcontain('All IP addresses are allowed')
518
519 @pytest.mark.parametrize("test_name, ip, ip_range, failure", [
520 ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
521 ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
522 ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
523 ('0/8', '0.0.0.0/8', '0.0.0.0 - 0.255.255.255', False),
524 ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
525 ('127_bad_ip', 'foobar', 'foobar', True),
526 ])
527 def test_add_ip(self, test_name, ip, ip_range, failure):
528 self.log_user()
529 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
530 user_id = user.user_id
531
532 response = self.app.post(url('edit_user_ips', user_id=user_id),
533 params={'new_ip': ip, '_method': 'put',
534 'csrf_token': self.csrf_token})
535
536 if failure:
537 assert_session_flash(
538 response, 'Please enter a valid IPv4 or IpV6 address')
539 response = self.app.get(url('edit_user_ips', user_id=user_id))
540 response.mustcontain(no=[ip])
541 response.mustcontain(no=[ip_range])
542
543 else:
544 response = self.app.get(url('edit_user_ips', user_id=user_id))
545 response.mustcontain(ip)
546 response.mustcontain(ip_range)
547
548 # cleanup
549 for del_ip in UserIpMap.query().filter(
550 UserIpMap.user_id == user_id).all():
551 Session().delete(del_ip)
552 Session().commit()
553
554 def test_delete_ip(self):
555 self.log_user()
556 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
557 user_id = user.user_id
558 ip = '127.0.0.1/32'
559 ip_range = '127.0.0.1 - 127.0.0.1'
560 new_ip = UserModel().add_extra_ip(user_id, ip)
561 Session().commit()
562 new_ip_id = new_ip.ip_id
563
564 response = self.app.get(url('edit_user_ips', user_id=user_id))
565 response.mustcontain(ip)
566 response.mustcontain(ip_range)
567
568 self.app.post(url('edit_user_ips', user_id=user_id),
569 params={'_method': 'delete', 'del_ip_id': new_ip_id,
570 'csrf_token': self.csrf_token})
571
572 response = self.app.get(url('edit_user_ips', user_id=user_id))
573 response.mustcontain('All IP addresses are allowed')
574 response.mustcontain(no=[ip])
575 response.mustcontain(no=[ip_range])
@@ -489,7 +489,22 b' class TestCompareController(object):'
489 compare_page = ComparePage(response)
489 compare_page = ComparePage(response)
490 compare_page.contains_commits(commits=[commit1], ancestors=[commit0])
490 compare_page.contains_commits(commits=[commit1], ancestors=[commit0])
491
491
492 def test_errors_when_comparing_unknown_repo(self, backend):
492 def test_errors_when_comparing_unknown_source_repo(self, backend):
493 repo = backend.repo
494 badrepo = 'badrepo'
495
496 response = self.app.get(
497 url('compare_url',
498 repo_name=badrepo,
499 source_ref_type="rev",
500 source_ref='tip',
501 target_ref_type="rev",
502 target_ref='tip',
503 target_repo=repo.repo_name,
504 merge='1',),
505 status=404)
506
507 def test_errors_when_comparing_unknown_target_repo(self, backend):
493 repo = backend.repo
508 repo = backend.repo
494 badrepo = 'badrepo'
509 badrepo = 'badrepo'
495
510
@@ -504,7 +519,8 b' class TestCompareController(object):'
504 merge='1',),
519 merge='1',),
505 status=302)
520 status=302)
506 redirected = response.follow()
521 redirected = response.follow()
507 redirected.mustcontain('Could not find the other repo: %s' % badrepo)
522 redirected.mustcontain(
523 'Could not find the target repo: `{}`'.format(badrepo))
508
524
509 def test_compare_not_in_preview_mode(self, backend_stub):
525 def test_compare_not_in_preview_mode(self, backend_stub):
510 commit0 = backend_stub.repo.get_commit(commit_idx=0)
526 commit0 = backend_stub.repo.get_commit(commit_idx=0)
@@ -30,6 +30,12 b' from rhodecode.tests.fixture import Fixt'
30 fixture = Fixture()
30 fixture = Fixture()
31
31
32
32
33 def route_path(name, **kwargs):
34 return {
35 'home': '/',
36 }[name].format(**kwargs)
37
38
33 class TestAdminUsersGroupsController(TestController):
39 class TestAdminUsersGroupsController(TestController):
34
40
35 def test_regular_user_cannot_see_admin_interfaces(self, user_util):
41 def test_regular_user_cannot_see_admin_interfaces(self, user_util):
@@ -37,7 +43,7 b' class TestAdminUsersGroupsController(Tes'
37 self.log_user(user.username, 'qweqwe')
43 self.log_user(user.username, 'qweqwe')
38
44
39 # check if in home view, such user doesn't see the "admin" menus
45 # check if in home view, such user doesn't see the "admin" menus
40 response = self.app.get(url('home'))
46 response = self.app.get(route_path('home'))
41
47
42 assert_response = response.assert_response()
48 assert_response = response.assert_response()
43
49
@@ -69,7 +75,7 b' class TestAdminUsersGroupsController(Tes'
69
75
70 self.log_user(username, 'qweqwe')
76 self.log_user(username, 'qweqwe')
71 # check if in home view, such user doesn't see the "admin" menus
77 # check if in home view, such user doesn't see the "admin" menus
72 response = self.app.get(url('home'))
78 response = self.app.get(route_path('home'))
73
79
74 assert_response = response.assert_response()
80 assert_response = response.assert_response()
75
81
@@ -109,7 +115,7 b' class TestAdminUsersGroupsController(Tes'
109
115
110 self.log_user(username, 'qweqwe')
116 self.log_user(username, 'qweqwe')
111 # check if in home view, such user doesn't see the "admin" menus
117 # check if in home view, such user doesn't see the "admin" menus
112 response = self.app.get(url('home'))
118 response = self.app.get(route_path('home'))
113
119
114 assert_response = response.assert_response()
120 assert_response = response.assert_response()
115
121
@@ -484,7 +484,7 b' class TestRawFileHandling(object):'
484
484
485 msg = (
485 msg = (
486 "There is no file nor directory at the given path: "
486 "There is no file nor directory at the given path: "
487 "&#39;%s&#39; at commit %s" % (f_path, commit.short_id))
487 "`%s` at commit %s" % (f_path, commit.short_id))
488 response.mustcontain(msg)
488 response.mustcontain(msg)
489
489
490 def test_raw_ok(self, backend):
490 def test_raw_ok(self, backend):
@@ -517,7 +517,7 b' class TestRawFileHandling(object):'
517 f_path=f_path), status=404)
517 f_path=f_path), status=404)
518 msg = (
518 msg = (
519 "There is no file nor directory at the given path: "
519 "There is no file nor directory at the given path: "
520 "&#39;%s&#39; at commit %s" % (f_path, commit.short_id))
520 "`%s` at commit %s" % (f_path, commit.short_id))
521 response.mustcontain(msg)
521 response.mustcontain(msg)
522
522
523 def test_raw_svg_should_not_be_rendered(self, backend):
523 def test_raw_svg_should_not_be_rendered(self, backend):
@@ -758,9 +758,8 b' class TestChangingFiles:'
758 'csrf_token': csrf_token,
758 'csrf_token': csrf_token,
759 },
759 },
760 status=302)
760 status=302)
761 assert_session_flash(
761 assert_session_flash(response,
762 response, 'Successfully committed to %s'
762 'Successfully committed new file `{}`'.format(os.path.join(filename)))
763 % os.path.join(filename))
764
763
765 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
764 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
766 response = self.app.post(
765 response = self.app.post(
@@ -796,11 +795,12 b' class TestChangingFiles:'
796
795
797 # Not allowed, redirect to the summary
796 # Not allowed, redirect to the summary
798 redirected = response.follow()
797 redirected = response.follow()
799 summary_url = url('summary_home', repo_name=repo.repo_name)
798 summary_url = h.route_path('repo_summary', repo_name=repo.repo_name)
800
799
801 # As there are no commits, displays the summary page with the error of
800 # As there are no commits, displays the summary page with the error of
802 # creating a file with no filename
801 # creating a file with no filename
803 assert redirected.req.path == summary_url
802
803 assert redirected.request.path == summary_url
804
804
805 @pytest.mark.parametrize("location, filename", [
805 @pytest.mark.parametrize("location, filename", [
806 ('/abs', 'foo'),
806 ('/abs', 'foo'),
@@ -847,9 +847,9 b' class TestChangingFiles:'
847 'csrf_token': csrf_token,
847 'csrf_token': csrf_token,
848 },
848 },
849 status=302)
849 status=302)
850 assert_session_flash(
850 assert_session_flash(response,
851 response, 'Successfully committed to %s'
851 'Successfully committed new file `{}`'.format(
852 % os.path.join(location, filename))
852 os.path.join(location, filename)))
853
853
854 def test_edit_file_view(self, backend):
854 def test_edit_file_view(self, backend):
855 response = self.app.get(
855 response = self.app.get(
@@ -893,7 +893,7 b' class TestChangingFiles:'
893 },
893 },
894 status=302)
894 status=302)
895 assert_session_flash(
895 assert_session_flash(
896 response, 'Successfully committed to vcs/nodes.py')
896 response, 'Successfully committed changes to file `vcs/nodes.py`')
897 tip = repo.get_commit(commit_idx=-1)
897 tip = repo.get_commit(commit_idx=-1)
898 assert tip.message == 'I committed'
898 assert tip.message == 'I committed'
899
899
@@ -920,7 +920,7 b' class TestChangingFiles:'
920 },
920 },
921 status=302)
921 status=302)
922 assert_session_flash(
922 assert_session_flash(
923 response, 'Successfully committed to vcs/nodes.py')
923 response, 'Successfully committed changes to file `vcs/nodes.py`')
924 tip = repo.get_commit(commit_idx=-1)
924 tip = repo.get_commit(commit_idx=-1)
925 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
925 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
926
926
@@ -960,7 +960,7 b' class TestChangingFiles:'
960 },
960 },
961 status=302)
961 status=302)
962 assert_session_flash(
962 assert_session_flash(
963 response, 'Successfully deleted file vcs/nodes.py')
963 response, 'Successfully deleted file `vcs/nodes.py`')
964
964
965
965
966 def assert_files_in_response(response, files, params):
966 def assert_files_in_response(response, files, params):
@@ -22,6 +22,7 b' import pytest'
22
22
23 from rhodecode.tests import *
23 from rhodecode.tests import *
24 from rhodecode.tests.fixture import Fixture
24 from rhodecode.tests.fixture import Fixture
25 from rhodecode.lib import helpers as h
25
26
26 from rhodecode.model.db import Repository
27 from rhodecode.model.db import Repository
27 from rhodecode.model.repo import RepoModel
28 from rhodecode.model.repo import RepoModel
@@ -74,7 +75,7 b' class _BaseTest(TestController):'
74 repo_name = self.REPO
75 repo_name = self.REPO
75 self.app.post(
76 self.app.post(
76 url(controller='forks', action='fork_create', repo_name=repo_name),
77 url(controller='forks', action='fork_create', repo_name=repo_name),
77 {'csrf_token': self.csrf_token}, status=403)
78 {'csrf_token': self.csrf_token}, status=404)
78
79
79 def test_index_with_fork(self):
80 def test_index_with_fork(self):
80 self.log_user()
81 self.log_user()
@@ -106,9 +107,7 b' class _BaseTest(TestController):'
106 )
107 )
107
108
108 # remove this fork
109 # remove this fork
109 response = self.app.post(
110 fixture.destroy_repo(fork_name)
110 url('repo', repo_name=fork_name),
111 params={'_method': 'delete', 'csrf_token': self.csrf_token})
112
111
113 def test_fork_create_into_group(self):
112 def test_fork_create_into_group(self):
114 self.log_user()
113 self.log_user()
@@ -149,7 +148,7 b' class _BaseTest(TestController):'
149 assert fork_repo.fork.repo_name == repo_name
148 assert fork_repo.fork.repo_name == repo_name
150
149
151 # test if the repository is visible in the list ?
150 # test if the repository is visible in the list ?
152 response = self.app.get(url('summary_home', repo_name=fork_name_full))
151 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name_full))
153 response.mustcontain(fork_name_full)
152 response.mustcontain(fork_name_full)
154 response.mustcontain(self.REPO_TYPE)
153 response.mustcontain(self.REPO_TYPE)
155
154
@@ -195,7 +194,7 b' class _BaseTest(TestController):'
195 assert fork_repo.fork.repo_name == repo_name
194 assert fork_repo.fork.repo_name == repo_name
196
195
197 # test if the repository is visible in the list ?
196 # test if the repository is visible in the list ?
198 response = self.app.get(url('summary_home', repo_name=fork_name))
197 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name))
199 response.mustcontain(fork_name)
198 response.mustcontain(fork_name)
200 response.mustcontain(self.REPO_TYPE)
199 response.mustcontain(self.REPO_TYPE)
201 response.mustcontain('Fork of')
200 response.mustcontain('Fork of')
@@ -256,7 +255,7 b' class TestHG(_BaseTest):'
256
255
257 @pytest.mark.usefixtures('app', 'autologin_user')
256 @pytest.mark.usefixtures('app', 'autologin_user')
258 @pytest.mark.skip_backends('git','hg')
257 @pytest.mark.skip_backends('git','hg')
259 class TestSVNFork:
258 class TestSVNFork(object):
260
259
261 def test_fork_redirects(self, backend):
260 def test_fork_redirects(self, backend):
262 denied_actions = ['fork','fork_create']
261 denied_actions = ['fork','fork_create']
@@ -268,7 +267,7 b' class TestSVNFork:'
268
267
269 # Not allowed, redirect to the summary
268 # Not allowed, redirect to the summary
270 redirected = response.follow()
269 redirected = response.follow()
271 summary_url = url('summary_home', repo_name=backend.repo_name)
270 summary_url = h.route_path('repo_summary', repo_name=backend.repo_name)
272
271
273 # URL adds leading slash and path doesn't have it
272 # URL adds leading slash and path doesn't have it
274 assert redirected.req.path == summary_url
273 assert redirected.request.path == summary_url
@@ -214,7 +214,7 b' def _post_integration_test_helper(app, u'
214 checks if the redirect url is correct.
214 checks if the redirect url is correct.
215 """
215 """
216
216
217 app.post(url, params={}, status=403) # missing csrf check
217 app.post(url, params={}, status=403) # missing csrf check
218 response = app.post(url, params={'csrf_token': csrf_token})
218 response = app.post(url, params={'csrf_token': csrf_token})
219 assert response.status_code == 200
219 assert response.status_code == 200
220 assert 'Errors exist' in response.body
220 assert 'Errors exist' in response.body
@@ -25,10 +25,11 b' import pytest'
25
25
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN)
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN,
29 no_newline_id_generator)
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 from rhodecode.lib.auth import check_password
31 from rhodecode.lib.auth import check_password
32 from rhodecode.lib import helpers as h
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model import validators
34 from rhodecode.model import validators
34 from rhodecode.model.db import User, Notification, UserApiKeys
35 from rhodecode.model.db import User, Notification, UserApiKeys
@@ -71,7 +72,7 b' class TestLoginController(object):'
71 {'username': 'test_admin',
72 {'username': 'test_admin',
72 'password': 'test12'})
73 'password': 'test12'})
73 assert response.status == '302 Found'
74 assert response.status == '302 Found'
74 session = get_session_from_response(response)
75 session = response.get_session_from_response()
75 username = session['rhodecode_user'].get('username')
76 username = session['rhodecode_user'].get('username')
76 assert username == 'test_admin'
77 assert username == 'test_admin'
77 response = response.follow()
78 response = response.follow()
@@ -83,7 +84,7 b' class TestLoginController(object):'
83 'password': 'test12'})
84 'password': 'test12'})
84
85
85 assert response.status == '302 Found'
86 assert response.status == '302 Found'
86 session = get_session_from_response(response)
87 session = response.get_session_from_response()
87 username = session['rhodecode_user'].get('username')
88 username = session['rhodecode_user'].get('username')
88 assert username == 'test_regular'
89 assert username == 'test_regular'
89 response = response.follow()
90 response = response.follow()
@@ -105,8 +106,9 b' class TestLoginController(object):'
105 with fixture.anon_access(False):
106 with fixture.anon_access(False):
106 kwargs = {'branch': 'stable'}
107 kwargs = {'branch': 'stable'}
107 response = self.app.get(
108 response = self.app.get(
108 url('summary_home', repo_name=HG_REPO, **kwargs))
109 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs))
109 assert response.status == '302 Found'
110 assert response.status == '302 Found'
111
110 response_query = urlparse.parse_qsl(response.location)
112 response_query = urlparse.parse_qsl(response.location)
111 assert 'branch=stable' in response_query[0][1]
113 assert 'branch=stable' in response_query[0][1]
112
114
@@ -122,7 +124,7 b' class TestLoginController(object):'
122 'ftp://some.ftp.server',
124 'ftp://some.ftp.server',
123 'http://other.domain',
125 'http://other.domain',
124 '/\r\nX-Forwarded-Host: http://example.org',
126 '/\r\nX-Forwarded-Host: http://example.org',
125 ])
127 ], ids=no_newline_id_generator)
126 def test_login_bad_came_froms(self, url_came_from):
128 def test_login_bad_came_froms(self, url_came_from):
127 _url = '{}?came_from={}'.format(login_url, url_came_from)
129 _url = '{}?came_from={}'.format(login_url, url_came_from)
128 response = self.app.post(
130 response = self.app.post(
@@ -183,7 +185,7 b' class TestLoginController(object):'
183 'password': 'test123'})
185 'password': 'test123'})
184
186
185 assert response.status == '302 Found'
187 assert response.status == '302 Found'
186 session = get_session_from_response(response)
188 session = response.get_session_from_response()
187 username = session['rhodecode_user'].get('username')
189 username = session['rhodecode_user'].get('username')
188 assert username == temp_user
190 assert username == temp_user
189 response = response.follow()
191 response = response.follow()
@@ -212,7 +214,7 b' class TestLoginController(object):'
212 }
214 }
213 )
215 )
214
216
215 assertr = AssertResponse(response)
217 assertr = response.assert_response()
216 msg = validators.ValidUsername()._messages['username_exists']
218 msg = validators.ValidUsername()._messages['username_exists']
217 msg = msg % {'username': uname}
219 msg = msg % {'username': uname}
218 assertr.element_contains('#username+.error-message', msg)
220 assertr.element_contains('#username+.error-message', msg)
@@ -230,7 +232,7 b' class TestLoginController(object):'
230 }
232 }
231 )
233 )
232
234
233 assertr = AssertResponse(response)
235 assertr = response.assert_response()
234 msg = validators.UniqSystemEmail()()._messages['email_taken']
236 msg = validators.UniqSystemEmail()()._messages['email_taken']
235 assertr.element_contains('#email+.error-message', msg)
237 assertr.element_contains('#email+.error-message', msg)
236
238
@@ -246,7 +248,7 b' class TestLoginController(object):'
246 'lastname': 'test'
248 'lastname': 'test'
247 }
249 }
248 )
250 )
249 assertr = AssertResponse(response)
251 assertr = response.assert_response()
250 msg = validators.UniqSystemEmail()()._messages['email_taken']
252 msg = validators.UniqSystemEmail()()._messages['email_taken']
251 assertr.element_contains('#email+.error-message', msg)
253 assertr.element_contains('#email+.error-message', msg)
252
254
@@ -300,7 +302,7 b' class TestLoginController(object):'
300 }
302 }
301 )
303 )
302
304
303 assertr = AssertResponse(response)
305 assertr = response.assert_response()
304 msg = validators.ValidUsername()._messages['username_exists']
306 msg = validators.ValidUsername()._messages['username_exists']
305 msg = msg % {'username': usr}
307 msg = msg % {'username': usr}
306 assertr.element_contains('#username+.error-message', msg)
308 assertr.element_contains('#username+.error-message', msg)
@@ -24,20 +24,21 b' from webob.exc import HTTPNotFound'
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.lib import helpers as h
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
29 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification)
30 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
30 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.model.user import UserModel
33 from rhodecode.model.repo import RepoModel
34 from rhodecode.tests import (
34 from rhodecode.tests import assert_session_flash, url, TEST_USER_ADMIN_LOGIN
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 from rhodecode.tests.utils import AssertResponse
36 from rhodecode.tests.utils import AssertResponse
36
37
37
38
38 @pytest.mark.usefixtures('app', 'autologin_user')
39 @pytest.mark.usefixtures('app', 'autologin_user')
39 @pytest.mark.backends("git", "hg")
40 @pytest.mark.backends("git", "hg")
40 class TestPullrequestsController:
41 class TestPullrequestsController(object):
41
42
42 def test_index(self, backend):
43 def test_index(self, backend):
43 self.app.get(url(
44 self.app.get(url(
@@ -46,25 +47,12 b' class TestPullrequestsController:'
46
47
47 def test_option_menu_create_pull_request_exists(self, backend):
48 def test_option_menu_create_pull_request_exists(self, backend):
48 repo_name = backend.repo_name
49 repo_name = backend.repo_name
49 response = self.app.get(url('summary_home', repo_name=repo_name))
50 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
50
51
51 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
52 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
52 'pullrequest', repo_name=repo_name)
53 'pullrequest', repo_name=repo_name)
53 response.mustcontain(create_pr_link)
54 response.mustcontain(create_pr_link)
54
55
55 def test_global_redirect_of_pr(self, backend, pr_util):
56 pull_request = pr_util.create_pull_request()
57
58 response = self.app.get(
59 url('pull_requests_global',
60 pull_request_id=pull_request.pull_request_id))
61
62 repo_name = pull_request.target_repo.repo_name
63 redirect_url = url('pullrequest_show', repo_name=repo_name,
64 pull_request_id=pull_request.pull_request_id)
65 assert response.status == '302 Found'
66 assert redirect_url in response.location
67
68 def test_create_pr_form_with_raw_commit_id(self, backend):
56 def test_create_pr_form_with_raw_commit_id(self, backend):
69 repo = backend.repo
57 repo = backend.repo
70
58
@@ -97,7 +85,7 b' class TestPullrequestsController:'
97 'Server-side pull request merging is disabled.'
85 'Server-side pull request merging is disabled.'
98 in response) != pr_merge_enabled
86 in response) != pr_merge_enabled
99
87
100 def test_close_status_visibility(self, pr_util, csrf_token):
88 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
101 from rhodecode.tests.functional.test_login import login_url, logut_url
89 from rhodecode.tests.functional.test_login import login_url, logut_url
102 # Logout
90 # Logout
103 response = self.app.post(
91 response = self.app.post(
@@ -105,10 +93,11 b' class TestPullrequestsController:'
105 params={'csrf_token': csrf_token})
93 params={'csrf_token': csrf_token})
106 # Login as regular user
94 # Login as regular user
107 response = self.app.post(login_url,
95 response = self.app.post(login_url,
108 {'username': 'test_regular',
96 {'username': TEST_USER_REGULAR_LOGIN,
109 'password': 'test12'})
97 'password': 'test12'})
110
98
111 pull_request = pr_util.create_pull_request(author='test_regular')
99 pull_request = pr_util.create_pull_request(
100 author=TEST_USER_REGULAR_LOGIN)
112
101
113 response = self.app.get(url(
102 response = self.app.get(url(
114 controller='pullrequests', action='show',
103 controller='pullrequests', action='show',
@@ -118,6 +107,22 b' class TestPullrequestsController:'
118 response.mustcontain('Server-side pull request merging is disabled.')
107 response.mustcontain('Server-side pull request merging is disabled.')
119
108
120 assert_response = response.assert_response()
109 assert_response = response.assert_response()
110 # for regular user without a merge permissions, we don't see it
111 assert_response.no_element_exists('#close-pull-request-action')
112
113 user_util.grant_user_permission_to_repo(
114 pull_request.target_repo,
115 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
116 'repository.write')
117 response = self.app.get(url(
118 controller='pullrequests', action='show',
119 repo_name=pull_request.target_repo.scm_instance().name,
120 pull_request_id=str(pull_request.pull_request_id)))
121
122 response.mustcontain('Server-side pull request merging is disabled.')
123
124 assert_response = response.assert_response()
125 # now regular user has a merge permissions, we have CLOSE button
121 assert_response.one_element_exists('#close-pull-request-action')
126 assert_response.one_element_exists('#close-pull-request-action')
122
127
123 def test_show_invalid_commit_id(self, pr_util):
128 def test_show_invalid_commit_id(self, pr_util):
@@ -232,7 +237,9 b' class TestPullrequestsController:'
232 assertr.element_contains(
237 assertr.element_contains(
233 'span[data-role="merge-message"]', str(expected_msg))
238 'span[data-role="merge-message"]', str(expected_msg))
234
239
235 def test_comment_and_close_pull_request(self, pr_util, csrf_token):
240 def test_comment_and_close_pull_request_custom_message_approved(
241 self, pr_util, csrf_token, xhr_header):
242
236 pull_request = pr_util.create_pull_request(approved=True)
243 pull_request = pr_util.create_pull_request(approved=True)
237 pull_request_id = pull_request.pull_request_id
244 pull_request_id = pull_request.pull_request_id
238 author = pull_request.user_id
245 author = pull_request.user_id
@@ -244,75 +251,82 b' class TestPullrequestsController:'
244 repo_name=pull_request.target_repo.scm_instance().name,
251 repo_name=pull_request.target_repo.scm_instance().name,
245 pull_request_id=str(pull_request_id)),
252 pull_request_id=str(pull_request_id)),
246 params={
253 params={
247 'changeset_status': ChangesetStatus.STATUS_APPROVED,
248 'close_pull_request': '1',
254 'close_pull_request': '1',
249 'text': 'Closing a PR',
255 'text': 'Closing a PR',
250 'csrf_token': csrf_token},
256 'csrf_token': csrf_token},
251 status=302)
257 extra_environ=xhr_header,)
252
258
253 action = 'user_closed_pull_request:%d' % pull_request_id
254 journal = UserLog.query()\
259 journal = UserLog.query()\
255 .filter(UserLog.user_id == author)\
260 .filter(UserLog.user_id == author)\
256 .filter(UserLog.repository_id == repo)\
261 .filter(UserLog.repository_id == repo) \
257 .filter(UserLog.action == action)\
262 .order_by('user_log_id') \
258 .all()
263 .all()
259 assert len(journal) == 1
264 assert journal[-1].action == 'repo.pull_request.close'
260
265
261 pull_request = PullRequest.get(pull_request_id)
266 pull_request = PullRequest.get(pull_request_id)
262 assert pull_request.is_closed()
267 assert pull_request.is_closed()
263
268
264 # check only the latest status, not the review status
265 status = ChangesetStatusModel().get_status(
269 status = ChangesetStatusModel().get_status(
266 pull_request.source_repo, pull_request=pull_request)
270 pull_request.source_repo, pull_request=pull_request)
267 assert status == ChangesetStatus.STATUS_APPROVED
271 assert status == ChangesetStatus.STATUS_APPROVED
272 comments = ChangesetComment().query() \
273 .filter(ChangesetComment.pull_request == pull_request) \
274 .order_by(ChangesetComment.comment_id.asc())\
275 .all()
276 assert comments[-1].text == 'Closing a PR'
268
277
269 def test_reject_and_close_pull_request(self, pr_util, csrf_token):
278 def test_comment_force_close_pull_request_rejected(
279 self, pr_util, csrf_token, xhr_header):
270 pull_request = pr_util.create_pull_request()
280 pull_request = pr_util.create_pull_request()
271 pull_request_id = pull_request.pull_request_id
281 pull_request_id = pull_request.pull_request_id
272 response = self.app.post(
282 PullRequestModel().update_reviewers(
283 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
284 pull_request.author)
285 author = pull_request.user_id
286 repo = pull_request.target_repo.repo_id
287
288 self.app.post(
273 url(controller='pullrequests',
289 url(controller='pullrequests',
274 action='update',
290 action='comment',
275 repo_name=pull_request.target_repo.scm_instance().name,
291 repo_name=pull_request.target_repo.scm_instance().name,
276 pull_request_id=str(pull_request.pull_request_id)),
292 pull_request_id=str(pull_request_id)),
277 params={'close_pull_request': 'true', '_method': 'put',
293 params={
278 'csrf_token': csrf_token})
294 'close_pull_request': '1',
295 'csrf_token': csrf_token},
296 extra_environ=xhr_header)
279
297
280 pull_request = PullRequest.get(pull_request_id)
298 pull_request = PullRequest.get(pull_request_id)
281
299
282 assert response.json is True
300 journal = UserLog.query()\
283 assert pull_request.is_closed()
301 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
302 .order_by('user_log_id') \
303 .all()
304 assert journal[-1].action == 'repo.pull_request.close'
284
305
285 # check only the latest status, not the review status
306 # check only the latest status, not the review status
286 status = ChangesetStatusModel().get_status(
307 status = ChangesetStatusModel().get_status(
287 pull_request.source_repo, pull_request=pull_request)
308 pull_request.source_repo, pull_request=pull_request)
288 assert status == ChangesetStatus.STATUS_REJECTED
309 assert status == ChangesetStatus.STATUS_REJECTED
289
310
290 def test_comment_force_close_pull_request(self, pr_util, csrf_token):
311 def test_comment_and_close_pull_request(
312 self, pr_util, csrf_token, xhr_header):
291 pull_request = pr_util.create_pull_request()
313 pull_request = pr_util.create_pull_request()
292 pull_request_id = pull_request.pull_request_id
314 pull_request_id = pull_request.pull_request_id
293 reviewers_data = [(1, ['reason']), (2, ['reason2'])]
315
294 PullRequestModel().update_reviewers(pull_request_id, reviewers_data)
316 response = self.app.post(
295 author = pull_request.user_id
296 repo = pull_request.target_repo.repo_id
297 self.app.post(
298 url(controller='pullrequests',
317 url(controller='pullrequests',
299 action='comment',
318 action='comment',
300 repo_name=pull_request.target_repo.scm_instance().name,
319 repo_name=pull_request.target_repo.scm_instance().name,
301 pull_request_id=str(pull_request_id)),
320 pull_request_id=str(pull_request.pull_request_id)),
302 params={
321 params={
303 'changeset_status': 'rejected',
322 'close_pull_request': 'true',
304 'close_pull_request': '1',
305 'csrf_token': csrf_token},
323 'csrf_token': csrf_token},
306 status=302)
324 extra_environ=xhr_header)
325
326 assert response.json
307
327
308 pull_request = PullRequest.get(pull_request_id)
328 pull_request = PullRequest.get(pull_request_id)
309
329 assert pull_request.is_closed()
310 action = 'user_closed_pull_request:%d' % pull_request_id
311 journal = UserLog.query().filter(
312 UserLog.user_id == author,
313 UserLog.repository_id == repo,
314 UserLog.action == action).all()
315 assert len(journal) == 1
316
330
317 # check only the latest status, not the review status
331 # check only the latest status, not the review status
318 status = ChangesetStatusModel().get_status(
332 status = ChangesetStatusModel().get_status(
@@ -340,6 +354,7 b' class TestPullrequestsController:'
340 ('source_ref', 'branch:default:' + commit_ids['change2']),
354 ('source_ref', 'branch:default:' + commit_ids['change2']),
341 ('target_repo', target.repo_name),
355 ('target_repo', target.repo_name),
342 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
356 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
357 ('common_ancestor', commit_ids['ancestor']),
343 ('pullrequest_desc', 'Description'),
358 ('pullrequest_desc', 'Description'),
344 ('pullrequest_title', 'Title'),
359 ('pullrequest_title', 'Title'),
345 ('__start__', 'review_members:sequence'),
360 ('__start__', 'review_members:sequence'),
@@ -348,6 +363,7 b' class TestPullrequestsController:'
348 ('__start__', 'reasons:sequence'),
363 ('__start__', 'reasons:sequence'),
349 ('reason', 'Some reason'),
364 ('reason', 'Some reason'),
350 ('__end__', 'reasons:sequence'),
365 ('__end__', 'reasons:sequence'),
366 ('mandatory', 'False'),
351 ('__end__', 'reviewer:mapping'),
367 ('__end__', 'reviewer:mapping'),
352 ('__end__', 'review_members:sequence'),
368 ('__end__', 'review_members:sequence'),
353 ('__start__', 'revisions:sequence'),
369 ('__start__', 'revisions:sequence'),
@@ -360,8 +376,9 b' class TestPullrequestsController:'
360 status=302)
376 status=302)
361
377
362 location = response.headers['Location']
378 location = response.headers['Location']
363 pull_request_id = int(location.rsplit('/', 1)[1])
379 pull_request_id = location.rsplit('/', 1)[1]
364 pull_request = PullRequest.get(pull_request_id)
380 assert pull_request_id != 'new'
381 pull_request = PullRequest.get(int(pull_request_id))
365
382
366 # check that we have now both revisions
383 # check that we have now both revisions
367 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
384 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
@@ -398,6 +415,7 b' class TestPullrequestsController:'
398 ('source_ref', 'branch:default:' + commit_ids['change']),
415 ('source_ref', 'branch:default:' + commit_ids['change']),
399 ('target_repo', target.repo_name),
416 ('target_repo', target.repo_name),
400 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
417 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
418 ('common_ancestor', commit_ids['ancestor']),
401 ('pullrequest_desc', 'Description'),
419 ('pullrequest_desc', 'Description'),
402 ('pullrequest_title', 'Title'),
420 ('pullrequest_title', 'Title'),
403 ('__start__', 'review_members:sequence'),
421 ('__start__', 'review_members:sequence'),
@@ -406,6 +424,7 b' class TestPullrequestsController:'
406 ('__start__', 'reasons:sequence'),
424 ('__start__', 'reasons:sequence'),
407 ('reason', 'Some reason'),
425 ('reason', 'Some reason'),
408 ('__end__', 'reasons:sequence'),
426 ('__end__', 'reasons:sequence'),
427 ('mandatory', 'False'),
409 ('__end__', 'reviewer:mapping'),
428 ('__end__', 'reviewer:mapping'),
410 ('__end__', 'review_members:sequence'),
429 ('__end__', 'review_members:sequence'),
411 ('__start__', 'revisions:sequence'),
430 ('__start__', 'revisions:sequence'),
@@ -417,21 +436,23 b' class TestPullrequestsController:'
417 status=302)
436 status=302)
418
437
419 location = response.headers['Location']
438 location = response.headers['Location']
420 pull_request_id = int(location.rsplit('/', 1)[1])
439
421 pull_request = PullRequest.get(pull_request_id)
440 pull_request_id = location.rsplit('/', 1)[1]
441 assert pull_request_id != 'new'
442 pull_request = PullRequest.get(int(pull_request_id))
422
443
423 # Check that a notification was made
444 # Check that a notification was made
424 notifications = Notification.query()\
445 notifications = Notification.query()\
425 .filter(Notification.created_by == pull_request.author.user_id,
446 .filter(Notification.created_by == pull_request.author.user_id,
426 Notification.type_ == Notification.TYPE_PULL_REQUEST,
447 Notification.type_ == Notification.TYPE_PULL_REQUEST,
427 Notification.subject.contains("wants you to review "
448 Notification.subject.contains(
428 "pull request #%d"
449 "wants you to review pull request #%s" % pull_request_id))
429 % pull_request_id))
430 assert len(notifications.all()) == 1
450 assert len(notifications.all()) == 1
431
451
432 # Change reviewers and check that a notification was made
452 # Change reviewers and check that a notification was made
433 PullRequestModel().update_reviewers(
453 PullRequestModel().update_reviewers(
434 pull_request.pull_request_id, [(1, [])])
454 pull_request.pull_request_id, [(1, [], False)],
455 pull_request.author)
435 assert len(notifications.all()) == 2
456 assert len(notifications.all()) == 2
436
457
437 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
458 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
@@ -462,6 +483,7 b' class TestPullrequestsController:'
462 ('source_ref', 'branch:default:' + commit_ids['change']),
483 ('source_ref', 'branch:default:' + commit_ids['change']),
463 ('target_repo', target.repo_name),
484 ('target_repo', target.repo_name),
464 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
485 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
486 ('common_ancestor', commit_ids['ancestor']),
465 ('pullrequest_desc', 'Description'),
487 ('pullrequest_desc', 'Description'),
466 ('pullrequest_title', 'Title'),
488 ('pullrequest_title', 'Title'),
467 ('__start__', 'review_members:sequence'),
489 ('__start__', 'review_members:sequence'),
@@ -470,6 +492,7 b' class TestPullrequestsController:'
470 ('__start__', 'reasons:sequence'),
492 ('__start__', 'reasons:sequence'),
471 ('reason', 'Some reason'),
493 ('reason', 'Some reason'),
472 ('__end__', 'reasons:sequence'),
494 ('__end__', 'reasons:sequence'),
495 ('mandatory', 'False'),
473 ('__end__', 'reviewer:mapping'),
496 ('__end__', 'reviewer:mapping'),
474 ('__end__', 'review_members:sequence'),
497 ('__end__', 'review_members:sequence'),
475 ('__start__', 'revisions:sequence'),
498 ('__start__', 'revisions:sequence'),
@@ -481,8 +504,10 b' class TestPullrequestsController:'
481 status=302)
504 status=302)
482
505
483 location = response.headers['Location']
506 location = response.headers['Location']
484 pull_request_id = int(location.rsplit('/', 1)[1])
507
485 pull_request = PullRequest.get(pull_request_id)
508 pull_request_id = location.rsplit('/', 1)[1]
509 assert pull_request_id != 'new'
510 pull_request = PullRequest.get(int(pull_request_id))
486
511
487 # target_ref has to point to the ancestor's commit_id in order to
512 # target_ref has to point to the ancestor's commit_id in order to
488 # show the correct diff
513 # show the correct diff
@@ -519,18 +544,21 b' class TestPullrequestsController:'
519 pull_request, ChangesetStatus.STATUS_APPROVED)
544 pull_request, ChangesetStatus.STATUS_APPROVED)
520
545
521 # Check the relevant log entries were added
546 # Check the relevant log entries were added
522 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
547 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
523 actions = [log.action for log in user_logs]
548 actions = [log.action for log in user_logs]
524 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
549 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
525 expected_actions = [
550 expected_actions = [
526 u'user_closed_pull_request:%d' % pull_request_id,
551 u'repo.pull_request.close',
527 u'user_merged_pull_request:%d' % pull_request_id,
552 u'repo.pull_request.merge',
528 # The action below reflect that the post push actions were executed
553 u'repo.pull_request.comment.create'
529 u'user_commented_pull_request:%d' % pull_request_id,
530 u'push:%s' % ','.join(pr_commit_ids),
531 ]
554 ]
532 assert actions == expected_actions
555 assert actions == expected_actions
533
556
557 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
558 actions = [log for log in user_logs]
559 assert actions[-1].action == 'user.push'
560 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
561
534 # Check post_push rcextension was really executed
562 # Check post_push rcextension was really executed
535 push_calls = rhodecode.EXTENSIONS.calls['post_push']
563 push_calls = rhodecode.EXTENSIONS.calls['post_push']
536 assert len(push_calls) == 1
564 assert len(push_calls) == 1
@@ -941,18 +969,9 b' class TestPullrequestsController:'
941 assert target.text.strip() == 'tag: target'
969 assert target.text.strip() == 'tag: target'
942 assert target.getchildren() == []
970 assert target.getchildren() == []
943
971
944 def test_description_is_escaped_on_index_page(self, backend, pr_util):
945 xss_description = "<script>alert('Hi!')</script>"
946 pull_request = pr_util.create_pull_request(description=xss_description)
947 response = self.app.get(url(
948 controller='pullrequests', action='show_all',
949 repo_name=pull_request.target_repo.repo_name))
950 response.mustcontain(
951 "&lt;script&gt;alert(&#39;Hi!&#39;)&lt;/script&gt;")
952
953 @pytest.mark.parametrize('mergeable', [True, False])
972 @pytest.mark.parametrize('mergeable', [True, False])
954 def test_shadow_repository_link(
973 def test_shadow_repository_link(
955 self, mergeable, pr_util, http_host_stub):
974 self, mergeable, pr_util, http_host_only_stub):
956 """
975 """
957 Check that the pull request summary page displays a link to the shadow
976 Check that the pull request summary page displays a link to the shadow
958 repository if the pull request is mergeable. If it is not mergeable
977 repository if the pull request is mergeable. If it is not mergeable
@@ -963,7 +982,7 b' class TestPullrequestsController:'
963 target_repo = pull_request.target_repo.scm_instance()
982 target_repo = pull_request.target_repo.scm_instance()
964 pr_id = pull_request.pull_request_id
983 pr_id = pull_request.pull_request_id
965 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
984 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
966 host=http_host_stub, repo=target_repo.name, pr_id=pr_id)
985 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
967
986
968 response = self.app.get(url(
987 response = self.app.get(url(
969 controller='pullrequests', action='show',
988 controller='pullrequests', action='show',
@@ -1041,6 +1060,17 b' class TestPullrequestsControllerDelete(o'
1041 response.mustcontain('id="delete_pullrequest"')
1060 response.mustcontain('id="delete_pullrequest"')
1042 response.mustcontain(no=['Confirm to delete this pull request'])
1061 response.mustcontain(no=['Confirm to delete this pull request'])
1043
1062
1063 def test_delete_comment_returns_404_if_comment_does_not_exist(
1064 self, autologin_user, pr_util, user_admin):
1065
1066 pull_request = pr_util.create_pull_request(
1067 author=user_admin.username, enable_notifications=False)
1068
1069 self.app.get(url(
1070 controller='pullrequests', action='delete_comment',
1071 repo_name=pull_request.target_repo.scm_instance().name,
1072 comment_id=1024404), status=404)
1073
1044
1074
1045 def assert_pull_request_status(pull_request, expected_status):
1075 def assert_pull_request_status(pull_request, expected_status):
1046 status = ChangesetStatusModel().calculated_review_status(
1076 status = ChangesetStatusModel().calculated_review_status(
@@ -1048,30 +1078,17 b' def assert_pull_request_status(pull_requ'
1048 assert status == expected_status
1078 assert status == expected_status
1049
1079
1050
1080
1051 @pytest.mark.parametrize('action', ['show_all', 'index', 'create'])
1081 @pytest.mark.parametrize('action', ['index', 'create'])
1052 @pytest.mark.usefixtures("autologin_user")
1082 @pytest.mark.usefixtures("autologin_user")
1053 def test_redirects_to_repo_summary_for_svn_repositories(
1083 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1054 backend_svn, app, action):
1084 response = app.get(url(
1055 denied_actions = ['show_all', 'index', 'create']
1085 controller='pullrequests', action=action,
1056 for action in denied_actions:
1086 repo_name=backend_svn.repo_name))
1057 response = app.get(url(
1087 assert response.status_int == 302
1058 controller='pullrequests', action=action,
1059 repo_name=backend_svn.repo_name))
1060 assert response.status_int == 302
1061
1088
1062 # Not allowed, redirect to the summary
1089 # Not allowed, redirect to the summary
1063 redirected = response.follow()
1090 redirected = response.follow()
1064 summary_url = url('summary_home', repo_name=backend_svn.repo_name)
1091 summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name)
1065
1066 # URL adds leading slash and path doesn't have it
1067 assert redirected.req.path == summary_url
1068
1069
1092
1070 def test_delete_comment_returns_404_if_comment_does_not_exist(pylonsapp):
1093 # URL adds leading slash and path doesn't have it
1071 # TODO: johbo: Global import not possible because models.forms blows up
1094 assert redirected.request.path == summary_url
1072 from rhodecode.controllers.pullrequests import PullrequestsController
1073 controller = PullrequestsController()
1074 patcher = mock.patch(
1075 'rhodecode.model.db.BaseModel.get', return_value=None)
1076 with pytest.raises(HTTPNotFound), patcher:
1077 controller._delete_comment(1)
@@ -21,8 +21,13 b''
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, url
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests.utils import AssertResponse
25
26
27 def route_path(name, **kwargs):
28 return {
29 'home': '/',
30 }[name].format(**kwargs)
26
31
27
32
28 class TestSessionBehaviorOnPasswordChange(object):
33 class TestSessionBehaviorOnPasswordChange(object):
@@ -39,18 +44,23 b' class TestSessionBehaviorOnPasswordChang'
39
44
40 def test_sessions_are_ok_when_password_is_not_changed(
45 def test_sessions_are_ok_when_password_is_not_changed(
41 self, app, autologin_user):
46 self, app, autologin_user):
42 response = app.get(url('home'))
47 response = app.get(route_path('home'))
43 assert_response = AssertResponse(response)
48 assert_response = response.assert_response()
44 assert_response.element_contains(
49 assert_response.element_contains(
45 '#quick_login_link .menu_link_user', TEST_USER_ADMIN_LOGIN)
50 '#quick_login_link .menu_link_user', TEST_USER_ADMIN_LOGIN)
46 assert 'rhodecode_user' in response.session
51
47 assert response.session.was_invalidated is False
52 session = response.get_session_from_response()
53
54 assert 'rhodecode_user' in session
55 assert session.was_invalidated is False
48
56
49 def test_sessions_invalidated_when_password_is_changed(
57 def test_sessions_invalidated_when_password_is_changed(
50 self, app, autologin_user):
58 self, app, autologin_user):
51 self.password_changed_mock.return_value = True
59 self.password_changed_mock.return_value = True
52 response = app.get(url('home'))
60 response = app.get(route_path('home'))
53 assert_response = AssertResponse(response)
61 assert_response = response.assert_response()
54 assert_response.element_contains('#quick_login_link .user', 'Sign in')
62 assert_response.element_contains('#quick_login_link .user', 'Sign in')
55 assert 'rhodecode_user' not in response.session
63
56 assert response.session.was_invalidated is True
64 session = response.get_session_from_response()
65 assert 'rhodecode_user' not in session
66 assert session.was_invalidated is True
@@ -32,7 +32,12 b' def base_data():'
32 'repo_name': 'foo',
32 'repo_name': 'foo',
33 'repo_type': 'hg',
33 'repo_type': 'hg',
34 'repo_id': '12',
34 'repo_id': '12',
35 'url': 'http://repo.url/foo'
35 'url': 'http://repo.url/foo',
36 'extra_fields': {},
37 },
38 'actor': {
39 'username': 'actor_name',
40 'user_id': 1
36 }
41 }
37 }
42 }
38
43
@@ -35,7 +35,6 b' from rhodecode.model.meta import Session'
35 from rhodecode.tests import (
35 from rhodecode.tests import (
36 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
36 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
37 from rhodecode.tests.lib.middleware import mock_scm_app
37 from rhodecode.tests.lib.middleware import mock_scm_app
38 from rhodecode.tests.utils import set_anonymous_access
39
38
40
39
41 class StubVCSController(simplevcs.SimpleVCS):
40 class StubVCSController(simplevcs.SimpleVCS):
@@ -70,7 +69,7 b' def vcscontroller(pylonsapp, config_stub'
70 config_stub.testing_securitypolicy()
69 config_stub.testing_securitypolicy()
71 config_stub.include('rhodecode.authentication')
70 config_stub.include('rhodecode.authentication')
72
71
73 set_anonymous_access(True)
72 #set_anonymous_access(True)
74 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
73 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
75 app = HttpsFixup(controller, pylonsapp.config)
74 app = HttpsFixup(controller, pylonsapp.config)
76 app = CustomTestApp(app)
75 app = CustomTestApp(app)
@@ -87,19 +86,12 b' def vcscontroller(pylonsapp, config_stub'
87 def _remove_default_user_from_query_cache():
86 def _remove_default_user_from_query_cache():
88 user = User.get_default_user(cache=True)
87 user = User.get_default_user(cache=True)
89 query = Session().query(User).filter(User.username == user.username)
88 query = Session().query(User).filter(User.username == user.username)
90 query = query.options(FromCache(
89 query = query.options(
91 "sql_cache_short", "get_user_%s" % _hash_key(user.username)))
90 FromCache("sql_cache_short", "get_user_%s" % _hash_key(user.username)))
92 query.invalidate()
91 query.invalidate()
93 Session().expire(user)
92 Session().expire(user)
94
93
95
94
96 @pytest.fixture
97 def disable_anonymous_user(request, pylonsapp):
98 set_anonymous_access(False)
99
100 @request.addfinalizer
101 def cleanup():
102 set_anonymous_access(True)
103
95
104
96
105 def test_handles_exceptions_during_permissions_checks(
97 def test_handles_exceptions_during_permissions_checks(
@@ -275,7 +267,7 b' class TestShadowRepoExposure(object):'
275 controller.vcs_repo_name ==
267 controller.vcs_repo_name ==
276 controller._get_repository_name(environ_stub))
268 controller._get_repository_name(environ_stub))
277
269
278 def test_set_repo_names_with_shadow(self, pylonsapp, pr_util):
270 def test_set_repo_names_with_shadow(self, pylonsapp, pr_util, config_stub):
279 """
271 """
280 Check that the set_repo_names method sets correct names on a request
272 Check that the set_repo_names method sets correct names on a request
281 to a shadow repo.
273 to a shadow repo.
@@ -304,7 +296,7 b' class TestShadowRepoExposure(object):'
304 assert controller.is_shadow_repo
296 assert controller.is_shadow_repo
305
297
306 def test_set_repo_names_with_shadow_but_missing_pr(
298 def test_set_repo_names_with_shadow_but_missing_pr(
307 self, pylonsapp, pr_util):
299 self, pylonsapp, pr_util, config_stub):
308 """
300 """
309 Checks that the set_repo_names method enforces matching target repos
301 Checks that the set_repo_names method enforces matching target repos
310 and pull request IDs.
302 and pull request IDs.
@@ -326,7 +318,7 b' class TestShadowRepoExposure(object):'
326
318
327
319
328 @pytest.mark.usefixtures('db')
320 @pytest.mark.usefixtures('db')
329 class TestGenerateVcsResponse:
321 class TestGenerateVcsResponse(object):
330
322
331 def test_ensures_that_start_response_is_called_early_enough(self):
323 def test_ensures_that_start_response_is_called_early_enough(self):
332 self.call_controller_with_response_body(iter(['a', 'b']))
324 self.call_controller_with_response_body(iter(['a', 'b']))
@@ -418,7 +410,7 b' class TestGenerateVcsResponse:'
418 return self.controller._invalidate_cache.called
410 return self.controller._invalidate_cache.called
419
411
420
412
421 class TestInitializeGenerator:
413 class TestInitializeGenerator(object):
422
414
423 def test_drains_first_element(self):
415 def test_drains_first_element(self):
424 gen = self.factory(['__init__', 1, 2])
416 gen = self.factory(['__init__', 1, 2])
@@ -20,10 +20,10 b''
20
20
21 import pytest
21 import pytest
22 from mock import Mock, patch
22 from mock import Mock, patch
23 from pylons import url
24
23
25 from rhodecode.lib import base
24 from rhodecode.lib import base
26 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.lib import helpers as h
27 from rhodecode.model import db
27 from rhodecode.model import db
28
28
29
29
@@ -34,8 +34,9 b' from rhodecode.model import db'
34 ('scm', 'stub_scm'),
34 ('scm', 'stub_scm'),
35 ('hooks', ['stub_hook']),
35 ('hooks', ['stub_hook']),
36 ('config', 'stub_ini_filename'),
36 ('config', 'stub_ini_filename'),
37 ('ip', 'fake_ip'),
37 ('ip', '1.2.3.4'),
38 ('server_url', 'https://example.com'),
38 ('server_url', 'https://example.com'),
39 ('user_agent', 'client-text-v1.1'),
39 # TODO: johbo: Commpare locking parameters with `_get_rc_scm_extras`
40 # TODO: johbo: Commpare locking parameters with `_get_rc_scm_extras`
40 # in hooks_utils.
41 # in hooks_utils.
41 ('make_lock', None),
42 ('make_lock', None),
@@ -71,7 +72,6 b' def test_vcs_operation_context_can_skip_'
71
72
72 @patch.object(
73 @patch.object(
73 base, 'get_enabled_hook_classes', Mock(return_value=['stub_hook']))
74 base, 'get_enabled_hook_classes', Mock(return_value=['stub_hook']))
74 @patch.object(base, 'get_ip_addr', Mock(return_value="fake_ip"))
75 @patch('rhodecode.lib.utils2.get_server_url',
75 @patch('rhodecode.lib.utils2.get_server_url',
76 Mock(return_value='https://example.com'))
76 Mock(return_value='https://example.com'))
77 def call_vcs_operation_context(**kwargs_override):
77 def call_vcs_operation_context(**kwargs_override):
@@ -87,7 +87,9 b' def call_vcs_operation_context(**kwargs_'
87 'rhodecode.CONFIG', {'__file__': 'stub_ini_filename'})
87 'rhodecode.CONFIG', {'__file__': 'stub_ini_filename'})
88 settings_patch = patch.object(base, 'VcsSettingsModel')
88 settings_patch = patch.object(base, 'VcsSettingsModel')
89 with config_file_patch, settings_patch as settings_mock:
89 with config_file_patch, settings_patch as settings_mock:
90 result = base.vcs_operation_context(environ={}, **kwargs)
90 result = base.vcs_operation_context(
91 environ={'HTTP_USER_AGENT': 'client-text-v1.1',
92 'REMOTE_ADDR': '1.2.3.4'}, **kwargs)
91 settings_mock.assert_called_once_with(repo='stub_repo_name')
93 settings_mock.assert_called_once_with(repo='stub_repo_name')
92 return result
94 return result
93
95
@@ -163,7 +165,7 b' class TestBaseRepoControllerHandleMissin'
163 context_mock.repo_name = repo_name
165 context_mock.repo_name = repo_name
164 controller._handle_missing_requirements(error)
166 controller._handle_missing_requirements(error)
165
167
166 expected_url = url('summary_home', repo_name=repo_name)
168 expected_url = h.route_path('repo_summary', repo_name=repo_name)
167 if should_redirect:
169 if should_redirect:
168 redirect_mock.assert_called_once_with(expected_url)
170 redirect_mock.assert_called_once_with(expected_url)
169 else:
171 else:
@@ -19,11 +19,12 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from pygments.lexers import get_lexer_by_name
22
23
24 from rhodecode.tests import no_newline_id_generator
23 from rhodecode.lib.codeblocks import (
25 from rhodecode.lib.codeblocks import (
24 tokenize_string, split_token_stream, rollup_tokenstream,
26 tokenize_string, split_token_stream, rollup_tokenstream,
25 render_tokenstream)
27 render_tokenstream)
26 from pygments.lexers import get_lexer_by_name
27
28
28
29
29 class TestTokenizeString(object):
30 class TestTokenizeString(object):
@@ -298,7 +299,7 b' class TestRenderTokenStream(object):'
298 [('A', '', u'hel'), ('B', 'ins', u'lo')],
299 [('A', '', u'hel'), ('B', 'ins', u'lo')],
299 '<span class="A">hel</span><span class="B"><ins>lo</ins></span>',
300 '<span class="A">hel</span><span class="B"><ins>lo</ins></span>',
300 ),
301 ),
301 ])
302 ], ids=no_newline_id_generator)
302 def test_render_tokenstream_with_ops(self, tokenstream, output):
303 def test_render_tokenstream_with_ops(self, tokenstream, output):
303 html = render_tokenstream(tokenstream)
304 html = render_tokenstream(tokenstream)
304 assert html == output
305 assert html == output
@@ -27,6 +27,7 b' from pylons.util import ContextObj'
27 from rhodecode.lib import helpers
27 from rhodecode.lib import helpers
28 from rhodecode.lib.utils2 import AttributeDict
28 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.model.settings import IssueTrackerSettingsModel
29 from rhodecode.model.settings import IssueTrackerSettingsModel
30 from rhodecode.tests import no_newline_id_generator
30
31
31
32
32 @pytest.mark.parametrize('url, expected_url', [
33 @pytest.mark.parametrize('url, expected_url', [
@@ -103,55 +104,62 b' def test_extract_issues(backend, text_st'
103 assert issues == expected
104 assert issues == expected
104
105
105
106
106 @pytest.mark.parametrize('text_string, pattern, expected_text', [
107 @pytest.mark.parametrize('text_string, pattern, link_format, expected_text', [
107 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
108 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'html',
108 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'
109 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'),
109 ),
110
110 ('Fix #42', '(?:#)?<issue_id>\d+)', 'Fix #42'), # Broken regex
111 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'markdown',
112 'Fix [#42](http://r.io/{repo}/i/42)'),
113
114 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'rst',
115 'Fix `#42 <http://r.io/{repo}/i/42>`_'),
116
117 ('Fix #42', '(?:#)?<issue_id>\d+)', 'html',
118 'Fix #42'), # Broken regex
111 ])
119 ])
112 def test_process_patterns_repo(backend, text_string, pattern, expected_text):
120 def test_process_patterns_repo(backend, text_string, pattern, expected_text, link_format):
113 repo = backend.create_repo()
121 repo = backend.create_repo()
114 config = {'123': {
115 'uid': '123',
116 'pat': pattern,
117 'url': 'http://r.io/${repo}/i/${issue_id}',
118 'pref': '#',
119 }
120 }
121
122
122 def get_settings_mock(self, cache=True):
123 def get_settings_mock(self, cache=True):
123 return config
124 return {
125 '123': {
126 'uid': '123',
127 'pat': pattern,
128 'url': 'http://r.io/${repo}/i/${issue_id}',
129 'pref': '#',
130 }
131 }
124
132
125 with mock.patch.object(IssueTrackerSettingsModel,
133 with mock.patch.object(IssueTrackerSettingsModel,
126 'get_settings', get_settings_mock):
134 'get_settings', get_settings_mock):
127 processed_text, issues = helpers.process_patterns(
135 processed_text, issues = helpers.process_patterns(
128 text_string, repo.repo_name, config)
136 text_string, repo.repo_name, link_format)
129
137
130 assert processed_text == expected_text.format(repo=repo.repo_name)
138 assert processed_text == expected_text.format(repo=repo.repo_name)
131
139
132
140
133 @pytest.mark.parametrize('text_string, pattern, expected_text', [
141 @pytest.mark.parametrize('text_string, pattern, expected_text', [
134 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
142 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
135 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'
143 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'),
136 ),
144 ('Fix #42', '(?:#)?<issue_id>\d+)',
137 ('Fix #42', '(?:#)?<issue_id>\d+)', 'Fix #42'), # Broken regex
145 'Fix #42'), # Broken regex
138 ])
146 ])
139 def test_process_patterns_no_repo(text_string, pattern, expected_text):
147 def test_process_patterns_no_repo(text_string, pattern, expected_text):
140 config = {'123': {
141 'uid': '123',
142 'pat': pattern,
143 'url': 'http://r.io/i/${issue_id}',
144 'pref': '#',
145 }
146 }
147
148
148 def get_settings_mock(self, cache=True):
149 def get_settings_mock(self, cache=True):
149 return config
150 return {
151 '123': {
152 'uid': '123',
153 'pat': pattern,
154 'url': 'http://r.io/i/${issue_id}',
155 'pref': '#',
156 }
157 }
150
158
151 with mock.patch.object(IssueTrackerSettingsModel,
159 with mock.patch.object(IssueTrackerSettingsModel,
152 'get_global_settings', get_settings_mock):
160 'get_global_settings', get_settings_mock):
153 processed_text, issues = helpers.process_patterns(
161 processed_text, issues = helpers.process_patterns(
154 text_string, '', config)
162 text_string, '')
155
163
156 assert processed_text == expected_text
164 assert processed_text == expected_text
157
165
@@ -161,21 +169,21 b' def test_process_patterns_non_existent_r'
161 pattern = '(?:#)(?P<issue_id>\d+)'
169 pattern = '(?:#)(?P<issue_id>\d+)'
162 expected_text = ('Fix <a class="issue-tracker-link" '
170 expected_text = ('Fix <a class="issue-tracker-link" '
163 'href="http://r.io/do-not-exist/i/42">#42</a>')
171 'href="http://r.io/do-not-exist/i/42">#42</a>')
164 config = {'123': {
165 'uid': '123',
166 'pat': pattern,
167 'url': 'http://r.io/${repo}/i/${issue_id}',
168 'pref': '#',
169 }
170 }
171
172
172 def get_settings_mock(self, cache=True):
173 def get_settings_mock(self, cache=True):
173 return config
174 return {
175 '123': {
176 'uid': '123',
177 'pat': pattern,
178 'url': 'http://r.io/${repo}/i/${issue_id}',
179 'pref': '#',
180 }
181 }
174
182
175 with mock.patch.object(IssueTrackerSettingsModel,
183 with mock.patch.object(IssueTrackerSettingsModel,
176 'get_global_settings', get_settings_mock):
184 'get_global_settings', get_settings_mock):
177 processed_text, issues = helpers.process_patterns(
185 processed_text, issues = helpers.process_patterns(
178 text_string, 'do-not-exist', config)
186 text_string, 'do-not-exist')
179
187
180 assert processed_text == expected_text
188 assert processed_text == expected_text
181
189
@@ -197,7 +205,7 b' def test_get_visual_attr(pylonsapp):'
197 ('just a string\n', False, 'just a string'),
205 ('just a string\n', False, 'just a string'),
198 ('just a string\n next line', False, 'just a string...'),
206 ('just a string\n next line', False, 'just a string...'),
199 ('just a string\n next line', True, 'just a string\n...'),
207 ('just a string\n next line', True, 'just a string\n...'),
200 ])
208 ], ids=no_newline_id_generator)
201 def test_chop_at(test_text, inclusive, expected_text):
209 def test_chop_at(test_text, inclusive, expected_text):
202 assert helpers.chop_at_smart(
210 assert helpers.chop_at_smart(
203 test_text, '\n', inclusive, '...') == expected_text
211 test_text, '\n', inclusive, '...') == expected_text
@@ -20,15 +20,10 b''
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23 from rhodecode.model.db import Session, UserLog
24 from rhodecode.lib import hooks_base, utils2
24 from rhodecode.lib import hooks_base, utils2
25
25
26
26
27 @mock.patch.multiple(
28 hooks_base,
29 action_logger=mock.Mock(),
30 post_push_extension=mock.Mock(),
31 Repository=mock.Mock())
32 def test_post_push_truncates_commits(user_regular, repo_stub):
27 def test_post_push_truncates_commits(user_regular, repo_stub):
33 extras = {
28 extras = {
34 'ip': '127.0.0.1',
29 'ip': '127.0.0.1',
@@ -39,6 +34,7 b' def test_post_push_truncates_commits(use'
39 'config': '',
34 'config': '',
40 'server_url': 'http://example.com',
35 'server_url': 'http://example.com',
41 'make_lock': None,
36 'make_lock': None,
37 'user_agent': 'some-client',
42 'locked_by': [None],
38 'locked_by': [None],
43 'commit_ids': ['abcde12345' * 4] * 30000,
39 'commit_ids': ['abcde12345' * 4] * 30000,
44 'is_shadow_repo': False,
40 'is_shadow_repo': False,
@@ -48,11 +44,13 b' def test_post_push_truncates_commits(use'
48 hooks_base.post_push(extras)
44 hooks_base.post_push(extras)
49
45
50 # Calculate appropriate action string here
46 # Calculate appropriate action string here
51 expected_action = 'push_local:%s' % ','.join(extras.commit_ids[:29000])
47 commit_ids = extras.commit_ids[:10000]
52
48
53 hooks_base.action_logger.assert_called_with(
49 entry = UserLog.query().order_by('-user_log_id').first()
54 extras.username, expected_action, extras.repository, extras.ip,
50 assert entry.action == 'user.push'
55 commit=True)
51 assert entry.action_data['commit_ids'] == commit_ids
52 Session().delete(entry)
53 Session().commit()
56
54
57
55
58 def assert_called_with_mock(callable_, expected_mock_name):
56 def assert_called_with_mock(callable_, expected_mock_name):
@@ -72,6 +70,7 b' def hook_extras(user_regular, repo_stub)'
72 'config': '',
70 'config': '',
73 'server_url': 'http://example.com',
71 'server_url': 'http://example.com',
74 'make_lock': None,
72 'make_lock': None,
73 'user_agent': 'some-client',
75 'locked_by': [None],
74 'locked_by': [None],
76 'commit_ids': [],
75 'commit_ids': [],
77 'is_shadow_repo': False,
76 'is_shadow_repo': False,
@@ -3,16 +3,17 b' import collections'
3 import pytest
3 import pytest
4
4
5 from rhodecode.lib.utils import PartialRenderer
5 from rhodecode.lib.utils import PartialRenderer
6 from rhodecode.lib.utils2 import AttributeDict
6 from rhodecode.model.notification import EmailNotificationModel
7 from rhodecode.model.notification import EmailNotificationModel
7
8
8
9
9 def test_get_template_obj(pylonsapp):
10 def test_get_template_obj(app):
10 template = EmailNotificationModel().get_renderer(
11 template = EmailNotificationModel().get_renderer(
11 EmailNotificationModel.TYPE_TEST)
12 EmailNotificationModel.TYPE_TEST)
12 assert isinstance(template, PartialRenderer)
13 assert isinstance(template, PartialRenderer)
13
14
14
15
15 def test_render_email(pylonsapp):
16 def test_render_email(app, http_host_only_stub):
16 kwargs = {}
17 kwargs = {}
17 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
18 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
18 EmailNotificationModel.TYPE_TEST, **kwargs)
19 EmailNotificationModel.TYPE_TEST, **kwargs)
@@ -27,12 +28,13 b' def test_render_email(pylonsapp):'
27 assert body_plaintext == 'Email Plaintext Body'
28 assert body_plaintext == 'Email Plaintext Body'
28
29
29 # body
30 # body
30 assert 'This is a notification ' \
31 notification_footer = 'This is a notification from RhodeCode. http://%s/' \
31 'from RhodeCode. http://test.example.com:80/' in body
32 % http_host_only_stub
33 assert notification_footer in body
32 assert 'Email Body' in body
34 assert 'Email Body' in body
33
35
34
36
35 def test_render_pr_email(pylonsapp, user_admin):
37 def test_render_pr_email(app, user_admin):
36
38
37 ref = collections.namedtuple('Ref',
39 ref = collections.namedtuple('Ref',
38 'name, type')(
40 'name, type')(
@@ -66,3 +68,57 b' def test_render_pr_email(pylonsapp, user'
66
68
67 # subject
69 # subject
68 assert subject == 'Marcin Kuzminski wants you to review pull request #200: "Example Pull Request"'
70 assert subject == 'Marcin Kuzminski wants you to review pull request #200: "Example Pull Request"'
71
72
73 @pytest.mark.parametrize('mention', [
74 True,
75 False
76 ])
77 @pytest.mark.parametrize('email_type', [
78 EmailNotificationModel.TYPE_COMMIT_COMMENT,
79 EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
80 ])
81 def test_render_comment_subject_no_newlines(app, mention, email_type):
82 ref = collections.namedtuple('Ref',
83 'name, type')(
84 'fxies123', 'book'
85 )
86
87 pr = collections.namedtuple('PullRequest',
88 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
89 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
90
91 source_repo = target_repo = collections.namedtuple('Repo',
92 'type, repo_name')(
93 'hg', 'pull_request_1')
94
95 kwargs = {
96 'user': '<marcin@rhodecode.com> Marcin Kuzminski',
97 'commit': AttributeDict(raw_id='a'*40, message='Commit message'),
98 'status_change': 'approved',
99 'commit_target_repo': AttributeDict(),
100 'repo_name': 'test-repo',
101 'comment_file': 'test-file.py',
102 'comment_line': 'n100',
103 'comment_type': 'note',
104 'commit_comment_url': 'http://comment-url',
105 'instance_url': 'http://rc-instance',
106 'comment_body': 'hello world',
107 'mention': mention,
108
109 'pr_comment_url': 'http://comment-url',
110 'pr_source_repo': AttributeDict(repo_name='foobar'),
111 'pr_source_repo_url': 'http://soirce-repo/url',
112 'pull_request': pr,
113 'pull_request_commits': [],
114
115 'pull_request_target_repo': target_repo,
116 'pull_request_target_repo_url': 'x',
117
118 'pull_request_source_repo': source_repo,
119 'pull_request_source_repo_url': 'x',
120 }
121 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
122 email_type, **kwargs)
123
124 assert '\n' not in subject
1 NO CONTENT: file renamed from rhodecode/tests/controllers/test_search.py to rhodecode/tests/lib/test_search.py
NO CONTENT: file renamed from rhodecode/tests/controllers/test_search.py to rhodecode/tests/lib/test_search.py
@@ -86,22 +86,24 b' HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH'
86 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
86 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
87 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
87 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
88 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
88 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
89 HOOK_PUSH_KEY = db.RhodeCodeUi.HOOK_PUSH_KEY
89
90
90 HG_HOOKS = frozenset(
91 HG_HOOKS = frozenset(
91 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
92 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
92 HOOK_REPO_SIZE))
93 HOOK_REPO_SIZE, HOOK_PUSH_KEY))
93
94
94
95
95 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
96 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
96 ([], HG_HOOKS),
97 ([], HG_HOOKS),
97 (HG_HOOKS, []),
98 (HG_HOOKS, []),
98
99
99 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
100 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE, HOOK_PUSH_KEY], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
100
101
101 # When a pull/push hook is disabled, its pre-pull/push counterpart should
102 # When a pull/push hook is disabled, its pre-pull/push counterpart should
102 # be disabled too.
103 # be disabled too.
103 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
104 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
104 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE]),
105 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE,
106 HOOK_PUSH_KEY]),
105 ])
107 ])
106 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
108 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
107 expected_hooks):
109 expected_hooks):
@@ -219,11 +221,6 b' def platform_encodes_filenames():'
219 return path_with_latin1 != read_path
221 return path_with_latin1 != read_path
220
222
221
223
222 def test_action_logger_action_size(pylonsapp, test_repo):
223 action = 'x' * 1200001
224 utils.action_logger(TEST_USER_ADMIN_LOGIN, action, test_repo, commit=True)
225
226
227 @pytest.fixture
224 @pytest.fixture
228 def repo_groups(request):
225 def repo_groups(request):
229 session = meta.Session()
226 session = meta.Session()
@@ -52,18 +52,18 b' class TestRepoGroupSchema(object):'
52 )
52 )
53
53
54 schema_data = schema.deserialize(dict(
54 schema_data = schema.deserialize(dict(
55 repo_group_name='dupa',
55 repo_group_name='my_schema_group',
56 repo_group_owner=user_admin.username
56 repo_group_owner=user_admin.username
57 ))
57 ))
58
58
59 assert schema_data['repo_group_name'] == 'dupa'
59 assert schema_data['repo_group_name'] == u'my_schema_group'
60 assert schema_data['repo_group'] == {
60 assert schema_data['repo_group'] == {
61 'repo_group_id': None,
61 'repo_group_id': None,
62 'repo_group_name': types.RootLocation,
62 'repo_group_name': types.RootLocation,
63 'repo_group_name_without_group': 'dupa'}
63 'repo_group_name_without_group': u'my_schema_group'}
64
64
65 @pytest.mark.parametrize('given, err_key, expected_exc', [
65 @pytest.mark.parametrize('given, err_key, expected_exc', [
66 ('xxx/dupa', 'repo_group', 'Parent repository group `xxx` does not exist'),
66 ('xxx/my_schema_group', 'repo_group', 'Parent repository group `xxx` does not exist'),
67 ('', 'repo_group_name', 'Name must start with a letter or number. Got ``'),
67 ('', 'repo_group_name', 'Name must start with a letter or number. Got ``'),
68 ])
68 ])
69 def test_deserialize_with_bad_group_name(
69 def test_deserialize_with_bad_group_name(
@@ -86,7 +86,7 b' class TestRepoGroupSchema(object):'
86 user=user_admin
86 user=user_admin
87 )
87 )
88
88
89 full_name = test_repo_group.group_name + '/dupa'
89 full_name = test_repo_group.group_name + u'/my_schema_group'
90 schema_data = schema.deserialize(dict(
90 schema_data = schema.deserialize(dict(
91 repo_group_name=full_name,
91 repo_group_name=full_name,
92 repo_group_owner=user_admin.username
92 repo_group_owner=user_admin.username
@@ -96,7 +96,7 b' class TestRepoGroupSchema(object):'
96 assert schema_data['repo_group'] == {
96 assert schema_data['repo_group'] == {
97 'repo_group_id': test_repo_group.group_id,
97 'repo_group_id': test_repo_group.group_id,
98 'repo_group_name': test_repo_group.group_name,
98 'repo_group_name': test_repo_group.group_name,
99 'repo_group_name_without_group': 'dupa'}
99 'repo_group_name_without_group': u'my_schema_group'}
100
100
101 def test_deserialize_with_group_name_regular_user_no_perms(
101 def test_deserialize_with_group_name_regular_user_no_perms(
102 self, app, user_regular, test_repo_group):
102 self, app, user_regular, test_repo_group):
@@ -104,7 +104,7 b' class TestRepoGroupSchema(object):'
104 user=user_regular
104 user=user_regular
105 )
105 )
106
106
107 full_name = test_repo_group.group_name + '/dupa'
107 full_name = test_repo_group.group_name + u'/my_schema_group'
108 with pytest.raises(colander.Invalid) as excinfo:
108 with pytest.raises(colander.Invalid) as excinfo:
109 schema.deserialize(dict(
109 schema.deserialize(dict(
110 repo_group_name=full_name,
110 repo_group_name=full_name,
@@ -57,19 +57,20 b' class TestRepoSchema(object):'
57 )
57 )
58
58
59 schema_data = schema.deserialize(dict(
59 schema_data = schema.deserialize(dict(
60 repo_name='dupa',
60 repo_name='my_schema_repo',
61 repo_type='hg',
61 repo_type='hg',
62 repo_owner=user_admin.username
62 repo_owner=user_admin.username
63 ))
63 ))
64
64
65 assert schema_data['repo_name'] == 'dupa'
65 assert schema_data['repo_name'] == u'my_schema_repo'
66 assert schema_data['repo_group'] == {
66 assert schema_data['repo_group'] == {
67 'repo_group_id': None,
67 'repo_group_id': None,
68 'repo_group_name': types.RootLocation,
68 'repo_group_name': types.RootLocation,
69 'repo_name_without_group': 'dupa'}
69 'repo_name_with_group': u'my_schema_repo',
70 'repo_name_without_group': u'my_schema_repo'}
70
71
71 @pytest.mark.parametrize('given, err_key, expected_exc', [
72 @pytest.mark.parametrize('given, err_key, expected_exc', [
72 ('xxx/dupa','repo_group', 'Repository group `xxx` does not exist'),
73 ('xxx/my_schema_repo','repo_group', 'Repository group `xxx` does not exist'),
73 ('', 'repo_name', 'Name must start with a letter or number. Got ``'),
74 ('', 'repo_name', 'Name must start with a letter or number. Got ``'),
74 ])
75 ])
75 def test_deserialize_with_bad_group_name(
76 def test_deserialize_with_bad_group_name(
@@ -95,7 +96,7 b' class TestRepoSchema(object):'
95 user=user_admin
96 user=user_admin
96 )
97 )
97
98
98 full_name = test_repo_group.group_name + '/dupa'
99 full_name = test_repo_group.group_name + u'/my_schema_repo'
99 schema_data = schema.deserialize(dict(
100 schema_data = schema.deserialize(dict(
100 repo_name=full_name,
101 repo_name=full_name,
101 repo_type='hg',
102 repo_type='hg',
@@ -106,7 +107,8 b' class TestRepoSchema(object):'
106 assert schema_data['repo_group'] == {
107 assert schema_data['repo_group'] == {
107 'repo_group_id': test_repo_group.group_id,
108 'repo_group_id': test_repo_group.group_id,
108 'repo_group_name': test_repo_group.group_name,
109 'repo_group_name': test_repo_group.group_name,
109 'repo_name_without_group': 'dupa'}
110 'repo_name_with_group': full_name,
111 'repo_name_without_group': u'my_schema_repo'}
110
112
111 def test_deserialize_with_group_name_regular_user_no_perms(
113 def test_deserialize_with_group_name_regular_user_no_perms(
112 self, app, user_regular, test_repo_group):
114 self, app, user_regular, test_repo_group):
@@ -115,7 +117,7 b' class TestRepoSchema(object):'
115 user=user_regular
117 user=user_regular
116 )
118 )
117
119
118 full_name = test_repo_group.group_name + '/dupa'
120 full_name = test_repo_group.group_name + '/my_schema_repo'
119 with pytest.raises(colander.Invalid) as excinfo:
121 with pytest.raises(colander.Invalid) as excinfo:
120 schema.deserialize(dict(
122 schema.deserialize(dict(
121 repo_name=full_name,
123 repo_name=full_name,
@@ -500,7 +500,8 b' class TestCreateOrUpdateUi(object):'
500 def test_update(self, repo_stub, settings_util):
500 def test_update(self, repo_stub, settings_util):
501 model = VcsSettingsModel(repo=repo_stub.repo_name)
501 model = VcsSettingsModel(repo=repo_stub.repo_name)
502
502
503 largefiles, phases = model.HG_SETTINGS
503 largefiles, phases, evolve = model.HG_SETTINGS
504
504 section = 'test-section'
505 section = 'test-section'
505 key = 'test-key'
506 key = 'test-key'
506 settings_util.create_repo_rhodecode_ui(
507 settings_util.create_repo_rhodecode_ui(
@@ -519,6 +520,7 b' class TestCreateOrUpdateUi(object):'
519 class TestCreateOrUpdateRepoHgSettings(object):
520 class TestCreateOrUpdateRepoHgSettings(object):
520 FORM_DATA = {
521 FORM_DATA = {
521 'extensions_largefiles': False,
522 'extensions_largefiles': False,
523 'extensions_evolve': False,
522 'phases_publish': False
524 'phases_publish': False
523 }
525 }
524
526
@@ -529,6 +531,8 b' class TestCreateOrUpdateRepoHgSettings(o'
529 expected_calls = [
531 expected_calls = [
530 mock.call(model.repo_settings, 'extensions', 'largefiles',
532 mock.call(model.repo_settings, 'extensions', 'largefiles',
531 active=False, value=''),
533 active=False, value=''),
534 mock.call(model.repo_settings, 'extensions', 'evolve',
535 active=False, value=''),
532 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
536 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
533 ]
537 ]
534 assert expected_calls == create_mock.call_args_list
538 assert expected_calls == create_mock.call_args_list
@@ -574,7 +578,8 b' class TestCreateOrUpdateGlobalHgSettings'
574 'extensions_largefiles': False,
578 'extensions_largefiles': False,
575 'largefiles_usercache': '/example/largefiles-store',
579 'largefiles_usercache': '/example/largefiles-store',
576 'phases_publish': False,
580 'phases_publish': False,
577 'extensions_hgsubversion': False
581 'extensions_hgsubversion': False,
582 'extensions_evolve': False
578 }
583 }
579
584
580 def test_creates_repo_hg_settings_when_data_is_correct(self):
585 def test_creates_repo_hg_settings_when_data_is_correct(self):
@@ -589,7 +594,9 b' class TestCreateOrUpdateGlobalHgSettings'
589 mock.call(model.global_settings, 'phases', 'publish',
594 mock.call(model.global_settings, 'phases', 'publish',
590 value='False'),
595 value='False'),
591 mock.call(model.global_settings, 'extensions', 'hgsubversion',
596 mock.call(model.global_settings, 'extensions', 'hgsubversion',
592 active=False)
597 active=False),
598 mock.call(model.global_settings, 'extensions', 'evolve',
599 active=False, value='')
593 ]
600 ]
594 assert expected_calls == create_mock.call_args_list
601 assert expected_calls == create_mock.call_args_list
595
602
@@ -957,6 +964,7 b' class TestCreateOrUpdateRepoSettings(obj'
957 'hooks_changegroup_push_logger': False,
964 'hooks_changegroup_push_logger': False,
958 'hooks_outgoing_pull_logger': False,
965 'hooks_outgoing_pull_logger': False,
959 'extensions_largefiles': False,
966 'extensions_largefiles': False,
967 'extensions_evolve': False,
960 'largefiles_usercache': '/example/largefiles-store',
968 'largefiles_usercache': '/example/largefiles-store',
961 'vcs_git_lfs_enabled': False,
969 'vcs_git_lfs_enabled': False,
962 'vcs_git_lfs_store_location': '/',
970 'vcs_git_lfs_store_location': '/',
@@ -30,7 +30,7 b' pytestmark = ['
30 ]
30 ]
31
31
32
32
33 def test_new_pull_request_is_under_review(pr_util):
33 def test_new_pull_request_is_under_review(pr_util, config_stub):
34 pull_request = pr_util.create_pull_request()
34 pull_request = pr_util.create_pull_request()
35
35
36 # Expect that review status "Under Review"
36 # Expect that review status "Under Review"
@@ -44,7 +44,7 b' def test_new_pull_request_is_under_revie'
44 db.ChangesetStatus.STATUS_UNDER_REVIEW,
44 db.ChangesetStatus.STATUS_UNDER_REVIEW,
45 ])
45 ])
46 def test_pull_request_under_review_if_one_reviewer_voted(
46 def test_pull_request_under_review_if_one_reviewer_voted(
47 pr_util, voted_status):
47 pr_util, voted_status, config_stub):
48 pull_request = pr_util.create_pull_request()
48 pull_request = pr_util.create_pull_request()
49 pr_util.create_status_votes(
49 pr_util.create_status_votes(
50 voted_status, pull_request.reviewers[0])
50 voted_status, pull_request.reviewers[0])
@@ -59,7 +59,7 b' def test_pull_request_under_review_if_on'
59 db.ChangesetStatus.STATUS_REJECTED,
59 db.ChangesetStatus.STATUS_REJECTED,
60 db.ChangesetStatus.STATUS_UNDER_REVIEW,
60 db.ChangesetStatus.STATUS_UNDER_REVIEW,
61 ])
61 ])
62 def test_pull_request_has_voted_status_if_all_voted(pr_util, voted_status):
62 def test_pull_request_has_voted_status_if_all_voted(pr_util, voted_status, config_stub):
63 pull_request = pr_util.create_pull_request()
63 pull_request = pr_util.create_pull_request()
64 pr_util.create_status_votes(
64 pr_util.create_status_votes(
65 voted_status, *pull_request.reviewers)
65 voted_status, *pull_request.reviewers)
@@ -74,7 +74,8 b' def test_pull_request_has_voted_status_i'
74 db.ChangesetStatus.STATUS_REJECTED,
74 db.ChangesetStatus.STATUS_REJECTED,
75 db.ChangesetStatus.STATUS_UNDER_REVIEW,
75 db.ChangesetStatus.STATUS_UNDER_REVIEW,
76 ])
76 ])
77 def test_pull_request_stays_if_update_without_change(pr_util, voted_status):
77 def test_pull_request_stays_if_update_without_change(
78 pr_util, voted_status, config_stub):
78 pull_request = pr_util.create_pull_request()
79 pull_request = pr_util.create_pull_request()
79 pr_util.create_status_votes(
80 pr_util.create_status_votes(
80 voted_status, *pull_request.reviewers)
81 voted_status, *pull_request.reviewers)
@@ -92,7 +93,7 b' def test_pull_request_stays_if_update_wi'
92 db.ChangesetStatus.STATUS_REJECTED,
93 db.ChangesetStatus.STATUS_REJECTED,
93 db.ChangesetStatus.STATUS_UNDER_REVIEW,
94 db.ChangesetStatus.STATUS_UNDER_REVIEW,
94 ])
95 ])
95 def test_pull_request_under_review_if_update(pr_util, voted_status):
96 def test_pull_request_under_review_if_update(pr_util, voted_status, config_stub):
96 pull_request = pr_util.create_pull_request()
97 pull_request = pr_util.create_pull_request()
97 pr_util.create_status_votes(
98 pr_util.create_status_votes(
98 voted_status, *pull_request.reviewers)
99 voted_status, *pull_request.reviewers)
@@ -106,7 +107,7 b' def test_pull_request_under_review_if_up'
106 assert pull_request.calculated_review_status() == expected_review_status
107 assert pull_request.calculated_review_status() == expected_review_status
107
108
108
109
109 def test_commit_under_review_if_part_of_new_pull_request(pr_util):
110 def test_commit_under_review_if_part_of_new_pull_request(pr_util, config_stub):
110 pull_request = pr_util.create_pull_request()
111 pull_request = pr_util.create_pull_request()
111 for commit_id in pull_request.revisions:
112 for commit_id in pull_request.revisions:
112 status = ChangesetStatusModel().get_status(
113 status = ChangesetStatusModel().get_status(
@@ -120,7 +121,7 b' def test_commit_under_review_if_part_of_'
120 db.ChangesetStatus.STATUS_UNDER_REVIEW,
121 db.ChangesetStatus.STATUS_UNDER_REVIEW,
121 ])
122 ])
122 def test_commit_has_voted_status_after_vote_on_pull_request(
123 def test_commit_has_voted_status_after_vote_on_pull_request(
123 pr_util, voted_status):
124 pr_util, voted_status, config_stub):
124 pull_request = pr_util.create_pull_request()
125 pull_request = pr_util.create_pull_request()
125 pr_util.create_status_votes(
126 pr_util.create_status_votes(
126 voted_status, pull_request.reviewers[0])
127 voted_status, pull_request.reviewers[0])
@@ -130,7 +131,7 b' def test_commit_has_voted_status_after_v'
130 assert status == voted_status
131 assert status == voted_status
131
132
132
133
133 def test_commit_under_review_if_added_to_pull_request(pr_util):
134 def test_commit_under_review_if_added_to_pull_request(pr_util, config_stub):
134 pull_request = pr_util.create_pull_request()
135 pull_request = pr_util.create_pull_request()
135 pr_util.create_status_votes(
136 pr_util.create_status_votes(
136 db.ChangesetStatus.STATUS_APPROVED, pull_request.reviewers[0])
137 db.ChangesetStatus.STATUS_APPROVED, pull_request.reviewers[0])
@@ -147,7 +148,7 b' def test_commit_under_review_if_added_to'
147 db.ChangesetStatus.STATUS_UNDER_REVIEW,
148 db.ChangesetStatus.STATUS_UNDER_REVIEW,
148 ])
149 ])
149 def test_commit_keeps_status_if_removed_from_pull_request(
150 def test_commit_keeps_status_if_removed_from_pull_request(
150 pr_util, voted_status):
151 pr_util, voted_status, config_stub):
151 pull_request = pr_util.create_pull_request()
152 pull_request = pr_util.create_pull_request()
152 pr_util.add_one_commit()
153 pr_util.add_one_commit()
153 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
154 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
@@ -165,7 +166,7 b' def test_commit_keeps_status_if_removed_'
165 db.ChangesetStatus.STATUS_UNDER_REVIEW,
166 db.ChangesetStatus.STATUS_UNDER_REVIEW,
166 ])
167 ])
167 def test_commit_keeps_status_if_unchanged_after_update_of_pull_request(
168 def test_commit_keeps_status_if_unchanged_after_update_of_pull_request(
168 pr_util, voted_status):
169 pr_util, voted_status, config_stub):
169 pull_request = pr_util.create_pull_request()
170 pull_request = pr_util.create_pull_request()
170 commit_id = pull_request.revisions[-1]
171 commit_id = pull_request.revisions[-1]
171 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
172 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
@@ -30,7 +30,7 b' from rhodecode.model.user import UserMod'
30 fixture = Fixture()
30 fixture = Fixture()
31
31
32
32
33 class TestNotifications:
33 class TestNotifications(object):
34 destroy_users = set()
34 destroy_users = set()
35
35
36 @classmethod
36 @classmethod
@@ -38,7 +38,7 b' class TestNotifications:'
38 fixture.destroy_users(cls.destroy_users)
38 fixture.destroy_users(cls.destroy_users)
39
39
40 @pytest.fixture(autouse=True)
40 @pytest.fixture(autouse=True)
41 def create_users(self, request, pylonsapp):
41 def create_users(self, request, app):
42 Session.remove()
42 Session.remove()
43 self.u1 = UserModel().create_or_update(
43 self.u1 = UserModel().create_or_update(
44 username=u'u1', password=u'qweqwe',
44 username=u'u1', password=u'qweqwe',
@@ -62,7 +62,7 b' class TestNotifications:'
62 self.destroy_users.add('u3')
62 self.destroy_users.add('u3')
63
63
64 @pytest.fixture(autouse=True)
64 @pytest.fixture(autouse=True)
65 def _clean_notifications(self, request, pylonsapp):
65 def _clean_notifications(self, request, app):
66 for n in Notification.query().all():
66 for n in Notification.query().all():
67 Session().delete(n)
67 Session().delete(n)
68
68
@@ -41,7 +41,8 b' pytestmark = ['
41 ]
41 ]
42
42
43
43
44 class TestPullRequestModel:
44 @pytest.mark.usefixtures('config_stub')
45 class TestPullRequestModel(object):
45
46
46 @pytest.fixture
47 @pytest.fixture
47 def pull_request(self, request, backend, pr_util):
48 def pull_request(self, request, backend, pr_util):
@@ -49,7 +50,9 b' class TestPullRequestModel:'
49 A pull request combined with multiples patches.
50 A pull request combined with multiples patches.
50 """
51 """
51 BackendClass = get_backend(backend.alias)
52 BackendClass = get_backend(backend.alias)
52 self.merge_patcher = mock.patch.object(BackendClass, 'merge')
53 self.merge_patcher = mock.patch.object(
54 BackendClass, 'merge', return_value=MergeResponse(
55 False, False, None, MergeFailureReason.UNKNOWN))
53 self.workspace_remove_patcher = mock.patch.object(
56 self.workspace_remove_patcher = mock.patch.object(
54 BackendClass, 'cleanup_merge_workspace')
57 BackendClass, 'cleanup_merge_workspace')
55
58
@@ -116,7 +119,8 b' class TestPullRequestModel:'
116
119
117 def test_get_awaiting_my_review(self, pull_request):
120 def test_get_awaiting_my_review(self, pull_request):
118 PullRequestModel().update_reviewers(
121 PullRequestModel().update_reviewers(
119 pull_request, [(pull_request.author, ['author'])])
122 pull_request, [(pull_request.author, ['author'], False)],
123 pull_request.author)
120 prs = PullRequestModel().get_awaiting_my_review(
124 prs = PullRequestModel().get_awaiting_my_review(
121 pull_request.target_repo, user_id=pull_request.author.user_id)
125 pull_request.target_repo, user_id=pull_request.author.user_id)
122 assert isinstance(prs, list)
126 assert isinstance(prs, list)
@@ -124,13 +128,14 b' class TestPullRequestModel:'
124
128
125 def test_count_awaiting_my_review(self, pull_request):
129 def test_count_awaiting_my_review(self, pull_request):
126 PullRequestModel().update_reviewers(
130 PullRequestModel().update_reviewers(
127 pull_request, [(pull_request.author, ['author'])])
131 pull_request, [(pull_request.author, ['author'], False)],
132 pull_request.author)
128 pr_count = PullRequestModel().count_awaiting_my_review(
133 pr_count = PullRequestModel().count_awaiting_my_review(
129 pull_request.target_repo, user_id=pull_request.author.user_id)
134 pull_request.target_repo, user_id=pull_request.author.user_id)
130 assert pr_count == 1
135 assert pr_count == 1
131
136
132 def test_delete_calls_cleanup_merge(self, pull_request):
137 def test_delete_calls_cleanup_merge(self, pull_request):
133 PullRequestModel().delete(pull_request)
138 PullRequestModel().delete(pull_request, pull_request.author)
134
139
135 self.workspace_remove_mock.assert_called_once_with(
140 self.workspace_remove_mock.assert_called_once_with(
136 self.workspace_id)
141 self.workspace_id)
@@ -372,6 +377,7 b' class TestPullRequestModel:'
372 assert type(title) == unicode
377 assert type(title) == unicode
373
378
374
379
380 @pytest.mark.usefixtures('config_stub')
375 class TestIntegrationMerge(object):
381 class TestIntegrationMerge(object):
376 @pytest.mark.parametrize('extra_config', (
382 @pytest.mark.parametrize('extra_config', (
377 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
383 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
@@ -433,7 +439,7 b' class TestIntegrationMerge(object):'
433 (True, 0, 1),
439 (True, 0, 1),
434 ])
440 ])
435 def test_outdated_comments(
441 def test_outdated_comments(
436 pr_util, use_outdated, inlines_count, outdated_count):
442 pr_util, use_outdated, inlines_count, outdated_count, config_stub):
437 pull_request = pr_util.create_pull_request()
443 pull_request = pr_util.create_pull_request()
438 pr_util.create_inline_comment(file_path='not_in_updated_diff')
444 pr_util.create_inline_comment(file_path='not_in_updated_diff')
439
445
@@ -465,6 +471,7 b' def merge_extras(user_regular):'
465 return extras
471 return extras
466
472
467
473
474 @pytest.mark.usefixtures('config_stub')
468 class TestUpdateCommentHandling(object):
475 class TestUpdateCommentHandling(object):
469
476
470 @pytest.fixture(autouse=True, scope='class')
477 @pytest.fixture(autouse=True, scope='class')
@@ -572,6 +579,7 b' class TestUpdateCommentHandling(object):'
572 assert_inline_comments(pull_request, visible=0, outdated=1)
579 assert_inline_comments(pull_request, visible=0, outdated=1)
573
580
574
581
582 @pytest.mark.usefixtures('config_stub')
575 class TestUpdateChangedFiles(object):
583 class TestUpdateChangedFiles(object):
576
584
577 def test_no_changes_on_unchanged_diff(self, pr_util):
585 def test_no_changes_on_unchanged_diff(self, pr_util):
@@ -681,7 +689,7 b' class TestUpdateChangedFiles(object):'
681 removed=['file_a', 'file_b', 'file_c'])
689 removed=['file_a', 'file_b', 'file_c'])
682
690
683
691
684 def test_update_writes_snapshot_into_pull_request_version(pr_util):
692 def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub):
685 model = PullRequestModel()
693 model = PullRequestModel()
686 pull_request = pr_util.create_pull_request()
694 pull_request = pr_util.create_pull_request()
687 pr_util.update_source_repository()
695 pr_util.update_source_repository()
@@ -692,7 +700,7 b' def test_update_writes_snapshot_into_pul'
692 assert len(model.get_versions(pull_request)) == 1
700 assert len(model.get_versions(pull_request)) == 1
693
701
694
702
695 def test_update_skips_new_version_if_unchanged(pr_util):
703 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
696 pull_request = pr_util.create_pull_request()
704 pull_request = pr_util.create_pull_request()
697 model = PullRequestModel()
705 model = PullRequestModel()
698 model.update_commits(pull_request)
706 model.update_commits(pull_request)
@@ -701,7 +709,7 b' def test_update_skips_new_version_if_unc'
701 assert len(model.get_versions(pull_request)) == 0
709 assert len(model.get_versions(pull_request)) == 0
702
710
703
711
704 def test_update_assigns_comments_to_the_new_version(pr_util):
712 def test_update_assigns_comments_to_the_new_version(pr_util, config_stub):
705 model = PullRequestModel()
713 model = PullRequestModel()
706 pull_request = pr_util.create_pull_request()
714 pull_request = pr_util.create_pull_request()
707 comment = pr_util.create_comment()
715 comment = pr_util.create_comment()
@@ -713,7 +721,7 b' def test_update_assigns_comments_to_the_'
713 assert comment.pull_request_version == model.get_versions(pull_request)[0]
721 assert comment.pull_request_version == model.get_versions(pull_request)[0]
714
722
715
723
716 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util):
724 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, config_stub):
717 model = PullRequestModel()
725 model = PullRequestModel()
718 pull_request = pr_util.create_pull_request()
726 pull_request = pr_util.create_pull_request()
719 pr_util.update_source_repository()
727 pr_util.update_source_repository()
@@ -745,7 +753,7 b' def test_update_adds_a_comment_to_the_pu'
745 assert update_comment.text == expected_message
753 assert update_comment.text == expected_message
746
754
747
755
748 def test_create_version_from_snapshot_updates_attributes(pr_util):
756 def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub):
749 pull_request = pr_util.create_pull_request()
757 pull_request = pr_util.create_pull_request()
750
758
751 # Avoiding default values
759 # Avoiding default values
@@ -784,7 +792,7 b' def test_create_version_from_snapshot_up'
784 assert version.pull_request == pull_request
792 assert version.pull_request == pull_request
785
793
786
794
787 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util):
795 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config_stub):
788 version1 = pr_util.create_version_of_pull_request()
796 version1 = pr_util.create_version_of_pull_request()
789 comment_linked = pr_util.create_comment(linked_to=version1)
797 comment_linked = pr_util.create_comment(linked_to=version1)
790 comment_unlinked = pr_util.create_comment()
798 comment_unlinked = pr_util.create_comment()
@@ -25,6 +25,7 b' from rhodecode.lib.vcs.exceptions import'
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26
26
27
27
28 @pytest.mark.usefixtures('config_stub')
28 @pytest.mark.backends('git')
29 @pytest.mark.backends('git')
29 class TestGetDiffForPrOrVersion(object):
30 class TestGetDiffForPrOrVersion(object):
30
31
@@ -29,10 +29,9 b' from rhodecode.model.db import Repositor'
29 from rhodecode.model.meta import Session
29 from rhodecode.model.meta import Session
30 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.scm import ScmModel
31 from rhodecode.model.scm import ScmModel
32 from rhodecode.lib.utils2 import safe_unicode
33
32
34
33
35 class TestRepoModel:
34 class TestRepoModel(object):
36
35
37 def test_remove_repo(self, backend):
36 def test_remove_repo(self, backend):
38 repo = backend.create_repo()
37 repo = backend.create_repo()
@@ -170,141 +169,3 b' class TestRepoModel:'
170 repo.update_commit_cache(config=config)
169 repo.update_commit_cache(config=config)
171 scm.assert_called_with(
170 scm.assert_called_with(
172 cache=False, config=config)
171 cache=False, config=config)
173
174
175 class TestGetUsers(object):
176 def test_returns_active_users(self, backend, user_util):
177 for i in range(4):
178 is_active = i % 2 == 0
179 user_util.create_user(active=is_active, lastname='Fake user')
180
181 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
182 users = RepoModel().get_users()
183 fake_users = [u for u in users if u['last_name'] == 'Fake user']
184 assert len(fake_users) == 2
185
186 expected_keys = (
187 'id', 'first_name', 'last_name', 'username', 'icon_link',
188 'value_display', 'value', 'value_type')
189 for user in users:
190 assert user['value_type'] is 'user'
191 for key in expected_keys:
192 assert key in user
193
194 def test_returns_user_filtered_by_last_name(self, backend, user_util):
195 keywords = ('aBc', u'ünicode')
196 for keyword in keywords:
197 for i in range(2):
198 user_util.create_user(
199 active=True, lastname=u'Fake {} user'.format(keyword))
200
201 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
202 keyword = keywords[1].lower()
203 users = RepoModel().get_users(name_contains=keyword)
204
205 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
206 assert len(fake_users) == 2
207 for user in fake_users:
208 assert user['last_name'] == safe_unicode('Fake ünicode user')
209
210 def test_returns_user_filtered_by_first_name(self, backend, user_util):
211 created_users = []
212 keywords = ('aBc', u'ünicode')
213 for keyword in keywords:
214 for i in range(2):
215 created_users.append(user_util.create_user(
216 active=True, lastname='Fake user',
217 firstname=u'Fake {} user'.format(keyword)))
218
219 keyword = keywords[1].lower()
220 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
221 users = RepoModel().get_users(name_contains=keyword)
222
223 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
224 assert len(fake_users) == 2
225 for user in fake_users:
226 assert user['first_name'] == safe_unicode('Fake ünicode user')
227
228 def test_returns_user_filtered_by_username(self, backend, user_util):
229 created_users = []
230 for i in range(5):
231 created_users.append(user_util.create_user(
232 active=True, lastname='Fake user'))
233
234 user_filter = created_users[-1].username[-2:]
235 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
236 users = RepoModel().get_users(name_contains=user_filter)
237
238 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
239 assert len(fake_users) == 1
240 assert fake_users[0]['username'] == created_users[-1].username
241
242 def test_returns_limited_user_list(self, backend, user_util):
243 created_users = []
244 for i in range(5):
245 created_users.append(user_util.create_user(
246 active=True, lastname='Fake user'))
247
248 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
249 users = RepoModel().get_users(name_contains='Fake', limit=3)
250
251 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
252 assert len(fake_users) == 3
253
254
255 class TestGetUserGroups(object):
256 def test_returns_filtered_list(self, backend, user_util):
257 created_groups = []
258 for i in range(4):
259 created_groups.append(
260 user_util.create_user_group(users_group_active=True))
261
262 group_filter = created_groups[-1].users_group_name[-2:]
263 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
264 with self._patch_user_group_list():
265 groups = RepoModel().get_user_groups(group_filter)
266
267 fake_groups = [
268 u for u in groups if u['value'].startswith('test_returns')]
269 assert len(fake_groups) == 1
270 assert fake_groups[0]['value'] == created_groups[-1].users_group_name
271 assert fake_groups[0]['value_display'].startswith(
272 'Group: test_returns')
273
274 def test_returns_limited_list(self, backend, user_util):
275 created_groups = []
276 for i in range(3):
277 created_groups.append(
278 user_util.create_user_group(users_group_active=True))
279 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
280 with self._patch_user_group_list():
281 groups = RepoModel().get_user_groups('test_returns')
282
283 fake_groups = [
284 u for u in groups if u['value'].startswith('test_returns')]
285 assert len(fake_groups) == 3
286
287 def test_returns_active_user_groups(self, backend, user_util):
288 for i in range(4):
289 is_active = i % 2 == 0
290 user_util.create_user_group(users_group_active=is_active)
291 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
292 with self._patch_user_group_list():
293 groups = RepoModel().get_user_groups()
294 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
295 for group in groups:
296 assert group['value_type'] is 'user_group'
297 for key in expected:
298 assert key in group
299
300 fake_groups = [
301 u for u in groups if u['value'].startswith('test_returns')]
302 assert len(fake_groups) == 2
303 for user in fake_groups:
304 assert user['value_display'].startswith('Group: test_returns')
305
306 def _patch_user_group_list(self):
307 def side_effect(group_list, perm_set):
308 return group_list
309 return mock.patch(
310 'rhodecode.model.repo.UserGroupList', side_effect=side_effect)
@@ -27,6 +27,7 b' from mock import Mock, patch, DEFAULT'
27
27
28 import rhodecode
28 import rhodecode
29 from rhodecode.model import db, scm
29 from rhodecode.model import db, scm
30 from rhodecode.tests import no_newline_id_generator
30
31
31
32
32 def test_scm_instance_config(backend):
33 def test_scm_instance_config(backend):
@@ -295,7 +296,7 b' class TestCheckRhodecodeHook(object):'
295 @pytest.mark.parametrize("file_content, expected_result", [
296 @pytest.mark.parametrize("file_content, expected_result", [
296 ("RC_HOOK_VER = '3.3.3'\n", True),
297 ("RC_HOOK_VER = '3.3.3'\n", True),
297 ("RC_HOOK = '3.3.3'\n", False),
298 ("RC_HOOK = '3.3.3'\n", False),
298 ])
299 ], ids=no_newline_id_generator)
299 @patch('os.path.exists', Mock(return_value=True))
300 @patch('os.path.exists', Mock(return_value=True))
300 def test_signatures(self, file_content, expected_result):
301 def test_signatures(self, file_content, expected_result):
301 hook_content_patcher = patch.object(
302 hook_content_patcher = patch.object(
@@ -35,6 +35,64 b' def teardown_module(self):'
35 _delete_all_user_groups()
35 _delete_all_user_groups()
36
36
37
37
38 class TestGetUserGroups(object):
39 def test_returns_filtered_list(self, backend, user_util):
40 created_groups = []
41 for i in range(4):
42 created_groups.append(
43 user_util.create_user_group(users_group_active=True))
44
45 group_filter = created_groups[-1].users_group_name[-2:]
46 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
47 with self._patch_user_group_list():
48 groups = UserGroupModel().get_user_groups(group_filter)
49
50 fake_groups = [
51 u for u in groups if u['value'].startswith('test_returns')]
52 assert len(fake_groups) == 1
53 assert fake_groups[0]['value'] == created_groups[-1].users_group_name
54 assert fake_groups[0]['value_display'].startswith(
55 'Group: test_returns')
56
57 def test_returns_limited_list(self, backend, user_util):
58 created_groups = []
59 for i in range(3):
60 created_groups.append(
61 user_util.create_user_group(users_group_active=True))
62 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
63 with self._patch_user_group_list():
64 groups = UserGroupModel().get_user_groups('test_returns')
65
66 fake_groups = [
67 u for u in groups if u['value'].startswith('test_returns')]
68 assert len(fake_groups) == 3
69
70 def test_returns_active_user_groups(self, backend, user_util):
71 for i in range(4):
72 is_active = i % 2 == 0
73 user_util.create_user_group(users_group_active=is_active)
74 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
75 with self._patch_user_group_list():
76 groups = UserGroupModel().get_user_groups()
77 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
78 for group in groups:
79 assert group['value_type'] is 'user_group'
80 for key in expected:
81 assert key in group
82
83 fake_groups = [
84 u for u in groups if u['value'].startswith('test_returns')]
85 assert len(fake_groups) == 2
86 for user in fake_groups:
87 assert user['value_display'].startswith('Group: test_returns')
88
89 def _patch_user_group_list(self):
90 def side_effect(group_list, perm_set):
91 return group_list
92 return mock.patch(
93 'rhodecode.model.user_group.UserGroupList', side_effect=side_effect)
94
95
38 @pytest.mark.parametrize(
96 @pytest.mark.parametrize(
39 "pre_existing, regular_should_be, external_should_be, groups, "
97 "pre_existing, regular_should_be, external_should_be, groups, "
40 "expected", [
98 "expected", [
@@ -19,10 +19,11 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from sqlalchemy.sql.expression import true
22 import mock
23
23
24 from rhodecode.model.db import User, UserGroup, UserGroupMember, UserEmailMap,\
24 from rhodecode.lib.utils2 import safe_unicode
25 Permission, UserIpMap
25 from rhodecode.model.db import (
26 true, User, UserGroup, UserGroupMember, UserEmailMap, Permission, UserIpMap)
26 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
27 from rhodecode.model.user import UserModel
28 from rhodecode.model.user import UserModel
28 from rhodecode.model.user_group import UserGroupModel
29 from rhodecode.model.user_group import UserGroupModel
@@ -33,6 +34,86 b' from rhodecode.tests.fixture import Fixt'
33 fixture = Fixture()
34 fixture = Fixture()
34
35
35
36
37 class TestGetUsers(object):
38 def test_returns_active_users(self, backend, user_util):
39 for i in range(4):
40 is_active = i % 2 == 0
41 user_util.create_user(active=is_active, lastname='Fake user')
42
43 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
44 users = UserModel().get_users()
45 fake_users = [u for u in users if u['last_name'] == 'Fake user']
46 assert len(fake_users) == 2
47
48 expected_keys = (
49 'id', 'first_name', 'last_name', 'username', 'icon_link',
50 'value_display', 'value', 'value_type')
51 for user in users:
52 assert user['value_type'] is 'user'
53 for key in expected_keys:
54 assert key in user
55
56 def test_returns_user_filtered_by_last_name(self, backend, user_util):
57 keywords = ('aBc', u'ünicode')
58 for keyword in keywords:
59 for i in range(2):
60 user_util.create_user(
61 active=True, lastname=u'Fake {} user'.format(keyword))
62
63 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
64 keyword = keywords[1].lower()
65 users = UserModel().get_users(name_contains=keyword)
66
67 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
68 assert len(fake_users) == 2
69 for user in fake_users:
70 assert user['last_name'] == safe_unicode('Fake ünicode user')
71
72 def test_returns_user_filtered_by_first_name(self, backend, user_util):
73 created_users = []
74 keywords = ('aBc', u'ünicode')
75 for keyword in keywords:
76 for i in range(2):
77 created_users.append(user_util.create_user(
78 active=True, lastname='Fake user',
79 firstname=u'Fake {} user'.format(keyword)))
80
81 keyword = keywords[1].lower()
82 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
83 users = UserModel().get_users(name_contains=keyword)
84
85 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
86 assert len(fake_users) == 2
87 for user in fake_users:
88 assert user['first_name'] == safe_unicode('Fake ünicode user')
89
90 def test_returns_user_filtered_by_username(self, backend, user_util):
91 created_users = []
92 for i in range(5):
93 created_users.append(user_util.create_user(
94 active=True, lastname='Fake user'))
95
96 user_filter = created_users[-1].username[-2:]
97 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
98 users = UserModel().get_users(name_contains=user_filter)
99
100 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
101 assert len(fake_users) == 1
102 assert fake_users[0]['username'] == created_users[-1].username
103
104 def test_returns_limited_user_list(self, backend, user_util):
105 created_users = []
106 for i in range(5):
107 created_users.append(user_util.create_user(
108 active=True, lastname='Fake user'))
109
110 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
111 users = UserModel().get_users(name_contains='Fake', limit=3)
112
113 fake_users = [u for u in users if u['last_name'].startswith('Fake')]
114 assert len(fake_users) == 3
115
116
36 @pytest.fixture
117 @pytest.fixture
37 def test_user(request, pylonsapp):
118 def test_user(request, pylonsapp):
38 usr = UserModel().create_or_update(
119 usr = UserModel().create_or_update(
@@ -27,6 +27,8 b' import datetime'
27 import string
27 import string
28 import mock
28 import mock
29 import pytest
29 import pytest
30
31 from rhodecode.tests import no_newline_id_generator
30 from rhodecode.tests.utils import run_test_concurrently
32 from rhodecode.tests.utils import run_test_concurrently
31 from rhodecode.lib.helpers import InitialsGravatar
33 from rhodecode.lib.helpers import InitialsGravatar
32
34
@@ -113,7 +115,7 b' def test_str2bool(str_bool, expected):'
113 (pref+"user.dot hej ! not-needed maril@domain.org", []),
115 (pref+"user.dot hej ! not-needed maril@domain.org", []),
114 (pref+"\n@marcin", ['marcin']),
116 (pref+"\n@marcin", ['marcin']),
115 ]
117 ]
116 for pref in ['', '\n', 'hi !', '\t', '\n\n']]))
118 for pref in ['', '\n', 'hi !', '\t', '\n\n']]), ids=no_newline_id_generator)
117 def test_mention_extractor(text, expected):
119 def test_mention_extractor(text, expected):
118 from rhodecode.lib.utils2 import extract_mentioned_users
120 from rhodecode.lib.utils2 import extract_mentioned_users
119 got = extract_mentioned_users(text)
121 got = extract_mentioned_users(text)
@@ -330,8 +332,18 b' def test_initials_gravatar_mapping_algo('
330 ])
332 ])
331 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
333 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
332 from rhodecode.lib.utils2 import get_clone_url
334 from rhodecode.lib.utils2 import get_clone_url
333 clone_url = get_clone_url(uri_tmpl=tmpl, qualifed_home_url='http://vps1:8000'+prefix,
335
334 repo_name=repo_name, repo_id=23, **overrides)
336 class RequestStub(object):
337 def request_url(self, name):
338 return 'http://vps1:8000' + prefix
339
340 def route_url(self, name):
341 return self.request_url(name)
342
343 clone_url = get_clone_url(
344 request=RequestStub(),
345 uri_tmpl=tmpl,
346 repo_name=repo_name, repo_id=23, **overrides)
335 assert clone_url == expected
347 assert clone_url == expected
336
348
337
349
@@ -378,7 +390,7 b' def _quick_url(text, tmpl="""<a class="r'
378 some text url[123123123123]
390 some text url[123123123123]
379 sometimes !
391 sometimes !
380 """)
392 """)
381 ])
393 ], ids=no_newline_id_generator)
382 def test_urlify_commits(sample, expected):
394 def test_urlify_commits(sample, expected):
383 def fake_url(self, *args, **kwargs):
395 def fake_url(self, *args, **kwargs):
384 return '/some-url'
396 return '/some-url'
@@ -410,7 +422,7 b' def test_urlify_commits(sample, expected'
410 url[https://foo.bar.com]
422 url[https://foo.bar.com]
411 some text lalala""",
423 some text lalala""",
412 "https://foo.bar.com")
424 "https://foo.bar.com")
413 ])
425 ], ids=no_newline_id_generator)
414 def test_urlify_test(sample, expected, url_):
426 def test_urlify_test(sample, expected, url_):
415 from rhodecode.lib.helpers import urlify_text
427 from rhodecode.lib.helpers import urlify_text
416 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
428 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
@@ -131,6 +131,13 b' def _check_proper_git_push('
131 assert "Setting default branch" not in stderr
131 assert "Setting default branch" not in stderr
132
132
133
133
134 def _check_proper_hg_push(stdout, stderr, branch='default'):
135 assert 'pushing to' in stdout
136 assert 'searching for changes' in stdout
137
138 assert 'abort:' not in stderr
139
140
134 def _check_proper_clone(stdout, stderr, vcs):
141 def _check_proper_clone(stdout, stderr, vcs):
135 if vcs == 'hg':
142 if vcs == 'hg':
136 assert 'requesting all changes' in stdout
143 assert 'requesting all changes' in stdout
@@ -41,8 +41,7 b' from rhodecode.model.settings import Set'
41 from rhodecode.tests import (
41 from rhodecode.tests import (
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
43 from rhodecode.tests.fixture import Fixture
43 from rhodecode.tests.fixture import Fixture
44 from rhodecode.tests.utils import (
44 from rhodecode.tests.utils import is_url_reachable, wait_for_url
45 set_anonymous_access, is_url_reachable, wait_for_url)
46
45
47 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
46 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
48 REPO_GROUP = 'a_repo_group'
47 REPO_GROUP = 'a_repo_group'
@@ -84,6 +83,9 b' class RcWebServer(object):'
84 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
83 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
85 return _url
84 return _url
86
85
86 def host_url(self):
87 return 'http://' + get_host_url(self.pylons_config)
88
87
89
88 @pytest.fixture(scope="module")
90 @pytest.fixture(scope="module")
89 def rcextensions(request, pylonsapp, tmpdir_factory):
91 def rcextensions(request, pylonsapp, tmpdir_factory):
@@ -185,11 +187,6 b' def rc_web_server('
185 return RcWebServer(rc_web_server_config)
187 return RcWebServer(rc_web_server_config)
186
188
187
189
188 @pytest.fixture(scope='class', autouse=True)
189 def disable_anonymous_user_access(pylonsapp):
190 set_anonymous_access(False)
191
192
193 @pytest.fixture
190 @pytest.fixture
194 def disable_locking(pylonsapp):
191 def disable_locking(pylonsapp):
195 r = Repository.get_by_repo_name(GIT_REPO)
192 r = Repository.get_by_repo_name(GIT_REPO)
@@ -43,8 +43,8 b' def rc_web_server_config(testini_factory'
43 return testini_factory(CUSTOM_PARAMS)
43 return testini_factory(CUSTOM_PARAMS)
44
44
45
45
46 @pytest.mark.usefixtures("disable_locking")
46 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
47 class TestVCSOperationsOnCustomIniConfig:
47 class TestVCSOperationsOnCustomIniConfig(object):
48
48
49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
50 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
50 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
@@ -43,8 +43,8 b' def rc_web_server_config(testini_factory'
43 return testini_factory(CUSTOM_PARAMS)
43 return testini_factory(CUSTOM_PARAMS)
44
44
45
45
46 @pytest.mark.usefixtures("disable_locking")
46 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
47 class TestVCSOperationsOnCustomIniConfig:
47 class TestVCSOperationsOnCustomIniConfig(object):
48
48
49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
50 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
50 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
@@ -43,8 +43,8 b' def rc_web_server_config(testini_factory'
43 return testini_factory(CUSTOM_PARAMS)
43 return testini_factory(CUSTOM_PARAMS)
44
44
45
45
46 @pytest.mark.usefixtures("disable_locking")
46 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
47 class TestVCSOperationsOnCustomIniConfig:
47 class TestVCSOperationsOnCustomIniConfig(object):
48
48
49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
50 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
50 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
@@ -34,6 +34,7 b' import time'
34 import pytest
34 import pytest
35
35
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
37 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
37 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.db import Repository, UserIpMap, CacheKey
40 from rhodecode.model.db import Repository, UserIpMap, CacheKey
@@ -42,11 +43,12 b' from rhodecode.model.user import UserMod'
42 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
43 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
43
44
44 from rhodecode.tests.other.vcs_operations import (
45 from rhodecode.tests.other.vcs_operations import (
45 Command, _check_proper_clone, _check_proper_git_push, _add_files_and_push,
46 Command, _check_proper_clone, _check_proper_git_push,
47 _check_proper_hg_push, _add_files_and_push,
46 HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
48 HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
47
49
48
50
49 @pytest.mark.usefixtures("disable_locking")
51 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
50 class TestVCSOperations(object):
52 class TestVCSOperations(object):
51
53
52 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
54 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
@@ -62,6 +64,13 b' class TestVCSOperations(object):'
62 _check_proper_clone(stdout, stderr, 'git')
64 _check_proper_clone(stdout, stderr, 'git')
63 cmd.assert_returncode_success()
65 cmd.assert_returncode_success()
64
66
67 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
68 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
69 cmd = Command('/tmp')
70 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
71 _check_proper_clone(stdout, stderr, 'git')
72 cmd.assert_returncode_success()
73
65 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
74 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
66 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
75 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
67 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
76 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
@@ -389,7 +398,6 b' class TestVCSOperations(object):'
389 'hg clone', clone_url, tmpdir.strpath)
398 'hg clone', clone_url, tmpdir.strpath)
390 assert 'abort: authorization failed' in stderr
399 assert 'abort: authorization failed' in stderr
391
400
392
393 def test_clone_by_auth_token_with_scope(
401 def test_clone_by_auth_token_with_scope(
394 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
402 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
395 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
403 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
@@ -472,3 +480,176 b' def test_git_fetches_from_remote_reposit'
472 source_repo = backend_git['annotated-tag']
480 source_repo = backend_git['annotated-tag']
473 target_vcs_repo = backend_git.create_repo().scm_instance()
481 target_vcs_repo = backend_git.create_repo().scm_instance()
474 target_vcs_repo.fetch(rc_web_server.repo_clone_url(source_repo.repo_name))
482 target_vcs_repo.fetch(rc_web_server.repo_clone_url(source_repo.repo_name))
483
484
485 def test_git_push_shows_pull_request_refs(backend_git, rc_web_server, tmpdir):
486 """
487 test if remote info about refs is visible
488 """
489 empty_repo = backend_git.create_repo()
490
491 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
492
493 cmd = Command(tmpdir.strpath)
494 cmd.execute('git clone', clone_url)
495
496 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
497 repo.in_memory_commit.add(FileNode('readme.md', content='## Hello'))
498 repo.in_memory_commit.commit(
499 message='Commit on branch Master',
500 author='Automatic test',
501 branch='master')
502
503 repo_cmd = Command(repo.path)
504 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
505 _check_proper_git_push(stdout, stderr, branch='master')
506
507 ref = '{}/{}/pull-request/new?branch=master'.format(
508 rc_web_server.host_url(), empty_repo.repo_name)
509 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
510 assert 'remote: RhodeCode: push completed' in stderr
511
512 # push on the same branch
513 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
514 repo.in_memory_commit.add(FileNode('setup.py', content='print\n'))
515 repo.in_memory_commit.commit(
516 message='Commit2 on branch Master',
517 author='Automatic test2',
518 branch='master')
519
520 repo_cmd = Command(repo.path)
521 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
522 _check_proper_git_push(stdout, stderr, branch='master')
523
524 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
525 assert 'remote: RhodeCode: push completed' in stderr
526
527 # new Branch
528 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
529 repo.in_memory_commit.add(FileNode('feature1.py', content='## Hello world'))
530 repo.in_memory_commit.commit(
531 message='Commit on branch feature',
532 author='Automatic test',
533 branch='feature')
534
535 repo_cmd = Command(repo.path)
536 stdout, stderr = repo_cmd.execute('git push --verbose origin feature')
537 _check_proper_git_push(stdout, stderr, branch='feature')
538
539 ref = '{}/{}/pull-request/new?branch=feature'.format(
540 rc_web_server.host_url(), empty_repo.repo_name)
541 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
542 assert 'remote: RhodeCode: push completed' in stderr
543
544
545 def test_hg_push_shows_pull_request_refs(backend_hg, rc_web_server, tmpdir):
546 empty_repo = backend_hg.create_repo()
547
548 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
549
550 cmd = Command(tmpdir.strpath)
551 cmd.execute('hg clone', clone_url)
552
553 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
554 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
555 repo.in_memory_commit.commit(
556 message=u'Commit on branch default',
557 author=u'Automatic test',
558 branch='default')
559
560 repo_cmd = Command(repo.path)
561 repo_cmd.execute('hg checkout default')
562
563 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
564 _check_proper_hg_push(stdout, stderr, branch='default')
565
566 ref = '{}/{}/pull-request/new?branch=default'.format(
567 rc_web_server.host_url(), empty_repo.repo_name)
568 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
569 assert 'remote: RhodeCode: push completed' in stdout
570
571 # push on the same branch
572 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
573 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
574 repo.in_memory_commit.commit(
575 message=u'Commit2 on branch default',
576 author=u'Automatic test2',
577 branch=u'default')
578
579 repo_cmd = Command(repo.path)
580 repo_cmd.execute('hg checkout default')
581
582 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
583 _check_proper_hg_push(stdout, stderr, branch='default')
584
585 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
586 assert 'remote: RhodeCode: push completed' in stdout
587
588 # new Branch
589 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
590 repo.in_memory_commit.add(FileNode(u'feature1.py', content=u'## Hello world'))
591 repo.in_memory_commit.commit(
592 message=u'Commit on branch feature',
593 author=u'Automatic test',
594 branch=u'feature')
595
596 repo_cmd = Command(repo.path)
597 repo_cmd.execute('hg checkout feature')
598
599 stdout, stderr = repo_cmd.execute('hg push --new-branch --verbose', clone_url)
600 _check_proper_hg_push(stdout, stderr, branch='feature')
601
602 ref = '{}/{}/pull-request/new?branch=feature'.format(
603 rc_web_server.host_url(), empty_repo.repo_name)
604 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
605 assert 'remote: RhodeCode: push completed' in stdout
606
607
608 def test_hg_push_shows_pull_request_refs_book(backend_hg, rc_web_server, tmpdir):
609 empty_repo = backend_hg.create_repo()
610
611 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
612
613 cmd = Command(tmpdir.strpath)
614 cmd.execute('hg clone', clone_url)
615
616 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
617 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
618 repo.in_memory_commit.commit(
619 message=u'Commit on branch default',
620 author=u'Automatic test',
621 branch='default')
622
623 repo_cmd = Command(repo.path)
624 repo_cmd.execute('hg checkout default')
625
626 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
627 _check_proper_hg_push(stdout, stderr, branch='default')
628
629 ref = '{}/{}/pull-request/new?branch=default'.format(
630 rc_web_server.host_url(), empty_repo.repo_name)
631 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
632 assert 'remote: RhodeCode: push completed' in stdout
633
634 # add bookmark
635 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
636 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
637 repo.in_memory_commit.commit(
638 message=u'Commit2 on branch default',
639 author=u'Automatic test2',
640 branch=u'default')
641
642 repo_cmd = Command(repo.path)
643 repo_cmd.execute('hg checkout default')
644 repo_cmd.execute('hg bookmark feature2')
645 stdout, stderr = repo_cmd.execute('hg push -B feature2 --verbose', clone_url)
646 _check_proper_hg_push(stdout, stderr, branch='default')
647
648 ref = '{}/{}/pull-request/new?branch=default'.format(
649 rc_web_server.host_url(), empty_repo.repo_name)
650 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
651 ref = '{}/{}/pull-request/new?bookmark=feature2'.format(
652 rc_web_server.host_url(), empty_repo.repo_name)
653 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
654 assert 'remote: RhodeCode: push completed' in stdout
655 assert 'exporting bookmark feature2' in stdout
@@ -50,8 +50,8 b' def rc_web_server_config(testini_factory'
50 return testini_factory(CUSTOM_PARAMS)
50 return testini_factory(CUSTOM_PARAMS)
51
51
52
52
53 @pytest.mark.usefixtures("disable_locking")
53 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
54 class TestVCSOperationsOnCustomIniConfig:
54 class TestVCSOperationsOnCustomIniConfig(object):
55
55
56 def test_clone_and_create_lock_hg(self, rc_web_server, tmpdir):
56 def test_clone_and_create_lock_hg(self, rc_web_server, tmpdir):
57 # enable locking
57 # enable locking
@@ -49,8 +49,8 b' def rc_web_server_config(testini_factory'
49 return testini_factory(CUSTOM_PARAMS)
49 return testini_factory(CUSTOM_PARAMS)
50
50
51
51
52 @pytest.mark.usefixtures("disable_locking")
52 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
53 class TestVCSOperationsOnCustomIniConfig:
53 class TestVCSOperationsOnCustomIniConfig(object):
54
54
55 def test_clone_after_repo_was_locked_hg(self, rc_web_server, tmpdir):
55 def test_clone_after_repo_was_locked_hg(self, rc_web_server, tmpdir):
56 # lock repo
56 # lock repo
@@ -62,7 +62,7 b' from rhodecode.tests import ('
62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
64 TEST_USER_REGULAR_PASS)
64 TEST_USER_REGULAR_PASS)
65 from rhodecode.tests.utils import CustomTestApp
65 from rhodecode.tests.utils import CustomTestApp, set_anonymous_access, add_test_routes
66 from rhodecode.tests.fixture import Fixture
66 from rhodecode.tests.fixture import Fixture
67
67
68
68
@@ -191,7 +191,15 b' def http_host_stub():'
191 """
191 """
192 Value of HTTP_HOST in the test run.
192 Value of HTTP_HOST in the test run.
193 """
193 """
194 return 'test.example.com:80'
194 return 'example.com:80'
195
196
197 @pytest.fixture
198 def http_host_only_stub():
199 """
200 Value of HTTP_HOST in the test run.
201 """
202 return http_host_stub().split(':')[0]
195
203
196
204
197 @pytest.fixture
205 @pytest.fixture
@@ -204,7 +212,7 b' def http_environ(http_host_stub):'
204 to override this for a specific test case.
212 to override this for a specific test case.
205 """
213 """
206 return {
214 return {
207 'SERVER_NAME': http_host_stub.split(':')[0],
215 'SERVER_NAME': http_host_only_stub(),
208 'SERVER_PORT': http_host_stub.split(':')[1],
216 'SERVER_PORT': http_host_stub.split(':')[1],
209 'HTTP_HOST': http_host_stub,
217 'HTTP_HOST': http_host_stub,
210 'HTTP_USER_AGENT': 'rc-test-agent',
218 'HTTP_USER_AGENT': 'rc-test-agent',
@@ -213,7 +221,7 b' def http_environ(http_host_stub):'
213
221
214
222
215 @pytest.fixture(scope='function')
223 @pytest.fixture(scope='function')
216 def app(request, pylonsapp, http_environ):
224 def app(request, config_stub, pylonsapp, http_environ):
217 app = CustomTestApp(
225 app = CustomTestApp(
218 pylonsapp,
226 pylonsapp,
219 extra_environ=http_environ)
227 extra_environ=http_environ)
@@ -884,7 +892,7 b' class RepoServer(object):'
884
892
885
893
886 @pytest.fixture
894 @pytest.fixture
887 def pr_util(backend, request):
895 def pr_util(backend, request, config_stub):
888 """
896 """
889 Utility for tests of models and for functional tests around pull requests.
897 Utility for tests of models and for functional tests around pull requests.
890
898
@@ -986,10 +994,9 b' class PRTestUtility(object):'
986 return reference
994 return reference
987
995
988 def _get_reviewers(self):
996 def _get_reviewers(self):
989 model = UserModel()
990 return [
997 return [
991 model.get_by_username(TEST_USER_REGULAR_LOGIN),
998 (TEST_USER_REGULAR_LOGIN, ['default1'], False),
992 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
999 (TEST_USER_REGULAR2_LOGIN, ['default2'], False),
993 ]
1000 ]
994
1001
995 def update_source_repository(self, head=None):
1002 def update_source_repository(self, head=None):
@@ -1078,7 +1085,7 b' class PRTestUtility(object):'
1078 # request will already be deleted.
1085 # request will already be deleted.
1079 pull_request = PullRequest().get(self.pull_request_id)
1086 pull_request = PullRequest().get(self.pull_request_id)
1080 if pull_request:
1087 if pull_request:
1081 PullRequestModel().delete(pull_request)
1088 PullRequestModel().delete(pull_request, pull_request.author)
1082 Session().commit()
1089 Session().commit()
1083
1090
1084 if self.notification_patcher:
1091 if self.notification_patcher:
@@ -1641,14 +1648,6 b' def no_notifications(request):'
1641 request.addfinalizer(notification_patcher.stop)
1648 request.addfinalizer(notification_patcher.stop)
1642
1649
1643
1650
1644 @pytest.fixture
1645 def silence_action_logger(request):
1646 notification_patcher = mock.patch(
1647 'rhodecode.lib.utils.action_logger')
1648 notification_patcher.start()
1649 request.addfinalizer(notification_patcher.stop)
1650
1651
1652 @pytest.fixture(scope='session')
1651 @pytest.fixture(scope='session')
1653 def repeat(request):
1652 def repeat(request):
1654 """
1653 """
@@ -1676,11 +1675,21 b' def request_stub():'
1676
1675
1677
1676
1678 @pytest.fixture
1677 @pytest.fixture
1678 def context_stub():
1679 """
1680 Stub context object.
1681 """
1682 context = pyramid.testing.DummyResource()
1683 return context
1684
1685
1686 @pytest.fixture
1679 def config_stub(request, request_stub):
1687 def config_stub(request, request_stub):
1680 """
1688 """
1681 Set up pyramid.testing and return the Configurator.
1689 Set up pyramid.testing and return the Configurator.
1682 """
1690 """
1683 config = pyramid.testing.setUp(request=request_stub)
1691 config = pyramid.testing.setUp(request=request_stub)
1692 add_test_routes(config)
1684
1693
1685 @request.addfinalizer
1694 @request.addfinalizer
1686 def cleanup():
1695 def cleanup():
@@ -1701,7 +1710,7 b' def StubIntegrationType():'
1701
1710
1702 def __init__(self, settings):
1711 def __init__(self, settings):
1703 super(_StubIntegrationType, self).__init__(settings)
1712 super(_StubIntegrationType, self).__init__(settings)
1704 self.sent_events = [] # for testing
1713 self.sent_events = [] # for testing
1705
1714
1706 def send_event(self, event):
1715 def send_event(self, event):
1707 self.sent_events.append(event)
1716 self.sent_events.append(event)
@@ -1812,3 +1821,12 b' def local_dt_to_utc():'
1812 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1821 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1813 dateutil.tz.tzutc()).replace(tzinfo=None)
1822 dateutil.tz.tzutc()).replace(tzinfo=None)
1814 return _factory
1823 return _factory
1824
1825
1826 @pytest.fixture
1827 def disable_anonymous_user(request, pylonsapp):
1828 set_anonymous_access(False)
1829
1830 @request.addfinalizer
1831 def cleanup():
1832 set_anonymous_access(True)
@@ -138,14 +138,6 b' def create_test_repo(force=True):'
138 print 'done'
138 print 'done'
139
139
140
140
141 def set_anonymous_access(enable=True):
142 sa = get_session()
143 user = sa.query(User).filter(User.username == 'default').one()
144 user.active = enable
145 sa.add(user)
146 sa.commit()
147
148
149 def get_anonymous_access():
141 def get_anonymous_access():
150 sa = get_session()
142 sa = get_session()
151 return sa.query(User).filter(User.username == 'default').one().active
143 return sa.query(User).filter(User.username == 'default').one().active
@@ -94,6 +94,14 b' class CustomTestResponse(TestResponse):'
94 def assert_response(self):
94 def assert_response(self):
95 return AssertResponse(self)
95 return AssertResponse(self)
96
96
97 def get_session_from_response(self):
98 """
99 This returns the session from a response object. Pylons has some magic
100 to make the session available as `response.session`. But pyramid
101 doesn't expose it.
102 """
103 return self.request.environ['beaker.session']
104
97
105
98 class TestRequest(Request):
106 class TestRequest(Request):
99
107
@@ -109,15 +117,13 b' class CustomTestApp(TestApp):'
109 RequestClass = TestRequest
117 RequestClass = TestRequest
110
118
111
119
112
113
114
115 def set_anonymous_access(enabled):
120 def set_anonymous_access(enabled):
116 """(Dis)allows anonymous access depending on parameter `enabled`"""
121 """(Dis)allows anonymous access depending on parameter `enabled`"""
117 user = User.get_default_user()
122 user = User.get_default_user()
118 user.active = enabled
123 user.active = enabled
119 Session().add(user)
124 Session().add(user)
120 Session().commit()
125 Session().commit()
126 time.sleep(1.5) # must sleep for cache (1s to expire)
121 log.info('anonymous access is now: %s', enabled)
127 log.info('anonymous access is now: %s', enabled)
122 assert enabled == User.get_default_user().active, (
128 assert enabled == User.get_default_user().active, (
123 'Cannot set anonymous access')
129 'Cannot set anonymous access')
@@ -357,16 +363,6 b' def is_url_reachable(url):'
357 return True
363 return True
358
364
359
365
360 def get_session_from_response(response):
361 """
362 This returns the session from a response object. Pylons has some magic
363 to make the session available as `response.session`. But pyramid
364 doesn't expose it.
365 """
366 # TODO: Try to look up the session key also.
367 return response.request.environ['beaker.session']
368
369
370 def repo_on_filesystem(repo_name):
366 def repo_on_filesystem(repo_name):
371 from rhodecode.lib import vcs
367 from rhodecode.lib import vcs
372 from rhodecode.tests import TESTS_TMP_PATH
368 from rhodecode.tests import TESTS_TMP_PATH
@@ -407,3 +403,21 b' def commit_change('
407 f_path=filename
403 f_path=filename
408 )
404 )
409 return commit
405 return commit
406
407
408 def add_test_routes(config):
409 """
410 Adds test routing that can be used in different functional tests
411
412 """
413 config.add_route(name='home', pattern='/')
414 config.add_route(name='repo_summary', pattern='/{repo_name}')
415 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
416 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
417
418 config.add_route(name='pullrequest_show',
419 pattern='/{repo_name}/pull-request/{pull_request_id}')
420 config.add_route(name='pull_requests_global',
421 pattern='/pull-request/{pull_request_id}')
422 config.add_route(name='repo_commit',
423 pattern='/{repo_name}/changeset/{commit_id}')
@@ -114,7 +114,7 b' class TestMercurialRemoteRepoInvalidatio'
114 return shadow_repo, source_ref, target_ref
114 return shadow_repo, source_ref, target_ref
115
115
116 @pytest.mark.backends('hg')
116 @pytest.mark.backends('hg')
117 def test_commit_does_not_exist_error_happens(self, pr_util, pylonsapp):
117 def test_commit_does_not_exist_error_happens(self, pr_util, app):
118 """
118 """
119 This test is somewhat special. It does not really test the system
119 This test is somewhat special. It does not really test the system
120 instead it is more or less a precondition for the
120 instead it is more or less a precondition for the
@@ -152,8 +152,7 b' class TestMercurialRemoteRepoInvalidatio'
152 shadow_repo.get_commit(source_ref.commit_id)
152 shadow_repo.get_commit(source_ref.commit_id)
153
153
154 @pytest.mark.backends('hg')
154 @pytest.mark.backends('hg')
155 def test_commit_does_not_exist_error_does_not_happen(
155 def test_commit_does_not_exist_error_does_not_happen(self, pr_util, app):
156 self, pr_util, pylonsapp):
157 """
156 """
158 This test simulates a pull request merge in which the pull operations
157 This test simulates a pull request merge in which the pull operations
159 are handled by a different VCSServer process than all other operations.
158 are handled by a different VCSServer process than all other operations.
@@ -1,89 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Controller for Admin panel of RhodeCode Enterprise
23 """
24
25
26 import logging
27
28 from pylons import request, tmpl_context as c, url
29 from pylons.controllers.util import redirect
30 from sqlalchemy.orm import joinedload
31
32 from rhodecode.model.db import UserLog, PullRequest
33 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
35 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.utils2 import safe_int
37 from rhodecode.lib.helpers import Page
38
39
40 log = logging.getLogger(__name__)
41
42
43 class AdminController(BaseController):
44
45 @LoginRequired()
46 def __before__(self):
47 super(AdminController, self).__before__()
48
49 @HasPermissionAllDecorator('hg.admin')
50 def index(self):
51 users_log = UserLog.query()\
52 .options(joinedload(UserLog.user))\
53 .options(joinedload(UserLog.repository))
54
55 # FILTERING
56 c.search_term = request.GET.get('filter')
57 try:
58 users_log = user_log_filter(users_log, c.search_term)
59 except Exception:
60 # we want this to crash for now
61 raise
62
63 users_log = users_log.order_by(UserLog.action_date.desc())
64
65 p = safe_int(request.GET.get('page', 1), 1)
66
67 def url_generator(**kw):
68 return url.current(filter=c.search_term, **kw)
69
70 c.users_log = Page(users_log, page=p, items_per_page=10,
71 url=url_generator)
72 c.log_data = render('admin/admin_log.mako')
73
74 if request.is_xhr:
75 return c.log_data
76 return render('admin/admin.mako')
77
78 # global redirect doesn't need permissions
79 def pull_requests(self, pull_request_id):
80 """
81 Global redirect for Pull Requests
82
83 :param pull_request_id: id of pull requests in the system
84 """
85 pull_request = PullRequest.get_or_404(pull_request_id)
86 repo_name = pull_request.target_repo.repo_name
87 return redirect(url(
88 'pullrequest_show', repo_name=repo_name,
89 pull_request_id=pull_request_id))
@@ -1,85 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from pylons import tmpl_context as c
22
23 from rhodecode.controllers import utils
24 from rhodecode.lib import helpers as h
25 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
26 from rhodecode.lib.base import BaseRepoController, render
27 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.utils import PartialRenderer
29 from rhodecode.lib.utils2 import datetime_to_time
30
31
32 class BaseReferencesController(BaseRepoController):
33 """
34 Base for reference controllers for branches, tags and bookmarks.
35
36 Implement and set the following things:
37
38 - `partials_template` is the source for the partials to use.
39
40 - `template` is the template to render in the end.
41
42 - `_get_reference_items(repo)` should return a sequence of tuples which
43 map from `name` to `commit_id`.
44
45 """
46
47 @LoginRequired()
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 'repository.admin')
50 def index(self):
51 _render = PartialRenderer(self.partials_template)
52 _data = []
53 pre_load = ["author", "date", "message"]
54 repo = c.rhodecode_repo
55 is_svn = h.is_svn(repo)
56 format_ref_id = utils.get_format_ref_id(repo)
57
58 for ref_name, commit_id in self._get_reference_items(repo):
59 commit = repo.get_commit(
60 commit_id=commit_id, pre_load=pre_load)
61
62 # TODO: johbo: Unify generation of reference links
63 use_commit_id = '/' in ref_name or is_svn
64 files_url = h.url(
65 'files_home',
66 repo_name=c.repo_name,
67 f_path=ref_name if is_svn else '',
68 revision=commit_id if use_commit_id else ref_name,
69 at=ref_name)
70
71 _data.append({
72 "name": _render('name', ref_name, files_url),
73 "name_raw": ref_name,
74 "date": _render('date', commit.date),
75 "date_raw": datetime_to_time(commit.date),
76 "author": _render('author', commit.author),
77 "commit": _render(
78 'commit', commit.message, commit.raw_id, commit.idx),
79 "commit_raw": commit.idx,
80 "compare": _render(
81 'compare', format_ref_id(ref_name, commit.raw_id)),
82 })
83 c.has_references = bool(_data)
84 c.data = json.dumps(_data)
85 return render(self.template)
@@ -1,46 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 """
21 Bookmarks controller for rhodecode
22 """
23
24 import logging
25
26 from pylons import tmpl_context as c
27 from webob.exc import HTTPNotFound
28
29 from rhodecode.controllers.base_references import BaseReferencesController
30 from rhodecode.lib import helpers as h
31
32 log = logging.getLogger(__name__)
33
34
35 class BookmarksController(BaseReferencesController):
36
37 partials_template = 'bookmarks/bookmarks_data.mako'
38 template = 'bookmarks/bookmarks.mako'
39
40 def __before__(self):
41 super(BookmarksController, self).__before__()
42 if not h.is_hg(c.rhodecode_repo):
43 raise HTTPNotFound()
44
45 def _get_reference_items(self, repo):
46 return repo.bookmarks.items()
@@ -1,45 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 branches controller for rhodecode
23 """
24
25 import logging
26
27 from pylons import tmpl_context as c
28
29 from rhodecode.controllers.base_references import BaseReferencesController
30
31
32 log = logging.getLogger(__name__)
33
34
35 class BranchesController(BaseReferencesController):
36
37 partials_template = 'branches/branches_data.mako'
38 template = 'branches/branches.mako'
39
40 def __before__(self):
41 super(BranchesController, self).__before__()
42 c.closed_branches = c.rhodecode_repo.branches_closed
43
44 def _get_reference_items(self, repo):
45 return repo.branches_all.items()
@@ -1,111 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Search controller for RhodeCode
23 """
24
25 import logging
26 import urllib
27
28 from pylons import request, config, tmpl_context as c
29
30 from webhelpers.util import update_params
31
32 from rhodecode.lib.auth import LoginRequired, AuthUser
33 from rhodecode.lib.base import BaseRepoController, render
34 from rhodecode.lib.helpers import Page
35 from rhodecode.lib.utils2 import safe_str, safe_int
36 from rhodecode.lib.index import searcher_from_config
37 from rhodecode.model import validation_schema
38 from rhodecode.model.validation_schema.schemas import search_schema
39
40 log = logging.getLogger(__name__)
41
42
43 class SearchController(BaseRepoController):
44
45 @LoginRequired()
46 def index(self, repo_name=None):
47
48 searcher = searcher_from_config(config)
49 formatted_results = []
50 execution_time = ''
51
52 schema = search_schema.SearchParamsSchema()
53
54 search_params = {}
55 errors = []
56 try:
57 search_params = schema.deserialize(
58 dict(search_query=request.GET.get('q'),
59 search_type=request.GET.get('type'),
60 search_sort=request.GET.get('sort'),
61 page_limit=request.GET.get('page_limit'),
62 requested_page=request.GET.get('page'))
63 )
64 except validation_schema.Invalid as e:
65 errors = e.children
66
67 def url_generator(**kw):
68 q = urllib.quote(safe_str(search_query))
69 return update_params(
70 "?q=%s&type=%s" % (q, safe_str(search_type)), **kw)
71
72 search_query = search_params.get('search_query')
73 search_type = search_params.get('search_type')
74 search_sort = search_params.get('search_sort')
75 if search_params.get('search_query'):
76 page_limit = search_params['page_limit']
77 requested_page = search_params['requested_page']
78
79 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
80 ip_addr=self.ip_addr)
81
82 try:
83 search_result = searcher.search(
84 search_query, search_type, c.perm_user, repo_name,
85 requested_page, page_limit, search_sort)
86
87 formatted_results = Page(
88 search_result['results'], page=requested_page,
89 item_count=search_result['count'],
90 items_per_page=page_limit, url=url_generator)
91 finally:
92 searcher.cleanup()
93
94 if not search_result['error']:
95 execution_time = '%s results (%.3f seconds)' % (
96 search_result['count'],
97 search_result['runtime'])
98 elif not errors:
99 node = schema['search_query']
100 errors = [
101 validation_schema.Invalid(node, search_result['error'])]
102
103 c.sort = search_sort
104 c.url_generator = url_generator
105 c.errors = errors
106 c.formatted_results = formatted_results
107 c.runtime = execution_time
108 c.cur_query = search_query
109 c.search_type = search_type
110 # Return a rendered template
111 return render('/search/search.mako')
@@ -1,326 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Summary controller for RhodeCode Enterprise
23 """
24
25 import logging
26 from string import lower
27
28 from pylons import tmpl_context as c, request
29 from pylons.i18n.translation import _
30 from beaker.cache import cache_region, region_invalidate
31
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
33 from rhodecode.controllers import utils
34 from rhodecode.controllers.changelog import _load_changelog_summary
35 from rhodecode.lib import caches, helpers as h
36 from rhodecode.lib.utils import jsonify
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired)
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
42 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.exceptions import (
45 CommitError, EmptyRepositoryError, NodeDoesNotExistError)
46 from rhodecode.model.db import Statistics, CacheKey, User
47 from rhodecode.model.repo import ReadmeFinder
48
49
50 log = logging.getLogger(__name__)
51
52
53 class SummaryController(BaseRepoController):
54
55 def __before__(self):
56 super(SummaryController, self).__before__()
57
58 def __get_readme_data(self, db_repo):
59 repo_name = db_repo.repo_name
60 log.debug('Looking for README file')
61 default_renderer = c.visual.default_renderer
62
63 @cache_region('long_term')
64 def _generate_readme(cache_key):
65 readme_data = None
66 readme_node = None
67 readme_filename = None
68 commit = self._get_landing_commit_or_none(db_repo)
69 if commit:
70 log.debug("Searching for a README file.")
71 readme_node = ReadmeFinder(default_renderer).search(commit)
72 if readme_node:
73 relative_url = h.url('files_raw_home',
74 repo_name=repo_name,
75 revision=commit.raw_id,
76 f_path=readme_node.path)
77 readme_data = self._render_readme_or_none(
78 commit, readme_node, relative_url)
79 readme_filename = readme_node.path
80 return readme_data, readme_filename
81
82 invalidator_context = CacheKey.repo_context_cache(
83 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
84
85 with invalidator_context as context:
86 context.invalidate()
87 computed = context.compute()
88
89 return computed
90
91 def _get_landing_commit_or_none(self, db_repo):
92 log.debug("Getting the landing commit.")
93 try:
94 commit = db_repo.get_landing_commit()
95 if not isinstance(commit, EmptyCommit):
96 return commit
97 else:
98 log.debug("Repository is empty, no README to render.")
99 except CommitError:
100 log.exception(
101 "Problem getting commit when trying to render the README.")
102
103 def _render_readme_or_none(self, commit, readme_node, relative_url):
104 log.debug(
105 'Found README file `%s` rendering...', readme_node.path)
106 renderer = MarkupRenderer()
107 try:
108 html_source = renderer.render(
109 readme_node.content, filename=readme_node.path)
110 if relative_url:
111 return relative_links(html_source, relative_url)
112 return html_source
113 except Exception:
114 log.exception(
115 "Exception while trying to render the README")
116
117 @LoginRequired()
118 @HasRepoPermissionAnyDecorator(
119 'repository.read', 'repository.write', 'repository.admin')
120 def index(self, repo_name):
121
122 # Prepare the clone URL
123
124 username = ''
125 if c.rhodecode_user.username != User.DEFAULT_USER:
126 username = safe_str(c.rhodecode_user.username)
127
128 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
129 if '{repo}' in _def_clone_uri:
130 _def_clone_uri_by_id = _def_clone_uri.replace(
131 '{repo}', '_{repoid}')
132 elif '{repoid}' in _def_clone_uri:
133 _def_clone_uri_by_id = _def_clone_uri.replace(
134 '_{repoid}', '{repo}')
135
136 c.clone_repo_url = c.rhodecode_db_repo.clone_url(
137 user=username, uri_tmpl=_def_clone_uri)
138 c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(
139 user=username, uri_tmpl=_def_clone_uri_by_id)
140
141 # If enabled, get statistics data
142
143 c.show_stats = bool(c.rhodecode_db_repo.enable_statistics)
144
145 stats = self.sa.query(Statistics)\
146 .filter(Statistics.repository == c.rhodecode_db_repo)\
147 .scalar()
148
149 c.stats_percentage = 0
150
151 if stats and stats.languages:
152 c.no_data = False is c.rhodecode_db_repo.enable_statistics
153 lang_stats_d = json.loads(stats.languages)
154
155 # Sort first by decreasing count and second by the file extension,
156 # so we have a consistent output.
157 lang_stats_items = sorted(lang_stats_d.iteritems(),
158 key=lambda k: (-k[1], k[0]))[:10]
159 lang_stats = [(x, {"count": y,
160 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
161 for x, y in lang_stats_items]
162
163 c.trending_languages = json.dumps(lang_stats)
164 else:
165 c.no_data = True
166 c.trending_languages = json.dumps({})
167
168 c.enable_downloads = c.rhodecode_db_repo.enable_downloads
169 c.repository_followers = self.scm_model.get_followers(
170 c.rhodecode_db_repo)
171 c.repository_forks = self.scm_model.get_forks(c.rhodecode_db_repo)
172 c.repository_is_user_following = self.scm_model.is_following_repo(
173 c.repo_name, c.rhodecode_user.user_id)
174
175 if c.repository_requirements_missing:
176 return render('summary/missing_requirements.mako')
177
178 c.readme_data, c.readme_file = \
179 self.__get_readme_data(c.rhodecode_db_repo)
180
181 _load_changelog_summary()
182
183 if request.is_xhr:
184 return render('changelog/changelog_summary_data.mako')
185
186 return render('summary/summary.mako')
187
188 @LoginRequired()
189 @XHRRequired()
190 @HasRepoPermissionAnyDecorator(
191 'repository.read', 'repository.write', 'repository.admin')
192 @jsonify
193 def repo_stats(self, repo_name, commit_id):
194 _namespace = caches.get_repo_namespace_key(
195 caches.SUMMARY_STATS, repo_name)
196 show_stats = bool(c.rhodecode_db_repo.enable_statistics)
197 cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
198 _cache_key = caches.compute_key_from_params(
199 repo_name, commit_id, show_stats)
200
201 def compute_stats():
202 code_stats = {}
203 size = 0
204 try:
205 scm_instance = c.rhodecode_db_repo.scm_instance()
206 commit = scm_instance.get_commit(commit_id)
207
208 for node in commit.get_filenodes_generator():
209 size += node.size
210 if not show_stats:
211 continue
212 ext = lower(node.extension)
213 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
214 if ext_info:
215 if ext in code_stats:
216 code_stats[ext]['count'] += 1
217 else:
218 code_stats[ext] = {"count": 1, "desc": ext_info}
219 except EmptyRepositoryError:
220 pass
221 return {'size': h.format_byte_size_binary(size),
222 'code_stats': code_stats}
223
224 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
225 return stats
226
227 def _switcher_reference_data(self, repo_name, references, is_svn):
228 """Prepare reference data for given `references`"""
229 items = []
230 for name, commit_id in references.items():
231 use_commit_id = '/' in name or is_svn
232 items.append({
233 'name': name,
234 'commit_id': commit_id,
235 'files_url': h.url(
236 'files_home',
237 repo_name=repo_name,
238 f_path=name if is_svn else '',
239 revision=commit_id if use_commit_id else name,
240 at=name)
241 })
242 return items
243
244 @LoginRequired()
245 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
246 'repository.admin')
247 @jsonify
248 def repo_refs_data(self, repo_name):
249 repo = c.rhodecode_repo
250 refs_to_create = [
251 (_("Branch"), repo.branches, 'branch'),
252 (_("Tag"), repo.tags, 'tag'),
253 (_("Bookmark"), repo.bookmarks, 'book'),
254 ]
255 res = self._create_reference_data(repo, repo_name, refs_to_create)
256 data = {
257 'more': False,
258 'results': res
259 }
260 return data
261
262 @LoginRequired()
263 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
264 'repository.admin')
265 @jsonify
266 def repo_default_reviewers_data(self, repo_name):
267 return {
268 'reviewers': [utils.reviewer_as_json(
269 user=c.rhodecode_db_repo.user, reasons=None)]
270 }
271
272 @jsonify
273 def repo_refs_changelog_data(self, repo_name):
274 repo = c.rhodecode_repo
275
276 refs_to_create = [
277 (_("Branches"), repo.branches, 'branch'),
278 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
279 # TODO: enable when vcs can handle bookmarks filters
280 # (_("Bookmarks"), repo.bookmarks, "book"),
281 ]
282 res = self._create_reference_data(repo, repo_name, refs_to_create)
283 data = {
284 'more': False,
285 'results': res
286 }
287 return data
288
289 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
290 format_ref_id = utils.get_format_ref_id(repo)
291
292 result = []
293 for title, refs, ref_type in refs_to_create:
294 if refs:
295 result.append({
296 'text': title,
297 'children': self._create_reference_items(
298 repo, full_repo_name, refs, ref_type, format_ref_id),
299 })
300 return result
301
302 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
303 format_ref_id):
304 result = []
305 is_svn = h.is_svn(repo)
306 for ref_name, raw_id in refs.iteritems():
307 files_url = self._create_files_url(
308 repo, full_repo_name, ref_name, raw_id, is_svn)
309 result.append({
310 'text': ref_name,
311 'id': format_ref_id(ref_name, raw_id),
312 'raw_id': raw_id,
313 'type': ref_type,
314 'files_url': files_url,
315 })
316 return result
317
318 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id,
319 is_svn):
320 use_commit_id = '/' in ref_name or is_svn
321 return h.url(
322 'files_home',
323 repo_name=full_repo_name,
324 f_path=ref_name if is_svn else '',
325 revision=raw_id if use_commit_id else ref_name,
326 at=ref_name)
@@ -1,38 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Tags controller for rhodecode
23 """
24
25 import logging
26
27 from rhodecode.controllers.base_references import BaseReferencesController
28
29 log = logging.getLogger(__name__)
30
31
32 class TagsController(BaseReferencesController):
33
34 partials_template = 'tags/tags_data.mako'
35 template = 'tags/tags.mako'
36
37 def _get_reference_items(self, repo):
38 return repo.tags.items()
@@ -1,60 +0,0 b''
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
3
4 %if c.users_log:
5 <table class="rctable admin_log">
6 <tr>
7 <th>${_('Username')}</th>
8 <th>${_('Action')}</th>
9 <th>${_('Repository')}</th>
10 <th>${_('Date')}</th>
11 <th>${_('From IP')}</th>
12 </tr>
13
14 %for cnt,l in enumerate(c.users_log):
15 <tr class="parity${cnt%2}">
16 <td class="td-user">
17 %if l.user is not None:
18 ${base.gravatar_with_user(l.user.email)}
19 %else:
20 ${l.username}
21 %endif
22 </td>
23 <td class="td-journalaction">${h.action_parser(l)[0]()}
24 <div class="journal_action_params">
25 ${h.literal(h.action_parser(l)[1]())}
26 </div>
27 </td>
28 <td class="td-componentname">
29 %if l.repository is not None:
30 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
31 %else:
32 ${l.repository_name}
33 %endif
34 </td>
35
36 <td class="td-time">${h.format_date(l.action_date)}</td>
37 <td class="td-ip">${l.user_ip}</td>
38 </tr>
39 %endfor
40 </table>
41
42 <div class="pagination-wh pagination-left">
43 ${c.users_log.pager('$link_previous ~2~ $link_next')}
44 </div>
45 %else:
46 ${_('No actions yet')}
47 %endif
48
49 <script type="text/javascript">
50 $(function(){
51 //because this is loaded on every pjax request, it must run only once
52 //therefore the .one method
53 $(document).on('pjax:complete',function(){
54 show_more_event();
55 });
56
57 $(document).pjax('#user_log .pager_link', '#user_log');
58 });
59 </script>
60
@@ -1,136 +0,0 b''
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
3 %if c.repo_commits:
4 <table class="rctable repo_summary table_disp">
5 <tr>
6
7 <th class="status" colspan="2"></th>
8 <th>${_('Commit')}</th>
9 <th>${_('Commit message')}</th>
10 <th>${_('Age')}</th>
11 <th>${_('Author')}</th>
12 <th>${_('Refs')}</th>
13 </tr>
14 %for cnt,cs in enumerate(c.repo_commits):
15 <tr class="parity${cnt%2}">
16
17 <td class="td-status">
18 %if c.statuses.get(cs.raw_id):
19 <div class="changeset-status-ico shortlog">
20 %if c.statuses.get(cs.raw_id)[2]:
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
23 </a>
24 %else:
25 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
27 </a>
28 %endif
29 </div>
30 %else:
31 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
32 %endif
33 </td>
34 <td class="td-comments">
35 %if c.comments.get(cs.raw_id,[]):
36 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
37 <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])}
38 </a>
39 %endif
40 </td>
41 <td class="td-commit">
42 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
43 </td>
44
45 <td class="td-description mid">
46 <div class="log-container truncate-wrap">
47 <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div>
48 </div>
49 </td>
50
51 <td class="td-time">
52 ${h.age_component(cs.date)}
53 </td>
54 <td class="td-user author">
55 ${base.gravatar_with_user(cs.author)}
56 </td>
57
58 <td class="td-tags">
59 <div class="autoexpand">
60 %if h.is_hg(c.rhodecode_repo):
61 %for book in cs.bookmarks:
62 <span class="booktag tag" title="${_('Bookmark %s') % book}">
63 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
64 </span>
65 %endfor
66 %endif
67 ## tags
68 %for tag in cs.tags:
69 <span class="tagtag tag" title="${_('Tag %s') % tag}">
70 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
71 </span>
72 %endfor
73
74 ## branch
75 %if cs.branch:
76 <span class="branchtag tag" title="${_('Branch %s') % cs.branch}">
77 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch)}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
78 </span>
79 %endif
80 </div>
81 </td>
82 </tr>
83 %endfor
84
85 </table>
86
87 <script type="text/javascript">
88 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
89 $(document).on('pjax:success', function(){ timeagoActivate(); });
90 </script>
91
92 <div class="pagination-wh pagination-left">
93 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
94 </div>
95 %else:
96
97 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
98 <div class="quick_start">
99 <div class="fieldset">
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
101 <div class="right-content">
102 <div id="add_node_id" class="add_node">
103 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
104 </div>
105 </div>
106 %endif
107 </div>
108
109 %if not h.is_svn(c.rhodecode_repo):
110 <div class="fieldset">
111 <div class="left-label">${_('Push new repo:')}</div>
112 <div class="right-content">
113 <pre>
114 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
115 ${c.rhodecode_repo.alias} add README # add first file
116 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
117 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
118 </pre>
119 </div>
120 </div>
121 <div class="fieldset">
122 <div class="left-label">${_('Existing repository?')}</div>
123 <div class="right-content">
124 <pre>
125 %if h.is_git(c.rhodecode_repo):
126 git remote add origin ${c.clone_repo_url}
127 git push -u origin master
128 %else:
129 hg push ${c.clone_repo_url}
130 %endif
131 </pre>
132 </div>
133 </div>
134 %endif
135 </div>
136 %endif
@@ -1,172 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22 import csv
23 import datetime
24
25 import pytest
26
27 from rhodecode.tests import *
28 from rhodecode.model.db import UserLog
29 from rhodecode.model.meta import Session
30 from rhodecode.lib.utils2 import safe_unicode
31
32 dn = os.path.dirname
33 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'fixtures')
34
35
36 class TestAdminController(TestController):
37
38 @pytest.fixture(scope='class', autouse=True)
39 def prepare(self, request, pylonsapp):
40 UserLog.query().delete()
41 Session().commit()
42
43 def strptime(val):
44 fmt = '%Y-%m-%d %H:%M:%S'
45 if '.' not in val:
46 return datetime.datetime.strptime(val, fmt)
47
48 nofrag, frag = val.split(".")
49 date = datetime.datetime.strptime(nofrag, fmt)
50
51 frag = frag[:6] # truncate to microseconds
52 frag += (6 - len(frag)) * '0' # add 0s
53 return date.replace(microsecond=int(frag))
54
55 with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
56 for row in csv.DictReader(f):
57 ul = UserLog()
58 for k, v in row.iteritems():
59 v = safe_unicode(v)
60 if k == 'action_date':
61 v = strptime(v)
62 if k in ['user_id', 'repository_id']:
63 # nullable due to FK problems
64 v = None
65 setattr(ul, k, v)
66 Session().add(ul)
67 Session().commit()
68
69 @request.addfinalizer
70 def cleanup():
71 UserLog.query().delete()
72 Session().commit()
73
74 def test_index(self):
75 self.log_user()
76 response = self.app.get(url(controller='admin/admin', action='index'))
77 response.mustcontain('Admin journal')
78
79 def test_filter_all_entries(self):
80 self.log_user()
81 response = self.app.get(url(controller='admin/admin', action='index',))
82 response.mustcontain('2034 entries')
83
84 def test_filter_journal_filter_exact_match_on_repository(self):
85 self.log_user()
86 response = self.app.get(url(controller='admin/admin', action='index',
87 filter='repository:rhodecode'))
88 response.mustcontain('3 entries')
89
90 def test_filter_journal_filter_exact_match_on_repository_CamelCase(self):
91 self.log_user()
92 response = self.app.get(url(controller='admin/admin', action='index',
93 filter='repository:RhodeCode'))
94 response.mustcontain('3 entries')
95
96 def test_filter_journal_filter_wildcard_on_repository(self):
97 self.log_user()
98 response = self.app.get(url(controller='admin/admin', action='index',
99 filter='repository:*test*'))
100 response.mustcontain('862 entries')
101
102 def test_filter_journal_filter_prefix_on_repository(self):
103 self.log_user()
104 response = self.app.get(url(controller='admin/admin', action='index',
105 filter='repository:test*'))
106 response.mustcontain('257 entries')
107
108 def test_filter_journal_filter_prefix_on_repository_CamelCase(self):
109 self.log_user()
110 response = self.app.get(url(controller='admin/admin', action='index',
111 filter='repository:Test*'))
112 response.mustcontain('257 entries')
113
114 def test_filter_journal_filter_prefix_on_repository_and_user(self):
115 self.log_user()
116 response = self.app.get(url(controller='admin/admin', action='index',
117 filter='repository:test* AND username:demo'))
118 response.mustcontain('130 entries')
119
120 def test_filter_journal_filter_prefix_on_repository_or_target_repo(self):
121 self.log_user()
122 response = self.app.get(url(controller='admin/admin', action='index',
123 filter='repository:test* OR repository:rhodecode'))
124 response.mustcontain('260 entries') # 257 + 3
125
126 def test_filter_journal_filter_exact_match_on_username(self):
127 self.log_user()
128 response = self.app.get(url(controller='admin/admin', action='index',
129 filter='username:demo'))
130 response.mustcontain('1087 entries')
131
132 def test_filter_journal_filter_exact_match_on_username_camelCase(self):
133 self.log_user()
134 response = self.app.get(url(controller='admin/admin', action='index',
135 filter='username:DemO'))
136 response.mustcontain('1087 entries')
137
138 def test_filter_journal_filter_wildcard_on_username(self):
139 self.log_user()
140 response = self.app.get(url(controller='admin/admin', action='index',
141 filter='username:*test*'))
142 response.mustcontain('100 entries')
143
144 def test_filter_journal_filter_prefix_on_username(self):
145 self.log_user()
146 response = self.app.get(url(controller='admin/admin', action='index',
147 filter='username:demo*'))
148 response.mustcontain('1101 entries')
149
150 def test_filter_journal_filter_prefix_on_user_or_other_user(self):
151 self.log_user()
152 response = self.app.get(url(controller='admin/admin', action='index',
153 filter='username:demo OR username:volcan'))
154 response.mustcontain('1095 entries') # 1087 + 8
155
156 def test_filter_journal_filter_wildcard_on_action(self):
157 self.log_user()
158 response = self.app.get(url(controller='admin/admin', action='index',
159 filter='action:*pull_request*'))
160 response.mustcontain('187 entries')
161
162 def test_filter_journal_filter_on_date(self):
163 self.log_user()
164 response = self.app.get(url(controller='admin/admin', action='index',
165 filter='date:20121010'))
166 response.mustcontain('47 entries')
167
168 def test_filter_journal_filter_on_date_2(self):
169 self.log_user()
170 response = self.app.get(url(controller='admin/admin', action='index',
171 filter='date:20121020'))
172 response.mustcontain('17 entries')
@@ -1,118 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import mock
22 import pytest
23
24 import rhodecode
25 from rhodecode.model.settings import SettingsModel
26 from rhodecode.tests import url, HG_REPO
27 from rhodecode.tests.utils import AssertResponse
28
29
30 @pytest.mark.usefixtures('autologin_user', 'app')
31 class TestAdminRepoSettingsController:
32 @pytest.mark.parametrize('urlname', [
33 'edit_repo',
34 'edit_repo_perms',
35 'edit_repo_advanced',
36 'repo_vcs_settings',
37 'edit_repo_fields',
38 'repo_settings_issuetracker',
39 'edit_repo_caches',
40 'edit_repo_remote',
41 'edit_repo_statistics',
42 ])
43 def test_simple_get(self, urlname, app):
44 app.get(url(urlname, repo_name=HG_REPO))
45
46 @pytest.mark.parametrize('setting_name, setting_backends', [
47 ('hg_use_rebase_for_merging', ['hg']),
48 ])
49 def test_labs_settings_visible_if_enabled(
50 self, setting_name, setting_backends, backend):
51 if backend.alias not in setting_backends:
52 pytest.skip('Setting not available for backend {}'.format(backend))
53
54 vcs_settings_url = url(
55 'repo_vcs_settings', repo_name=backend.repo.repo_name)
56
57 with mock.patch.dict(
58 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
59 response = self.app.get(vcs_settings_url)
60
61 assertr = AssertResponse(response)
62 assertr.one_element_exists('#rhodecode_{}'.format(setting_name))
63
64 @pytest.mark.parametrize('setting_name, setting_backends', [
65 ('hg_use_rebase_for_merging', ['hg']),
66 ])
67 def test_labs_settings_not_visible_if_disabled(
68 self, setting_name, setting_backends, backend):
69 if backend.alias not in setting_backends:
70 pytest.skip('Setting not available for backend {}'.format(backend))
71
72 vcs_settings_url = url(
73 'repo_vcs_settings', repo_name=backend.repo.repo_name)
74
75 with mock.patch.dict(
76 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
77 response = self.app.get(vcs_settings_url)
78
79 assertr = AssertResponse(response)
80 assertr.no_element_exists('#rhodecode_{}'.format(setting_name))
81
82 @pytest.mark.parametrize('setting_name, setting_backends', [
83 ('hg_use_rebase_for_merging', ['hg']),
84 ])
85 def test_update_boolean_settings(
86 self, csrf_token, setting_name, setting_backends, backend):
87 if backend.alias not in setting_backends:
88 pytest.skip('Setting not available for backend {}'.format(backend))
89
90 repo = backend.create_repo()
91
92 settings_model = SettingsModel(repo=repo)
93 vcs_settings_url = url(
94 'repo_vcs_settings', repo_name=repo.repo_name)
95
96 self.app.post(
97 vcs_settings_url,
98 params={
99 'inherit_global_settings': False,
100 'new_svn_branch': 'dummy-value-for-testing',
101 'new_svn_tag': 'dummy-value-for-testing',
102 'rhodecode_{}'.format(setting_name): 'true',
103 'csrf_token': csrf_token,
104 })
105 setting = settings_model.get_setting_by_name(setting_name)
106 assert setting.app_settings_value
107
108 self.app.post(
109 vcs_settings_url,
110 params={
111 'inherit_global_settings': False,
112 'new_svn_branch': 'dummy-value-for-testing',
113 'new_svn_tag': 'dummy-value-for-testing',
114 'rhodecode_{}'.format(setting_name): 'false',
115 'csrf_token': csrf_token,
116 })
117 setting = settings_model.get_setting_by_name(setting_name)
118 assert not setting.app_settings_value
@@ -1,202 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22
23 import mock
24 import pytest
25 from whoosh import query
26
27 from rhodecode.tests import (
28 TestController, url, SkipTest, HG_REPO,
29 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
30 from rhodecode.tests.utils import AssertResponse
31
32
33 class TestSearchController(TestController):
34
35 def test_index(self):
36 self.log_user()
37 response = self.app.get(url(controller='search', action='index'))
38 assert_response = AssertResponse(response)
39 assert_response.one_element_exists('input#q')
40
41 def test_search_files_empty_search(self):
42 if os.path.isdir(self.index_location):
43 raise SkipTest('skipped due to existing index')
44 else:
45 self.log_user()
46 response = self.app.get(url(controller='search', action='index'),
47 {'q': HG_REPO})
48 response.mustcontain('There is no index to search in. '
49 'Please run whoosh indexer')
50
51 def test_search_validation(self):
52 self.log_user()
53 response = self.app.get(
54 url(controller='search', action='index'), {'q': query,
55 'type': 'content',
56 'page_limit': 1000})
57
58 response.mustcontain(
59 'page_limit - 1000 is greater than maximum value 500')
60
61 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
62 ('todo', 23, [
63 'vcs/backends/hg/inmemory.py',
64 'vcs/tests/test_git.py']),
65 ('extension:rst installation', 6, [
66 'docs/index.rst',
67 'docs/installation.rst']),
68 ('def repo', 87, [
69 'vcs/tests/test_git.py',
70 'vcs/tests/test_changesets.py']),
71 ('repository:%s def test' % HG_REPO, 18, [
72 'vcs/tests/test_git.py',
73 'vcs/tests/test_changesets.py']),
74 ('"def main"', 9, [
75 'vcs/__init__.py',
76 'vcs/tests/__init__.py',
77 'vcs/utils/progressbar.py']),
78 ('owner:test_admin', 358, [
79 'vcs/tests/base.py',
80 'MANIFEST.in',
81 'vcs/utils/termcolors.py',
82 'docs/theme/ADC/static/documentation.png']),
83 ('owner:test_admin def main', 72, [
84 'vcs/__init__.py',
85 'vcs/tests/test_utils_filesize.py',
86 'vcs/tests/test_cli.py']),
87 ('owner:michał test', 0, []),
88 ])
89 def test_search_files(self, query, expected_hits, expected_paths):
90 self.log_user()
91 response = self.app.get(
92 url(controller='search', action='index'), {'q': query,
93 'type': 'content',
94 'page_limit': 500})
95
96 response.mustcontain('%s results' % expected_hits)
97 for path in expected_paths:
98 response.mustcontain(path)
99
100 @pytest.mark.parametrize("query, expected_hits, expected_commits", [
101 ('bother to ask where to fetch repo during tests', 3, [
102 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1'),
103 ('git', 'c6eb379775c578a95dad8ddab53f963b80894850'),
104 ('svn', '98')]),
105 ('michał', 0, []),
106 ('changed:tests/utils.py', 36, [
107 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')]),
108 ('changed:vcs/utils/archivers.py', 11, [
109 ('hg', '25213a5fbb048dff8ba65d21e466a835536e5b70'),
110 ('hg', '47aedd538bf616eedcb0e7d630ea476df0e159c7'),
111 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
112 ('hg', '04ad456aefd6461aea24f90b63954b6b1ce07b3e'),
113 ('git', 'c994f0de03b2a0aa848a04fc2c0d7e737dba31fc'),
114 ('git', 'd1f898326327e20524fe22417c22d71064fe54a1'),
115 ('git', 'fe568b4081755c12abf6ba673ba777fc02a415f3'),
116 ('git', 'bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1')]),
117 ('added:README.rst', 3, [
118 ('hg', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'),
119 ('git', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
120 ('svn', '8')]),
121 ('changed:lazy.py', 15, [
122 ('hg', 'eaa291c5e6ae6126a203059de9854ccf7b5baa12'),
123 ('git', '17438a11f72b93f56d0e08e7d1fa79a378578a82'),
124 ('svn', '82'),
125 ('svn', '262'),
126 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
127 ('git', '33fa3223355104431402a888fa77a4e9956feb3e')
128 ]),
129 ('author:marcin@python-blog.com '
130 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
131 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
132 ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
133 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
134 ('b986218b', 1, [
135 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
136 ])
137 def test_search_commit_messages(
138 self, query, expected_hits, expected_commits, enabled_backends):
139 self.log_user()
140 response = self.app.get(
141 url(controller='search', action='index'), {'q': query,
142 'type': 'commit',
143 'page_limit': 500})
144
145 response.mustcontain('%s results' % expected_hits)
146 for backend, commit_id in expected_commits:
147 if backend in enabled_backends:
148 response.mustcontain(commit_id)
149
150 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
151 ('readme.rst', 3, []),
152 ('test*', 75, []),
153 ('*model*', 1, []),
154 ('extension:rst', 48, []),
155 ('extension:rst api', 24, []),
156 ])
157 def test_search_file_paths(self, query, expected_hits, expected_paths):
158 self.log_user()
159 response = self.app.get(
160 url(controller='search', action='index'), {'q': query,
161 'type': 'path',
162 'page_limit': 500})
163
164 response.mustcontain('%s results' % expected_hits)
165 for path in expected_paths:
166 response.mustcontain(path)
167
168 def test_search_commit_message_specific_repo(self, backend):
169 self.log_user()
170 response = self.app.get(
171 url(controller='search', action='index',
172 repo_name=backend.repo_name),
173 {'q': 'bother to ask where to fetch repo during tests',
174 'type': 'commit'})
175
176 response.mustcontain('1 results')
177
178 def test_filters_are_not_applied_for_admin_user(self):
179 self.log_user()
180 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
181 self.app.get(
182 url(controller='search', action='index'),
183 {'q': 'test query', 'type': 'commit'})
184 assert search_mock.call_count == 1
185 _, kwargs = search_mock.call_args
186 assert kwargs['filter'] is None
187
188 def test_filters_are_applied_for_normal_user(self, enabled_backends):
189 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
190 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
191 self.app.get(
192 url(controller='search', action='index'),
193 {'q': 'test query', 'type': 'commit'})
194 assert search_mock.call_count == 1
195 _, kwargs = search_mock.call_args
196 assert isinstance(kwargs['filter'], query.Or)
197 expected_repositories = [
198 'vcs_test_{}'.format(b) for b in enabled_backends]
199 queried_repositories = [
200 name for type_, name in kwargs['filter'].all_terms()]
201 for repository in expected_repositories:
202 assert repository in queried_repositories
General Comments 0
You need to be logged in to leave comments. Login now