##// 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
@@ -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
@@ -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
@@ -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 1 [bumpversion]
2 current_version = 4.7.2
2 current_version = 4.8.0
3 3 message = release: Bump version {current_version} to {new_version}
4 4
5 5 [bumpversion:file:rhodecode/VERSION]
@@ -5,25 +5,20 b' done = false'
5 5 done = true
6 6
7 7 [task:rc_tools_pinned]
8 done = true
9 8
10 9 [task:fixes_on_stable]
11 done = true
12 10
13 11 [task:pip2nix_generated]
14 done = true
15 12
16 13 [task:changelog_updated]
17 done = true
18 14
19 15 [task:generate_api_docs]
20 done = true
16
17 [task:updated_translation]
21 18
22 19 [release]
23 state = prepared
24 version = 4.7.2
25
26 [task:updated_translation]
20 state = in_progress
21 version = 4.8.0
27 22
28 23 [task:generate_js_routes]
29 24
@@ -33,6 +33,7 b' include rhodecode/public/502.html'
33 33 # images, css
34 34 include rhodecode/public/css/*.css
35 35 include rhodecode/public/images/*.*
36 include rhodecode/public/images/ee_features/*.*
36 37
37 38 # sound files
38 39 include rhodecode/public/sounds/*.mp3
@@ -174,32 +174,33 b' let'
174 174 '';
175 175
176 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 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 185 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
179 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
180 186 ln -s ${self.PasteScript}/bin/paster $out/bin/
181 187 ln -s ${self.channelstream}/bin/channelstream $out/bin/
182 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
183 188
184 189 # rhodecode-tools
185 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
186 190 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
187 191
188 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 195 wrapProgram $file \
196 --prefix PATH : $PATH \
191 197 --prefix PYTHONPATH : $PYTHONPATH \
192 --prefix PATH : $PATH \
193 198 --set PYTHONHASHSEED random
194 199 done
195 200
196 201 mkdir $out/etc
197 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 205 # TODO: johbo: Make part of ac-tests
205 206 if [ ! -f rhodecode/public/js/scripts.js ]; then
@@ -62,6 +62,9 b' Below config if for an Apache Reverse Pr'
62 62 ProxyPass / http://127.0.0.1:10002/ timeout=7200 Keepalive=On
63 63 ProxyPassReverse / http://127.0.0.1:10002/
64 64
65 # Increase headers for large Mercurial headers
66 LimitRequestLine 16380
67
65 68 # strict http prevents from https -> http downgrade
66 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 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 12 log_format log_custom '$remote_addr - $remote_user [$time_local] '
10 13 '"$request" $status $body_bytes_sent '
11 14 '"$http_referer" "$http_user_agent" '
@@ -109,6 +112,12 b' Use the following example to configure N'
109 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 121 location / {
113 122 try_files $uri @rhode;
114 123 }
@@ -3,8 +3,12 b''
3 3 Repository Extra Fields
4 4 =======================
5 5
6 Extra fields attached to a |repo| allow you to configure additional actions for
7 |RCX|. To install and read more about |RCX|, see the :ref:`install-rcx` section.
6 Extra fields attached to a |repo| allow you to configure additional fields for
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 13 Enabling Extra Fields
10 14 ---------------------
@@ -27,9 +31,14 b' 2. On the |repo| settings page, select t'
27 31
28 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
32 -------------
39
40 Example Usage in extensions
41 ---------------------------
33 42
34 43 To use the extra fields in an extension, see the example below. For more
35 44 information and examples, see the :ref:`extensions-hooks-ref` section.
@@ -7,7 +7,7 b' The VCS Server handles |RCM| backend fun'
7 7 a VCS Server to run with a |RCM| instance. If you do not, you will be missing
8 8 the connection between |RCM| and its |repos|. This will cause error messages
9 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 12 * One VCS Server per |RCM| instance.
13 13 * One VCS Server handling multiple instances.
@@ -59,7 +59,8 b' instance in the'
59 59 \vcs.backends <available-vcs-systems>
60 60 Set a comma-separated list of the |repo| options available from the
61 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 65 \vcs.connection_timeout <seconds>
65 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 161 - NAME: vcsserver-1
161 162 - STATUS: RUNNING
162 - TYPE: VCSServer
163 - VERSION: 1.0.0
164 - URL: http://127.0.0.1:10001
163 logs:/home/ubuntu/.rccontrol/vcsserver-1/vcsserver.log
164 - VERSION: 4.7.2 VCSServer
165 - URL: http://127.0.0.1:10008
166 - CONFIG: /home/ubuntu/.rccontrol/vcsserver-1/vcsserver.ini
165 167
166 168 $ rccontrol restart vcsserver-1
167 169 Instance "vcsserver-1" successfully stopped.
@@ -181,7 +183,9 b' For a more detailed explanation of the l'
181 183 .. rst-class:: dl-horizontal
182 184
183 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 190 \port <int>
187 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 193 \locale <locale_utf>
190 194 Set the locale the VCS Server expects.
191 195
192 \threadpool_size <int>
193 Set the size of the threadpool used to communicate
194 with the WSGI workers. This should be at least 6 times the number of
195 WSGI worker processes.
196 \workers <int>
197 Set the number of process workers.Recommended
198 value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
196 199
197 \timeout <seconds>
198 Set the timeout for RPC communication in seconds.
200 \max_requests <int>
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 213 .. note::
201 214
@@ -204,27 +217,54 b' For a more detailed explanation of the l'
204 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 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 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 262 # cache regions, please don't change
222 263 beaker.cache.regions = repo_object
223 264 beaker.cache.repo_object.type = memorylru
224 beaker.cache.repo_object.max_items = 1000
225
265 beaker.cache.repo_object.max_items = 100
226 266 # cache auto-expires after N seconds
227 beaker.cache.repo_object.expire = 10
267 beaker.cache.repo_object.expire = 300
228 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 310 level = DEBUG
271 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 314 ## FORMATTERS ##
289 315 ################
@@ -6,7 +6,7 b' pull_request methods'
6 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 11 Close the pull request specified by `pullrequestid`.
12 12
@@ -19,6 +19,9 b' close_pull_request'
19 19 :type pullrequestid: int
20 20 :param userid: Close the pull request as this user.
21 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 26 Example output:
24 27
@@ -27,6 +30,7 b' close_pull_request'
27 30 "id": <id_given_in_input>,
28 31 "result": {
29 32 "pull_request_id": "<int>",
33 "close_status": "<str:status_lbl>,
30 34 "closed": "<bool>"
31 35 },
32 36 "error": null
@@ -105,10 +109,12 b' create_pull_request'
105 109 :param description: Set the pull request description.
106 110 :type description: Optional(str)
107 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 114 :type reviewers: Optional(list)
109 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 120 get_pull_request
@@ -320,7 +326,7 b' merge_pull_request'
320 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 331 Updates a pull request.
326 332
@@ -336,10 +342,12 b' update_pull_request'
336 342 :type description: Optional(str)
337 343 :param reviewers: Update pull request reviewers list with new value.
338 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 349 :param update_commits: Trigger update of commits for this pull request
340 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 352 Example output:
345 353
@@ -527,6 +527,7 b' get_repo_settings'
527 527 "id": 237,
528 528 "result": {
529 529 "extensions_largefiles": true,
530 "extensions_evolve": true,
530 531 "hooks_changegroup_push_logger": true,
531 532 "hooks_changegroup_repo_size": false,
532 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 809 pull
766 810 ----
767 811
@@ -66,7 +66,7 b' RhodeCode VCSServer repositories into th'
66 66 RhodeCode currently is using Mercurial Version Control System, please make sure
67 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 71 mkdir rhodecode-develop && cd rhodecode-develop
72 72 hg clone https://code.rhodecode.com/rhodecode-enterprise-ce
@@ -80,9 +80,9 b' To obtain the required sources, use the '
80 80 Install some required libraries
81 81 -------------------------------
82 82
83 There are some required drivers that we need to install to test RhodeCode
84 under different types of databases. For example in Ubuntu we need to install
85 the following.
83 There are some required drivers and dev libraries that we need to install to
84 test RhodeCode under different types of databases. For example in Ubuntu we
85 need to install the following.
86 86
87 87 required libraries::
88 88
@@ -20,7 +20,7 b' and commit files and |repos| while manag'
20 20 * Migration from existing databases.
21 21 * |RCM| SDK.
22 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 24 * Pluggable authentication system.
25 25 * Support for AD, |LDAP|, Crowd, CAS, PAM.
26 26 * Support for external authentication via Oauth Google, Github, Bitbucket, Twitter.
@@ -17,7 +17,8 b' Type/Name |RC| Edi'
17 17 :ref:`integrations-slack` |RCCEshort| https://slack.com/
18 18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
19 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 22 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
22 23 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
23 24 ============================ ============ =====================================
@@ -3,9 +3,9 b''
3 3 Webhook integration
4 4 ===================
5 5
6 The Webhook integration allows you to POST events such as repository pushes
7 or pull requests to a custom http endpoint as a json dict with details of the
8 event.
6 The :ref:`creating-integrations` integration allows you to POST events such as
7 repository pushes or pull requests to a custom http endpoint as a JSON dict
8 with details of the event.
9 9
10 10 Starting from 4.5.0 release, webhook integration allows to use variables
11 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 14 `${branch}` will result in webhook be called multiple times when multiple
15 15 branches are pushed.
16 16
17 Some of the variables like `${pull_request_id}` will be replaced only in
18 the pull request related events.
17 Starting from 4.8.0 also repository extra fields can be used. A format to use
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 22 To create a webhook integration, select "webhook" in the integration settings
21 23 and use the URL and key from your any previous custom webhook created. See
@@ -9,6 +9,7 b' Release Notes'
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.8.0.rst
12 13 release-notes-4.7.2.rst
13 14 release-notes-4.7.1.rst
14 15 release-notes-4.7.0.rst
@@ -3,8 +3,13 b''
3 3 Getting Started with VCS
4 4 ------------------------
5 5
6 When using |RCM|, you will be working with |git| or |hg| |repos| from the
7 command line.
6 When using |RCM|, you will be working with |git|, |svn| or |hg| |repos| from the
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 14 If you have never used either before, the following information should
10 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 7 (fetchbower "paper-tooltip" "PolymerElements/paper-tooltip#1.1.3" "PolymerElements/paper-tooltip#^1.1.2" "0vmrm1n8k9sk9nvqy03q177axy22pia6i3j1gxbk72j3pqiqvg6k")
8 8 (fetchbower "paper-toast" "PolymerElements/paper-toast#1.3.0" "PolymerElements/paper-toast#^1.3.0" "0x9rqxsks5455s8pk4aikpp99ijdn6kxr9gvhwh99nbcqdzcxq1m")
9 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 11 (fetchbower "iron-autogrow-textarea" "PolymerElements/iron-autogrow-textarea#1.0.13" "PolymerElements/iron-autogrow-textarea#^1.0.13" "0zwhpl97vii1s8k0lgain8i9dnw29b0mxc5ixdscx9las13n2lqq")
12 12 (fetchbower "iron-a11y-keys" "PolymerElements/iron-a11y-keys#1.0.6" "PolymerElements/iron-a11y-keys#^1.0.6" "1xz3mgghfcxixq28sdb654iaxj4nyi1bzcwf77ydkms6fviqs9mv")
13 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 603 deform = super.buildPythonPackage {
604 name = "deform-2.0a2";
604 name = "deform-2.0.4";
605 605 buildInputs = with self; [];
606 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 608 src = fetchurl {
609 url = "https://pypi.python.org/packages/8d/b3/aab57e81da974a806dc9c5fa024a6404720f890a6dcf2e80885e3cb4609a/deform-2.0a2.tar.gz";
610 md5 = "7a90d41f7fbc18002ce74f39bd90a5e4";
609 url = "https://pypi.python.org/packages/66/3b/eefcb07abcab7a97f6665aa2d0cf1af741d9d6e78a2e4657fd2b89f89880/deform-2.0.4.tar.gz";
610 md5 = "34756e42cf50dd4b4430809116c4ec0a";
611 611 };
612 612 meta = {
613 613 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
@@ -887,13 +887,13 b''
887 887 };
888 888 };
889 889 ipython-genutils = super.buildPythonPackage {
890 name = "ipython-genutils-0.1.0";
890 name = "ipython-genutils-0.2.0";
891 891 buildInputs = with self; [];
892 892 doCheck = false;
893 893 propagatedBuildInputs = with self; [];
894 894 src = fetchurl {
895 url = "https://pypi.python.org/packages/71/b7/a64c71578521606edbbce15151358598f3dfb72a3431763edc2baf19e71f/ipython_genutils-0.1.0.tar.gz";
896 md5 = "9a8afbe0978adbcbfcb3b35b2d015a56";
895 url = "https://pypi.python.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
896 md5 = "5a4f9781f78466da0ea1a648f3e1f79f";
897 897 };
898 898 meta = {
899 899 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -1004,13 +1004,13 b''
1004 1004 };
1005 1005 };
1006 1006 mistune = super.buildPythonPackage {
1007 name = "mistune-0.7.3";
1007 name = "mistune-0.7.4";
1008 1008 buildInputs = with self; [];
1009 1009 doCheck = false;
1010 1010 propagatedBuildInputs = with self; [];
1011 1011 src = fetchurl {
1012 url = "https://pypi.python.org/packages/88/1e/be99791262b3a794332fda598a07c2749a433b9378586361ba9d8e824607/mistune-0.7.3.tar.gz";
1013 md5 = "4eba50bd121b83716fa4be6a4049004b";
1012 url = "https://pypi.python.org/packages/25/a4/12a584c0c59c9fed529f8b3c47ca8217c0cf8bcc5e1089d3256410cfbdbc/mistune-0.7.4.tar.gz";
1013 md5 = "92d01cb717e9e74429e9bde9d29ac43b";
1014 1014 };
1015 1015 meta = {
1016 1016 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -1082,13 +1082,13 b''
1082 1082 };
1083 1083 };
1084 1084 objgraph = super.buildPythonPackage {
1085 name = "objgraph-2.0.0";
1085 name = "objgraph-3.1.0";
1086 1086 buildInputs = with self; [];
1087 1087 doCheck = false;
1088 1088 propagatedBuildInputs = with self; [];
1089 1089 src = fetchurl {
1090 url = "https://pypi.python.org/packages/d7/33/ace750b59247496ed769b170586c5def7202683f3d98e737b75b767ff29e/objgraph-2.0.0.tar.gz";
1091 md5 = "25b0d5e5adc74aa63ead15699614159c";
1090 url = "https://pypi.python.org/packages/f4/b3/082e54e62094cb2ec84f8d5a49e0142cef99016491cecba83309cff920ae/objgraph-3.1.0.tar.gz";
1091 md5 = "eddbd96039796bfbd13eee403701e64a";
1092 1092 };
1093 1093 meta = {
1094 1094 license = [ pkgs.lib.licenses.mit ];
@@ -1186,13 +1186,13 b''
1186 1186 };
1187 1187 };
1188 1188 prompt-toolkit = super.buildPythonPackage {
1189 name = "prompt-toolkit-1.0.13";
1189 name = "prompt-toolkit-1.0.14";
1190 1190 buildInputs = with self; [];
1191 1191 doCheck = false;
1192 1192 propagatedBuildInputs = with self; [six wcwidth];
1193 1193 src = fetchurl {
1194 url = "https://pypi.python.org/packages/23/be/4876b52d5cc159cbd4b0ff6e7aa419a26470849a43a8f647857a4a24467b/prompt_toolkit-1.0.13.tar.gz";
1195 md5 = "427b496d2c147bd3819bc3a7f6e0d493";
1194 url = "https://pypi.python.org/packages/55/56/8c39509b614bda53e638b7500f12577d663ac1b868aef53426fc6a26c3f5/prompt_toolkit-1.0.14.tar.gz";
1195 md5 = "f24061ae133ed32c6b764e92bd48c496";
1196 1196 };
1197 1197 meta = {
1198 1198 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -1641,7 +1641,7 b''
1641 1641 };
1642 1642 };
1643 1643 rhodecode-enterprise-ce = super.buildPythonPackage {
1644 name = "rhodecode-enterprise-ce-4.7.2";
1644 name = "rhodecode-enterprise-ce-4.8.0";
1645 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 1646 doCheck = true;
1647 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 16 configobj==5.0.6
17 17 cssselect==1.0.1
18 18 decorator==4.0.11
19 deform==2.0a2
19 deform==2.0.4
20 20 docutils==0.12
21 21 dogpile.cache==0.6.1
22 22 dogpile.core==0.4.1
@@ -38,7 +38,7 b' meld3==1.0.2'
38 38 msgpack-python==0.4.8
39 39 MySQL-python==1.2.5
40 40 nose==1.3.6
41 objgraph==2.0.0
41 objgraph==3.1.0
42 42 packaging==15.2
43 43 paramiko==1.15.1
44 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 51 EXTENSIONS = {}
52 52
53 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 55 __platform__ = platform.system()
56 56 __license__ = 'AGPLv3, and Commercial License'
57 57 __author__ = 'RhodeCode GmbH'
@@ -35,8 +35,9 b' from pyramid.httpexceptions import HTTPN'
35 35
36 36 from rhodecode.api.exc import (
37 37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 from rhodecode.apps._base import TemplateArgs
38 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 41 from rhodecode.lib.ext_json import json
41 42 from rhodecode.lib.utils2 import safe_str
42 43 from rhodecode.lib.plugins.utils import get_plugin_settings
@@ -278,6 +279,11 b' def request_view(request):'
278 279 'request': request,
279 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 287 try:
282 288 ret_value = func(**call_params)
283 289 return jsonrpc_response(request, ret_value)
@@ -43,16 +43,16 b' class TestClosePullRequest(object):'
43 43 response = api_call(self.app, params)
44 44 expected = {
45 45 'pull_request_id': pull_request_id,
46 'close_status': 'Rejected',
46 47 'closed': True,
47 48 }
48 49 assert_ok(id_, expected, response.body)
49 action = 'user_closed_pull_request:%d' % pull_request_id
50 50 journal = UserLog.query()\
51 51 .filter(UserLog.user_id == author)\
52 .order_by('user_log_id') \
52 53 .filter(UserLog.repository_id == repo)\
53 .filter(UserLog.action == action)\
54 54 .all()
55 assert len(journal) == 1
55 assert journal[-1].action == 'repo.pull_request.close'
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 def test_api_close_pull_request_already_closed_error(self, pr_util):
@@ -62,13 +62,12 b' class TestCommentPullRequest(object):'
62 62 }
63 63 assert_ok(id_, expected, response.body)
64 64
65 action = 'user_commented_pull_request:%d' % pull_request_id
66 65 journal = UserLog.query()\
67 66 .filter(UserLog.user_id == author)\
68 67 .filter(UserLog.repository_id == repo)\
69 .filter(UserLog.action == action)\
68 .order_by('user_log_id') \
70 69 .all()
71 assert len(journal) == 2
70 assert journal[-1].action == 'repo.pull_request.comment.create'
72 71
73 72 @pytest.mark.backends("git", "hg")
74 73 def test_api_comment_pull_request_change_status(
@@ -77,7 +77,7 b' class TestCreatePullRequestApi(object):'
77 77 assert pull_request.source_repo.repo_name == data['source_repo']
78 78 assert pull_request.target_repo.repo_name == data['target_repo']
79 79 assert pull_request.revisions == [self.commit_ids['change']]
80 assert pull_request.reviewers == []
80 assert len(pull_request.reviewers) == 1
81 81
82 82 @pytest.mark.backends("git", "hg")
83 83 def test_create_with_empty_description(self, backend):
@@ -98,7 +98,12 b' class TestCreatePullRequestApi(object):'
98 98 def test_create_with_reviewers_specified_by_names(
99 99 self, backend, no_notifications):
100 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 107 data['reviewers'] = reviewers
103 108 id_, params = build_data(
104 109 self.apikey_regular, 'create_pull_request', **data)
@@ -110,16 +115,26 b' class TestCreatePullRequestApi(object):'
110 115 assert result['result']['msg'] == expected_message
111 116 pull_request_id = result['result']['pull_request_id']
112 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 123 assert sorted(actual_reviewers) == sorted(reviewers)
115 124
116 125 @pytest.mark.backends("git", "hg")
117 126 def test_create_with_reviewers_specified_by_ids(
118 127 self, backend, no_notifications):
119 128 data = self._prepare_data(backend)
120 reviewer_names = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
121 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 138 data['reviewers'] = reviewers
124 139 id_, params = build_data(
125 140 self.apikey_regular, 'create_pull_request', **data)
@@ -131,14 +146,17 b' class TestCreatePullRequestApi(object):'
131 146 assert result['result']['msg'] == expected_message
132 147 pull_request_id = result['result']['pull_request_id']
133 148 pull_request = PullRequestModel().get(pull_request_id)
134 actual_reviewers = [r.user.username for r in pull_request.reviewers]
135 assert sorted(actual_reviewers) == sorted(reviewer_names)
149 actual_reviewers = [
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 156 @pytest.mark.backends("git", "hg")
138 157 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
139 158 data = self._prepare_data(backend)
140 reviewers = ['somebody']
141 data['reviewers'] = reviewers
159 data['reviewers'] = [{'username': 'somebody'}]
142 160 id_, params = build_data(
143 161 self.apikey_regular, 'create_pull_request', **data)
144 162 response = api_call(self.app, params)
@@ -153,7 +171,7 b' class TestCreatePullRequestApi(object):'
153 171 id_, params = build_data(
154 172 self.apikey_regular, 'create_pull_request', **data)
155 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 175 assert_error(id_, expected_message, given=response.body)
158 176
159 177 @pytest.mark.backends("git", "hg")
@@ -59,6 +59,21 b' class TestCreateUser(object):'
59 59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 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 77 def test_api_create_user(self):
63 78 username = 'test_new_api_user'
64 79 email = username + "@foo.com"
@@ -175,7 +190,6 b' class TestCreateUser(object):'
175 190 fixture.destroy_repo_group(username)
176 191 fixture.destroy_user(usr.user_id)
177 192
178
179 193 @mock.patch.object(UserModel, 'create_or_update', crash)
180 194 def test_api_create_user_when_exception_happened(self):
181 195
@@ -112,3 +112,16 b' class TestCreateUserGroup(object):'
112 112
113 113 expected = 'failed to create group `%s`' % (group_name,)
114 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 30 class TestApiDeleteRepo(object):
31 31 def test_api_delete_repo(self, backend):
32 32 repo = backend.create_repo()
33
33 repo_name = repo.repo_name
34 34 id_, params = build_data(
35 35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 36 response = api_call(self.app, params)
37 37
38 38 expected = {
39 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
39 'msg': 'Deleted repository `%s`' % (repo_name,),
40 40 'success': True
41 41 }
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 45 repo = backend.create_repo(cur_user=user_regular.username)
46 repo_name = repo.repo_name
46 47 id_, params = build_data(
47 48 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 49 response = api_call(self.app, params)
49 50
50 51 expected = {
51 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
52 'msg': 'Deleted repository `%s`' % (repo_name,),
52 53 'success': True
53 54 }
54 55 assert_ok(id_, expected, given=response.body)
55 56
56 57 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 58 repo = backend.create_repo()
59 repo_name = repo.repo_name
58 60 id_, params = build_data(
59 61 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
60 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 64 assert_error(id_, expected, given=response.body)
63 65
64 66 def test_api_delete_repo_exception_occurred(self, backend):
65 67 repo = backend.create_repo()
68 repo_name = repo.repo_name
66 69 id_, params = build_data(
67 70 self.apikey, 'delete_repo', repoid=repo.repo_name, )
68 71 with mock.patch.object(RepoModel, 'delete', crash):
69 72 response = api_call(self.app, params)
70 expected = 'failed to delete repository `%s`' % (
71 repo.repo_name,)
73 expected = 'failed to delete repository `%s`' % (repo_name,)
72 74 assert_error(id_, expected, given=response.body)
@@ -28,7 +28,7 b' from rhodecode.api.tests.utils import ('
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 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 32 gist = gist_util.create_gist()
33 33 gist_id = gist.gist_access_id
34 34 gist_created_on = gist.created_on
@@ -45,14 +45,14 b' class TestApiGetGist(object):'
45 45 'expires': -1.0,
46 46 'gist_id': int(gist_id),
47 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 49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 50 'content': None,
51 51 }
52 52
53 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 56 mapping = {
57 57 u'filename1.txt': {'content': u'hello world'},
58 58 u'filename1ą.txt': {'content': u'hello worldę'}
@@ -73,7 +73,7 b' class TestApiGetGist(object):'
73 73 'expires': -1.0,
74 74 'gist_id': int(gist_id),
75 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 77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 78 'content': {
79 79 u'filename1.txt': u'hello world',
@@ -19,13 +19,13 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 import mock
23 22 import pytest
24 23 import urlobject
25 24 from pylons import url
26 25
27 26 from rhodecode.api.tests.utils import (
28 27 build_data, api_call, assert_error, assert_ok)
28 from rhodecode.lib.utils2 import safe_unicode
29 29
30 30 pytestmark = pytest.mark.backends("git", "hg")
31 31
@@ -33,7 +33,7 b' pytestmark = pytest.mark.backends("git",'
33 33 @pytest.mark.usefixtures("testuser_api", "app")
34 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 37 from rhodecode.model.pull_request import PullRequestModel
38 38 pull_request = pr_util.create_pull_request(mergeable=True)
39 39 id_, params = build_data(
@@ -50,16 +50,16 b' class TestGetPullRequest(object):'
50 50 'pullrequest_show',
51 51 repo_name=pull_request.target_repo.repo_name,
52 52 pull_request_id=pull_request.pull_request_id, qualified=True))
53 pr_url = unicode(
54 url_obj.with_netloc('test.example.com:80'))
55 source_url = unicode(
56 pull_request.source_repo.clone_url()
57 .with_netloc('test.example.com:80'))
58 target_url = unicode(
59 pull_request.target_repo.clone_url()
60 .with_netloc('test.example.com:80'))
61 shadow_url = unicode(
53
54 pr_url = safe_unicode(
55 url_obj.with_netloc(http_host_only_stub))
56 source_url = safe_unicode(
57 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
58 target_url = safe_unicode(
59 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
60 shadow_url = safe_unicode(
62 61 PullRequestModel().get_shadow_clone_url(pull_request))
62
63 63 expected = {
64 64 'pull_request_id': pull_request.pull_request_id,
65 65 'url': pr_url,
@@ -109,7 +109,8 b' class TestGetPullRequest(object):'
109 109 'reasons': reasons,
110 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 116 assert_ok(id_, expected, response.body)
@@ -95,13 +95,13 b' class TestMergePullRequest(object):'
95 95
96 96 assert_ok(id_, expected, response.body)
97 97
98 action = 'user_merged_pull_request:%d' % (pull_request_id, )
99 98 journal = UserLog.query()\
100 99 .filter(UserLog.user_id == author)\
101 100 .filter(UserLog.repository_id == repo)\
102 .filter(UserLog.action == action)\
101 .order_by('user_log_id') \
103 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 106 id_, params = build_data(
107 107 self.apikey, 'merge_pull_request',
@@ -33,7 +33,7 b' class TestUpdatePullRequest(object):'
33 33
34 34 @pytest.mark.backends("git", "hg")
35 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 37 pull_request = pr_util.create_pull_request()
38 38
39 39 id_, params = build_data(
@@ -61,7 +61,7 b' class TestUpdatePullRequest(object):'
61 61
62 62 @pytest.mark.backends("git", "hg")
63 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 65 pull_request = pr_util.create_pull_request()
66 66 PullRequestModel().close_pull_request(
67 67 pull_request, TEST_USER_ADMIN_LOGIN)
@@ -78,8 +78,7 b' class TestUpdatePullRequest(object):'
78 78 assert_error(id_, expected, response.body)
79 79
80 80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(
82 self, pr_util, silence_action_logger, no_notifications):
81 def test_api_update_update_commits(self, pr_util, no_notifications):
83 82 commits = [
84 83 {'message': 'a'},
85 84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
@@ -119,20 +118,28 b' class TestUpdatePullRequest(object):'
119 118
120 119 @pytest.mark.backends("git", "hg")
121 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()]
125 new = [users.pop(0)]
126 removed = sorted(new)
127 added = sorted(users)
132 added = [b.username, c.username]
133 removed = [a.username]
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 138 id_, params = build_data(
132 139 self.apikey, 'update_pull_request',
133 140 repoid=pull_request.target_repo.repo_name,
134 141 pullrequestid=pull_request.pull_request_id,
135 reviewers=added)
142 reviewers=new_reviewers)
136 143 response = api_call(self.app, params)
137 144 expected = {
138 145 "msg": "Updated pull request `{}`".format(
@@ -152,7 +159,7 b' class TestUpdatePullRequest(object):'
152 159 self.apikey, 'update_pull_request',
153 160 repoid=pull_request.target_repo.repo_name,
154 161 pullrequestid=pull_request.pull_request_id,
155 reviewers=['bad_name'])
162 reviewers=[{'username': 'bad_name'}])
156 163 response = api_call(self.app, params)
157 164
158 165 expected = 'user `bad_name` does not exist'
@@ -165,7 +172,7 b' class TestUpdatePullRequest(object):'
165 172 self.apikey, 'update_pull_request',
166 173 repoid='fake',
167 174 pullrequestid='fake',
168 reviewers=['bad_name'])
175 reviewers=[{'username': 'bad_name'}])
169 176 response = api_call(self.app, params)
170 177
171 178 expected = 'repository `fake` does not exist'
@@ -181,7 +188,7 b' class TestUpdatePullRequest(object):'
181 188 self.apikey, 'update_pull_request',
182 189 repoid=pull_request.target_repo.repo_name,
183 190 pullrequestid=999999,
184 reviewers=['bad_name'])
191 reviewers=[{'username': 'bad_name'}])
185 192 response = api_call(self.app, params)
186 193
187 194 expected = 'pull request `999999` does not exist'
@@ -26,7 +26,7 b' from rhodecode.tests import TEST_USER_AD'
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 28 from rhodecode.tests.fixture import Fixture
29
29 from rhodecode.tests.plugin import http_host_stub, http_host_only_stub
30 30
31 31 fixture = Fixture()
32 32
@@ -71,14 +71,15 b' class TestApiUpdateRepo(object):'
71 71 ({'repo_name': 'new_repo_name'},
72 72 {
73 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 77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
78 78 '_group': 'test_group_for_update'},
79 79 {
80 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 85 def test_api_update_repo(self, updates, expected, backend):
@@ -115,7 +116,8 b' class TestApiUpdateRepo(object):'
115 116 master_repo = backend.create_repo()
116 117 repo = backend.create_repo()
117 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 122 expected_api_data = repo.get_api_data(include_secrets=True)
121 123 expected_api_data.update(updates)
@@ -130,6 +132,7 b' class TestApiUpdateRepo(object):'
130 132 assert_ok(id_, expected, given=response.body)
131 133 result = response.json['result']['repository']
132 134 assert result['fork_of'] == master_repo.repo_name
135 assert result['fork_of_id'] == master_repo.repo_id
133 136
134 137 def test_api_update_repo_fork_of_not_found(self, backend):
135 138 master_repo_name = 'fake-parent-repo'
@@ -21,7 +21,8 b''
21 21
22 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 26 from rhodecode.api.utils import (
26 27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 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 35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 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 42 log = logging.getLogger(__name__)
39 43
@@ -224,7 +228,8 b' def get_pull_requests(request, apiuser, '
224 228
225 229
226 230 @jsonrpc_method()
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
231 def merge_pull_request(
232 request, apiuser, repoid, pullrequestid,
228 233 userid=Optional(OAttr('apiuser'))):
229 234 """
230 235 Merge the pull request specified by `pullrequestid` into its target
@@ -273,7 +278,12 b' def merge_pull_request(request, apiuser,'
273 278 merge_possible = not check.failed
274 279
275 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 287 raise JSONRPCError(
278 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 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 313 def comment_pull_request(
361 314 request, apiuser, repoid, pullrequestid, message=Optional(None),
362 315 commit_id=Optional(None), status=Optional(None),
@@ -529,24 +482,26 b' def create_pull_request('
529 482 :param description: Set the pull request description.
530 483 :type description: Optional(str)
531 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 487 :type reviewers: Optional(list)
533 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)
539 target = get_repo_or_error(target_repo)
493 source_db_repo = get_repo_or_error(source_repo)
494 target_db_repo = get_repo_or_error(target_repo)
540 495 if not has_superadmin_permission(apiuser):
541 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)
545 full_target_ref = resolve_ref_or_error(target_ref, target)
546 source_commit = get_commit_or_error(full_source_ref, source)
547 target_commit = get_commit_or_error(full_target_ref, target)
548 source_scm = source.scm_instance()
549 target_scm = target.scm_instance()
499 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
500 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
501 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
502 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
503 source_scm = source_db_repo.scm_instance()
504 target_scm = target_db_repo.scm_instance()
550 505
551 506 commit_ranges = target_scm.compare(
552 507 target_commit.raw_id, source_commit.raw_id, source_scm,
@@ -562,20 +517,36 b' def create_pull_request('
562 517 raise JSONRPCError('no common ancestor found')
563 518
564 519 reviewer_objects = Optional.extract(reviewers) or []
565 if not isinstance(reviewer_objects, list):
566 raise JSONRPCError('reviewers should be specified as a list')
567 520
568 reviewers_reasons = []
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
569 529 for reviewer_object in reviewer_objects:
570 reviewer_reasons = []
571 if isinstance(reviewer_object, (basestring, int)):
572 reviewer_username = reviewer_object
573 else:
574 reviewer_username = reviewer_object['username']
575 reviewer_reasons = reviewer_object.get('reasons', [])
530 user = get_user_or_error(reviewer_object['username'])
531 reviewer_object['user_id'] = user.user_id
532
533 get_default_reviewers_data, get_validated_reviewers = \
534 PullRequestModel().get_reviewer_functions()
535
536 reviewer_rules = get_default_reviewers_data(
537 apiuser.get_instance(), source_db_repo,
538 source_commit, target_db_repo, target_commit)
576 539
577 user = get_user_or_error(reviewer_username)
578 reviewers_reasons.append((user.user_id, reviewer_reasons))
540 # specified rules are later re-validated, thus we can assume users will
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 551 pull_request_model = PullRequestModel()
581 552 pull_request = pull_request_model.create(
@@ -586,7 +557,7 b' def create_pull_request('
586 557 target_ref=full_target_ref,
587 558 revisions=reversed(
588 559 [commit.raw_id for commit in reversed(commit_ranges)]),
589 reviewers=reviewers_reasons,
560 reviewers=reviewers,
590 561 title=title,
591 562 description=Optional.extract(description)
592 563 )
@@ -603,7 +574,7 b' def create_pull_request('
603 574 def update_pull_request(
604 575 request, apiuser, repoid, pullrequestid, title=Optional(''),
605 576 description=Optional(''), reviewers=Optional(None),
606 update_commits=Optional(None), close_pull_request=Optional(None)):
577 update_commits=Optional(None)):
607 578 """
608 579 Updates a pull request.
609 580
@@ -619,10 +590,12 b' def update_pull_request('
619 590 :type description: Optional(str)
620 591 :param reviewers: Update pull request reviewers list with new value.
621 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 597 :param update_commits: Trigger update of commits for this pull request
623 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 600 Example output:
628 601
@@ -665,29 +638,38 b' def update_pull_request('
665 638 pullrequestid,))
666 639
667 640 reviewer_objects = Optional.extract(reviewers) or []
668 if not isinstance(reviewer_objects, list):
669 raise JSONRPCError('reviewers should be specified as a list')
670 641
671 reviewers_reasons = []
672 reviewer_ids = set()
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
673 650 for reviewer_object in reviewer_objects:
674 reviewer_reasons = []
675 if isinstance(reviewer_object, (int, basestring)):
676 reviewer_username = reviewer_object
651 user = get_user_or_error(reviewer_object['username'])
652 reviewer_object['user_id'] = user.user_id
653
654 get_default_reviewers_data, get_validated_reviewers = \
655 PullRequestModel().get_reviewer_functions()
656
657 # re-use stored rules
658 reviewer_rules = pull_request.reviewer_data
659 try:
660 reviewers = get_validated_reviewers(
661 reviewer_objects, reviewer_rules)
662 except ValueError as e:
663 raise JSONRPCError('Reviewers Validation: {}'.format(e))
677 664 else:
678 reviewer_username = reviewer_object['username']
679 reviewer_reasons = reviewer_object.get('reasons', [])
680
681 user = get_user_or_error(reviewer_username)
682 reviewer_ids.add(user.user_id)
683 reviewers_reasons.append((user.user_id, reviewer_reasons))
665 reviewers = []
684 666
685 667 title = Optional.extract(title)
686 668 description = Optional.extract(description)
687 669 if title or description:
688 670 PullRequestModel().edit(
689 671 pull_request, title or pull_request.title,
690 description or pull_request.description)
672 description or pull_request.description, apiuser)
691 673 Session().commit()
692 674
693 675 commit_changes = {"added": [], "common": [], "removed": []}
@@ -699,9 +681,9 b' def update_pull_request('
699 681 Session().commit()
700 682
701 683 reviewers_changes = {"added": [], "removed": []}
702 if reviewer_ids:
684 if reviewers:
703 685 added_reviewers, removed_reviewers = \
704 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
686 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
705 687
706 688 reviewers_changes['added'] = sorted(
707 689 [get_user_or_error(n).username for n in added_reviewers])
@@ -709,11 +691,6 b' def update_pull_request('
709 691 [get_user_or_error(n).username for n in removed_reviewers])
710 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 694 data = {
718 695 'msg': 'Updated pull request `{}`'.format(
719 696 pull_request.pull_request_id),
@@ -723,3 +700,80 b' def update_pull_request('
723 700 }
724 701
725 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 29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib import repo_maintenance
32 34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 36 from rhodecode.lib.ext_json import json
@@ -915,12 +917,13 b' def update_repo('
915 917
916 918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
917 919
920 old_values = repo.get_api_data()
918 921 schema = repo_schema.RepoSchema().bind(
919 922 repo_type_options=rhodecode.BACKENDS.keys(),
920 923 repo_ref_options=ref_choices,
921 924 # user caller
922 925 user=apiuser,
923 old_values=repo.get_api_data())
926 old_values=old_values)
924 927 try:
925 928 schema_data = schema.deserialize(dict(
926 929 # we save old value, users cannot change type
@@ -965,6 +968,9 b' def update_repo('
965 968
966 969 try:
967 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 974 Session().commit()
969 975 return {
970 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 1161 repo = get_repo_or_error(repoid)
1162 repo_name = repo.repo_name
1156 1163 if not has_superadmin_permission(apiuser):
1157 1164 _perms = ('repository.admin',)
1158 1165 validate_repo_permissions(apiuser, repoid, repo, _perms)
@@ -1170,18 +1177,26 b' def delete_repo(request, apiuser, repoid'
1170 1177 'Cannot delete `%s` it still contains attached forks' %
1171 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 1191 Session().commit()
1176 1192 return {
1177 'msg': 'Deleted repository `%s`%s' % (
1178 repo.repo_name, _forks_msg),
1193 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1179 1194 'success': True
1180 1195 }
1181 1196 except Exception:
1182 1197 log.exception("Exception occurred while trying to delete repo")
1183 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 1475 rc_config = SettingsModel().get_all_settings()
1461 1476 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1462 1477 status_change_label = ChangesetStatus.get_status_lbl(status)
1463 comm = CommentsModel().create(
1478 comment = CommentsModel().create(
1464 1479 message, repo, user, commit_id=commit_id,
1465 1480 status_change=status_change_label,
1466 1481 status_change_type=status,
@@ -1472,7 +1487,7 b' def comment_commit('
1472 1487 # also do a status change
1473 1488 try:
1474 1489 ChangesetStatusModel().set_status(
1475 repo, status, user, comm, revision=commit_id,
1490 repo, status, user, comment, revision=commit_id,
1476 1491 dont_allow_on_closed_pull_request=True
1477 1492 )
1478 1493 except StatusChangeOnClosedPullRequestError:
@@ -1486,7 +1501,7 b' def comment_commit('
1486 1501 return {
1487 1502 'msg': (
1488 1503 'Commented on commit `%s` for repository `%s`' % (
1489 comm.revision, repo.repo_name)),
1504 comment.revision, repo.repo_name)),
1490 1505 'status_change': status,
1491 1506 'success': True,
1492 1507 }
@@ -1867,6 +1882,11 b' def strip(request, apiuser, repoid, revi'
1867 1882
1868 1883 try:
1869 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 1890 return {
1871 1891 'msg': 'Stripped commit %s from repo `%s`' % (
1872 1892 revision, repo.repo_name),
@@ -1902,6 +1922,7 b' def get_repo_settings(request, apiuser, '
1902 1922 "id": 237,
1903 1923 "result": {
1904 1924 "extensions_largefiles": true,
1925 "extensions_evolve": true,
1905 1926 "hooks_changegroup_push_logger": true,
1906 1927 "hooks_changegroup_repo_size": false,
1907 1928 "hooks_outgoing_pull_logger": true,
@@ -1985,3 +2006,65 b' def set_repo_settings(request, apiuser, '
1985 2006
1986 2007 # Indicate success.
1987 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 27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 from rhodecode.lib import audit_logger
30 31 from rhodecode.lib.auth import (
31 32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 33 from rhodecode.model.db import Session
@@ -222,6 +223,13 b' def create_repo_group('
222 223 group_name=validated_group_name,
223 224 group_description=schema_data['repo_group_name'],
224 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 233 Session().commit()
226 234 return {
227 235 'msg': 'Created new repo group `%s`' % validated_group_name,
@@ -310,8 +318,13 b' def update_repo_group('
310 318 enable_locking=schema_data['repo_group_enable_locking'],
311 319 )
312 320
321 old_data = repo_group.get_api_data()
313 322 try:
314 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 328 Session().commit()
316 329 return {
317 330 'msg': 'updated repository group ID:%s %s' % (
@@ -365,8 +378,12 b' def delete_repo_group(request, apiuser, '
365 378 validate_repo_group_permissions(
366 379 apiuser, repogroupid, repo_group, ('group.admin',))
367 380
381 old_data = repo_group.get_api_data()
368 382 try:
369 383 RepoGroupModel().delete(repo_group)
384 audit_logger.store_api(
385 'repo_group.delete', action_data={'old_data': old_data},
386 user=apiuser)
370 387 Session().commit()
371 388 return {
372 389 'msg': 'deleted repo group ID:%s %s' %
@@ -20,14 +20,18 b''
20 20
21 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 25 from rhodecode.api.utils import (
25 26 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
27 from rhodecode.lib import audit_logger
26 28 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 29 from rhodecode.lib.exceptions import DefaultUserException
28 30 from rhodecode.lib.utils2 import safe_int, str2bool
29 31 from rhodecode.model.db import Session, User, Repository
30 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 36 log = logging.getLogger(__name__)
33 37
@@ -237,20 +241,54 b' def create_user(request, apiuser, userna'
237 241 if isinstance(create_repo_group, basestring):
238 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 272 try:
241 273 user = UserModel().create_or_update(
242 username=Optional.extract(username),
243 password=Optional.extract(password),
244 email=Optional.extract(email),
245 firstname=Optional.extract(firstname),
246 lastname=Optional.extract(lastname),
247 active=Optional.extract(active),
248 admin=Optional.extract(admin),
249 extern_type=Optional.extract(extern_type),
250 extern_name=Optional.extract(extern_name),
274 username=schema_data['username'],
275 password=schema_data['password'],
276 email=schema_data['email'],
277 firstname=schema_data['first_name'],
278 lastname=schema_data['last_name'],
279 active=schema_data['active'],
280 admin=schema_data['admin'],
281 extern_type=schema_data['extern_type'],
282 extern_name=schema_data['extern_name'],
251 283 force_password_change=Optional.extract(force_password_change),
252 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 292 Session().commit()
255 293 return {
256 294 'msg': 'created new user `%s`' % username,
@@ -326,7 +364,7 b' def update_user(request, apiuser, userid'
326 364 raise JSONRPCForbidden()
327 365
328 366 user = get_user_or_error(userid)
329
367 old_data = user.get_api_data()
330 368 # only non optional arguments will be stored in updates
331 369 updates = {}
332 370
@@ -343,6 +381,9 b' def update_user(request, apiuser, userid'
343 381 store_update(updates, extern_type, 'extern_type')
344 382
345 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 387 Session().commit()
347 388 return {
348 389 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
@@ -405,9 +446,13 b' def delete_user(request, apiuser, userid'
405 446 raise JSONRPCForbidden()
406 447
407 448 user = get_user_or_error(userid)
408
449 old_data = user.get_api_data()
409 450 try:
410 451 UserModel().delete(userid)
452 audit_logger.store_api(
453 'user.delete', action_data={'old_data': old_data},
454 user=apiuser)
455
411 456 Session().commit()
412 457 return {
413 458 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
@@ -20,15 +20,19 b''
20 20
21 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 25 from rhodecode.api.utils import (
25 26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
28 from rhodecode.lib import audit_logger
27 29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
28 30 from rhodecode.lib.exceptions import UserGroupAssignedException
29 31 from rhodecode.model.db import Session
30 32 from rhodecode.model.scm import UserGroupList
31 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 37 log = logging.getLogger(__name__)
34 38
@@ -210,20 +214,41 b' def create_user_group('
210 214 if UserGroupModel().get_by_name(group_name):
211 215 raise JSONRPCError("user group `%s` already exist" % (group_name,))
212 216
213 try:
214 217 if isinstance(owner, Optional):
215 218 owner = apiuser.user_id
216 219
217 220 owner = get_user_or_error(owner)
218 221 active = Optional.extract(active)
219 222 description = Optional.extract(description)
220 ug = UserGroupModel().create(
221 name=group_name, description=description, owner=owner,
222 active=active)
223
224 schema = user_group_schema.UserGroupSchema().bind(
225 # user caller
226 user=apiuser)
227 try:
228 schema_data = schema.deserialize(dict(
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 248 Session().commit()
224 249 return {
225 250 'msg': 'created new user group `%s`' % group_name,
226 'user_group': ug.get_api_data()
251 'user_group': creation_data
227 252 }
228 253 except Exception:
229 254 log.exception("Error occurred during creation of user group")
@@ -291,6 +316,7 b' def update_user_group(request, apiuser, '
291 316 if not isinstance(owner, Optional):
292 317 owner = get_user_or_error(owner)
293 318
319 old_data = user_group.get_api_data()
294 320 updates = {}
295 321 store_update(updates, group_name, 'users_group_name')
296 322 store_update(updates, description, 'user_group_description')
@@ -298,6 +324,9 b' def update_user_group(request, apiuser, '
298 324 store_update(updates, active, 'users_group_active')
299 325 try:
300 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 330 Session().commit()
302 331 return {
303 332 'msg': 'updated user group ID:%s %s' % (
@@ -359,8 +388,12 b' def delete_user_group(request, apiuser, '
359 388 raise JSONRPCError(
360 389 'user group `%s` does not exist' % (usergroupid,))
361 390
391 old_data = user_group.get_api_data()
362 392 try:
363 393 UserGroupModel().delete(user_group)
394 audit_logger.store_api(
395 'user_group.delete', action_data={'old_data': old_data},
396 user=apiuser)
364 397 Session().commit()
365 398 return {
366 399 'msg': 'deleted user group ID:%s %s' % (
@@ -438,6 +471,12 b' def add_user_to_user_group(request, apiu'
438 471 user.username, user_group.users_group_name
439 472 )
440 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 480 Session().commit()
442 481
443 482 return {
@@ -501,6 +540,12 b' def remove_user_from_user_group(request,'
501 540 user.username, user_group.users_group_name
502 541 )
503 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 549 Session().commit()
505 550 return {'success': success, 'msg': msg}
506 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 24 from pyramid.httpexceptions import HTTPFound
25 25
26 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 31 from rhodecode.model import repo
32 from rhodecode.model import repo_group
29 33 from rhodecode.model.db import User
30 34 from rhodecode.model.scm import ScmModel
31 35
@@ -36,6 +40,30 b" ADMIN_PREFIX = '/_admin'"
36 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 67 class TemplateArgs(StrictAttributeDict):
40 68 pass
41 69
@@ -77,9 +105,14 b' class BaseAppView(object):'
77 105 raise HTTPFound(
78 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 109 c = TemplateArgs()
82 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 116 return c
84 117
85 118 def _register_global_c(self, tmpl_args):
@@ -121,15 +154,106 b' class RepoAppView(BaseAppView):'
121 154 self.db_repo_name = self.db_repo.repo_name
122 155 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
123 156
124 def _get_local_tmpl_context(self):
125 c = super(RepoAppView, self)._get_local_tmpl_context()
157 def _handle_missing_requirements(self, error):
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 166 # register common vars for this type of view
127 167 c.rhodecode_db_repo = self.db_repo
128 168 c.repo_name = self.db_repo_name
129 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 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 257 class RepoRoutePredicate(object):
134 258 def __init__(self, val, config):
135 259 self.val = val
@@ -140,11 +264,15 b' class RepoRoutePredicate(object):'
140 264 phash = text
141 265
142 266 def __call__(self, info, request):
267
268 if hasattr(request, 'vcs_call'):
269 # skip vcs calls
270 return
271
143 272 repo_name = info['match']['repo_name']
144 273 repo_model = repo.RepoModel()
145 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,
147 # and validate repo based on the type.
275
148 276 if by_name_match:
149 277 # register this as request object we can re-use later
150 278 request.db_repo = by_name_match
@@ -158,6 +286,72 b' class RepoRoutePredicate(object):'
158 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 351 def includeme(config):
162 352 config.add_route_predicate(
163 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 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 47 name='admin_settings_open_source',
34 48 pattern='/settings/open_source')
35 49 config.add_route(
@@ -50,6 +64,11 b' def admin_routes(config):'
50 64 name='admin_settings_sessions_cleanup',
51 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 72 # users admin
54 73 config.add_route(
55 74 name='users',
@@ -70,6 +89,28 b' def admin_routes(config):'
70 89 name='edit_user_auth_tokens_delete',
71 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 114 # user groups management
74 115 config.add_route(
75 116 name='edit_user_groups_management',
@@ -93,6 +134,8 b' def includeme(config):'
93 134 navigation_registry = NavigationRegistry(labs_active=labs_active)
94 135 config.registry.registerUtility(navigation_registry)
95 136
137 # main admin routes
138 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
96 139 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
97 140
98 141 # Scan module for configuration decorators.
@@ -20,7 +20,9 b''
20 20
21 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 27 from rhodecode.tests import (
26 28 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
@@ -44,6 +46,20 b' def route_path(name, params=None, **kwar'
44 46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
45 47 'edit_user_auth_tokens_delete':
46 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 63 }[name].format(**kwargs)
48 64
49 65 if params:
@@ -135,8 +151,131 b' class TestAdminUsersView(TestController)'
135 151
136 152 response = self.app.post(
137 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 157 assert_session_flash(response, 'Auth token successfully deleted')
141 158 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
142 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 21 import collections
22 22 import logging
23 23
24 from pylons import tmpl_context as c
24
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import BaseAppView
@@ -34,15 +34,21 b' log = logging.getLogger(__name__)'
34 34
35 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 42 @LoginRequired()
38 43 @HasPermissionAllDecorator('hg.admin')
39 44 @view_config(
40 45 route_name='admin_settings_open_source', request_method='GET',
41 46 renderer='rhodecode:templates/admin/settings/settings.mako')
42 47 def open_source_licenses(self):
48 c = self.load_default_context()
43 49 c.active = 'open_source'
44 50 c.navlist = navigation_list(self.request)
45 51 c.opensource_licenses = collections.OrderedDict(
46 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 21 import logging
22 22
23 from pylons import tmpl_context as c
24 23 from pyramid.view import view_config
25 24 from pyramid.httpexceptions import HTTPFound
26 25
@@ -37,6 +36,11 b' log = logging.getLogger(__name__)'
37 36
38 37
39 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 45 @LoginRequired()
42 46 @HasPermissionAllDecorator('hg.admin')
@@ -44,6 +48,8 b' class AdminSessionSettingsView(BaseAppVi'
44 48 route_name='admin_settings_sessions', request_method='GET',
45 49 renderer='rhodecode:templates/admin/settings/settings.mako')
46 50 def settings_sessions(self):
51 c = self.load_default_context()
52
47 53 c.active = 'sessions'
48 54 c.navlist = navigation_list(self.request)
49 55
@@ -59,11 +65,11 b' class AdminSessionSettingsView(BaseAppVi'
59 65 c.session_expired_count = c.session_model.get_expired_count(
60 66 older_than_seconds)
61 67
62 return {}
68 return self._get_template_context(c)
63 69
64 70 @LoginRequired()
71 @HasPermissionAllDecorator('hg.admin')
65 72 @CSRFRequired()
66 @HasPermissionAllDecorator('hg.admin')
67 73 @view_config(
68 74 route_name='admin_settings_sessions_cleanup', request_method='POST')
69 75 def settings_sessions_cleanup(self):
@@ -33,8 +33,8 b' log = logging.getLogger(__name__)'
33 33 class SvnConfigAdminSettingsView(BaseAppView):
34 34
35 35 @LoginRequired()
36 @HasPermissionAllDecorator('hg.admin')
36 37 @CSRFRequired()
37 @HasPermissionAllDecorator('hg.admin')
38 38 @view_config(
39 39 route_name='admin_settings_vcs_svn_generate_cfg',
40 40 request_method='POST', renderer='json')
@@ -22,7 +22,6 b' import logging'
22 22 import urllib2
23 23 import packaging.version
24 24
25 from pylons import tmpl_context as c
26 25 from pyramid.view import view_config
27 26
28 27 import rhodecode
@@ -39,6 +38,10 b' log = logging.getLogger(__name__)'
39 38
40 39
41 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 46 @staticmethod
44 47 def get_update_data(update_url):
@@ -64,6 +67,7 b' class AdminSystemInfoSettingsView(BaseAp'
64 67 renderer='rhodecode:templates/admin/settings/settings.mako')
65 68 def settings_system_info(self):
66 69 _ = self.request.translate
70 c = self.load_default_context()
67 71
68 72 c.active = 'system'
69 73 c.navlist = navigation_list(self.request)
@@ -106,6 +110,7 b' class AdminSystemInfoSettingsView(BaseAp'
106 110 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
107 111 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
108 112 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
113 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
109 114 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
110 115 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
111 116 ('', '', ''), # spacer
@@ -163,7 +168,7 b' class AdminSystemInfoSettingsView(BaseAp'
163 168 else:
164 169 self.request.session.flash(
165 170 'You are not allowed to do this', queue='warning')
166 return {}
171 return self._get_template_context(c)
167 172
168 173 @LoginRequired()
169 174 @HasPermissionAllDecorator('hg.admin')
@@ -172,6 +177,7 b' class AdminSystemInfoSettingsView(BaseAp'
172 177 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
173 178 def settings_system_info_check_update(self):
174 179 _ = self.request.translate
180 c = self.load_default_context()
175 181
176 182 update_url = self.get_update_url()
177 183
@@ -200,4 +206,4 b' class AdminSystemInfoSettingsView(BaseAp'
200 206 c.should_upgrade = True
201 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 21 import logging
22 22 import datetime
23 import formencode
23 24
24 25 from pyramid.httpexceptions import HTTPFound
25 26 from pyramid.view import view_config
26 27 from sqlalchemy.sql.functions import coalesce
27 28
28 from rhodecode.lib.helpers import Page
29 from rhodecode_tools.lib.ext_json import json
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
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 33 from rhodecode.lib.auth import (
33 34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 35 from rhodecode.lib import helpers as h
@@ -37,13 +38,13 b' from rhodecode.lib.utils2 import safe_in'
37 38 from rhodecode.model.auth_token import AuthTokenModel
38 39 from rhodecode.model.user import UserModel
39 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 42 from rhodecode.model.meta import Session
42 43
43 44 log = logging.getLogger(__name__)
44 45
45 46
46 class AdminUsersView(BaseAppView):
47 class AdminUsersView(BaseAppView, DataGridAppView):
47 48 ALLOW_SCOPED_TOKENS = False
48 49 """
49 50 This view has alternative version inside EE, if modified please take a look
@@ -64,28 +65,6 b' class AdminUsersView(BaseAppView):'
64 65 # is a pyramid view
65 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 68 @HasPermissionAllDecorator('hg.admin')
90 69 @view_config(
91 70 route_name='users', request_method='GET',
@@ -97,8 +76,8 b' class AdminUsersView(BaseAppView):'
97 76 @HasPermissionAllDecorator('hg.admin')
98 77 @view_config(
99 78 # renderer defined below
100 route_name='users_data', request_method='GET', renderer='json',
101 xhr=True)
79 route_name='users_data', request_method='GET',
80 renderer='json_ext', xhr=True)
102 81 def users_list_data(self):
103 82 draw, start, limit = self._extract_chunk(self.request)
104 83 search_q, order_by, order_dir = self._extract_ordering(self.request)
@@ -149,8 +128,8 b' class AdminUsersView(BaseAppView):'
149 128 users_data.append({
150 129 "username": h.gravatar_with_user(user.username),
151 130 "email": user.email,
152 "first_name": h.escape(user.name),
153 "last_name": h.escape(user.lastname),
131 "first_name": user.first_name,
132 "last_name": user.last_name,
154 133 "last_login": h.format_date(user.last_login),
155 134 "last_activity": h.format_date(user.last_activity),
156 135 "active": h.bool2icon(user.active),
@@ -216,15 +195,23 b' class AdminUsersView(BaseAppView):'
216 195
217 196 user_id = self.request.matchdict.get('user_id')
218 197 c.user = User.get_or_404(user_id, pyramid_exc=True)
198
219 199 self._redirect_for_default_user(c.user.username)
220 200
201 user_data = c.user.get_api_data()
221 202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
222 203 description = self.request.POST.get('description')
223 204 role = self.request.POST.get('role')
224 205
225 206 token = AuthTokenModel().create(
226 207 c.user.user_id, description, lifetime, role)
208 token_data = token.get_api_data()
209
227 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 215 Session().commit()
229 216
230 217 h.flash(_("Auth token successfully created"), category='success')
@@ -242,11 +229,19 b' class AdminUsersView(BaseAppView):'
242 229 user_id = self.request.matchdict.get('user_id')
243 230 c.user = User.get_or_404(user_id, pyramid_exc=True)
244 231 self._redirect_for_default_user(c.user.username)
232 user_data = c.user.get_api_data()
245 233
246 234 del_auth_token = self.request.POST.get('del_auth_token')
247 235
248 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 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 245 Session().commit()
251 246 h.flash(_("Auth token successfully deleted"), category='success')
252 247
@@ -255,6 +250,186 b' class AdminUsersView(BaseAppView):'
255 250 @LoginRequired()
256 251 @HasPermissionAllDecorator('hg.admin')
257 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 433 route_name='edit_user_groups_management', request_method='GET',
259 434 renderer='rhodecode:templates/admin/users/user_edit.mako')
260 435 def groups_management(self):
@@ -264,7 +439,8 b' class AdminUsersView(BaseAppView):'
264 439 c.user = User.get_or_404(user_id, pyramid_exc=True)
265 440 c.data = c.user.group_member
266 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 444 c.groups = json.dumps(groups)
269 445 c.active = 'groups'
270 446
@@ -272,6 +448,7 b' class AdminUsersView(BaseAppView):'
272 448
273 449 @LoginRequired()
274 450 @HasPermissionAllDecorator('hg.admin')
451 @CSRFRequired()
275 452 @view_config(
276 453 route_name='edit_user_groups_management_updates', request_method='POST')
277 454 def groups_management_updates(self):
@@ -314,15 +491,15 b' class AdminUsersView(BaseAppView):'
314 491 p = safe_int(self.request.GET.get('page', 1), 1)
315 492
316 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 496 def url_generator(**kw):
320 497 if filter_term:
321 498 kw['filter'] = filter_term
322 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,
325 url=url_generator)
501 c.audit_logs = h.Page(
502 user_log, page=p, items_per_page=10, url=url_generator)
326 503 c.filter_term = filter_term
327 504 return self._get_template_context(c)
328 505
@@ -30,8 +30,6 b' Channel Stream controller for rhodecode'
30 30 import logging
31 31 import uuid
32 32
33 from pylons import tmpl_context as c
34 from pyramid.settings import asbool
35 33 from pyramid.view import view_config
36 34 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
37 35
@@ -46,7 +44,6 b' from rhodecode.lib.channelstream import '
46 44 update_history_from_logs,
47 45 STATE_PUBLIC_KEYS)
48 46 from rhodecode.lib.auth import NotAnonymous
49 from rhodecode.lib.utils2 import str2bool
50 47
51 48 log = logging.getLogger(__name__)
52 49
@@ -82,7 +79,7 b' class ChannelstreamView(object):'
82 79 log.error('Incorrect permissions for requested channels')
83 80 raise HTTPForbidden()
84 81
85 user = c.rhodecode_user
82 user = self._rhodecode_user
86 83 if user.user_id:
87 84 user_data = get_user_data(user.user_id)
88 85 else:
@@ -95,7 +92,7 b' class ChannelstreamView(object):'
95 92 'display_name': None,
96 93 'display_link': None,
97 94 }
98 user_data['permissions'] = c.rhodecode_user.permissions
95 user_data['permissions'] = self._rhodecode_user.permissions
99 96 payload = {
100 97 'username': user.username,
101 98 'user_state': user_data,
@@ -18,31 +18,34 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import json
22 21
23 from mock import patch
24 22 import pytest
25 from pylons import tmpl_context as c
26 23
27 24 import rhodecode
28 from rhodecode.lib.utils import map_groups
29 from rhodecode.model.db import Repository, User, RepoGroup
25 from rhodecode.model.db import Repository
30 26 from rhodecode.model.meta import Session
31 27 from rhodecode.model.repo import RepoModel
32 28 from rhodecode.model.repo_group import RepoGroupModel
33 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 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 44 class TestHomeController(TestController):
42 45
43 46 def test_index(self):
44 47 self.log_user()
45 response = self.app.get(url(controller='home', action='index'))
48 response = self.app.get(route_path('home'))
46 49 # if global permission is set
47 50 response.mustcontain('Add Repository')
48 51
@@ -51,8 +54,10 b' class TestHomeController(TestController)'
51 54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
52 55
53 56 def test_index_contains_statics_with_ver(self):
57 from pylons import tmpl_context as c
58
54 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 62 rhodecode_version_hash = c.rhodecode_version_hash
58 63 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
@@ -60,7 +65,7 b' class TestHomeController(TestController)'
60 65
61 66 def test_index_contains_backend_specific_details(self, backend):
62 67 self.log_user()
63 response = self.app.get(url(controller='home', action='index'))
68 response = self.app.get(route_path('home'))
64 69 tip = backend.repo.get_commit().raw_id
65 70
66 71 # html in javascript variable:
@@ -72,17 +77,16 b' class TestHomeController(TestController)'
72 77
73 78 def test_index_with_anonymous_access_disabled(self):
74 79 with fixture.anon_access(False):
75 response = self.app.get(url(controller='home', action='index'),
76 status=302)
80 response = self.app.get(route_path('home'), status=302)
77 81 assert 'login' in response.location
78 82
79 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 85 response.mustcontain("gr1/repo_in_group")
82 86
83 87 def test_index_page_on_group_with_trailing_slash(
84 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 90 response.mustcontain("gr1/repo_in_group")
87 91
88 92 @pytest.fixture(scope='class')
@@ -96,21 +100,19 b' class TestHomeController(TestController)'
96 100 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
97 101 Session().commit()
98 102
99 def test_index_with_name_with_tags(self, autologin_user):
100 user = User.get_by_username('test_admin')
103 def test_index_with_name_with_tags(self, user_util, autologin_user):
104 user = user_util.create_user()
105 username = user.username
101 106 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
102 user.lastname = (
103 '<img src="/image2" onload="alert(\'Hello, World!\');">')
107 user.lastname = '#"><img src=x onerror=prompt(document.cookie);>'
108
104 109 Session().add(user)
105 110 Session().commit()
111 user_util.create_repo(owner=username)
106 112
107 response = self.app.get(url(controller='home', action='index'))
108 response.mustcontain(
109 '&lt;img src=&#34;/image1&#34; onload=&#34;'
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;')
113 response = self.app.get(route_path('home'))
114 response.mustcontain(h.html_escape(user.first_name))
115 response.mustcontain(h.html_escape(user.last_name))
114 116
115 117 @pytest.mark.parametrize("name, state", [
116 118 ('Disabled', False),
@@ -125,266 +127,8 b' class TestHomeController(TestController)'
125 127 Session().commit()
126 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 131 if state is True:
130 132 response.mustcontain(version_string)
131 133 if state is False:
132 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 25 import logging
26 26 import urlparse
27 27
28 from pylons import url
29 28 from pyramid.httpexceptions import HTTPFound
30 29 from pyramid.view import view_config
31 30 from recaptcha.client.captcha import submit
@@ -34,6 +33,7 b' from rhodecode.apps._base import BaseApp'
34 33 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 34 from rhodecode.events import UserRegistered
36 35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import audit_logger
37 37 from rhodecode.lib.auth import (
38 38 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
39 39 from rhodecode.lib.base import get_ip_addr
@@ -90,20 +90,21 b' def get_came_from(request):'
90 90 came_from = safe_str(request.GET.get('came_from', ''))
91 91 parsed = urlparse.urlparse(came_from)
92 92 allowed_schemes = ['http', 'https']
93 default_came_from = h.route_path('home')
93 94 if parsed.scheme and parsed.scheme not in allowed_schemes:
94 95 log.error('Suspicious URL scheme detected %s for url %s' %
95 96 (parsed.scheme, parsed))
96 came_from = url('home')
97 came_from = default_came_from
97 98 elif parsed.netloc and request.host != parsed.netloc:
98 99 log.error('Suspicious NETLOC detected %s for url %s server url '
99 100 'is: %s' % (parsed.netloc, parsed, request.host))
100 came_from = url('home')
101 came_from = default_came_from
101 102 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
102 103 log.error('Header injection detected `%s` for url %s server url ' %
103 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 110 class LoginView(BaseAppView):
@@ -166,6 +167,15 b' class LoginView(BaseAppView):'
166 167 username=form_result['username'],
167 168 remember=form_result['remember'])
168 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 179 raise HTTPFound(c.came_from, headers=headers)
170 180 except formencode.Invalid as errors:
171 181 defaults = errors.value
@@ -176,6 +186,14 b' class LoginView(BaseAppView):'
176 186 'errors': errors.error_dict,
177 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 197 return render_ctx
180 198
181 199 except UserCreationError as e:
@@ -191,8 +209,13 b' class LoginView(BaseAppView):'
191 209 def logout(self):
192 210 auth_user = self._rhodecode_user
193 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 217 self.session.delete()
195 return HTTPFound(url('home'))
218 return HTTPFound(h.route_path('home'))
196 219
197 220 @HasPermissionAnyDecorator(
198 221 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
@@ -338,6 +361,12 b' class LoginView(BaseAppView):'
338 361 form_result, password_reset_url)
339 362 # Display success message and redirect.
340 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 370 return HTTPFound(self.request.route_path('reset_password'))
342 371
343 372 except formencode.Invalid as errors:
@@ -41,12 +41,45 b' def includeme(config):'
41 41 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
42 42 config.add_route(
43 43 name='my_account_auth_tokens_add',
44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new',
45 )
44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
46 45 config.add_route(
47 46 name='my_account_auth_tokens_delete',
48 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete',
49 )
47 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
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 84 # Scan module for configuration decorators.
52 85 config.scan()
@@ -103,7 +103,7 b' class TestMyAccountAuthTokens(TestContro'
103 103
104 104 response = self.app.post(
105 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 107 assert_session_flash(response, 'Auth token successfully deleted')
108 108
109 109 user = User.get(user_id)
@@ -132,6 +132,7 b' class TestMyAccountPassword(TestControll'
132 132 self.app.post(route_path('my_account_password'), form_data)
133 133
134 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 138 assert old_password_hash != new_password_hash No newline at end of file
@@ -19,18 +19,28 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 import datetime
22 23
24 import formencode
23 25 from pyramid.httpexceptions import HTTPFound
24 26 from pyramid.view import view_config
25 27
26 28 from rhodecode.apps._base import BaseAppView
27 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 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 36 from rhodecode.lib.utils2 import safe_int, md5
31 37 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.db import (
39 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload)
32 40 from rhodecode.model.meta import Session
41 from rhodecode.model.scm import RepoList
33 42 from rhodecode.model.user import UserModel
43 from rhodecode.model.repo import RepoModel
34 44 from rhodecode.model.validation_schema.schemas import user_schema
35 45
36 46 log = logging.getLogger(__name__)
@@ -158,7 +168,7 b' class MyAccountView(BaseAppView):'
158 168 @NotAnonymous()
159 169 @CSRFRequired()
160 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 172 def my_account_auth_tokens_add(self):
163 173 _ = self.request.translate
164 174 c = self.load_default_context()
@@ -169,7 +179,13 b' class MyAccountView(BaseAppView):'
169 179
170 180 token = AuthTokenModel().create(
171 181 c.user.user_id, description, lifetime, role)
182 token_data = token.get_api_data()
183
172 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 189 Session().commit()
174 190
175 191 h.flash(_("Auth token successfully created"), category='success')
@@ -187,8 +203,197 b' class MyAccountView(BaseAppView):'
187 203 del_auth_token = self.request.POST.get('del_auth_token')
188 204
189 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 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 214 Session().commit()
192 215 h.flash(_("Auth token successfully deleted"), category='success')
193 216
194 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 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 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 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 125 config.add_route(
25 126 name='repo_maintenance',
26 pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
127 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
27 128
28 129 config.add_route(
29 130 name='repo_maintenance_execute',
30 pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
31
131 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
32 132
33 133 # Strip
34 134 config.add_route(
35 135 name='strip',
36 pattern='/{repo_name:.*?[^/]}/strip', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
37 137
38 138 config.add_route(
39 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 142 config.add_route(
43 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 152 # Scan module for configuration decorators.
46 153 config.scan()
@@ -18,24 +18,35 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import pytest
21 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 40 def test_index(self, backend):
28 self.log_user()
29 41 if backend.alias == 'hg':
30 response = self.app.get(url(controller='bookmarks',
31 action='index',
32 repo_name=backend.repo_name))
42 response = self.app.get(
43 route_path('bookmarks_home', repo_name=backend.repo_name))
33 44
34 45 repo = Repository.get_by_repo_name(backend.repo_name)
35 46 for commit_id, obj_name in repo.scm_instance().bookmarks.items():
36 47 assert commit_id in response
37 48 assert obj_name in response
38 49 else:
39 self.app.get(url(controller='bookmarks',
40 action='index',
41 repo_name=backend.repo_name), status=404) No newline at end of file
50 self.app.get(
51 route_path('bookmarks_home', repo_name=backend.repo_name),
52 status=404)
@@ -18,17 +18,28 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import pytest
21 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 40 def test_index(self, backend):
28 self.log_user()
29 response = self.app.get(url(controller='branches',
30 action='index',
31 repo_name=backend.repo_name))
41 response = self.app.get(
42 route_path('branches_home', repo_name=backend.repo_name))
32 43
33 44 repo = Repository.get_by_repo_name(backend.repo_name)
34 45
@@ -23,16 +23,16 b' import re'
23 23 import mock
24 24 import pytest
25 25
26 from rhodecode.controllers import summary
26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.compat import OrderedDict
29 from rhodecode.lib.utils2 import AttributeDict
29 30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 31 from rhodecode.model.db import Repository
31 32 from rhodecode.model.meta import Session
32 33 from rhodecode.model.repo import RepoModel
33 34 from rhodecode.model.scm import ScmModel
34 from rhodecode.tests import (
35 TestController, url, HG_REPO, assert_session_flash)
35 from rhodecode.tests import assert_session_flash
36 36 from rhodecode.tests.fixture import Fixture
37 37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
38 38
@@ -40,14 +40,31 b' from rhodecode.tests.utils import Assert'
40 40 fixture = Fixture()
41 41
42 42
43 class TestSummaryController(TestController):
44 def test_index(self, backend):
45 self.log_user()
43 def route_path(name, params=None, **kwargs):
44 import urllib
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 62 repo_id = backend.repo.repo_id
47 63 repo_name = backend.repo_name
48 64 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
49 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 69 # repo type
53 70 response.mustcontain(
@@ -61,46 +78,47 b' class TestSummaryController(TestControll'
61 78 # clone url...
62 79 response.mustcontain(
63 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 82 response.mustcontain(
66 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):
70 self.log_user()
86 def test_index_svn_without_proxy(
87 self, autologin_user, backend_svn, http_host_only_stub):
71 88 repo_id = backend_svn.repo.repo_id
72 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 91 # clone url...
75 92 response.mustcontain(
76 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 95 response.mustcontain(
79 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 102 repo_id = backend.repo.repo_id
84 103 repo_name = backend.repo_name
85 104 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
86 105 return_value=False):
87 106 response = self.app.get(
88 url('summary_home', repo_name=repo_name) + '/',
107 route_path('repo_summary', repo_name=repo_name) + '/',
89 108 status=200)
90 109
91 110 # clone url...
92 111 response.mustcontain(
93 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 114 response.mustcontain(
96 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):
100 self.log_user()
118 def test_index_by_id(self, autologin_user, backend):
101 119 repo_id = backend.repo.repo_id
102 response = self.app.get(url(
103 'summary_home', repo_name='_%s' % (repo_id,)))
120 response = self.app.get(
121 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
104 122
105 123 # repo type
106 124 response.mustcontain(
@@ -111,10 +129,9 b' class TestSummaryController(TestControll'
111 129 """<i class="icon-unlock-alt">"""
112 130 )
113 131
114 def test_index_by_repo_having_id_path_in_name_hg(self):
115 self.log_user()
132 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
116 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 136 try:
120 137 response.mustcontain("repo_1")
@@ -122,10 +139,10 b' class TestSummaryController(TestControll'
122 139 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
123 140 Session().commit()
124 141
125 def test_index_with_anonymous_access_disabled(self):
126 with fixture.anon_access(False):
127 response = self.app.get(url('summary_home', repo_name=HG_REPO),
128 status=302)
142 def test_index_with_anonymous_access_disabled(
143 self, backend, disable_anonymous_user):
144 response = self.app.get(
145 route_path('repo_summary', repo_name=backend.repo_name), status=302)
129 146 assert 'login' in response.location
130 147
131 148 def _enable_stats(self, repo):
@@ -173,17 +190,15 b' class TestSummaryController(TestControll'
173 190 },
174 191 }
175 192
176 def test_repo_stats(self, backend, xhr_header):
177 self.log_user()
193 def test_repo_stats(self, autologin_user, backend, xhr_header):
178 194 response = self.app.get(
179 url('repo_stats',
180 repo_name=backend.repo_name, commit_id='tip'),
195 route_path(
196 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
181 197 extra_environ=xhr_header,
182 198 status=200)
183 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):
186 self.log_user()
201 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
187 202 repo_name = backend.repo_name
188 203
189 204 # codes stats
@@ -191,8 +206,8 b' class TestSummaryController(TestControll'
191 206 ScmModel().mark_for_invalidation(repo_name)
192 207
193 208 response = self.app.get(
194 url('repo_stats',
195 repo_name=backend.repo_name, commit_id='tip'),
209 route_path(
210 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
196 211 extra_environ=xhr_header,
197 212 status=200)
198 213
@@ -203,7 +218,7 b' class TestSummaryController(TestControll'
203 218
204 219 def test_repo_refs_data(self, backend):
205 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 222 status=200)
208 223
209 224 # Ensure that there is the correct amount of items in the result
@@ -220,72 +235,68 b' class TestSummaryController(TestControll'
220 235 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
221 236
222 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 239 assert_response = AssertResponse(response)
225 240 assert_response.element_contains(
226 241 '.main .alert-warning strong', 'Missing requirements')
227 242 assert_response.element_contains(
228 243 '.main .alert-warning',
229 'These commits cannot be displayed, because this repository'
230 ' uses the Mercurial largefiles extension, which was not enabled.')
244 'Commits cannot be displayed, because this repository '
245 'uses one or more extensions, which was not enabled.')
231 246
232 247 def test_missing_requirements_page_does_not_contains_switch_to(
233 self, backend):
234 self.log_user()
248 self, autologin_user, backend):
235 249 repo_name = backend.repo_name
236 250 scm_patcher = mock.patch.object(
237 251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
238 252
239 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 255 response.mustcontain(no='Switch To')
242 256
243 257
244 @pytest.mark.usefixtures('pylonsapp')
245 class TestSwitcherReferenceData:
258 @pytest.mark.usefixtures('app')
259 class TestRepoLocation(object):
246 260
247 def test_creates_reference_urls_based_on_name(self):
248 references = {
249 'name': 'commit_id',
250 }
251 controller = summary.SummaryController()
252 is_svn = False
253 result = controller._switcher_reference_data(
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
261 @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii'])
262 def test_manual_delete(self, autologin_user, backend, suffix, csrf_token):
263 repo = backend.create_repo(name_suffix=suffix)
264 repo_name = repo.repo_name
265
266 # delete from file system
267 RepoModel()._delete_filesystem_repo(repo)
259 268
260 def test_urls_contain_commit_id_if_slash_in_name(self):
261 references = {
262 'name/with/slash': 'commit_id',
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
269 # test if the repo is still in the database
270 new_repo = RepoModel().get_by_repo_name(repo_name)
271 assert new_repo.repo_name == repo_name
272 272
273 def test_adds_reference_to_path_for_svn(self):
274 references = {
275 'name/with/slash': 'commit_id',
276 }
277 controller = summary.SummaryController()
278 is_svn = True
279 result = controller._switcher_reference_data(
280 'repo_name', references, is_svn)
281 expected_url = h.url(
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
273 # check if repo is not in the filesystem
274 assert not repo_on_filesystem(repo_name)
275 self.assert_repo_not_found_redirect(repo_name)
276
277 def assert_repo_not_found_redirect(self, repo_name):
278 # run the check page that triggers the other flash message
279 response = self.app.get(h.url('repo_check_home', repo_name=repo_name))
280 assert_session_flash(
281 response, 'The repository at %s cannot be located.' % repo_name)
285 282
286 283
287 @pytest.mark.usefixtures('pylonsapp')
288 class TestCreateReferenceData:
284 @pytest.fixture()
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 301 @pytest.fixture
291 302 def example_refs(self):
@@ -296,14 +307,13 b' class TestCreateReferenceData:'
296 307 ]
297 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 311 repo = mock.Mock()
301 312 repo.name = 'test-repo'
302 313 repo.alias = 'git'
303 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 317 repo, full_repo_name, example_refs)
308 318
309 319 expected_files_url = '/{}/files/'.format(full_repo_name)
@@ -332,13 +342,13 b' class TestCreateReferenceData:'
332 342 }]
333 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 346 repo = mock.Mock()
337 347 repo.name = 'test-repo'
338 348 repo.alias = 'svn'
339 349 full_repo_name = 'pytest-repo-group/' + repo.name
340 controller = summary.SummaryController()
341 result = controller._create_reference_data(
350
351 result = summary_view._create_reference_data(
342 352 repo, full_repo_name, example_refs)
343 353
344 354 expected_files_url = '/{}/files/'.format(full_repo_name)
@@ -372,35 +382,9 b' class TestCreateReferenceData:'
372 382 assert result == expected_result
373 383
374 384
375 @pytest.mark.usefixtures("app")
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
385 class TestCreateFilesUrl(object):
389 386
390 # check if repo is not in the filesystem
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()
387 def test_creates_non_svn_url(self, summary_view):
404 388 repo = mock.Mock()
405 389 repo.name = 'abcde'
406 390 full_repo_name = 'test-repo-group/' + repo.name
@@ -408,16 +392,15 b' class TestCreateFilesUrl(object):'
408 392 raw_id = 'deadbeef0123456789'
409 393 is_svn = False
410 394
411 with mock.patch.object(summary.h, 'url') as url_mock:
412 result = controller._create_files_url(
395 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
396 result = summary_view._create_files_url(
413 397 repo, full_repo_name, ref_name, raw_id, is_svn)
414 398 url_mock.assert_called_once_with(
415 399 'files_home', repo_name=full_repo_name, f_path='',
416 400 revision=ref_name, at=ref_name)
417 401 assert result == url_mock.return_value
418 402
419 def test_creates_svn_url(self):
420 controller = summary.SummaryController()
403 def test_creates_svn_url(self, summary_view):
421 404 repo = mock.Mock()
422 405 repo.name = 'abcde'
423 406 full_repo_name = 'test-repo-group/' + repo.name
@@ -425,16 +408,15 b' class TestCreateFilesUrl(object):'
425 408 raw_id = 'deadbeef0123456789'
426 409 is_svn = True
427 410
428 with mock.patch.object(summary.h, 'url') as url_mock:
429 result = controller._create_files_url(
411 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
412 result = summary_view._create_files_url(
430 413 repo, full_repo_name, ref_name, raw_id, is_svn)
431 414 url_mock.assert_called_once_with(
432 415 'files_home', repo_name=full_repo_name, f_path=ref_name,
433 416 revision=raw_id, at=ref_name)
434 417 assert result == url_mock.return_value
435 418
436 def test_name_has_slashes(self):
437 controller = summary.SummaryController()
419 def test_name_has_slashes(self, summary_view):
438 420 repo = mock.Mock()
439 421 repo.name = 'abcde'
440 422 full_repo_name = 'test-repo-group/' + repo.name
@@ -442,8 +424,8 b' class TestCreateFilesUrl(object):'
442 424 raw_id = 'deadbeef0123456789'
443 425 is_svn = False
444 426
445 with mock.patch.object(summary.h, 'url') as url_mock:
446 result = controller._create_files_url(
427 with mock.patch('rhodecode.lib.helpers.url') as url_mock:
428 result = summary_view._create_files_url(
447 429 repo, full_repo_name, ref_name, raw_id, is_svn)
448 430 url_mock.assert_called_once_with(
449 431 'files_home', repo_name=full_repo_name, f_path='', revision=raw_id,
@@ -462,42 +444,39 b' class TestReferenceItems(object):'
462 444 def _format_function(name, id_):
463 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 448 amount = 100
467 449 refs = {
468 450 'ref{}'.format(i): '{0:040d}'.format(i)
469 451 for i in range(amount)
470 452 }
471 453
472 controller = summary.SummaryController()
473
474 url_patcher = mock.patch.object(
475 controller, '_create_files_url')
476 svn_patcher = mock.patch.object(
477 summary.h, 'is_svn', return_value=False)
454 url_patcher = mock.patch.object(summary_view, '_create_files_url')
455 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
456 return_value=False)
478 457
479 458 with url_patcher as url_mock, svn_patcher:
480 result = controller._create_reference_items(
459 result = summary_view._create_reference_items(
481 460 self.repo, self.repo_full_name, refs, self.ref_type,
482 461 self._format_function)
483 462 assert len(result) == amount
484 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 466 ref_name = 'ref1'
488 467 ref_id = 'deadbeef'
489 468 refs = {
490 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 475 url_patcher = mock.patch.object(
495 controller, '_create_files_url', return_value=self.fake_url)
496 svn_patcher = mock.patch.object(
497 summary.h, 'is_svn', return_value=False)
476 summary_view, '_create_files_url', return_value=self.fake_url)
498 477
499 478 with url_patcher as url_mock, svn_patcher:
500 result = controller._create_reference_items(
479 result = summary_view._create_reference_items(
501 480 self.repo, self.repo_full_name, refs, self.ref_type,
502 481 self._format_function)
503 482
@@ -18,16 +18,27 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import pytest
21 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 39 def test_index(self, backend):
27 self.log_user()
28 response = self.app.get(url(controller='tags',
29 action='index',
30 repo_name=backend.repo_name))
40 response = self.app.get(
41 route_path('tags_home', repo_name=backend.repo_name))
31 42
32 43 repo = Repository.get_by_repo_name(backend.repo_name)
33 44
@@ -41,7 +41,6 b' class RepoMaintenanceView(RepoAppView):'
41 41 return c
42 42
43 43 @LoginRequired()
44 @NotAnonymous()
45 44 @HasRepoPermissionAnyDecorator('repository.admin')
46 45 @view_config(
47 46 route_name='repo_maintenance', request_method='GET',
@@ -54,7 +53,6 b' class RepoMaintenanceView(RepoAppView):'
54 53 return self._get_template_context(c)
55 54
56 55 @LoginRequired()
57 @NotAnonymous()
58 56 @HasRepoPermissionAnyDecorator('repository.admin')
59 57 @view_config(
60 58 route_name='repo_maintenance_execute', request_method='GET',
@@ -1,6 +1,6 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
@@ -22,9 +22,11 b' import logging'
22 22 from pyramid.view import view_config
23 23
24 24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib import audit_logger
26 from rhodecode.lib import helpers as h
25 27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
26 NotAnonymous)
27
28 NotAnonymous, CSRFRequired)
29 from rhodecode.lib.ext_json import json
28 30
29 31 log = logging.getLogger(__name__)
30 32
@@ -40,7 +42,6 b' class StripView(RepoAppView):'
40 42 return c
41 43
42 44 @LoginRequired()
43 @NotAnonymous()
44 45 @HasRepoPermissionAnyDecorator('repository.admin')
45 46 @view_config(
46 47 route_name='strip', request_method='GET',
@@ -53,12 +54,11 b' class StripView(RepoAppView):'
53 54 return self._get_template_context(c)
54 55
55 56 @LoginRequired()
56 @NotAnonymous()
57 57 @HasRepoPermissionAnyDecorator('repository.admin')
58 @CSRFRequired()
58 59 @view_config(
59 60 route_name='strip_check', request_method='POST',
60 renderer='json', xhr=True
61 )
61 renderer='json', xhr=True)
62 62 def strip_check(self):
63 63 from rhodecode.lib.vcs.backends.base import EmptyCommit
64 64 data = {}
@@ -66,28 +66,27 b' class StripView(RepoAppView):'
66 66 for i in range(1, 11):
67 67 chset = 'changeset_id-%d'%(i,)
68 68 check = rp.get(chset)
69
69 70 if check:
70 71 data[i] = self.db_repo.get_changeset(rp[chset])
71 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 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 77 'comment': data[i].message}
76 78 else:
77 79 break
78 80 return data
79 81
80 82 @LoginRequired()
81 @NotAnonymous()
82 83 @HasRepoPermissionAnyDecorator('repository.admin')
84 @CSRFRequired()
83 85 @view_config(
84 86 route_name='strip_execute', request_method='POST',
85 renderer='json', xhr=True
86 )
87 renderer='json', xhr=True)
87 88 def strip_execute(self):
88
89 89 from rhodecode.model.scm import ScmModel
90 from rhodecode.lib.ext_json import json
91 90
92 91 c = self.load_default_context()
93 92 user = self._rhodecode_user
@@ -99,12 +98,19 b' class StripView(RepoAppView):'
99 98 if commit['branch'] in data.keys():
100 99 continue
101 100 try:
102 ScmModel().strip(repo=c.repo_info,
101 ScmModel().strip(
102 repo=c.repo_info,
103 103 commit_id=commit['rev'], branch=commit['branch'])
104 log.info('Stripped commit %s from repo `%s` by %s' % (commit['rev'], c.repo_info.repo_name, user))
104 log.info('Stripped commit %s from repo `%s` by %s' % (
105 commit['rev'], c.repo_info.repo_name, user))
105 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 113 data[commit['rev']] = False
108 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (commit['rev'],
109 c.repo_info.repo_name, user, e.message))
114 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
115 commit['rev'], self.db_repo_name, user, e.message))
110 116 return data
@@ -564,8 +564,6 b' def authenticate(username, password, env'
564 564 for plugin in authn_registry.get_plugins_for_authentication():
565 565 plugin.set_auth_type(auth_type)
566 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 568 if headers_only and not plugin.is_headers_auth:
571 569 log.debug('Auth type is for headers only and plugin `%s` is not '
@@ -71,15 +71,17 b' class LdapSettingsSchema(AuthnPluginSett'
71 71 host = colander.SchemaNode(
72 72 colander.String(),
73 73 default='',
74 description=_('Host of the LDAP Server \n'
75 '(e.g., 192.168.2.154, or ldap-server.domain.com'),
74 description=_('Host[s] of the LDAP Server \n'
75 '(e.g., 192.168.2.154, or ldap-server.domain.com.\n '
76 'Multiple servers can be specified using commas'),
76 77 preparer=strip_whitespace,
77 78 title=_('LDAP Host'),
78 79 widget='string')
79 80 port = colander.SchemaNode(
80 81 colander.Int(),
81 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 85 preparer=strip_whitespace,
84 86 title=_('Port'),
85 87 validator=colander.Range(min=0, max=65536),
@@ -112,7 +114,9 b' class LdapSettingsSchema(AuthnPluginSett'
112 114 tls_reqcert = colander.SchemaNode(
113 115 colander.String(),
114 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 120 title=_('Certificate Checks'),
117 121 validator=colander.OneOf(tls_reqcert_choices),
118 122 widget='select')
@@ -1,11 +1,11 b''
1 1 {
2 "libnghttp2-1.7.1": {
3 "MIT License": "http://spdx.org/licenses/MIT"
4 },
2 5 "nodejs-4.3.1": {
3 6 "MIT License": "http://spdx.org/licenses/MIT"
4 7 },
5 "postgresql-9.5.1": {
6 "PostgreSQL License": "http://spdx.org/licenses/PostgreSQL"
7 },
8 "python-2.7.11": {
8 "python-2.7.12": {
9 9 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
10 10 },
11 11 "python2.7-Babel-1.3": {
@@ -14,19 +14,25 b''
14 14 "python2.7-Beaker-1.7.0": {
15 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 20 "python2.7-FormEncode-1.2.4": {
18 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 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 30 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
25 31 },
26 32 "python2.7-MarkupSafe-0.23": {
27 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 36 "MIT License": "http://spdx.org/licenses/MIT"
31 37 },
32 38 "python2.7-PasteDeploy-1.5.2": {
@@ -35,10 +41,10 b''
35 41 "python2.7-PasteScript-1.7.5": {
36 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 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 48 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
43 49 },
44 50 "python2.7-Routes-1.13": {
@@ -65,7 +71,7 b''
65 71 "python2.7-WebOb-1.3.1": {
66 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 75 "BSD 2-clause \"Simplified\" License": "http://spdx.org/licenses/BSD-2-Clause",
70 76 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
71 77 },
@@ -87,9 +93,18 b''
87 93 "python2.7-backport-ipaddress-0.1": {
88 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 102 "python2.7-celery-2.2.10": {
91 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 108 "python2.7-click-5.1": {
94 109 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
95 110 },
@@ -99,82 +114,169 b''
99 114 "python2.7-configobj-5.0.6": {
100 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 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 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 129 "python2.7-docutils-0.12": {
109 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 138 "python2.7-elasticsearch-2.3.0": {
112 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 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 153 "python2.7-future-0.14.3": {
118 154 "MIT License": "http://spdx.org/licenses/MIT"
119 155 },
120 156 "python2.7-futures-3.0.2": {
121 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 162 "python2.7-gnureadline-6.3.3": {
124 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 171 "python2.7-gunicorn-19.6.0": {
127 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 177 "python2.7-infrae.cache-1.0.1": {
130 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 184 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
134 185 },
135 186 "python2.7-iso8601-0.1.11": {
136 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 201 "python2.7-kombu-1.5.1": {
139 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 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 216 "python2.7-packaging-15.2": {
145 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 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 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 247 "MIT License": "http://spdx.org/licenses/MIT"
155 248 },
156 249 "python2.7-py-bcrypt-0.4": {
157 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 255 "python2.7-pycrypto-2.6.1": {
160 256 "Public Domain": null
161 257 },
162 258 "python2.7-pycurl-7.19.5": {
163 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 264 "python2.7-pyparsing-1.5.7": {
166 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 268 "Repoze License": "http://www.repoze.org/LICENSE.txt"
170 269 },
171 270 "python2.7-pyramid-beaker-0.8": {
172 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 274 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause",
176 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 280 "python2.7-pyramid-mako-1.0.2": {
179 281 "Repoze License": "http://www.repoze.org/LICENSE.txt"
180 282 },
@@ -182,16 +284,25 b''
182 284 "libpng License": "http://spdx.org/licenses/Libpng",
183 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 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 300 "MIT License": "http://spdx.org/licenses/MIT"
190 301 },
191 "python2.7-python-dateutil-1.5": {
192 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0"
302 "python2.7-python-dateutil-2.1": {
303 "Simplified BSD": null
193 304 },
194 "python2.7-python-editor-1.0.1": {
305 "python2.7-python-editor-1.0.3": {
195 306 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
196 307 },
197 308 "python2.7-python-ldap-2.4.19": {
@@ -203,6 +314,9 b''
203 314 "python2.7-pytz-2015.4": {
204 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 320 "python2.7-recaptcha-client-1.0.6": {
207 321 "MIT License": "http://spdx.org/licenses/MIT"
208 322 },
@@ -216,16 +330,30 b''
216 330 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0",
217 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 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 339 "python2.7-simplejson-3.7.2": {
223 "Academic Free License": "http://spdx.org/licenses/AFL-2.1",
224 340 "MIT License": "http://spdx.org/licenses/MIT"
225 341 },
226 342 "python2.7-six-1.9.0": {
227 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 357 "python2.7-translationstring-1.3": {
230 358 "Repoze License": "http://www.repoze.org/LICENSE.txt"
231 359 },
@@ -235,9 +363,15 b''
235 363 "python2.7-venusian-1.0": {
236 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 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 375 "python2.7-zope.cachedescriptors-4.0.0": {
242 376 "Zope Public License 2.1": "http://spdx.org/licenses/ZPL-2.1"
243 377 },
@@ -246,5 +380,9 b''
246 380 },
247 381 "python2.7-zope.interface-4.1.3": {
248 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 388 } No newline at end of file
@@ -39,11 +39,15 b' from routes.middleware import RoutesMidd'
39 39 import routes.util
40 40
41 41 import rhodecode
42
42 43 from rhodecode.model import meta
43 44 from rhodecode.config import patches
44 45 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 46 from rhodecode.config.environment import (
46 47 load_environment, load_pyramid_environment)
48
49 from rhodecode.lib.vcs import VCSCommunicationError
50 from rhodecode.lib.exceptions import VCSServerUnavailable
47 51 from rhodecode.lib.middleware import csrf
48 52 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 53 from rhodecode.lib.middleware.error_handling import (
@@ -51,10 +55,10 b' from rhodecode.lib.middleware.error_hand'
51 55 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 56 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 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 59 from rhodecode.subscribers import (
56 scan_repositories_if_enabled, write_metadata_if_needed,
57 write_js_routes_if_enabled)
60 scan_repositories_if_enabled, write_js_routes_if_enabled,
61 write_metadata_if_needed)
58 62
59 63
60 64 log = logging.getLogger(__name__)
@@ -221,7 +225,7 b' def add_pylons_compat_data(registry, glo'
221 225
222 226 def error_handler(exception, request):
223 227 import rhodecode
224 from rhodecode.lib.utils2 import AttributeDict
228 from rhodecode.lib import helpers
225 229
226 230 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
227 231
@@ -229,6 +233,8 b' def error_handler(exception, request):'
229 233 # prefer original exception for the response since it may have headers set
230 234 if isinstance(exception, HTTPException):
231 235 base_response = exception
236 elif isinstance(exception, VCSCommunicationError):
237 base_response = VCSServerUnavailable()
232 238
233 239 def is_http_error(response):
234 240 # error which should have traceback
@@ -255,9 +261,10 b' def error_handler(exception, request):'
255 261 c.causes = []
256 262 if hasattr(base_response, 'causes'):
257 263 c.causes = base_response.causes
264 c.messages = helpers.flash.pop_messages()
258 265
259 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 268 response=base_response)
262 269
263 270 return response
@@ -284,11 +291,15 b' def includeme(config):'
284 291
285 292 # apps
286 293 config.include('rhodecode.apps._base')
294 config.include('rhodecode.apps.ops')
287 295
288 296 config.include('rhodecode.apps.admin')
289 297 config.include('rhodecode.apps.channelstream')
290 298 config.include('rhodecode.apps.login')
299 config.include('rhodecode.apps.home')
291 300 config.include('rhodecode.apps.repository')
301 config.include('rhodecode.apps.repo_group')
302 config.include('rhodecode.apps.search')
292 303 config.include('rhodecode.apps.user_profile')
293 304 config.include('rhodecode.apps.my_account')
294 305 config.include('rhodecode.apps.svn_support')
@@ -307,6 +318,12 b' def includeme(config):'
307 318 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
308 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 327 # Set the authorization policy.
311 328 authz_policy = ACLAuthorizationPolicy()
312 329 config.set_authorization_policy(authz_policy)
@@ -314,6 +331,10 b' def includeme(config):'
314 331 # Set the default renderer for HTML templates to mako.
315 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 338 # include RhodeCode plugins
318 339 includes = aslist(settings.get('rhodecode.includes', []))
319 340 for inc in includes:
@@ -395,7 +416,6 b' def wrap_app_in_wsgi_middlewares(pyramid'
395 416 pool = meta.Base.metadata.bind.engine.pool
396 417 log.debug('sa pool status: %s', pool.status())
397 418
398
399 419 return pyramid_app_with_cleanup
400 420
401 421
@@ -32,8 +32,6 b' import os'
32 32 import re
33 33 from routes import Mapper
34 34
35 from rhodecode.config import routing_links
36
37 35 # prefix for non repository related links needs to be prefixed with `/`
38 36 ADMIN_PREFIX = '/_admin'
39 37 STATIC_FILE_PREFIX = '/_static'
@@ -119,7 +117,8 b' class JSRoutesMapper(Mapper):'
119 117
120 118 def make_map(config):
121 119 """Create, configure and return the routes Mapper"""
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
120 rmap = JSRoutesMapper(
121 directory=config['pylons.paths']['controllers'],
123 122 always_scan=config['debug'])
124 123 rmap.minimization = False
125 124 rmap.explicit = False
@@ -186,36 +185,7 b' def make_map(config):'
186 185 # CUSTOM ROUTES HERE
187 186 #==========================================================================
188 187
189 # MAIN PAGE
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
188 # ping and pylons error test
219 189 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
220 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 198 action='index', conditions={'method': ['GET']})
229 199 m.connect('new_repo', '/create_repository', jsroute=True,
230 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 201 m.connect('delete_repo', '/repos/{repo_name}',
236 202 action='delete', conditions={'method': ['DELETE']},
237 203 requirements=URL_NAME_REQUIREMENTS)
@@ -321,19 +287,6 b' def make_map(config):'
321 287 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
322 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 291 # ADMIN USER GROUPS REST ROUTES
339 292 with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -519,37 +472,9 b' def make_map(config):'
519 472 m.connect('my_account_password', '/my_account/password',
520 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 475 m.connect('my_account_pullrequests', '/my_account/pull_requests',
529 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 478 # NOTIFICATION REST ROUTES
554 479 with rmap.submapper(path_prefix=ADMIN_PREFIX,
555 480 controller='admin/notifications') as m:
@@ -597,22 +522,6 b' def make_map(config):'
597 522 action='show', conditions={'method': ['GET']},
598 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 525 # USER JOURNAL
617 526 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
618 527 controller='journal', action='index')
@@ -642,15 +551,6 b' def make_map(config):'
642 551 controller='journal', action='toggle_following', jsroute=True,
643 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 554 # FEEDS
655 555 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
656 556 controller='feed', action='rss',
@@ -673,21 +573,6 b' def make_map(config):'
673 573 controller='admin/repos', action='repo_check',
674 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 576 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
692 577 controller='changeset', revision='tip',
693 578 conditions={'function': check_repo},
@@ -702,21 +587,6 b' def make_map(config):'
702 587 requirements=URL_NAME_REQUIREMENTS)
703 588
704 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 590 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
721 591 controller='admin/repos', action='edit_fields',
722 592 conditions={'method': ['GET'], 'function': check_repo},
@@ -730,39 +600,11 b' def make_map(config):'
730 600 conditions={'method': ['DELETE'], 'function': check_repo},
731 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 603 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
743 604 controller='admin/repos', action='toggle_locking',
744 605 conditions={'method': ['GET'], 'function': check_repo},
745 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 608 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
767 609 controller='admin/repos', action='edit_remote_form',
768 610 conditions={'method': ['GET'], 'function': check_repo},
@@ -931,13 +773,6 b' def make_map(config):'
931 773 'method': ['DELETE']},
932 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 776 rmap.connect('pullrequest_comment',
942 777 '/{repo_name}/pull-request-comment/{pull_request_id}',
943 778 controller='pullrequests',
@@ -951,31 +786,10 b' def make_map(config):'
951 786 conditions={'function': check_repo, 'method': ['DELETE']},
952 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 789 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
971 790 controller='changelog', conditions={'function': check_repo},
972 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 793 rmap.connect('changelog_file_home',
980 794 '/{repo_name}/changelog/{revision}/{f_path}',
981 795 controller='changelog', f_path=None,
@@ -1128,26 +942,4 b' def make_map(config):'
1128 942 conditions={'function': check_repo},
1129 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 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 46 # flake8: noqa
47 47 from __future__ import unicode_literals
48 48
49
50 49 link_config = [
51 {"name": "enterprise_docs",
50 {
51 "name": "enterprise_docs",
52 52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 54 },
55 {"name": "enterprise_log_file_locations",
55 {
56 "name": "enterprise_log_file_locations",
56 57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
57 58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
58 59 },
59 {"name": "enterprise_issue_tracker_settings",
60 {
61 "name": "enterprise_issue_tracker_settings",
60 62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
61 63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
62 64 },
63 {"name": "enterprise_svn_setup",
65 {
66 "name": "enterprise_svn_setup",
64 67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
65 68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
66 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 },
95
67 96 ]
68 97
69 98
70 def connect_redirection_links(rmap):
99 def connect_redirection_links(config):
71 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 26 import logging
27 import datetime
28 27
29 28 import formencode
30 29 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
32 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 33 from pylons.controllers.util import redirect
36 34 from pylons.i18n.translation import _
37 from sqlalchemy.orm import joinedload
38 35
39 36 from rhodecode.lib import helpers as h
40 37 from rhodecode.lib import auth
41 38 from rhodecode.lib.auth import (
42 39 LoginRequired, NotAnonymous, AuthUser)
43 40 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils import jsonify
45 41 from rhodecode.lib.utils2 import safe_int, str2bool
46 42 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.channelstream import channelstream_request, \
48 ChannelstreamException
49 43
50 44 from rhodecode.model.db import (
51 45 Repository, PullRequest, UserEmailMap, User, UserFollowing)
52 46 from rhodecode.model.forms import UserForm
53 from rhodecode.model.scm import RepoList
54 47 from rhodecode.model.user import UserModel
55 from rhodecode.model.repo import RepoModel
56 48 from rhodecode.model.meta import Session
57 49 from rhodecode.model.pull_request import PullRequestModel
58 50 from rhodecode.model.comment import CommentsModel
@@ -82,26 +74,6 b' class MyAccountController(BaseController'
82 74 c.auth_user = AuthUser(
83 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 77 @auth.CSRFRequired()
106 78 def my_account_update(self):
107 79 """
@@ -181,65 +153,6 b' class MyAccountController(BaseController'
181 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 156 def _extract_ordering(self, request):
244 157 column_index = safe_int(request.GET.get('order[0][column]'))
245 158 order_dir = request.GET.get('order[0][dir]', 'desc')
@@ -320,45 +233,4 b' class MyAccountController(BaseController'
320 233 else:
321 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 49 class NotificationsController(BaseController):
50 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 52 @LoginRequired()
57 53 @NotAnonymous()
@@ -62,8 +58,8 b' class NotificationsController(BaseContro'
62 58 """GET /_admin/notifications: All items in the collection"""
63 59 # url('notifications')
64 60 c.user = c.rhodecode_user
65 notif = NotificationModel().get_for_user(c.rhodecode_user.user_id,
66 filter_=request.GET.getall('type'))
61 notif = NotificationModel().get_for_user(
62 c.rhodecode_user.user_id, filter_=request.GET.getall('type'))
67 63
68 64 p = safe_int(request.GET.get('page', 1), 1)
69 65 notifications_url = webhelpers.paginate.PageURL(
@@ -86,7 +82,6 b' class NotificationsController(BaseContro'
86 82
87 83 return render('admin/notifications/notifications.mako')
88 84
89
90 85 @auth.CSRFRequired()
91 86 def mark_all_read(self):
92 87 if request.is_xhr:
@@ -115,15 +110,8 b' class NotificationsController(BaseContro'
115 110
116 111 @auth.CSRFRequired()
117 112 def update(self, notification_id):
118 """PUT /_admin/notifications/id: Update an existing item"""
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)
113 no = Notification.get_or_404(notification_id)
125 114 try:
126 no = Notification.get(notification_id)
127 115 if self._has_permissions(no):
128 116 # deletes only notification2user
129 117 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
@@ -136,15 +124,8 b' class NotificationsController(BaseContro'
136 124
137 125 @auth.CSRFRequired()
138 126 def delete(self, notification_id):
139 """DELETE /_admin/notifications/id: Delete an existing item"""
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)
127 no = Notification.get_or_404(notification_id)
146 128 try:
147 no = Notification.get(notification_id)
148 129 if self._has_permissions(no):
149 130 # deletes only notification2user
150 131 NotificationModel().delete(c.rhodecode_user.user_id, no)
@@ -156,10 +137,8 b' class NotificationsController(BaseContro'
156 137 raise HTTPBadRequest()
157 138
158 139 def show(self, notification_id):
159 """GET /_admin/notifications/id: Show a specific item"""
160 # url('notification', notification_id=ID)
161 140 c.user = c.rhodecode_user
162 no = Notification.get(notification_id)
141 no = Notification.get_or_404(notification_id)
163 142
164 143 if no and self._has_permissions(no):
165 144 unotification = NotificationModel()\
@@ -64,12 +64,7 b' class PermissionsController(BaseControll'
64 64 c.active = 'application'
65 65 self.__load_data()
66 66
67 c.user = User.get_default_user()
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)
67 c.user = User.get_default_user(refresh=True)
73 68
74 69 app_settings = SettingsModel().get_all_settings()
75 70 defaults = {
@@ -34,17 +34,18 b' from pylons.i18n.translation import _, u'
34 34
35 35 from rhodecode.lib import auth
36 36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
37 38 from rhodecode.lib.ext_json import json
38 39 from rhodecode.lib.auth import (
39 40 LoginRequired, NotAnonymous, HasPermissionAll,
40 41 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils2 import safe_int
42 44 from rhodecode.model.db import RepoGroup, User
43 45 from rhodecode.model.scm import RepoGroupList
44 46 from rhodecode.model.repo_group import RepoGroupModel
45 47 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
46 48 from rhodecode.model.meta import Session
47 from rhodecode.lib.utils2 import safe_int
48 49
49 50
50 51 log = logging.getLogger(__name__)
@@ -153,9 +154,6 b' class RepoGroupsController(BaseControlle'
153 154
154 155 @NotAnonymous()
155 156 def index(self):
156 """GET /repo_groups: All items in the collection"""
157 # url('repo_groups')
158
159 157 repo_group_list = RepoGroup.get_all_repo_groups()
160 158 _perms = ['group.admin']
161 159 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
@@ -168,8 +166,6 b' class RepoGroupsController(BaseControlle'
168 166 @NotAnonymous()
169 167 @auth.CSRFRequired()
170 168 def create(self):
171 """POST /repo_groups: Create a new item"""
172 # url('repo_groups')
173 169
174 170 parent_group_id = safe_int(request.POST.get('group_parent_id'))
175 171 can_create = self._can_create_repo_group(parent_group_id)
@@ -183,20 +179,29 b' class RepoGroupsController(BaseControlle'
183 179 try:
184 180 owner = c.rhodecode_user
185 181 form_result = repo_group_form.to_python(dict(request.POST))
186 RepoGroupModel().create(
182 repo_group = RepoGroupModel().create(
187 183 group_name=form_result['group_name_full'],
188 184 group_description=form_result['group_description'],
189 185 owner=owner.user_id,
190 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 195 Session().commit()
196
193 197 _new_group_name = form_result['group_name_full']
198
194 199 repo_group_url = h.link_to(
195 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 202 h.flash(h.literal(_('Created repository group %s')
198 203 % repo_group_url), category='success')
199 # TODO: in futureaction_logger(, '', '', '', self.sa)
204
200 205 except formencode.Invalid as errors:
201 206 return htmlfill.render(
202 207 render('admin/repo_groups/repo_group_add.mako'),
@@ -216,8 +221,6 b' class RepoGroupsController(BaseControlle'
216 221 # perm checks inside
217 222 @NotAnonymous()
218 223 def new(self):
219 """GET /repo_groups/new: Form to create a new item"""
220 # url('new_repo_group')
221 224 # perm check for admin, create_group perm or admin of parent_group
222 225 parent_group_id = safe_int(request.GET.get('parent_group'))
223 226 if not self._can_create_repo_group(parent_group_id):
@@ -229,12 +232,6 b' class RepoGroupsController(BaseControlle'
229 232 @HasRepoGroupPermissionAnyDecorator('group.admin')
230 233 @auth.CSRFRequired()
231 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 236 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
240 237 can_create_in_root = self._can_create_repo_group()
@@ -250,16 +247,21 b' class RepoGroupsController(BaseControlle'
250 247 available_groups=c.repo_groups_choices,
251 248 can_create_in_root=can_create_in_root, allow_disabled=True)()
252 249
250 old_values = c.repo_group.get_api_data()
253 251 try:
254 252 form_result = repo_group_form.to_python(dict(request.POST))
255 253 gr_name = form_result['group_name']
256 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 260 Session().commit()
258 261 h.flash(_('Updated repository group %s') % (gr_name,),
259 262 category='success')
260 263 # we now have new name !
261 264 group_name = new_gr.group_name
262 # TODO: in future action_logger(, '', '', '', self.sa)
263 265 except formencode.Invalid as errors:
264 266 c.active = 'settings'
265 267 return htmlfill.render(
@@ -279,13 +281,6 b' class RepoGroupsController(BaseControlle'
279 281 @HasRepoGroupPermissionAnyDecorator('group.admin')
280 282 @auth.CSRFRequired()
281 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 284 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
290 285 repos = gr.repositories.all()
291 286 if repos:
@@ -307,11 +302,16 b' class RepoGroupsController(BaseControlle'
307 302 return redirect(url('repo_groups'))
308 303
309 304 try:
305 old_values = gr.get_api_data()
310 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 312 Session().commit()
312 313 h.flash(_('Removed repository group %s') % group_name,
313 314 category='success')
314 # TODO: in future action_logger(, '', '', '', self.sa)
315 315 except Exception:
316 316 log.exception("Exception during deletion of repository group")
317 317 h.flash(_('Error occurred during deletion of repository group %s')
@@ -321,8 +321,7 b' class RepoGroupsController(BaseControlle'
321 321
322 322 @HasRepoGroupPermissionAnyDecorator('group.admin')
323 323 def edit(self, group_name):
324 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
325 # url('edit_repo_group', group_name=GROUP_NAME)
324
326 325 c.active = 'settings'
327 326
328 327 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
@@ -346,8 +345,6 b' class RepoGroupsController(BaseControlle'
346 345
347 346 @HasRepoGroupPermissionAnyDecorator('group.admin')
348 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 348 c.active = 'advanced'
352 349 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
353 350
@@ -355,8 +352,6 b' class RepoGroupsController(BaseControlle'
355 352
356 353 @HasRepoGroupPermissionAnyDecorator('group.admin')
357 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 355 c.active = 'perms'
361 356 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
362 357 self.__load_defaults()
@@ -374,8 +369,6 b' class RepoGroupsController(BaseControlle'
374 369 def update_perms(self, group_name):
375 370 """
376 371 Update permissions for given repository group
377
378 :param group_name:
379 372 """
380 373
381 374 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
@@ -393,14 +386,20 b' class RepoGroupsController(BaseControlle'
393 386 # iterate over all members(if in recursive mode) of this groups and
394 387 # set the permissions !
395 388 # this can be potentially heavy operation
396 RepoGroupModel().update_permissions(
389 changes = RepoGroupModel().update_permissions(
397 390 c.repo_group,
398 form['perm_additions'], form['perm_updates'],
399 form['perm_deletions'], form['recursive'])
391 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
392 form['recursive'])
400 393
401 # TODO: implement this
402 # action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
403 # repo_name, self.ip_addr, self.sa)
394 action_data = {
395 'added': changes['added'],
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 403 Session().commit()
405 404 h.flash(_('Repository Group permissions updated'), category='success')
406 405 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -41,15 +41,11 b' from rhodecode.lib.auth import ('
41 41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.exceptions import AttachedForksError
45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
44 from rhodecode.lib.utils import repo_name_slug, jsonify
46 45 from rhodecode.lib.utils2 import safe_int, str2bool
47 from rhodecode.lib.vcs import RepositoryError
48 from rhodecode.model.db import (
49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
46 from rhodecode.model.db import (Repository, RepoGroup, RepositoryField)
50 47 from rhodecode.model.forms import (
51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
52 IssueTrackerPatternsForm)
48 RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm)
53 49 from rhodecode.model.meta import Session
54 50 from rhodecode.model.repo import RepoModel
55 51 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
@@ -185,7 +181,7 b' class ReposController(BaseRepoController'
185 181 except Exception as e:
186 182 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 183 h.flash(msg, category='error')
188 return redirect(url('home'))
184 return redirect(h.route_path('home'))
189 185
190 186 return redirect(h.url('repo_creating_home',
191 187 repo_name=form_result['repo_name_full'],
@@ -265,7 +261,7 b' class ReposController(BaseRepoController'
265 261 if task.failed():
266 262 msg = self._log_creation_exception(task.result, c.repo)
267 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 266 repo = Repository.get_by_repo_name(repo_name)
271 267 if repo and repo.repo_state == Repository.STATE_CREATED:
@@ -274,9 +270,9 b' class ReposController(BaseRepoController'
274 270 h.flash(_('Created repository %s from %s')
275 271 % (repo.repo_name, clone_uri), category='success')
276 272 else:
277 repo_url = h.link_to(repo.repo_name,
278 h.url('summary_home',
279 repo_name=repo.repo_name))
273 repo_url = h.link_to(
274 repo.repo_name,
275 h.route_path('repo_summary',repo_name=repo.repo_name))
280 276 fork = repo.fork
281 277 if fork:
282 278 fork_name = fork.repo_name
@@ -288,165 +284,14 b' class ReposController(BaseRepoController'
288 284 return {'result': True}
289 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 287 @HasPermissionAllDecorator('hg.admin')
396 288 def show(self, repo_name, format='html'):
397 289 """GET /repos/repo_name: Show a specific item"""
398 290 # url('repo', repo_name=ID)
399 291
400 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 293 def edit_fields(self, repo_name):
448 294 """GET /repo_name/settings: Form to edit an existing item"""
449 # url('edit_repo', repo_name=ID)
450 295 c.repo_info = self._load_repo(repo_name)
451 296 c.repo_fields = RepositoryField.query()\
452 297 .filter(RepositoryField.repository == c.repo_info).all()
@@ -490,106 +335,6 b' class ReposController(BaseRepoController'
490 335 h.flash(msg, category='error')
491 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 338 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
594 339 @auth.CSRFRequired()
595 340 def toggle_locking(self, repo_name):
@@ -617,32 +362,7 b' class ReposController(BaseRepoController'
617 362 log.exception("Exception during unlocking")
618 363 h.flash(_('An error occurred during unlocking'),
619 364 category='error')
620 return redirect(url('summary_home', 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')
365 return redirect(h.route_path('repo_summary', repo_name=repo_name))
646 366
647 367 @HasRepoPermissionAllDecorator('repository.admin')
648 368 @auth.CSRFRequired()
@@ -660,7 +380,6 b' class ReposController(BaseRepoController'
660 380 @HasRepoPermissionAllDecorator('repository.admin')
661 381 def edit_remote_form(self, repo_name):
662 382 """GET /repo_name/settings: Form to edit an existing item"""
663 # url('edit_repo', repo_name=ID)
664 383 c.repo_info = self._load_repo(repo_name)
665 384 c.active = 'remote'
666 385
@@ -682,7 +401,6 b' class ReposController(BaseRepoController'
682 401 @HasRepoPermissionAllDecorator('repository.admin')
683 402 def edit_statistics_form(self, repo_name):
684 403 """GET /repo_name/settings: Form to edit an existing item"""
685 # url('edit_repo', repo_name=ID)
686 404 c.repo_info = self._load_repo(repo_name)
687 405 repo = c.repo_info.scm_instance()
688 406
@@ -195,9 +195,12 b' class SettingsController(BaseController)'
195 195 pyramid_settings = get_current_registry().settings
196 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 201 return htmlfill.render(
199 202 render('admin/settings/settings.mako'),
200 defaults=self._form_defaults(),
203 defaults=defaults,
201 204 encoding="UTF-8",
202 205 force_defaults=False)
203 206
@@ -35,9 +35,11 b' from sqlalchemy.orm import joinedload'
35 35
36 36 from rhodecode.lib import auth
37 37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.ext_json import json
38 40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
39 41 RepoGroupAssignmentError
40 from rhodecode.lib.utils import jsonify, action_logger
42 from rhodecode.lib.utils import jsonify
41 43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
42 44 from rhodecode.lib.auth import (
43 45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
@@ -52,8 +54,7 b' from rhodecode.model.forms import ('
52 54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
53 55 UserPermissionsForm)
54 56 from rhodecode.model.meta import Session
55 from rhodecode.lib.utils import action_logger
56 from rhodecode.lib.ext_json import json
57
57 58
58 59 log = logging.getLogger(__name__)
59 60
@@ -105,8 +106,6 b' class UserGroupsController(BaseControlle'
105 106 # permission check inside
106 107 @NotAnonymous()
107 108 def index(self):
108 """GET /users_groups: All items in the collection"""
109 # url('users_groups')
110 109
111 110 from rhodecode.lib.utils import PartialRenderer
112 111 _render = PartialRenderer('data_table/_dt_elements.mako')
@@ -142,8 +141,6 b' class UserGroupsController(BaseControlle'
142 141 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
143 142 @auth.CSRFRequired()
144 143 def create(self):
145 """POST /users_groups: Create a new item"""
146 # url('users_groups')
147 144
148 145 users_group_form = UserGroupForm()()
149 146 try:
@@ -154,14 +151,16 b' class UserGroupsController(BaseControlle'
154 151 owner=c.rhodecode_user.user_id,
155 152 active=form_result['users_group_active'])
156 153 Session().flush()
157
154 creation_data = user_group.get_api_data()
158 155 user_group_name = form_result['users_group_name']
159 action_logger(c.rhodecode_user,
160 'admin_created_users_group:%s' % user_group_name,
161 None, self.ip_addr, self.sa)
162 user_group_link = h.link_to(h.escape(user_group_name),
163 url('edit_users_group',
164 user_group_id=user_group.users_group_id))
156
157 audit_logger.store_web(
158 'user_group.create', action_data={'data': creation_data},
159 user=c.rhodecode_user)
160
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 164 h.flash(h.literal(_('Created user group %(user_group_link)s')
166 165 % {'user_group_link': user_group_link}),
167 166 category='success')
@@ -191,13 +190,6 b' class UserGroupsController(BaseControlle'
191 190 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
192 191 @auth.CSRFRequired()
193 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 194 user_group_id = safe_int(user_group_id)
203 195 c.user_group = UserGroup.get_or_404(user_group_id)
@@ -207,16 +199,22 b' class UserGroupsController(BaseControlle'
207 199 users_group_form = UserGroupForm(
208 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 203 try:
211 204 form_result = users_group_form.to_python(request.POST)
212 205 pstruct = peppercorn.parse(request.POST.items())
213 206 form_result['users_group_members'] = pstruct['user_group_members']
214 207
208 user_group, added_members, removed_members = \
215 209 UserGroupModel().update(c.user_group, form_result)
216 210 updated_user_group = form_result['users_group_name']
217 action_logger(c.rhodecode_user,
218 'admin_updated_users_group:%s' % updated_user_group,
219 None, self.ip_addr, self.sa)
211
212 audit_logger.store_web(
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 218 h.flash(_('Updated user group %s') % updated_user_group,
221 219 category='success')
222 220 Session().commit()
@@ -241,19 +239,16 b' class UserGroupsController(BaseControlle'
241 239 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
242 240 @auth.CSRFRequired()
243 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 242 user_group_id = safe_int(user_group_id)
252 243 c.user_group = UserGroup.get_or_404(user_group_id)
253 244 force = str2bool(request.POST.get('force'))
254 245
246 old_values = c.user_group.get_api_data()
255 247 try:
256 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 252 Session().commit()
258 253 h.flash(_('Successfully deleted user group'), category='success')
259 254 except UserGroupAssignedException as e:
@@ -330,9 +325,9 b' class UserGroupsController(BaseControlle'
330 325 except RepoGroupAssignmentError:
331 326 h.flash(_('Target group cannot be the same'), category='error')
332 327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
333 #TODO: implement this
334 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
335 # repo_name, self.ip_addr, self.sa)
328
329 # TODO(marcink): implement global permissions
330 # audit_log.store_web('user_group.edit.permissions')
336 331 Session().commit()
337 332 h.flash(_('User Group permissions updated'), category='success')
338 333 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
@@ -389,8 +384,6 b' class UserGroupsController(BaseControlle'
389 384 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
390 385 @auth.CSRFRequired()
391 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 387 user_group_id = safe_int(user_group_id)
395 388 user_group = UserGroup.get_or_404(user_group_id)
396 389 c.active = 'global_perms'
@@ -492,6 +485,9 b' class UserGroupsController(BaseControlle'
492 485 @XHRRequired()
493 486 @jsonify
494 487 def user_group_members(self, user_group_id):
488 """
489 Return members of given user group
490 """
495 491 user_group_id = safe_int(user_group_id)
496 492 user_group = UserGroup.get_or_404(user_group_id)
497 493 group_members_obj = sorted((x.user for x in user_group.members),
@@ -500,8 +496,8 b' class UserGroupsController(BaseControlle'
500 496 group_members = [
501 497 {
502 498 'id': user.user_id,
503 'first_name': user.name,
504 'last_name': user.lastname,
499 'first_name': user.first_name,
500 'last_name': user.last_name,
505 501 'username': user.username,
506 502 'icon_link': h.gravatar_url(user.email, 30),
507 503 'value_display': h.person(user.email),
@@ -31,15 +31,17 b' from pylons.controllers.util import redi'
31 31 from pylons.i18n.translation import _
32 32
33 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 41 from rhodecode.lib.exceptions import (
35 42 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
36 43 UserOwnsUserGroupsException, UserCreationError)
37 from rhodecode.lib import helpers as h
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
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
43 45
44 46 from rhodecode.model.db import (
45 47 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
@@ -49,8 +51,6 b' from rhodecode.model.repo_group import R'
49 51 from rhodecode.model.user import UserModel
50 52 from rhodecode.model.meta import Session
51 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 55 log = logging.getLogger(__name__)
56 56
@@ -88,7 +88,6 b' class UsersController(BaseController):'
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 @auth.CSRFRequired()
90 90 def create(self):
91 """POST /users: Create a new item"""
92 91 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
93 92 user_model = UserModel()
94 93 user_form = UserForm()()
@@ -96,9 +95,12 b' class UsersController(BaseController):'
96 95 form_result = user_form.to_python(dict(request.POST))
97 96 user = user_model.create(form_result)
98 97 Session().flush()
98 creation_data = user.get_api_data()
99 99 username = form_result['username']
100 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
101 None, self.ip_addr, self.sa)
100
101 audit_logger.store_web(
102 'user.create', action_data={'data': creation_data},
103 user=c.rhodecode_user)
102 104
103 105 user_link = h.link_to(h.escape(username),
104 106 url('edit_user',
@@ -125,8 +127,6 b' class UsersController(BaseController):'
125 127
126 128 @HasPermissionAllDecorator('hg.admin')
127 129 def new(self):
128 """GET /users/new: Form to create a new item"""
129 # url('new_user')
130 130 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
131 131 self._get_personal_repo_group_template_vars()
132 132 return render('admin/users/user_add.mako')
@@ -134,13 +134,7 b' class UsersController(BaseController):'
134 134 @HasPermissionAllDecorator('hg.admin')
135 135 @auth.CSRFRequired()
136 136 def update(self, user_id):
137 """PUT /users/user_id: Update an existing item"""
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)
137
144 138 user_id = safe_int(user_id)
145 139 c.user = User.get_or_404(user_id)
146 140 c.active = 'profile'
@@ -152,6 +146,7 b' class UsersController(BaseController):'
152 146 old_data={'user_id': user_id,
153 147 'email': c.user.email})()
154 148 form_result = {}
149 old_values = c.user.get_api_data()
155 150 try:
156 151 form_result = _form.to_python(dict(request.POST))
157 152 skip_attrs = ['extern_type', 'extern_name']
@@ -160,12 +155,15 b' class UsersController(BaseController):'
160 155 # forbid updating username for external accounts
161 156 skip_attrs.append('username')
162 157
163 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
164 usr = form_result['username']
165 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
166 None, self.ip_addr, self.sa)
158 UserModel().update_user(
159 user_id, skip_attrs=skip_attrs, **form_result)
160
161 audit_logger.store_web(
162 'user.edit', action_data={'old_data': old_values},
163 user=c.rhodecode_user)
164
165 Session().commit()
167 166 h.flash(_('User updated successfully'), category='success')
168 Session().commit()
169 167 except formencode.Invalid as errors:
170 168 defaults = errors.value
171 169 e = errors.error_dict or {}
@@ -188,13 +186,6 b' class UsersController(BaseController):'
188 186 @HasPermissionAllDecorator('hg.admin')
189 187 @auth.CSRFRequired()
190 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 189 user_id = safe_int(user_id)
199 190 c.user = User.get_or_404(user_id)
200 191
@@ -249,10 +240,16 b' class UsersController(BaseController):'
249 240 _('Deleted %s user groups') % len(_user_groups),
250 241 category='success')
251 242
243 old_values = c.user.get_api_data()
252 244 try:
253 245 UserModel().delete(c.user, handle_repos=handle_repos,
254 246 handle_repo_groups=handle_repo_groups,
255 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 253 Session().commit()
257 254 set_handle_flash_repos()
258 255 set_handle_flash_repo_groups()
@@ -272,19 +269,25 b' class UsersController(BaseController):'
272 269 def reset_password(self, user_id):
273 270 """
274 271 toggle reset password flag for this user
275
276 :param user_id:
277 272 """
278 273 user_id = safe_int(user_id)
279 274 c.user = User.get_or_404(user_id)
280 275 try:
281 276 old_value = c.user.user_data.get('force_password_change')
282 277 c.user.update_userdata(force_password_change=not old_value)
283 Session().commit()
278
284 279 if old_value:
285 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 284 else:
287 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 291 h.flash(msg, category='success')
289 292 except Exception:
290 293 log.exception("Exception during password reset for user")
@@ -298,8 +301,6 b' class UsersController(BaseController):'
298 301 def create_personal_repo_group(self, user_id):
299 302 """
300 303 Create personal repository group for this user
301
302 :param user_id:
303 304 """
304 305 from rhodecode.model.repo_group import RepoGroupModel
305 306
@@ -381,8 +382,7 b' class UsersController(BaseController):'
381 382 return redirect(h.route_path('users'))
382 383
383 384 c.active = 'advanced'
384 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
385 c.personal_repo_group = c.perm_user.personal_repo_group
385 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
386 386 c.personal_repo_group_name = RepoGroupModel()\
387 387 .get_personal_group_name(user)
388 388 c.first_admin = User.get_first_super_admin()
@@ -429,8 +429,6 b' class UsersController(BaseController):'
429 429 @HasPermissionAllDecorator('hg.admin')
430 430 @auth.CSRFRequired()
431 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 432 user_id = safe_int(user_id)
435 433 user = User.get_or_404(user_id)
436 434 c.active = 'global_perms'
@@ -457,11 +455,13 b' class UsersController(BaseController):'
457 455
458 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 461 Session().commit()
461 462 h.flash(_('User global permissions updated successfully'),
462 463 category='success')
463 464
464 Session().commit()
465 465 except formencode.Invalid as errors:
466 466 defaults = errors.value
467 467 c.user = user
@@ -491,140 +491,3 b' class UsersController(BaseController):'
491 491
492 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 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 49 class ChangelogController(BaseRepoController):
71 50
72 51 def __before__(self):
@@ -88,13 +67,11 b' class ChangelogController(BaseRepoContro'
88 67 except EmptyRepositoryError:
89 68 if not redirect_after:
90 69 return None
91 h.flash(h.literal(_('There are no commits yet')),
92 category='warning')
70 h.flash(_('There are no commits yet'), category='warning')
93 71 redirect(url('changelog_home', repo_name=repo.repo_name))
94 72 except RepositoryError as e:
95 msg = safe_str(e)
96 log.exception(msg)
97 h.flash(msg, category='warning')
73 log.exception(safe_str(e))
74 h.flash(safe_str(h.escape(e)), category='warning')
98 75 if not partial:
99 76 redirect(h.url('changelog_home', repo_name=repo.repo_name))
100 77 raise HTTPBadRequest()
@@ -134,7 +111,7 b' class ChangelogController(BaseRepoContro'
134 111
135 112 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
136 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 115 category='warning')
139 116 redirect(url('changelog_file_home', repo_name=repo_name,
140 117 revision=branch_name, f_path=f_path or ''))
@@ -210,12 +187,11 b' class ChangelogController(BaseRepoContro'
210 187 collection, p, chunk_size, c.branch_name, dynamic=f_path)
211 188
212 189 except EmptyRepositoryError as e:
213 h.flash(safe_str(e), category='warning')
214 return redirect(url('summary_home', repo_name=repo_name))
190 h.flash(safe_str(h.escape(e)), category='warning')
191 return redirect(h.route_path('repo_summary', repo_name=repo_name))
215 192 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
216 msg = safe_str(e)
217 log.exception(msg)
218 h.flash(msg, category='error')
193 log.exception(safe_str(e))
194 h.flash(safe_str(h.escape(e)), category='error')
219 195 return redirect(url('changelog_home', repo_name=repo_name))
220 196
221 197 if (request.environ.get('HTTP_X_PARTIAL_XHR')
@@ -279,12 +255,3 b' class ChangelogController(BaseRepoContro'
279 255 c.rhodecode_repo, c.pagination,
280 256 prev_data=prev_data, next_data=next_data)
281 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 39 from rhodecode.lib.compat import OrderedDict
40 40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 41 import rhodecode.lib.helpers as h
42 from rhodecode.lib.utils import action_logger, jsonify
43 from rhodecode.lib.utils2 import safe_unicode
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode, safe_int
44 44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 45 from rhodecode.lib.vcs.exceptions import (
46 46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
@@ -48,7 +48,6 b' from rhodecode.model.db import Changeset'
48 48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 49 from rhodecode.model.comment import CommentsModel
50 50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
52 51
53 52
54 53 log = logging.getLogger(__name__)
@@ -268,8 +267,10 b' class ChangesetController(BaseRepoContro'
268 267 repo_name=c.repo_name,
269 268 source_node_getter=_node_getter(commit1),
270 269 target_node_getter=_node_getter(commit2),
271 comments=inline_comments
272 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
270 comments=inline_comments)
271 diffset = diffset.render_patchset(
272 _parsed, commit1.raw_id, commit2.raw_id)
273
273 274 c.changes[commit.raw_id] = diffset
274 275 else:
275 276 # downloads/raw we only need RAW diff nothing else
@@ -368,7 +369,6 b' class ChangesetController(BaseRepoContro'
368 369 comment_type=comment_type,
369 370 resolves_comment_id=resolves_comment_id
370 371 )
371 c.inline_comment = True if comment.line_no else False
372 372
373 373 # get status if set !
374 374 if status:
@@ -433,20 +433,26 b' class ChangesetController(BaseRepoContro'
433 433 @auth.CSRFRequired()
434 434 @jsonify
435 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 437 if not comment:
438 438 log.debug('Comment with id:%s not found, skipping', comment_id)
439 439 # comment already deleted in another call probably
440 440 return True
441 441
442 owner = (comment.author.user_id == c.rhodecode_user.user_id)
443 442 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
444 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
445 CommentsModel().delete(comment=comment)
443 super_admin = h.HasPermissionAny('hg.admin')()
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 450 Session().commit()
447 451 return True
448 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 457 @LoginRequired()
452 458 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
@@ -24,7 +24,7 b' Compare controller for showing differenc'
24 24
25 25 import logging
26 26
27 from webob.exc import HTTPBadRequest
27 from webob.exc import HTTPBadRequest, HTTPNotFound
28 28 from pylons import request, tmpl_context as c, url
29 29 from pylons.controllers.util import redirect
30 30 from pylons.i18n.translation import _
@@ -63,14 +63,13 b' class CompareController(BaseRepoControll'
63 63 return repo.scm_instance().EMPTY_COMMIT
64 64 h.flash(h.literal(_('There are no commits yet')),
65 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 68 except RepositoryError as e:
69 msg = safe_str(e)
70 log.exception(msg)
71 h.flash(msg, category='warning')
69 log.exception(safe_str(e))
70 h.flash(safe_str(h.escape(e)), category='warning')
72 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 73 raise HTTPBadRequest()
75 74
76 75 @LoginRequired()
@@ -86,6 +85,10 b' class CompareController(BaseRepoControll'
86 85 target_repo = request.GET.get('target_repo', source_repo)
87 86 c.source_repo = Repository.get_by_repo_name(source_repo)
88 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 92 c.source_ref = c.target_ref = _('Select commit')
90 93 c.source_ref_type = ""
91 94 c.target_ref_type = ""
@@ -141,18 +144,17 b' class CompareController(BaseRepoControll'
141 144 target_repo = Repository.get_by_repo_name(target_repo_name)
142 145
143 146 if source_repo is None:
144 msg = _('Could not find the original repo: %(repo)s') % {
145 'repo': source_repo}
146
147 log.error(msg)
148 h.flash(msg, category='error')
147 log.error('Could not find the source repo: {}'
148 .format(source_repo_name))
149 h.flash(_('Could not find the source repo: `{}`')
150 .format(h.escape(source_repo_name)), category='error')
149 151 return redirect(url('compare_home', repo_name=c.repo_name))
150 152
151 153 if target_repo is None:
152 msg = _('Could not find the other repo: %(repo)s') % {
153 'repo': target_repo_name}
154 log.error(msg)
155 h.flash(msg, category='error')
154 log.error('Could not find the target repo: {}'
155 .format(source_repo_name))
156 h.flash(_('Could not find the target repo: `{}`')
157 .format(h.escape(target_repo_name)), category='error')
156 158 return redirect(url('compare_home', repo_name=c.repo_name))
157 159
158 160 source_scm = source_repo.scm_instance()
@@ -269,11 +271,13 b' class CompareController(BaseRepoControll'
269 271 return None
270 272 return get_node
271 273
272 c.diffset = codeblocks.DiffSet(
274 diffset = codeblocks.DiffSet(
273 275 repo_name=source_repo.repo_name,
274 276 source_node_getter=_node_getter(source_commit),
275 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 282 c.preview_mode = merge
279 283 c.source_commit = source_commit
@@ -113,7 +113,7 b' class FeedController(BaseRepoController)'
113 113 def _generate_feed(cache_key):
114 114 feed = Atom1Feed(
115 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 117 description=self.description % repo_name,
118 118 language=self.language,
119 119 ttl=self.ttl
@@ -150,8 +150,7 b' class FeedController(BaseRepoController)'
150 150 def _generate_feed(cache_key):
151 151 feed = Rss201rev2Feed(
152 152 title=self.title % repo_name,
153 link=url('summary_home', repo_name=repo_name,
154 qualified=True),
153 link=h.route_url('repo_summary', repo_name=repo_name),
155 154 description=self.description % repo_name,
156 155 language=self.language,
157 156 ttl=self.ttl
@@ -35,10 +35,10 b' from webob.exc import HTTPNotFound, HTTP'
35 35
36 36 from rhodecode.controllers.utils import parse_path_ref
37 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 39 from rhodecode.lib.codeblocks import (
40 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 42 from rhodecode.lib.utils2 import (
43 43 convert_line_endings, detect_mode, safe_str, str2bool)
44 44 from rhodecode.lib.auth import (
@@ -101,13 +101,13 b' class FilesController(BaseRepoController'
101 101 add_new = ""
102 102 h.flash(h.literal(
103 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 105 except (CommitDoesNotExistError, LookupError):
106 106 msg = _('No such commit exists for this repository')
107 107 h.flash(msg, category='error')
108 108 raise HTTPNotFound()
109 109 except RepositoryError as e:
110 h.flash(safe_str(e), category='error')
110 h.flash(safe_str(h.escape(e)), category='error')
111 111 raise HTTPNotFound()
112 112
113 113 def __get_filenode_or_redirect(self, repo_name, commit, path):
@@ -124,12 +124,11 b' class FilesController(BaseRepoController'
124 124 if file_node.is_dir():
125 125 raise RepositoryError('The given path is a directory')
126 126 except CommitDoesNotExistError:
127 msg = _('No such commit exists for this repository')
128 log.exception(msg)
129 h.flash(msg, category='error')
127 log.exception('No such commit exists for this repository')
128 h.flash(_('No such commit exists for this repository'), category='error')
130 129 raise HTTPNotFound()
131 130 except RepositoryError as e:
132 h.flash(safe_str(e), category='error')
131 h.flash(safe_str(h.escape(e)), category='error')
133 132 raise HTTPNotFound()
134 133
135 134 return file_node
@@ -257,7 +256,7 b' class FilesController(BaseRepoController'
257 256 repo_name, c.commit.raw_id, f_path)
258 257
259 258 except RepositoryError as e:
260 h.flash(safe_str(e), category='error')
259 h.flash(safe_str(h.escape(e)), category='error')
261 260 raise HTTPNotFound()
262 261
263 262 if request.environ.get('HTTP_X_PJAX'):
@@ -450,7 +449,7 b' class FilesController(BaseRepoController'
450 449 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
451 450
452 451 c.default_message = _(
453 'Deleted file %s via RhodeCode Enterprise') % (f_path)
452 'Deleted file {} via RhodeCode Enterprise').format(f_path)
454 453 c.f_path = f_path
455 454 node_path = f_path
456 455 author = c.rhodecode_user.full_contact
@@ -469,12 +468,12 b' class FilesController(BaseRepoController'
469 468 author=author,
470 469 )
471 470
472 h.flash(_('Successfully deleted file %s') % f_path,
473 category='success')
471 h.flash(
472 _('Successfully deleted file `{}`').format(
473 h.escape(f_path)), category='success')
474 474 except Exception:
475 msg = _('Error occurred during commit')
476 log.exception(msg)
477 h.flash(msg, category='error')
475 log.exception('Error during commit operation')
476 h.flash(_('Error occurred during commit'), category='error')
478 477 return redirect(url('changeset_home',
479 478 repo_name=c.repo_name, revision='tip'))
480 479
@@ -503,7 +502,7 b' class FilesController(BaseRepoController'
503 502 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
504 503
505 504 c.default_message = _(
506 'Deleted file %s via RhodeCode Enterprise') % (f_path)
505 'Deleted file {} via RhodeCode Enterprise').format(f_path)
507 506 c.f_path = f_path
508 507
509 508 return render('files/files_delete.mako')
@@ -537,7 +536,7 b' class FilesController(BaseRepoController'
537 536 return redirect(url('files_home', repo_name=c.repo_name,
538 537 revision=c.commit.raw_id, f_path=f_path))
539 538 c.default_message = _(
540 'Edited file %s via RhodeCode Enterprise') % (f_path)
539 'Edited file {} via RhodeCode Enterprise').format(f_path)
541 540 c.f_path = f_path
542 541 old_content = c.file.content
543 542 sl = old_content.splitlines(1)
@@ -575,12 +574,12 b' class FilesController(BaseRepoController'
575 574 parent_commit=c.commit,
576 575 )
577 576
578 h.flash(_('Successfully committed to %s') % f_path,
579 category='success')
577 h.flash(
578 _('Successfully committed changes to file `{}`').format(
579 h.escape(f_path)), category='success')
580 580 except Exception:
581 msg = _('Error occurred during commit')
582 log.exception(msg)
583 h.flash(msg, category='error')
581 log.exception('Error occurred during commit')
582 h.flash(_('Error occurred during commit'), category='error')
584 583 return redirect(url('changeset_home',
585 584 repo_name=c.repo_name, revision='tip'))
586 585
@@ -612,7 +611,7 b' class FilesController(BaseRepoController'
612 611 return redirect(url('files_home', repo_name=c.repo_name,
613 612 revision=c.commit.raw_id, f_path=f_path))
614 613 c.default_message = _(
615 'Edited file %s via RhodeCode Enterprise') % (f_path)
614 'Edited file {} via RhodeCode Enterprise').format(f_path)
616 615 c.f_path = f_path
617 616
618 617 return render('files/files_edit.mako')
@@ -660,7 +659,7 b' class FilesController(BaseRepoController'
660 659 file_obj = r_post.get('upload_file', None)
661 660
662 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 663 content = file_obj.file
665 664
666 665 if hasattr(content, 'file'):
@@ -669,14 +668,14 b' class FilesController(BaseRepoController'
669 668
670 669 # If there's no commit, redirect to repo summary
671 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 672 else:
674 redirect_url = "changeset_home"
673 redirect_url = url("changeset_home", repo_name=c.repo_name,
674 revision='tip')
675 675
676 676 if not filename:
677 677 h.flash(_('No filename'), category='warning')
678 return redirect(url(redirect_url, repo_name=c.repo_name,
679 revision='tip'))
678 return redirect(redirect_url)
680 679
681 680 # extract the location from filename,
682 681 # allows using foo/bar.txt syntax to create subdirectories
@@ -704,8 +703,9 b' class FilesController(BaseRepoController'
704 703 author=author,
705 704 )
706 705
707 h.flash(_('Successfully committed to %s') % node_path,
708 category='success')
706 h.flash(
707 _('Successfully committed new file `{}`').format(
708 h.escape(node_path)), category='success')
709 709 except NonRelativePathError as e:
710 710 h.flash(_(
711 711 'The location specified must be a relative path and must not '
@@ -713,11 +713,10 b' class FilesController(BaseRepoController'
713 713 return redirect(url('changeset_home', repo_name=c.repo_name,
714 714 revision='tip'))
715 715 except (NodeError, NodeAlreadyExistsError) as e:
716 h.flash(_(e), category='error')
716 h.flash(_(h.escape(e)), category='error')
717 717 except Exception:
718 msg = _('Error occurred during commit')
719 log.exception(msg)
720 h.flash(msg, category='error')
718 log.exception('Error occurred during commit')
719 h.flash(_('Error occurred during commit'), category='error')
721 720 return redirect(url('changeset_home',
722 721 repo_name=c.repo_name, revision='tip'))
723 722
@@ -801,7 +800,7 b' class FilesController(BaseRepoController'
801 800 if not use_cached_archive:
802 801 # generate new archive
803 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 804 try:
806 805 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
807 806 except ImproperArchiveTypeError:
@@ -809,10 +808,26 b' class FilesController(BaseRepoController'
809 808 if archive_cache_enabled:
810 809 # if we generated the archive and we have cache enabled
811 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 812 shutil.move(archive, cached_archive_path)
814 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 831 def get_chunked_archive(archive):
817 832 with open(archive, 'rb') as stream:
818 833 while True:
@@ -826,14 +841,6 b' class FilesController(BaseRepoController'
826 841 break
827 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 844 return get_chunked_archive(archive)
838 845
839 846 @LoginRequired()
@@ -139,7 +139,7 b' class ForksController(BaseRepoController'
139 139 c.repo_info = Repository.get_by_repo_name(repo_name)
140 140 if not c.repo_info:
141 141 h.not_mapped_error(repo_name)
142 return redirect(url('home'))
142 return redirect(h.route_path('home'))
143 143
144 144 defaults = self.__load_data(repo_name)
145 145
@@ -24,20 +24,15 b' Home controller for RhodeCode Enterprise'
24 24
25 25 import logging
26 26 import time
27 import re
28 27
29 from pylons import tmpl_context as c, request, url, config
30 from pylons.i18n.translation import _
31 from sqlalchemy.sql import func
28 from pylons import tmpl_context as c
32 29
33 30 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
31 LoginRequired, HasPermissionAllDecorator,
32 HasRepoGroupPermissionAnyDecorator)
36 33 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.index import searcher_from_config
34
38 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 36 from rhodecode.model.db import Repository, RepoGroup
42 37 from rhodecode.model.repo import RepoModel
43 38 from rhodecode.model.repo_group import RepoGroupModel
@@ -70,221 +65,3 b' class HomeController(BaseController):'
70 65 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
71 66 % (c.rhodecode_name, time.time()))
72 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 34 from pylons import request, tmpl_context as c, response, url
35 35 from pylons.i18n.translation import _
36 36
37 from rhodecode.controllers.admin.admin import user_log_filter
38 37 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
39 38 from rhodecode.model.meta import Session
40 39 import rhodecode.lib.helpers as h
41 40 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.user_log_filter import user_log_filter
42 42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
43 43 from rhodecode.lib.base import BaseController, render
44 44 from rhodecode.lib.utils2 import safe_int, AttributeDict
@@ -21,8 +21,6 b''
21 21 """
22 22 pull requests controller for rhodecode for initializing pull requests
23 23 """
24 import types
25
26 24 import peppercorn
27 25 import formencode
28 26 import logging
@@ -33,6 +31,7 b' from pylons import request, tmpl_context'
33 31 from pylons.controllers.util import redirect
34 32 from pylons.i18n.translation import _
35 33 from pyramid.threadlocal import get_current_registry
34 from pyramid.httpexceptions import HTTPFound
36 35 from sqlalchemy.sql import func
37 36 from sqlalchemy.sql.expression import or_
38 37
@@ -72,124 +71,6 b' class PullrequestsController(BaseRepoCon'
72 71 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
73 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 74 @LoginRequired()
194 75 @NotAnonymous()
195 76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
@@ -203,7 +84,7 b' class PullrequestsController(BaseRepoCon'
203 84 except EmptyRepositoryError:
204 85 h.flash(h.literal(_('There are no commits yet')),
205 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 89 commit_id = request.GET.get('commit')
209 90 branch_ref = request.GET.get('branch')
@@ -346,8 +227,6 b' class PullrequestsController(BaseRepoCon'
346 227 target_repo = _form['target_repo']
347 228 target_ref = _form['target_ref']
348 229 commit_ids = _form['revisions'][::-1]
349 reviewers = [
350 (r['user_id'], r['reasons']) for r in _form['review_members']]
351 230
352 231 # find the ancestor for this pr
353 232 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
@@ -375,22 +254,35 b' class PullrequestsController(BaseRepoCon'
375 254 )
376 255
377 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 269 try:
379 270 pull_request = PullRequestModel().create(
380 271 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
381 272 target_ref, commit_ids, reviewers, pullrequest_title,
382 description
273 description, reviewer_rules
383 274 )
384 275 Session().commit()
385 276 h.flash(_('Successfully opened new pull request'),
386 277 category='success')
387 278 except Exception as e:
388 msg = _('Error occurred during sending pull request')
279 msg = _('Error occurred during creation of this pull request.')
389 280 log.exception(msg)
390 281 h.flash(msg, category='error')
391 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(
285 h.route_path('pullrequest_show', repo_name=target_repo,
394 286 pull_request_id=pull_request.pull_request_id))
395 287
396 288 @LoginRequired()
@@ -410,11 +302,10 b' class PullrequestsController(BaseRepoCon'
410 302
411 303 if 'review_members' in controls:
412 304 self._update_reviewers(
413 pull_request_id, controls['review_members'])
305 pull_request_id, controls['review_members'],
306 pull_request.reviewer_data)
414 307 elif str2bool(request.POST.get('update_commits', 'false')):
415 308 self._update_commits(pull_request)
416 elif str2bool(request.POST.get('close_pull_request', 'false')):
417 self._reject_close(pull_request)
418 309 elif str2bool(request.POST.get('edit_pull_request', 'false')):
419 310 self._edit_pull_request(pull_request)
420 311 else:
@@ -426,7 +317,7 b' class PullrequestsController(BaseRepoCon'
426 317 try:
427 318 PullRequestModel().edit(
428 319 pull_request, request.POST.get('title'),
429 request.POST.get('description'))
320 request.POST.get('description'), c.rhodecode_user)
430 321 except ValueError:
431 322 msg = _(u'Cannot update closed pull requests.')
432 323 h.flash(msg, category='error')
@@ -492,7 +383,7 b' class PullrequestsController(BaseRepoCon'
492 383 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
493 384 warning_reasons = [
494 385 UpdateFailureReason.NO_CHANGE,
495 UpdateFailureReason.WRONG_REF_TPYE,
386 UpdateFailureReason.WRONG_REF_TYPE,
496 387 ]
497 388 category = 'warning' if resp.reason in warning_reasons else 'error'
498 389 h.flash(msg, category=category)
@@ -529,8 +420,8 b' class PullrequestsController(BaseRepoCon'
529 420 scm=pull_request.target_repo.repo_type)
530 421 self._merge_pull_request(pull_request, user, extras)
531 422
532 return redirect(url(
533 'pullrequest_show',
423 raise HTTPFound(
424 h.route_path('pullrequest_show',
534 425 repo_name=pull_request.target_repo.repo_name,
535 426 pull_request_id=pull_request.pull_request_id))
536 427
@@ -553,18 +444,21 b' class PullrequestsController(BaseRepoCon'
553 444 merge_resp.failure_reason)
554 445 h.flash(msg, category='error')
555 446
556 def _update_reviewers(self, pull_request_id, review_members):
557 reviewers = [
558 (int(r['user_id']), r['reasons']) for r in review_members]
559 PullRequestModel().update_reviewers(pull_request_id, reviewers)
560 Session().commit()
447 def _update_reviewers(self, pull_request_id, review_members, reviewer_rules):
448
449 get_default_reviewers_data, validate_default_reviewers = \
450 PullRequestModel().get_reviewer_functions()
561 451
562 def _reject_close(self, pull_request):
563 if pull_request.is_closed():
564 raise HTTPForbidden()
452 try:
453 reviewers = validate_default_reviewers(review_members, reviewer_rules)
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(
567 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
459 PullRequestModel().update_reviewers(
460 pull_request_id, reviewers, c.rhodecode_user)
461 h.flash(_('Pull request reviewers updated.'), category='success')
568 462 Session().commit()
569 463
570 464 @LoginRequired()
@@ -583,7 +477,7 b' class PullrequestsController(BaseRepoCon'
583 477
584 478 # only owner can delete it !
585 479 if allowed_to_delete:
586 PullRequestModel().delete(pull_request)
480 PullRequestModel().delete(pull_request, c.rhodecode_user)
587 481 Session().commit()
588 482 h.flash(_('Successfully deleted pull request'),
589 483 category='success')
@@ -608,7 +502,8 b' class PullrequestsController(BaseRepoCon'
608 502 _org_pull_request_obj = pull_request_ver.pull_request
609 503 at_version = pull_request_ver.pull_request_version_id
610 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 508 pull_request_display_obj = PullRequest.get_pr_display_object(
614 509 pull_request_obj, _org_pull_request_obj)
@@ -715,9 +610,9 b' class PullrequestsController(BaseRepoCon'
715 610 c.allowed_to_comment = False
716 611 c.allowed_to_close = False
717 612 else:
718 c.allowed_to_change_status = PullRequestModel(). \
719 check_user_change_status(pull_request_at_ver, c.rhodecode_user) \
720 and not pr_closed
613 can_change_status = PullRequestModel().check_user_change_status(
614 pull_request_at_ver, c.rhodecode_user)
615 c.allowed_to_change_status = can_change_status and not pr_closed
721 616
722 617 c.allowed_to_update = PullRequestModel().check_user_update(
723 618 pull_request_latest, c.rhodecode_user) and not pr_closed
@@ -726,7 +621,24 b' class PullrequestsController(BaseRepoCon'
726 621 c.allowed_to_delete = PullRequestModel().check_user_delete(
727 622 pull_request_latest, c.rhodecode_user) and not pr_closed
728 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 643 # check merge capabilities
732 644 _merge_check = MergeCheck.validate(
@@ -748,7 +660,7 b' class PullrequestsController(BaseRepoCon'
748 660 # GENERAL COMMENTS with versions #
749 661 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
750 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 665 # pick comments we want to render at current version
754 666 c.comment_versions = comments_model.aggregate_comments(
@@ -758,7 +670,8 b' class PullrequestsController(BaseRepoCon'
758 670 # INLINE COMMENTS with versions #
759 671 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
760 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 675 c.inline_versions = comments_model.aggregate_comments(
763 676 inline_comments, versions, c.at_version_num, inline=True)
764 677
@@ -841,12 +754,18 b' class PullrequestsController(BaseRepoCon'
841 754 c.commit_ranges.append(comm)
842 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 759 target_commit = commits_source_repo.get_commit(
845 760 commit_id=safe_str(target_ref_id))
761
846 762 source_commit = commits_source_repo.get_commit(
847 763 commit_id=safe_str(source_ref_id))
764
848 765 except CommitDoesNotExistError:
849 pass
766 log.warning(
767 'Failed to get commit from `{}` repo'.format(
768 commits_source_repo), exc_info=True)
850 769 except RepositoryRequirementError:
851 770 log.warning(
852 771 'Failed to get all required data from repo', exc_info=True)
@@ -960,6 +879,7 b' class PullrequestsController(BaseRepoCon'
960 879 pull_request_id = safe_int(pull_request_id)
961 880 pull_request = PullRequest.get_or_404(pull_request_id)
962 881 if pull_request.is_closed():
882 log.debug('comment: forbidden because pull request is closed')
963 883 raise HTTPForbidden()
964 884
965 885 status = request.POST.get('changeset_status', None)
@@ -968,15 +888,29 b' class PullrequestsController(BaseRepoCon'
968 888 resolves_comment_id = request.POST.get('resolves_comment_id', None)
969 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
892 # pr comment, use `close_pull_request_with_comment` function
893 # else handle regular comment logic
894 user = c.rhodecode_user
895 repo = c.rhodecode_db_repo
896
972 897 if close_pull_request:
973 close_pr = True
974 pull_request_review_status = pull_request.calculated_review_status()
975 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
976 # approved only if we have voting consent
977 status = ChangesetStatus.STATUS_APPROVED
898 # only owner or admin or person with write permissions
899 allowed_to_close = PullRequestModel().check_user_update(
900 pull_request, c.rhodecode_user)
901 if not allowed_to_close:
902 log.debug('comment: forbidden because not allowed to close '
903 'pull request %s', pull_request_id)
904 raise HTTPForbidden()
905 comment, status = PullRequestModel().close_pull_request_with_comment(
906 pull_request, user, repo, message=text)
907 Session().flush()
908 events.trigger(
909 events.PullRequestCommentEvent(pull_request, comment))
910
978 911 else:
979 status = ChangesetStatus.STATUS_REJECTED
912 # regular comment case, could be inline, or one with status.
913 # for that one we check also permissions
980 914
981 915 allowed_to_change_status = PullRequestModel().check_user_change_status(
982 916 pull_request, c.rhodecode_user)
@@ -985,10 +919,9 b' class PullrequestsController(BaseRepoCon'
985 919 message = (_('Status change %(transition_icon)s %(status)s')
986 920 % {'transition_icon': '>',
987 921 'status': ChangesetStatus.get_status_lbl(status)})
988 if close_pr:
989 message = _('Closing with') + ' ' + message
990 922 text = text or message
991 comm = CommentsModel().create(
923
924 comment = CommentsModel().create(
992 925 text=text,
993 926 repo=c.rhodecode_db_repo.repo_id,
994 927 user=c.rhodecode_user.user_id,
@@ -999,25 +932,28 b' class PullrequestsController(BaseRepoCon'
999 932 if status and allowed_to_change_status else None),
1000 933 status_change_type=(status
1001 934 if status and allowed_to_change_status else None),
1002 closing_pr=close_pr,
1003 935 comment_type=comment_type,
1004 936 resolves_comment_id=resolves_comment_id
1005 937 )
1006 938
1007 939 if allowed_to_change_status:
940 # calculate old status before we change it
1008 941 old_calculated_status = pull_request.calculated_review_status()
942
1009 943 # get status if set !
1010 944 if status:
1011 945 ChangesetStatusModel().set_status(
1012 946 c.rhodecode_db_repo.repo_id,
1013 947 status,
1014 948 c.rhodecode_user.user_id,
1015 comm,
949 comment,
1016 950 pull_request=pull_request_id
1017 951 )
1018 952
1019 953 Session().flush()
1020 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
954 events.trigger(
955 events.PullRequestCommentEvent(pull_request, comment))
956
1021 957 # we now calculate the status of pull request, and based on that
1022 958 # calculation we set the commits status
1023 959 calculated_status = pull_request.calculated_review_status()
@@ -1025,38 +961,22 b' class PullrequestsController(BaseRepoCon'
1025 961 PullRequestModel()._trigger_pull_request_hook(
1026 962 pull_request, c.rhodecode_user, 'review_status_change')
1027 963
1028 calculated_status_lbl = ChangesetStatus.get_status_lbl(
1029 calculated_status)
1030
1031 if close_pr:
1032 status_completed = (
1033 calculated_status in [ChangesetStatus.STATUS_APPROVED,
1034 ChangesetStatus.STATUS_REJECTED])
1035 if close_pull_request or status_completed:
1036 PullRequestModel().close_pull_request(
1037 pull_request_id, c.rhodecode_user)
1038 else:
1039 h.flash(_('Closing pull request on other statuses than '
1040 'rejected or approved is forbidden. '
1041 'Calculated status from all reviewers '
1042 'is currently: %s') % calculated_status_lbl,
1043 category='warning')
1044
1045 964 Session().commit()
1046 965
1047 966 if not request.is_xhr:
1048 return redirect(h.url('pullrequest_show', repo_name=repo_name,
967 raise HTTPFound(
968 h.route_path('pullrequest_show',
969 repo_name=repo_name,
1049 970 pull_request_id=pull_request_id))
1050 971
1051 972 data = {
1052 973 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
1053 974 }
1054 if comm:
1055 c.co = comm
1056 c.inline_comment = True if comm.line_no else False
1057 data.update(comm.get_dict())
1058 data.update({'rendered_text':
1059 render('changeset/changeset_comment_block.mako')})
975 if comment:
976 c.co = comment
977 rendered_comment = render('changeset/changeset_comment_block.mako')
978 data.update(comment.get_dict())
979 data.update({'rendered_text': rendered_comment})
1060 980
1061 981 return data
1062 982
@@ -1067,25 +987,32 b' class PullrequestsController(BaseRepoCon'
1067 987 @auth.CSRFRequired()
1068 988 @jsonify
1069 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):
1073 comment_id = safe_int(comment_id)
1074 co = ChangesetComment.get_or_404(comment_id)
1075 if co.pull_request.is_closed():
996 if comment.pull_request.is_closed():
1076 997 # don't allow deleting comments on closed pull request
1077 998 raise HTTPForbidden()
1078 999
1079 is_owner = co.author.user_id == c.rhodecode_user.user_id
1080 1000 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1081 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1082 old_calculated_status = co.pull_request.calculated_review_status()
1083 CommentsModel().delete(comment=co)
1001 super_admin = h.HasPermissionAny('hg.admin')()
1002 comment_owner = comment.author.user_id == c.rhodecode_user.user_id
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 1009 Session().commit()
1085 calculated_status = co.pull_request.calculated_review_status()
1010 calculated_status = comment.pull_request.calculated_review_status()
1086 1011 if old_calculated_status != calculated_status:
1087 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 1014 return True
1090 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 87 '%s "%s" does not exist' % (ref_type, ref_name))
88 88
89 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 19 import logging
20 20 from pyramid.threadlocal import get_current_registry
21 from rhodecode.events.base import RhodecodeEvent
22
21 23
22 24 log = logging.getLogger(__name__)
23 25
@@ -32,20 +34,21 b' def trigger(event, registry=None):'
32 34 # passing the registry as an argument to get rid of it.
33 35 registry = registry or get_current_registry()
34 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 39 # Until we can work around the problem that VCS operations do not have a
38 40 # pyramid context to work with, we send the events to integrations directly
39 41
40 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 48 from rhodecode.integrations import integrations_event_handler
43 49 if isinstance(event, RhodecodeEvent):
44 50 integrations_event_handler(event)
45 51
46
47 from rhodecode.events.base import RhodecodeEvent
48
49 52 from rhodecode.events.user import ( # noqa
50 53 UserPreCreate,
51 54 UserPostCreate,
@@ -24,7 +24,8 b' from rhodecode.lib.utils2 import Attribu'
24 24
25 25 # this is a user object to be used for events caused by the system (eg. shell)
26 26 SYSTEM_USER = AttributeDict(dict(
27 username='__SYSTEM__'
27 username='__SYSTEM__',
28 user_id='__SYSTEM_ID__'
28 29 ))
29 30
30 31 log = logging.getLogger(__name__)
@@ -32,12 +33,12 b' log = logging.getLogger(__name__)'
32 33
33 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 38 name = "RhodeCodeEvent"
38 39
39 def __init__(self):
40 self.request = get_current_request()
40 def __init__(self, request=None):
41 self.request = request or get_current_request()
41 42 self.utc_timestamp = datetime.utcnow()
42 43
43 44 @property
@@ -61,7 +62,8 b' class RhodecodeEvent(object):'
61 62 instance = auth_user.get_instance()
62 63 if not instance:
63 64 return AttributeDict(dict(
64 username=auth_user.username
65 username=auth_user.username,
66 user_id=auth_user.user_id,
65 67 ))
66 68 return instance
67 69
@@ -78,9 +80,8 b' class RhodecodeEvent(object):'
78 80 def server_url(self):
79 81 default = '<no server_url available>'
80 82 if self.request:
81 from rhodecode.lib import helpers as h
82 83 try:
83 return h.url('home', qualified=True)
84 return self.request.route_url('home')
84 85 except Exception:
85 86 log.exception('Failed to fetch URL for server')
86 87 return default
@@ -93,7 +94,8 b' class RhodecodeEvent(object):'
93 94 'utc_timestamp': self.utc_timestamp,
94 95 'actor_ip': self.actor_ip,
95 96 'actor': {
96 'username': self.actor.username
97 'username': self.actor.username,
98 'user_id': self.actor.user_id
97 99 },
98 100 'server_url': self.server_url
99 101 }
@@ -41,6 +41,7 b' class PullRequestEvent(RepoEvent):'
41 41 data = super(PullRequestEvent, self).as_dict()
42 42
43 43 commits = _commits_as_dict(
44 self,
44 45 commit_ids=self.pullrequest.revisions,
45 46 repos=[self.pullrequest.source_repo]
46 47 )
@@ -52,6 +53,8 b' class PullRequestEvent(RepoEvent):'
52 53 'issues': issues,
53 54 'pull_request_id': self.pullrequest.pull_request_id,
54 55 'url': PullRequestModel().get_url(self.pullrequest),
56 'permalink_url': PullRequestModel().get_url(
57 self.pullrequest, permalink=True),
55 58 'status': self.pullrequest.calculated_review_status(),
56 59 'commits': commits,
57 60 }
@@ -131,7 +134,9 b' class PullRequestCommentEvent(PullReques'
131 134 'type': self.comment.comment_type,
132 135 'file': self.comment.f_path,
133 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 142 return data
@@ -16,6 +16,7 b''
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 import collections
19 20 import logging
20 21
21 22 from rhodecode.translation import lazy_ugettext
@@ -26,17 +27,18 b' from rhodecode.lib.vcs.exceptions import'
26 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 32 Helper function to serialize commit_ids
32 33
34 :param event: class calling this method
33 35 :param commit_ids: commits to get
34 36 :param repos: list of repos to check
35 37 """
36 38 from rhodecode.lib.utils2 import extract_mentioned_users
37 from rhodecode.lib import helpers as h
38 39 from rhodecode.lib.helpers import (
39 40 urlify_commit_message, process_patterns, chop_at_smart)
41 from rhodecode.model.repo import RepoModel
40 42
41 43 if not repos:
42 44 raise Exception('no repo defined')
@@ -67,17 +69,17 b' def _commits_as_dict(commit_ids, repos):'
67 69 cs_data = cs.__json__()
68 70 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
69 71 cs_data['reviewers'] = reviewers
70 cs_data['url'] = h.url('changeset_home',
71 repo_name=repo.repo_name,
72 revision=cs_data['raw_id'],
73 qualified=True
74 )
72 cs_data['url'] = RepoModel().get_commit_url(
73 repo, cs_data['raw_id'], request=event.request)
74 cs_data['permalink_url'] = RepoModel().get_commit_url(
75 repo, cs_data['raw_id'], request=event.request, permalink=True)
75 76 urlified_message, issues_data = process_patterns(
76 77 cs_data['message'], repo.repo_name)
77 78 cs_data['issues'] = issues_data
78 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
79 repo.repo_name)
80 cs_data['message_html_title'] = chop_at_smart(cs_data['message'], '\n', suffix_if_chopped='...')
79 cs_data['message_html'] = urlify_commit_message(
80 cs_data['message'], repo.repo_name)
81 cs_data['message_html_title'] = chop_at_smart(
82 cs_data['message'], '\n', suffix_if_chopped='...')
81 83 commits.append(cs_data)
82 84
83 85 needed_commits.remove(commit_id)
@@ -118,12 +120,20 b' class RepoEvent(RhodecodeEvent):'
118 120 def as_dict(self):
119 121 from rhodecode.model.repo import RepoModel
120 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 127 data.update({
122 128 'repo': {
123 129 'repo_id': self.repo.repo_id,
124 130 'repo_name': self.repo.repo_name,
125 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 139 return data
@@ -235,10 +245,13 b' class RepoPushEvent(RepoVCSEvent):'
235 245
236 246 def as_dict(self):
237 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 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 256 last_branch = None
244 257 for commit in reversed(commits):
@@ -251,8 +264,7 b' class RepoPushEvent(RepoVCSEvent):'
251 264 branches = [
252 265 {
253 266 'name': branch,
254 'url': '{}/changelog?branch={}'.format(
255 data['repo']['url'], branch)
267 'url': branch_url(branch)
256 268 }
257 269 for branch in branches
258 270 ]
@@ -24,6 +24,9 b' deform - later can be replaced with some'
24 24 """
25 25
26 26 from rhodecode.translation import _
27 from rhodecode.translation import TranslationString
28
29 from mako.template import Template
27 30 from deform import Button, Form, widget, ValidationFailure
28 31
29 32
@@ -31,3 +34,16 b' class buttons:'
31 34 save = Button(name='Save', type='submit')
32 35 reset = Button(name=_('Reset'), type='reset')
33 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, (3029 lines changed) Show them Hide them
@@ -6,9 +6,9 b''
6 6 #, fuzzy
7 7 msgid ""
8 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 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 12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 14 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -24,7 +24,7 b' msgid "Global"'
24 24 msgstr ""
25 25
26 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 28 msgid "VCS"
29 29 msgstr ""
30 30
@@ -37,7 +37,7 b' msgid "Remap and Rescan"'
37 37 msgstr ""
38 38
39 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 41 msgid "Issue Tracker"
42 42 msgstr ""
43 43
@@ -48,7 +48,7 b' msgstr ""'
48 48 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:97
49 49 #: rhodecode/templates/admin/users/user_add.mako:86
50 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 52 #: rhodecode/templates/email_templates/user_registration.mako:25
53 53 #: rhodecode/templates/users/user_profile.mako:51
54 54 msgid "Email"
@@ -75,7 +75,7 b' msgstr ""'
75 75 #: rhodecode/templates/admin/integrations/new.mako:17
76 76 #: rhodecode/templates/admin/integrations/new.mako:23
77 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 79 #: rhodecode/templates/base/base.mako:82
80 80 msgid "Integrations"
81 81 msgstr ""
@@ -97,11 +97,11 b' msgstr ""'
97 97 msgid "Labs"
98 98 msgstr ""
99 99
100 #: rhodecode/apps/admin/views/sessions.py:86
100 #: rhodecode/apps/admin/views/sessions.py:92
101 101 msgid "Cleaned up old sessions"
102 102 msgstr ""
103 103
104 #: rhodecode/apps/admin/views/sessions.py:92
104 #: rhodecode/apps/admin/views/sessions.py:98
105 105 msgid "Failed to cleanup up old sessions"
106 106 msgstr ""
107 107
@@ -113,245 +113,409 b' msgstr ""'
113 113 msgid "Failed to generate the Apache configuration for Subversion."
114 114 msgstr ""
115 115
116 #: rhodecode/apps/admin/views/system_info.py:95
116 #: rhodecode/apps/admin/views/system_info.py:99
117 117 msgid "Note: please make sure this server can access `${url}` for the update link to work"
118 118 msgstr ""
119 119
120 #: rhodecode/apps/admin/views/system_info.py:98
120 #: rhodecode/apps/admin/views/system_info.py:102
121 121 msgid "Update info"
122 122 msgstr ""
123 123
124 #: rhodecode/apps/admin/views/system_info.py:100
124 #: rhodecode/apps/admin/views/system_info.py:104
125 125 msgid "Check for updates"
126 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 128 #: rhodecode/apps/admin/views/system_info.py:109
145 msgid "Workers"
129 msgid "RhodeCode Version"
146 130 msgstr ""
147 131
148 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 146 msgstr ""
151 147
152 148 #: rhodecode/apps/admin/views/system_info.py:114
153 msgid "Database"
149 msgid "Workers"
154 150 msgstr ""
155 151
156 152 #: rhodecode/apps/admin/views/system_info.py:115
157 msgid "Database version"
153 msgid "Worker Type"
158 154 msgstr ""
159 155
160 156 #: rhodecode/apps/admin/views/system_info.py:119
161 msgid "Platform"
157 msgid "Database"
162 158 msgstr ""
163 159
164 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 169 msgid "Platform UUID"
166 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 172 #: rhodecode/apps/admin/views/system_info.py:126
177 msgid "CPU"
173 msgid "Python version"
178 174 msgstr ""
179 175
180 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 185 msgid "Load"
182 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 188 #: rhodecode/apps/admin/views/system_info.py:133
193 msgid "Storage location"
189 msgid "Memory"
194 190 msgstr ""
195 191
196 192 #: rhodecode/apps/admin/views/system_info.py:134
197 msgid "Storage info"
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"
193 msgid "Uptime"
206 194 msgstr ""
207 195
208 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 202 msgstr ""
211 203
212 204 #: rhodecode/apps/admin/views/system_info.py:140
213 msgid "Archive cache storage location"
214 msgstr ""
215
216 #: rhodecode/apps/admin/views/system_info.py:141
217 msgid "Archive cache info"
205 msgid "Storage inodes"
206 msgstr ""
207
208 #: rhodecode/apps/admin/views/system_info.py:142
209 msgid "Gist storage location"
218 210 msgstr ""
219 211
220 212 #: rhodecode/apps/admin/views/system_info.py:143
221 msgid "Temp storage location"
222 msgstr ""
223
224 #: rhodecode/apps/admin/views/system_info.py:144
225 msgid "Temp storage info"
213 msgid "Gist storage info"
214 msgstr ""
215
216 #: rhodecode/apps/admin/views/system_info.py:145
217 msgid "Archive cache storage location"
226 218 msgstr ""
227 219
228 220 #: rhodecode/apps/admin/views/system_info.py:146
229 msgid "Search info"
230 msgstr ""
231
232 #: rhodecode/apps/admin/views/system_info.py:147
233 msgid "Search location"
221 msgid "Archive cache info"
222 msgstr ""
223
224 #: rhodecode/apps/admin/views/system_info.py:148
225 msgid "Temp storage location"
226 msgstr ""
227
228 #: rhodecode/apps/admin/views/system_info.py:149
229 msgid "Temp storage info"
234 230 msgstr ""
235 231
236 232 #: rhodecode/apps/admin/views/system_info.py:151
237 msgid "VCS Backends"
233 msgid "Search info"
238 234 msgstr ""
239 235
240 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 245 msgid "VCS Server"
242 246 msgstr ""
243 247
244 #: rhodecode/apps/admin/views/system_info.py:153
248 #: rhodecode/apps/admin/views/system_info.py:158
245 249 msgid "GIT"
246 250 msgstr ""
247 251
248 #: rhodecode/apps/admin/views/system_info.py:154
252 #: rhodecode/apps/admin/views/system_info.py:159
249 253 msgid "HG"
250 254 msgstr ""
251 255
252 #: rhodecode/apps/admin/views/system_info.py:155
256 #: rhodecode/apps/admin/views/system_info.py:160
253 257 msgid "SVN"
254 258 msgstr ""
255 259
256 #: rhodecode/apps/admin/views/users.py:60
257 #: rhodecode/controllers/admin/users.py:359
258 #: rhodecode/controllers/admin/users.py:380
260 #: rhodecode/apps/admin/views/users.py:63
261 #: rhodecode/controllers/admin/users.py:360
262 #: rhodecode/controllers/admin/users.py:381
259 263 #: rhodecode/controllers/admin/users.py:412
260 264 #: rhodecode/controllers/admin/users.py:486
261 #: rhodecode/controllers/admin/users.py:499
262 #: rhodecode/controllers/admin/users.py:557
263 265 msgid "You can't edit this user"
264 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 304 #: rhodecode/apps/admin/views/users.py:178
267 #: rhodecode/apps/my_account/views.py:138
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
305 #: rhodecode/apps/my_account/views.py:158
304 306 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:16
305 307 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:16
306 308 msgid "Role"
307 309 msgstr ""
308 310
309 #: rhodecode/apps/admin/views/users.py:219
310 #: rhodecode/apps/my_account/views.py:175
311 #: rhodecode/apps/admin/views/users.py:217
312 #: rhodecode/apps/my_account/views.py:191
311 313 msgid "Auth token successfully created"
312 314 msgstr ""
313 315
314 #: rhodecode/apps/admin/views/users.py:240
315 #: rhodecode/apps/my_account/views.py:192
316 #: rhodecode/apps/admin/views/users.py:246
317 #: rhodecode/apps/my_account/views.py:215
316 318 msgid "Auth token successfully deleted"
317 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 355 msgid "Groups successfully changed"
321 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 380 msgid "Bad captcha"
325 381 msgstr ""
326 382
327 #: rhodecode/apps/login/views.py:256
383 #: rhodecode/apps/login/views.py:279
328 384 msgid "You have successfully registered with RhodeCode"
329 385 msgstr ""
330 386
331 #: rhodecode/apps/login/views.py:292
387 #: rhodecode/apps/login/views.py:315
332 388 msgid "If such email exists, a password reset link was sent to it."
333 389 msgstr ""
334 390
335 #: rhodecode/apps/login/views.py:298
391 #: rhodecode/apps/login/views.py:321
336 392 msgid "Password reset has been disabled."
337 393 msgstr ""
338 394
339 #: rhodecode/apps/login/views.py:381
395 #: rhodecode/apps/login/views.py:410
340 396 msgid "Given reset token is invalid"
341 397 msgstr ""
342 398
343 #: rhodecode/apps/login/views.py:389
399 #: rhodecode/apps/login/views.py:418
344 400 msgid "Your password reset was successful, a new password has been sent to your email"
345 401 msgstr ""
346 402
347 #: rhodecode/apps/my_account/views.py:115
403 #: rhodecode/apps/my_account/views.py:125
348 404 msgid "Error occurred during update of user password"
349 405 msgstr ""
350 406
351 #: rhodecode/apps/my_account/views.py:122
407 #: rhodecode/apps/my_account/views.py:132
352 408 msgid "Successfully updated password"
353 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 519 #: rhodecode/apps/svn_support/events.py:30
356 520 msgid "Configuration for Apaache mad_dav_svn changed."
357 521 msgstr ""
@@ -408,7 +572,7 b' msgid "The Port in use by the Atlassian '
408 572 msgstr ""
409 573
410 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 576 msgid "Port"
413 577 msgstr ""
414 578
@@ -436,7 +600,7 b' msgstr ""'
436 600 msgid "Admin Groups"
437 601 msgstr ""
438 602
439 #: rhodecode/authentication/plugins/auth_crowd.py:215
603 #: rhodecode/authentication/plugins/auth_crowd.py:216
440 604 msgid "CROWD"
441 605 msgstr ""
442 606
@@ -483,126 +647,129 b' msgstr ""'
483 647
484 648 #: rhodecode/authentication/plugins/auth_ldap.py:74
485 649 msgid ""
486 "Host of the LDAP Server \n"
487 "(e.g., 192.168.2.154, or ldap-server.domain.com"
488 msgstr ""
489
490 #: rhodecode/authentication/plugins/auth_ldap.py:77
650 "Host[s] of the LDAP Server \n"
651 "(e.g., 192.168.2.154, or ldap-server.domain.com.\n"
652 " Multiple servers can be specified using commas"
653 msgstr ""
654
655 #: rhodecode/authentication/plugins/auth_ldap.py:78
491 656 msgid "LDAP Host"
492 657 msgstr ""
493 658
494 #: rhodecode/authentication/plugins/auth_ldap.py:82
495 msgid "Custom port that the LDAP server is listening on. Default: 389"
496 msgstr ""
497
498 #: rhodecode/authentication/plugins/auth_ldap.py:90
659 #: rhodecode/authentication/plugins/auth_ldap.py:83
660 msgid "Custom port that the LDAP server is listening on. Default value is: 389"
661 msgstr ""
662
663 #: rhodecode/authentication/plugins/auth_ldap.py:92
499 664 msgid ""
500 665 "Optional user DN/account to connect to LDAP if authentication is required. \n"
501 666 "e.g., cn=admin,dc=mydomain,dc=com, or uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com"
502 667 msgstr ""
503 668
504 #: rhodecode/authentication/plugins/auth_ldap.py:95
669 #: rhodecode/authentication/plugins/auth_ldap.py:97
505 670 msgid "Account"
506 671 msgstr ""
507 672
508 #: rhodecode/authentication/plugins/auth_ldap.py:100
673 #: rhodecode/authentication/plugins/auth_ldap.py:102
509 674 msgid "Password to authenticate for given user DN."
510 675 msgstr ""
511 676
512 #: rhodecode/authentication/plugins/auth_ldap.py:103
677 #: rhodecode/authentication/plugins/auth_ldap.py:105
513 678 #: rhodecode/templates/login.mako:50 rhodecode/templates/register.mako:48
514 679 #: rhodecode/templates/admin/my_account/my_account.mako:30
515 680 #: rhodecode/templates/admin/users/user_add.mako:44
516 #: rhodecode/templates/base/base.mako:313
681 #: rhodecode/templates/base/base.mako:315
517 682 #: rhodecode/templates/debug_style/login.html:45
518 683 msgid "Password"
519 684 msgstr ""
520 685
521 #: rhodecode/authentication/plugins/auth_ldap.py:108
686 #: rhodecode/authentication/plugins/auth_ldap.py:110
522 687 msgid "TLS Type"
523 688 msgstr ""
524 689
525 #: rhodecode/authentication/plugins/auth_ldap.py:109
690 #: rhodecode/authentication/plugins/auth_ldap.py:111
526 691 msgid "Connection Security"
527 692 msgstr ""
528 693
529 #: rhodecode/authentication/plugins/auth_ldap.py:115
530 msgid "Require Cert over TLS?"
531 msgstr ""
532
533 #: rhodecode/authentication/plugins/auth_ldap.py:116
694 #: rhodecode/authentication/plugins/auth_ldap.py:117
695 msgid ""
696 "Require Cert over TLS?. Self-signed and custom certificates can be used when\n"
697 " `RhodeCode Certificate` found in admin > settings > system info page is extended."
698 msgstr ""
699
700 #: rhodecode/authentication/plugins/auth_ldap.py:120
534 701 msgid "Certificate Checks"
535 702 msgstr ""
536 703
537 #: rhodecode/authentication/plugins/auth_ldap.py:122
704 #: rhodecode/authentication/plugins/auth_ldap.py:126
538 705 msgid ""
539 706 "Base DN to search. Dynamic bind is supported. Add `$login` marker in it to be replaced with current user credentials \n"
540 707 "(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)"
541 708 msgstr ""
542 709
543 #: rhodecode/authentication/plugins/auth_ldap.py:127
710 #: rhodecode/authentication/plugins/auth_ldap.py:131
544 711 msgid "Base DN"
545 712 msgstr ""
546 713
547 #: rhodecode/authentication/plugins/auth_ldap.py:132
714 #: rhodecode/authentication/plugins/auth_ldap.py:136
548 715 msgid ""
549 716 "Filter to narrow results \n"
550 717 "(e.g., (&(objectCategory=Person)(objectClass=user)), or \n"
551 718 "(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))"
552 719 msgstr ""
553 720
554 #: rhodecode/authentication/plugins/auth_ldap.py:137
721 #: rhodecode/authentication/plugins/auth_ldap.py:141
555 722 msgid "LDAP Search Filter"
556 723 msgstr ""
557 724
558 #: rhodecode/authentication/plugins/auth_ldap.py:143
725 #: rhodecode/authentication/plugins/auth_ldap.py:147
559 726 msgid "How deep to search LDAP. If unsure set to SUBTREE"
560 727 msgstr ""
561 728
562 #: rhodecode/authentication/plugins/auth_ldap.py:144
729 #: rhodecode/authentication/plugins/auth_ldap.py:148
563 730 msgid "LDAP Search Scope"
564 731 msgstr ""
565 732
566 #: rhodecode/authentication/plugins/auth_ldap.py:150
733 #: rhodecode/authentication/plugins/auth_ldap.py:154
567 734 msgid "LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)"
568 735 msgstr ""
569 736
570 #: rhodecode/authentication/plugins/auth_ldap.py:152
737 #: rhodecode/authentication/plugins/auth_ldap.py:156
571 738 msgid "Login Attribute"
572 739 msgstr ""
573 740
574 #: rhodecode/authentication/plugins/auth_ldap.py:153
741 #: rhodecode/authentication/plugins/auth_ldap.py:157
575 742 msgid "The LDAP Login attribute of the CN must be specified"
576 743 msgstr ""
577 744
578 #: rhodecode/authentication/plugins/auth_ldap.py:158
745 #: rhodecode/authentication/plugins/auth_ldap.py:162
579 746 msgid "LDAP Attribute to map to first name (e.g., givenName)"
580 747 msgstr ""
581 748
582 #: rhodecode/authentication/plugins/auth_ldap.py:161
749 #: rhodecode/authentication/plugins/auth_ldap.py:165
583 750 msgid "First Name Attribute"
584 751 msgstr ""
585 752
586 #: rhodecode/authentication/plugins/auth_ldap.py:166
753 #: rhodecode/authentication/plugins/auth_ldap.py:170
587 754 msgid "LDAP Attribute to map to last name (e.g., sn)"
588 755 msgstr ""
589 756
590 #: rhodecode/authentication/plugins/auth_ldap.py:169
757 #: rhodecode/authentication/plugins/auth_ldap.py:173
591 758 msgid "Last Name Attribute"
592 759 msgstr ""
593 760
594 #: rhodecode/authentication/plugins/auth_ldap.py:174
761 #: rhodecode/authentication/plugins/auth_ldap.py:178
595 762 msgid ""
596 763 "LDAP Attribute to map to email address (e.g., mail).\n"
597 764 "Emails are a crucial part of RhodeCode. \n"
598 765 "If possible add a valid email attribute to ldap users."
599 766 msgstr ""
600 767
601 #: rhodecode/authentication/plugins/auth_ldap.py:179
768 #: rhodecode/authentication/plugins/auth_ldap.py:183
602 769 msgid "Email Attribute"
603 770 msgstr ""
604 771
605 #: rhodecode/authentication/plugins/auth_ldap.py:360
772 #: rhodecode/authentication/plugins/auth_ldap.py:365
606 773 msgid "LDAP"
607 774 msgstr ""
608 775
@@ -634,74 +801,71 b' msgstr ""'
634 801 msgid "Rhodecode Token Auth"
635 802 msgstr ""
636 803
637 #: rhodecode/controllers/changelog.py:91 rhodecode/controllers/compare.py:64
638 #: rhodecode/controllers/pullrequests.py:204
804 #: rhodecode/controllers/changelog.py:70 rhodecode/controllers/compare.py:64
805 #: rhodecode/controllers/pullrequests.py:85
639 806 msgid "There are no commits yet"
640 807 msgstr ""
641 808
809 #: rhodecode/controllers/changeset.py:76
810 msgid "Show whitespace"
811 msgstr ""
812
642 813 #: rhodecode/controllers/changeset.py:77
643 msgid "Show whitespace"
644 msgstr ""
645
646 #: rhodecode/controllers/changeset.py:78
647 814 msgid "Show whitespace for all diffs"
648 815 msgstr ""
649 816
817 #: rhodecode/controllers/changeset.py:83
818 msgid "Ignore whitespace"
819 msgstr ""
820
650 821 #: rhodecode/controllers/changeset.py:84
651 msgid "Ignore whitespace"
652 msgstr ""
653
654 #: rhodecode/controllers/changeset.py:85
655 822 msgid "Ignore whitespace for all diffs"
656 823 msgstr ""
657 824
825 #: rhodecode/controllers/changeset.py:140
826 msgid "Increase context"
827 msgstr ""
828
658 829 #: rhodecode/controllers/changeset.py:141
659 msgid "Increase context"
660 msgstr ""
661
662 #: rhodecode/controllers/changeset.py:142
663 830 msgid "Increase context for all diffs"
664 831 msgstr ""
665 832
666 #: rhodecode/controllers/changeset.py:190 rhodecode/controllers/files.py:106
667 #: rhodecode/controllers/files.py:127
833 #: rhodecode/controllers/changeset.py:189 rhodecode/controllers/files.py:106
834 #: rhodecode/controllers/files.py:128
668 835 msgid "No such commit exists for this repository"
669 836 msgstr ""
670 837
671 #: rhodecode/controllers/changeset.py:344
672 #: rhodecode/controllers/pullrequests.py:985
673 #: rhodecode/model/pull_request.py:1055
838 #: rhodecode/controllers/changeset.py:343
839 #: rhodecode/controllers/pullrequests.py:919
674 840 #, python-format
675 841 msgid "Status change %(transition_icon)s %(status)s"
676 842 msgstr ""
677 843
678 #: rhodecode/controllers/changeset.py:389
844 #: rhodecode/controllers/changeset.py:387
679 845 msgid "Changing the status of a commit associated with a closed pull request is not allowed"
680 846 msgstr ""
681 847
682 #: rhodecode/controllers/compare.py:89
848 #: rhodecode/controllers/compare.py:92
683 849 msgid "Select commit"
684 850 msgstr ""
685 851
686 #: rhodecode/controllers/compare.py:144
687 #, python-format
688 msgid "Could not find the original repo: %(repo)s"
689 msgstr ""
690
691 #: rhodecode/controllers/compare.py:152
692 #, python-format
693 msgid "Could not find the other repo: %(repo)s"
694 msgstr ""
695
696 #: rhodecode/controllers/compare.py:164
852 #: rhodecode/controllers/compare.py:149
853 msgid "Could not find the source repo: `{}`"
854 msgstr ""
855
856 #: rhodecode/controllers/compare.py:156
857 msgid "Could not find the target repo: `{}`"
858 msgstr ""
859
860 #: rhodecode/controllers/compare.py:166
697 861 msgid "The comparison of two different kinds of remote repos is not available"
698 862 msgstr ""
699 863
700 #: rhodecode/controllers/compare.py:202
864 #: rhodecode/controllers/compare.py:204
701 865 msgid "Could not compare repos with different large file settings"
702 866 msgstr ""
703 867
704 #: rhodecode/controllers/compare.py:242
868 #: rhodecode/controllers/compare.py:244
705 869 #, python-format
706 870 msgid "Repositories unrelated. Cannot compare commit %(commit1)s from repository %(repo1)s with commit %(commit2)s from repository %(repo2)s."
707 871 msgstr ""
@@ -725,51 +889,47 b' msgstr ""'
725 889 msgid "There are no files yet. %s"
726 890 msgstr ""
727 891
728 #: rhodecode/controllers/files.py:435 rhodecode/controllers/files.py:488
729 #: rhodecode/controllers/files.py:519 rhodecode/controllers/files.py:594
730 #: rhodecode/controllers/files.py:639 rhodecode/controllers/files.py:730
892 #: rhodecode/controllers/files.py:434 rhodecode/controllers/files.py:487
893 #: rhodecode/controllers/files.py:518 rhodecode/controllers/files.py:593
894 #: rhodecode/controllers/files.py:638 rhodecode/controllers/files.py:729
731 895 #, python-format
732 896 msgid "This repository has been locked by %s on %s"
733 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 900 msgid "You can only delete files with revision being a valid branch "
737 901 msgstr ""
738 902
739 #: rhodecode/controllers/files.py:452 rhodecode/controllers/files.py:505
740 #, python-format
741 msgid "Deleted file %s via RhodeCode Enterprise"
903 #: rhodecode/controllers/files.py:451 rhodecode/controllers/files.py:504
904 msgid "Deleted file {} via RhodeCode Enterprise"
742 905 msgstr ""
743 906
744 907 #: rhodecode/controllers/files.py:472
745 #, python-format
746 msgid "Successfully deleted file %s"
747 msgstr ""
748
749 #: rhodecode/controllers/files.py:475 rhodecode/controllers/files.py:581
750 #: rhodecode/controllers/files.py:718
908 msgid "Successfully deleted file `{}`"
909 msgstr ""
910
911 #: rhodecode/controllers/files.py:476 rhodecode/controllers/files.py:582
912 #: rhodecode/controllers/files.py:719
751 913 msgid "Error occurred during commit"
752 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 917 msgid "You can only edit files with revision being a valid branch "
756 918 msgstr ""
757 919
758 #: rhodecode/controllers/files.py:539 rhodecode/controllers/files.py:614
759 #, python-format
760 msgid "Edited file %s via RhodeCode Enterprise"
761 msgstr ""
762
763 #: rhodecode/controllers/files.py:556
920 #: rhodecode/controllers/files.py:538 rhodecode/controllers/files.py:613
921 msgid "Edited file {} via RhodeCode Enterprise"
922 msgstr ""
923
924 #: rhodecode/controllers/files.py:555
764 925 msgid "No changes"
765 926 msgstr ""
766 927
767 #: rhodecode/controllers/files.py:578 rhodecode/controllers/files.py:707
768 #, python-format
769 msgid "Successfully committed to %s"
770 msgstr ""
771
772 #: rhodecode/controllers/files.py:652 rhodecode/controllers/files.py:741
928 #: rhodecode/controllers/files.py:578
929 msgid "Successfully committed changes to file `{}`"
930 msgstr ""
931
932 #: rhodecode/controllers/files.py:651 rhodecode/controllers/files.py:740
773 933 msgid "Added file via RhodeCode Enterprise"
774 934 msgstr ""
775 935
@@ -777,39 +937,37 b' msgstr ""'
777 937 msgid "No filename"
778 938 msgstr ""
779 939
940 #: rhodecode/controllers/files.py:707
941 msgid "Successfully committed new file `{}`"
942 msgstr ""
943
780 944 #: rhodecode/controllers/files.py:710
781 945 msgid "The location specified must be a relative path and must not contain .. in the path"
782 946 msgstr ""
783 947
784 #: rhodecode/controllers/files.py:764
948 #: rhodecode/controllers/files.py:763
785 949 msgid "Downloads disabled"
786 950 msgstr ""
787 951
788 #: rhodecode/controllers/files.py:770
952 #: rhodecode/controllers/files.py:769
789 953 #, python-format
790 954 msgid "Unknown revision %s"
791 955 msgstr ""
792 956
793 #: rhodecode/controllers/files.py:772
957 #: rhodecode/controllers/files.py:771
794 958 msgid "Empty repository"
795 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 962 msgid "Unknown archive type"
799 963 msgstr ""
800 964
801 #: rhodecode/controllers/files.py:993
965 #: rhodecode/controllers/files.py:1000
802 966 msgid "Changesets"
803 967 msgstr ""
804 968
805 #: rhodecode/controllers/files.py:1014 rhodecode/controllers/summary.py:277
806 #: rhodecode/model/pull_request.py:1280 rhodecode/model/scm.py:782
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
969 #: rhodecode/controllers/files.py:1025 rhodecode/model/scm.py:790
970 #: rhodecode/templates/base/vcs_settings.mako:280
813 971 msgid "Tags"
814 972 msgstr ""
815 973
@@ -818,27 +976,6 b' msgstr ""'
818 976 msgid "An error occurred during repository forking %s"
819 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 979 #: rhodecode/controllers/journal.py:107 rhodecode/controllers/journal.py:150
843 980 msgid "public journal"
844 981 msgstr ""
@@ -847,80 +984,58 b' msgstr ""'
847 984 msgid "journal"
848 985 msgstr ""
849 986
850 #: rhodecode/controllers/pullrequests.py:218
987 #: rhodecode/controllers/pullrequests.py:99
851 988 msgid "Commit does not exist"
852 989 msgstr ""
853 990
854 #: rhodecode/controllers/pullrequests.py:335
991 #: rhodecode/controllers/pullrequests.py:216
855 992 msgid "Pull request requires a title with min. 3 chars"
856 993 msgstr ""
857 994
858 #: rhodecode/controllers/pullrequests.py:337
995 #: rhodecode/controllers/pullrequests.py:218
859 996 msgid "Error creating pull request: {}"
860 997 msgstr ""
861 998
862 #: rhodecode/controllers/pullrequests.py:385
999 #: rhodecode/controllers/pullrequests.py:276
863 1000 msgid "Successfully opened new pull request"
864 1001 msgstr ""
865 1002
866 #: rhodecode/controllers/pullrequests.py:388
867 msgid "Error occurred during sending pull request"
868 msgstr ""
869
870 #: rhodecode/controllers/pullrequests.py:431
1003 #: rhodecode/controllers/pullrequests.py:279
1004 msgid "Error occurred during creation of this pull request."
1005 msgstr ""
1006
1007 #: rhodecode/controllers/pullrequests.py:322
871 1008 msgid "Cannot update closed pull requests."
872 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 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 1024 msgid "Pull request was successfully merged and closed."
888 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 1032 msgid "Successfully deleted pull request"
892 1033 msgstr ""
893 1034
894 #: rhodecode/controllers/pullrequests.py:592
1035 #: rhodecode/controllers/pullrequests.py:486
895 1036 msgid "Your are not allowed to delete this pull request"
896 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 1039 #: rhodecode/controllers/admin/defaults.py:84
925 1040 msgid "Default settings updated successfully"
926 1041 msgstr ""
@@ -976,89 +1091,73 b' msgstr ""'
976 1091 msgid "%(expiry)s - current value"
977 1092 msgstr ""
978 1093
979 #: rhodecode/controllers/admin/my_account.py:78
1094 #: rhodecode/controllers/admin/my_account.py:70
980 1095 msgid "You can't edit this user since it's crucial for entire application"
981 1096 msgstr ""
982 1097
983 #: rhodecode/controllers/admin/my_account.py:138
1098 #: rhodecode/controllers/admin/my_account.py:110
984 1099 msgid "Your account was updated successfully"
985 1100 msgstr ""
986 1101
987 #: rhodecode/controllers/admin/my_account.py:153
988 #: rhodecode/controllers/admin/users.py:184
1102 #: rhodecode/controllers/admin/my_account.py:125
1103 #: rhodecode/controllers/admin/users.py:182
989 1104 #, python-format
990 1105 msgid "Error occurred during update of user %s"
991 1106 msgstr ""
992 1107
993 #: rhodecode/controllers/admin/my_account.py:222
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
1108 #: rhodecode/controllers/admin/permissions.py:107
1010 1109 msgid "Application permissions updated successfully"
1011 1110 msgstr ""
1012 1111
1013 #: rhodecode/controllers/admin/permissions.py:127
1014 #: rhodecode/controllers/admin/permissions.py:176
1015 #: rhodecode/controllers/admin/permissions.py:230
1112 #: rhodecode/controllers/admin/permissions.py:122
1113 #: rhodecode/controllers/admin/permissions.py:171
1114 #: rhodecode/controllers/admin/permissions.py:225
1016 1115 msgid "Error occurred during update of permissions"
1017 1116 msgstr ""
1018 1117
1019 #: rhodecode/controllers/admin/permissions.py:161
1118 #: rhodecode/controllers/admin/permissions.py:156
1020 1119 msgid "Object permissions updated successfully"
1021 1120 msgstr ""
1022 1121
1023 #: rhodecode/controllers/admin/permissions.py:215
1122 #: rhodecode/controllers/admin/permissions.py:210
1024 1123 msgid "Global permissions updated successfully"
1025 1124 msgstr ""
1026 1125
1027 #: rhodecode/controllers/admin/repo_groups.py:197
1126 #: rhodecode/controllers/admin/repo_groups.py:202
1028 1127 #, python-format
1029 1128 msgid "Created repository group %s"
1030 1129 msgstr ""
1031 1130
1032 #: rhodecode/controllers/admin/repo_groups.py:210
1131 #: rhodecode/controllers/admin/repo_groups.py:215
1033 1132 #, python-format
1034 1133 msgid "Error occurred during creation of repository group %s"
1035 1134 msgstr ""
1036 1135
1037 #: rhodecode/controllers/admin/repo_groups.py:258
1136 #: rhodecode/controllers/admin/repo_groups.py:261
1038 1137 #, python-format
1039 1138 msgid "Updated repository group %s"
1040 1139 msgstr ""
1041 1140
1042 #: rhodecode/controllers/admin/repo_groups.py:274
1141 #: rhodecode/controllers/admin/repo_groups.py:276
1043 1142 #, python-format
1044 1143 msgid "Error occurred during update of repository group %s"
1045 1144 msgstr ""
1046 1145
1047 #: rhodecode/controllers/admin/repo_groups.py:296
1146 #: rhodecode/controllers/admin/repo_groups.py:291
1048 1147 #, python-format
1049 1148 msgid "This group contains %(num)d repository and cannot be deleted"
1050 1149 msgid_plural "This group contains %(num)d repositories and cannot be deleted"
1051 1150 msgstr[0] ""
1052 1151 msgstr[1] ""
1053 1152
1054 #: rhodecode/controllers/admin/repo_groups.py:305
1153 #: rhodecode/controllers/admin/repo_groups.py:300
1055 1154 #, python-format
1056 1155 msgid "This group contains %(num)d subgroup and cannot be deleted"
1057 1156 msgid_plural "This group contains %(num)d subgroups and cannot be deleted"
1058 1157 msgstr[0] ""
1059 1158 msgstr[1] ""
1060 1159
1061 #: rhodecode/controllers/admin/repo_groups.py:312
1160 #: rhodecode/controllers/admin/repo_groups.py:313
1062 1161 #, python-format
1063 1162 msgid "Removed repository group %s"
1064 1163 msgstr ""
@@ -1068,366 +1167,285 b' msgstr ""'
1068 1167 msgid "Error occurred during deletion of repository group %s"
1069 1168 msgstr ""
1070 1169
1071 #: rhodecode/controllers/admin/repo_groups.py:388
1072 #: rhodecode/controllers/admin/user_groups.py:323
1170 #: rhodecode/controllers/admin/repo_groups.py:381
1171 #: rhodecode/controllers/admin/user_groups.py:318
1073 1172 msgid "Cannot change permission for yourself as admin"
1074 1173 msgstr ""
1075 1174
1076 #: rhodecode/controllers/admin/repo_groups.py:405
1175 #: rhodecode/controllers/admin/repo_groups.py:404
1077 1176 msgid "Repository Group permissions updated"
1078 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 1184 #: rhodecode/controllers/admin/repos.py:129
1081 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 1186 msgid "Error creating repository %s"
1088 1187 msgstr ""
1089 1188
1090 #: rhodecode/controllers/admin/repos.py:274
1189 #: rhodecode/controllers/admin/repos.py:270
1091 1190 #, python-format
1092 1191 msgid "Created repository %s from %s"
1093 1192 msgstr ""
1094 1193
1095 #: rhodecode/controllers/admin/repos.py:283
1194 #: rhodecode/controllers/admin/repos.py:279
1096 1195 #, python-format
1097 1196 msgid "Forked repository %s as %s"
1098 1197 msgstr ""
1099 1198
1100 #: rhodecode/controllers/admin/repos.py:286
1199 #: rhodecode/controllers/admin/repos.py:282
1101 1200 #, python-format
1102 1201 msgid "Created repository %s"
1103 1202 msgstr ""
1104 1203
1105 #: rhodecode/controllers/admin/repos.py:327
1106 #, python-format
1107 msgid "Repository %s updated successfully"
1108 msgstr ""
1109
1110 #: rhodecode/controllers/admin/repos.py:346
1111 #, python-format
1112 msgid "Error occurred during update of repository %s"
1113 msgstr ""
1114
1115 #: rhodecode/controllers/admin/repos.py:374
1116 #, python-format
1117 msgid "Detached %s forks"
1118 msgstr ""
1119
1120 #: rhodecode/controllers/admin/repos.py:377
1121 #, python-format
1122 msgid "Deleted %s forks"
1123 msgstr ""
1124
1125 #: rhodecode/controllers/admin/repos.py:382
1126 #, python-format
1127 msgid "Deleted repository %s"
1128 msgstr ""
1129
1130 #: rhodecode/controllers/admin/repos.py:385
1131 #, python-format
1132 msgid "Cannot delete %s it still contains attached forks"
1133 msgstr ""
1134
1135 #: rhodecode/controllers/admin/repos.py:390
1136 #, python-format
1137 msgid "An error occurred during deletion of %s"
1204 #: rhodecode/controllers/admin/repos.py:319
1205 msgid "An error occurred during creation of field"
1206 msgstr ""
1207
1208 #: rhodecode/controllers/admin/repos.py:334
1209 msgid "An error occurred during removal of field"
1210 msgstr ""
1211
1212 #: rhodecode/controllers/admin/repos.py:353
1213 msgid "Unlocked"
1214 msgstr ""
1215
1216 #: rhodecode/controllers/admin/repos.py:357
1217 msgid "Locked"
1218 msgstr ""
1219
1220 #: rhodecode/controllers/admin/repos.py:359
1221 #, python-format
1222 msgid "Repository has been %s"
1223 msgstr ""
1224
1225 #: rhodecode/controllers/admin/repos.py:373
1226 msgid "Pulled from remote location"
1227 msgstr ""
1228
1229 #: rhodecode/controllers/admin/repos.py:376
1230 msgid "An error occurred during pull from remote location"
1231 msgstr ""
1232
1233 #: rhodecode/controllers/admin/repos.py:397
1234 msgid "An error occurred during deletion of repository stats"
1138 1235 msgstr ""
1139 1236
1140 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 1238 msgid "Error occurred during deleting issue tracker entry"
1221 1239 msgstr ""
1222 1240
1223 #: rhodecode/controllers/admin/repos.py:728
1224 #: rhodecode/controllers/admin/settings.py:381
1241 #: rhodecode/controllers/admin/repos.py:446
1242 #: rhodecode/controllers/admin/settings.py:384
1225 1243 msgid "Removed issue tracker entry"
1226 1244 msgstr ""
1227 1245
1228 #: rhodecode/controllers/admin/repos.py:758
1229 #: rhodecode/controllers/admin/settings.py:428
1246 #: rhodecode/controllers/admin/repos.py:476
1247 #: rhodecode/controllers/admin/settings.py:431
1230 1248 msgid "Updated issue tracker entries"
1231 1249 msgstr ""
1232 1250
1233 #: rhodecode/controllers/admin/repos.py:819
1251 #: rhodecode/controllers/admin/repos.py:537
1234 1252 #: rhodecode/controllers/admin/settings.py:147
1235 #: rhodecode/controllers/admin/settings.py:619
1253 #: rhodecode/controllers/admin/settings.py:622
1236 1254 msgid "Some form inputs contain invalid data."
1237 1255 msgstr ""
1238 1256
1239 #: rhodecode/controllers/admin/repos.py:837
1257 #: rhodecode/controllers/admin/repos.py:555
1240 1258 msgid "Error occurred during updating repository VCS settings"
1241 1259 msgstr ""
1242 1260
1243 #: rhodecode/controllers/admin/repos.py:841
1261 #: rhodecode/controllers/admin/repos.py:559
1244 1262 #: rhodecode/controllers/admin/settings.py:176
1245 1263 msgid "Updated VCS settings"
1246 1264 msgstr ""
1247 1265
1248 1266 #: rhodecode/controllers/admin/settings.py:172
1249 #: rhodecode/controllers/admin/settings.py:283
1267 #: rhodecode/controllers/admin/settings.py:286
1250 1268 msgid "Error occurred during updating application settings"
1251 1269 msgstr ""
1252 1270
1253 #: rhodecode/controllers/admin/settings.py:223
1271 #: rhodecode/controllers/admin/settings.py:226
1254 1272 #, python-format
1255 1273 msgid "Repositories successfully rescanned added: %s ; removed: %s"
1256 1274 msgstr ""
1257 1275
1258 #: rhodecode/controllers/admin/settings.py:279
1276 #: rhodecode/controllers/admin/settings.py:282
1259 1277 msgid "Updated application settings"
1260 1278 msgstr ""
1261 1279
1262 #: rhodecode/controllers/admin/settings.py:345
1263 msgid "Updated visualisation settings"
1264 msgstr ""
1265
1266 1280 #: rhodecode/controllers/admin/settings.py:348
1281 msgid "Updated visualisation settings"
1282 msgstr ""
1283
1284 #: rhodecode/controllers/admin/settings.py:351
1267 1285 msgid "Error occurred during updating visualisation settings"
1268 1286 msgstr ""
1269 1287
1270 #: rhodecode/controllers/admin/settings.py:441
1288 #: rhodecode/controllers/admin/settings.py:444
1271 1289 msgid "Please enter email address"
1272 1290 msgstr ""
1273 1291
1274 #: rhodecode/controllers/admin/settings.py:459
1292 #: rhodecode/controllers/admin/settings.py:462
1275 1293 msgid "Send email task created"
1276 1294 msgstr ""
1277 1295
1278 #: rhodecode/controllers/admin/settings.py:492
1296 #: rhodecode/controllers/admin/settings.py:495
1279 1297 msgid "Added new hook"
1280 1298 msgstr ""
1281 1299
1282 #: rhodecode/controllers/admin/settings.py:507
1300 #: rhodecode/controllers/admin/settings.py:510
1283 1301 msgid "Updated hooks"
1284 1302 msgstr ""
1285 1303
1286 #: rhodecode/controllers/admin/settings.py:511
1304 #: rhodecode/controllers/admin/settings.py:514
1287 1305 msgid "Error occurred during hook creation"
1288 1306 msgstr ""
1289 1307
1290 #: rhodecode/controllers/admin/settings.py:640
1308 #: rhodecode/controllers/admin/settings.py:643
1291 1309 msgid "Error occurred during updating labs settings"
1292 1310 msgstr ""
1293 1311
1294 #: rhodecode/controllers/admin/settings.py:645
1312 #: rhodecode/controllers/admin/settings.py:648
1295 1313 msgid "Updated Labs settings"
1296 1314 msgstr ""
1297 1315
1298 #: rhodecode/controllers/admin/user_groups.py:165
1316 #: rhodecode/controllers/admin/user_groups.py:164
1299 1317 #, python-format
1300 1318 msgid "Created user group %(user_group_link)s"
1301 1319 msgstr ""
1302 1320
1303 #: rhodecode/controllers/admin/user_groups.py:179
1321 #: rhodecode/controllers/admin/user_groups.py:178
1304 1322 #, python-format
1305 1323 msgid "Error occurred during creation of user group %s"
1306 1324 msgstr ""
1307 1325
1308 #: rhodecode/controllers/admin/user_groups.py:220
1326 #: rhodecode/controllers/admin/user_groups.py:218
1309 1327 #, python-format
1310 1328 msgid "Updated user group %s"
1311 1329 msgstr ""
1312 1330
1313 #: rhodecode/controllers/admin/user_groups.py:236
1331 #: rhodecode/controllers/admin/user_groups.py:234
1314 1332 #, python-format
1315 1333 msgid "Error occurred during update of user group %s"
1316 1334 msgstr ""
1317 1335
1336 #: rhodecode/controllers/admin/user_groups.py:253
1337 msgid "Successfully deleted user group"
1338 msgstr ""
1339
1318 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 1341 msgid "An error occurred during deletion of user group"
1324 1342 msgstr ""
1325 1343
1326 #: rhodecode/controllers/admin/user_groups.py:331
1344 #: rhodecode/controllers/admin/user_groups.py:326
1327 1345 msgid "Target group cannot be the same"
1328 1346 msgstr ""
1329 1347
1330 #: rhodecode/controllers/admin/user_groups.py:337
1348 #: rhodecode/controllers/admin/user_groups.py:332
1331 1349 msgid "User Group permissions updated"
1332 1350 msgstr ""
1333 1351
1334 #: rhodecode/controllers/admin/user_groups.py:422
1352 #: rhodecode/controllers/admin/user_groups.py:415
1335 1353 msgid "User Group global permissions updated successfully"
1336 1354 msgstr ""
1337 1355
1338 #: rhodecode/controllers/admin/user_groups.py:437
1356 #: rhodecode/controllers/admin/user_groups.py:430
1339 1357 #: rhodecode/controllers/admin/users.py:477
1340 1358 msgid "An error occurred during permissions saving"
1341 1359 msgstr ""
1342 1360
1343 #: rhodecode/controllers/admin/user_groups.py:481
1361 #: rhodecode/controllers/admin/user_groups.py:474
1344 1362 msgid "User Group synchronization updated successfully"
1345 1363 msgstr ""
1346 1364
1347 #: rhodecode/controllers/admin/user_groups.py:485
1365 #: rhodecode/controllers/admin/user_groups.py:478
1348 1366 msgid "An error occurred during synchronization update"
1349 1367 msgstr ""
1350 1368
1351 #: rhodecode/controllers/admin/users.py:106
1369 #: rhodecode/controllers/admin/users.py:108
1352 1370 #, python-format
1353 1371 msgid "Created user %(user_link)s"
1354 1372 msgstr ""
1355 1373
1356 #: rhodecode/controllers/admin/users.py:122
1374 #: rhodecode/controllers/admin/users.py:124
1357 1375 #, python-format
1358 1376 msgid "Error occurred during creation of user %s"
1359 1377 msgstr ""
1360 1378
1361 #: rhodecode/controllers/admin/users.py:167
1379 #: rhodecode/controllers/admin/users.py:166
1362 1380 msgid "User updated successfully"
1363 1381 msgstr ""
1364 1382
1365 #: rhodecode/controllers/admin/users.py:218
1383 #: rhodecode/controllers/admin/users.py:209
1366 1384 #, python-format
1367 1385 msgid "Detached %s repositories"
1368 1386 msgstr ""
1369 1387
1370 #: rhodecode/controllers/admin/users.py:223
1388 #: rhodecode/controllers/admin/users.py:214
1371 1389 #, python-format
1372 1390 msgid "Deleted %s repositories"
1373 1391 msgstr ""
1374 1392
1375 #: rhodecode/controllers/admin/users.py:231
1393 #: rhodecode/controllers/admin/users.py:222
1376 1394 #, python-format
1377 1395 msgid "Detached %s repository groups"
1378 1396 msgstr ""
1379 1397
1380 #: rhodecode/controllers/admin/users.py:236
1398 #: rhodecode/controllers/admin/users.py:227
1381 1399 #, python-format
1382 1400 msgid "Deleted %s repository groups"
1383 1401 msgstr ""
1384 1402
1385 #: rhodecode/controllers/admin/users.py:244
1403 #: rhodecode/controllers/admin/users.py:235
1386 1404 #, python-format
1387 1405 msgid "Detached %s user groups"
1388 1406 msgstr ""
1389 1407
1390 #: rhodecode/controllers/admin/users.py:249
1408 #: rhodecode/controllers/admin/users.py:240
1391 1409 #, python-format
1392 1410 msgid "Deleted %s user groups"
1393 1411 msgstr ""
1394 1412
1395 #: rhodecode/controllers/admin/users.py:260
1413 #: rhodecode/controllers/admin/users.py:257
1396 1414 msgid "Successfully deleted user"
1397 1415 msgstr ""
1398 1416
1399 #: rhodecode/controllers/admin/users.py:266
1417 #: rhodecode/controllers/admin/users.py:263
1400 1418 msgid "An error occurred during deletion of user"
1401 1419 msgstr ""
1402 1420
1421 #: rhodecode/controllers/admin/users.py:280
1422 msgid "Force password change disabled for user"
1423 msgstr ""
1424
1403 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 1426 msgid "Force password change enabled for user"
1409 1427 msgstr ""
1410 1428
1411 #: rhodecode/controllers/admin/users.py:291
1429 #: rhodecode/controllers/admin/users.py:294
1412 1430 msgid "An error occurred during password reset for user"
1413 1431 msgstr ""
1414 1432
1415 #: rhodecode/controllers/admin/users.py:324
1433 #: rhodecode/controllers/admin/users.py:325
1416 1434 #, python-format
1417 1435 msgid "Linked repository group `%s` as personal"
1418 1436 msgstr ""
1419 1437
1420 #: rhodecode/controllers/admin/users.py:330
1438 #: rhodecode/controllers/admin/users.py:331
1421 1439 #, python-format
1422 1440 msgid "Created repository group `%s`"
1423 1441 msgstr ""
1424 1442
1425 #: rhodecode/controllers/admin/users.py:334
1443 #: rhodecode/controllers/admin/users.py:335
1426 1444 #, python-format
1427 1445 msgid "Repository group `%s` is already taken"
1428 1446 msgstr ""
1429 1447
1430 #: rhodecode/controllers/admin/users.py:339
1448 #: rhodecode/controllers/admin/users.py:340
1431 1449 msgid "An error occurred during repository group creation for user"
1432 1450 msgstr ""
1433 1451
@@ -1435,81 +1453,63 b' msgstr ""'
1435 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 1454 msgstr ""
1437 1455
1438 #: rhodecode/controllers/admin/users.py:461
1456 #: rhodecode/controllers/admin/users.py:462
1439 1457 msgid "User global permissions updated successfully"
1440 1458 msgstr ""
1441 1459
1442 #: rhodecode/controllers/admin/users.py:589
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
1460 #: rhodecode/events/pullrequest.py:71
1461 1461 msgid "pullrequest created"
1462 1462 msgstr ""
1463 1463
1464 #: rhodecode/events/pullrequest.py:77
1464 #: rhodecode/events/pullrequest.py:80
1465 1465 msgid "pullrequest closed"
1466 1466 msgstr ""
1467 1467
1468 #: rhodecode/events/pullrequest.py:86
1468 #: rhodecode/events/pullrequest.py:89
1469 1469 msgid "pullrequest commits updated"
1470 1470 msgstr ""
1471 1471
1472 #: rhodecode/events/pullrequest.py:95
1472 #: rhodecode/events/pullrequest.py:98
1473 1473 msgid "pullrequest review changed"
1474 1474 msgstr ""
1475 1475
1476 #: rhodecode/events/pullrequest.py:104
1476 #: rhodecode/events/pullrequest.py:107
1477 1477 msgid "pullrequest merged"
1478 1478 msgstr ""
1479 1479
1480 #: rhodecode/events/pullrequest.py:113
1480 #: rhodecode/events/pullrequest.py:116
1481 1481 msgid "pullrequest commented"
1482 1482 msgstr ""
1483 1483
1484 #: rhodecode/events/repo.py:138
1484 #: rhodecode/events/repo.py:148
1485 1485 msgid "repository pre create"
1486 1486 msgstr ""
1487 1487
1488 #: rhodecode/events/repo.py:147
1488 #: rhodecode/events/repo.py:157
1489 1489 msgid "repository created"
1490 1490 msgstr ""
1491 1491
1492 #: rhodecode/events/repo.py:156
1492 #: rhodecode/events/repo.py:166
1493 1493 msgid "repository pre delete"
1494 1494 msgstr ""
1495 1495
1496 #: rhodecode/events/repo.py:165
1496 #: rhodecode/events/repo.py:175
1497 1497 msgid "repository deleted"
1498 1498 msgstr ""
1499 1499
1500 #: rhodecode/events/repo.py:201
1500 #: rhodecode/events/repo.py:211
1501 1501 msgid "repository pre pull"
1502 1502 msgstr ""
1503 1503
1504 #: rhodecode/events/repo.py:210
1504 #: rhodecode/events/repo.py:220
1505 1505 msgid "repository pull"
1506 1506 msgstr ""
1507 1507
1508 #: rhodecode/events/repo.py:219
1508 #: rhodecode/events/repo.py:229
1509 1509 msgid "repository pre push"
1510 1510 msgstr ""
1511 1511
1512 #: rhodecode/events/repo.py:230
1512 #: rhodecode/events/repo.py:240
1513 1513 msgid "repository push"
1514 1514 msgstr ""
1515 1515
@@ -1541,7 +1541,7 b' msgstr ""'
1541 1541 msgid "user pre update"
1542 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 1545 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:87
1546 1546 #: rhodecode/templates/admin/my_account/my_account_emails.mako:65
1547 1547 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:107
@@ -1552,8 +1552,8 b' msgstr ""'
1552 1552 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:68
1553 1553 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:66
1554 1554 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:80
1555 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:111
1556 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:161
1555 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:110
1556 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:195
1557 1557 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:44
1558 1558 #: rhodecode/templates/admin/settings/settings_global.mako:140
1559 1559 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:16
@@ -1563,13 +1563,13 b' msgstr ""'
1563 1563 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:121
1564 1564 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:83
1565 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 1567 #: rhodecode/templates/admin/users/user_edit_profile.mako:135
1568 1568 #: rhodecode/templates/base/default_perms_box.mako:89
1569 1569 msgid "Reset"
1570 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 1573 #: rhodecode/templates/admin/integrations/list.mako:211
1574 1574 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:49
1575 1575 #: rhodecode/templates/admin/my_account/my_account_emails.mako:32
@@ -1578,13 +1578,13 b' msgstr ""'
1578 1578 #: rhodecode/templates/admin/settings/settings_hooks.mako:46
1579 1579 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:45
1580 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 1582 #: rhodecode/templates/base/issue_tracker_settings.mako:69
1583 #: rhodecode/templates/base/vcs_settings.mako:251
1584 #: rhodecode/templates/base/vcs_settings.mako:276
1585 #: rhodecode/templates/changeset/changeset_file_comment.mako:137
1586 #: rhodecode/templates/changeset/changeset_file_comment.mako:139
1583 #: rhodecode/templates/base/vcs_settings.mako:264
1584 #: rhodecode/templates/base/vcs_settings.mako:289
1587 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 1588 #: rhodecode/templates/data_table/_dt_elements.mako:123
1589 1589 #: rhodecode/templates/data_table/_dt_elements.mako:184
1590 1590 #: rhodecode/templates/data_table/_dt_elements.mako:198
@@ -1732,16 +1732,15 b' msgstr ""'
1732 1732
1733 1733 #: rhodecode/integrations/types/slack.py:60 rhodecode/templates/login.mako:43
1734 1734 #: rhodecode/templates/register.mako:41
1735 #: rhodecode/templates/admin/admin_log.mako:7
1735 #: rhodecode/templates/admin/admin_log_base.mako:6
1736 1736 #: rhodecode/templates/admin/my_account/my_account_profile.mako:24
1737 1737 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:24
1738 1738 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:69
1739 1739 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:70
1740 1740 #: rhodecode/templates/admin/users/user_add.mako:35
1741 #: rhodecode/templates/admin/users/user_edit_audit.mako:22
1742 1741 #: rhodecode/templates/admin/users/user_edit_profile.mako:39
1743 #: rhodecode/templates/admin/users/users.mako:62
1744 #: rhodecode/templates/base/base.mako:304
1742 #: rhodecode/templates/admin/users/users.mako:63
1743 #: rhodecode/templates/base/base.mako:306
1745 1744 #: rhodecode/templates/debug_style/login.html:36
1746 1745 #: rhodecode/templates/email_templates/user_registration.mako:23
1747 1746 #: rhodecode/templates/users/user_profile.mako:27
@@ -1776,180 +1775,180 b' msgstr ""'
1776 1775 msgid "Send events such as repo pushes and pull requests to your slack channel."
1777 1776 msgstr ""
1778 1777
1779 #: rhodecode/integrations/types/webhook.py:152
1778 #: rhodecode/integrations/types/webhook.py:164
1780 1779 msgid "Webhook URL"
1781 1780 msgstr ""
1782 1781
1783 #: rhodecode/integrations/types/webhook.py:154
1782 #: rhodecode/integrations/types/webhook.py:166
1784 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 1784 msgstr ""
1786 1785
1787 #: rhodecode/integrations/types/webhook.py:168
1786 #: rhodecode/integrations/types/webhook.py:180
1788 1787 msgid "Secret Token"
1789 1788 msgstr ""
1790 1789
1791 #: rhodecode/integrations/types/webhook.py:169
1790 #: rhodecode/integrations/types/webhook.py:181
1792 1791 msgid "String used to validate received payloads."
1793 1792 msgstr ""
1794 1793
1795 #: rhodecode/integrations/types/webhook.py:178
1794 #: rhodecode/integrations/types/webhook.py:190
1796 1795 msgid "Call Method"
1797 1796 msgstr ""
1798 1797
1799 #: rhodecode/integrations/types/webhook.py:179
1798 #: rhodecode/integrations/types/webhook.py:191
1800 1799 msgid "Select if the webhook call should be made with POST or GET."
1801 1800 msgstr ""
1802 1801
1803 #: rhodecode/integrations/types/webhook.py:192
1802 #: rhodecode/integrations/types/webhook.py:204
1804 1803 msgid "Webhook"
1805 1804 msgstr ""
1806 1805
1807 #: rhodecode/integrations/types/webhook.py:193
1806 #: rhodecode/integrations/types/webhook.py:205
1808 1807 msgid "Post json events to a webhook endpoint"
1809 1808 msgstr ""
1810 1809
1811 #: rhodecode/lib/action_parser.py:89
1810 #: rhodecode/lib/action_parser.py:94
1812 1811 msgid "[deleted] repository"
1813 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 1815 msgid "[created] repository"
1817 1816 msgstr ""
1818 1817
1819 #: rhodecode/lib/action_parser.py:95
1818 #: rhodecode/lib/action_parser.py:100
1820 1819 msgid "[created] repository as fork"
1821 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 1823 msgid "[forked] repository"
1825 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 1827 msgid "[updated] repository"
1829 1828 msgstr ""
1830 1829
1831 #: rhodecode/lib/action_parser.py:104
1830 #: rhodecode/lib/action_parser.py:109
1832 1831 msgid "[downloaded] archive from repository"
1833 1832 msgstr ""
1834 1833
1835 #: rhodecode/lib/action_parser.py:107
1834 #: rhodecode/lib/action_parser.py:112
1836 1835 msgid "[delete] repository"
1837 1836 msgstr ""
1838 1837
1839 #: rhodecode/lib/action_parser.py:119
1838 #: rhodecode/lib/action_parser.py:124
1840 1839 msgid "[created] user"
1841 1840 msgstr ""
1842 1841
1843 #: rhodecode/lib/action_parser.py:122
1842 #: rhodecode/lib/action_parser.py:127
1844 1843 msgid "[updated] user"
1845 1844 msgstr ""
1846 1845
1847 #: rhodecode/lib/action_parser.py:125
1846 #: rhodecode/lib/action_parser.py:130
1848 1847 msgid "[created] user group"
1849 1848 msgstr ""
1850 1849
1851 #: rhodecode/lib/action_parser.py:128
1850 #: rhodecode/lib/action_parser.py:133
1852 1851 msgid "[updated] user group"
1853 1852 msgstr ""
1854 1853
1855 #: rhodecode/lib/action_parser.py:131
1854 #: rhodecode/lib/action_parser.py:136
1856 1855 msgid "[commented] on commit in repository"
1857 1856 msgstr ""
1858 1857
1859 #: rhodecode/lib/action_parser.py:134
1858 #: rhodecode/lib/action_parser.py:139
1860 1859 msgid "[commented] on pull request for"
1861 1860 msgstr ""
1862 1861
1863 #: rhodecode/lib/action_parser.py:137
1862 #: rhodecode/lib/action_parser.py:142
1864 1863 msgid "[closed] pull request for"
1865 1864 msgstr ""
1866 1865
1867 #: rhodecode/lib/action_parser.py:140
1866 #: rhodecode/lib/action_parser.py:145
1868 1867 msgid "[merged] pull request for"
1869 1868 msgstr ""
1870 1869
1871 #: rhodecode/lib/action_parser.py:143
1870 #: rhodecode/lib/action_parser.py:148
1872 1871 msgid "[pushed] into"
1873 1872 msgstr ""
1874 1873
1875 #: rhodecode/lib/action_parser.py:146
1874 #: rhodecode/lib/action_parser.py:151
1876 1875 msgid "[committed via RhodeCode] into repository"
1877 1876 msgstr ""
1878 1877
1879 #: rhodecode/lib/action_parser.py:149
1878 #: rhodecode/lib/action_parser.py:154
1880 1879 msgid "[pulled from remote] into repository"
1881 1880 msgstr ""
1882 1881
1883 #: rhodecode/lib/action_parser.py:152
1882 #: rhodecode/lib/action_parser.py:157
1884 1883 msgid "[pulled] from"
1885 1884 msgstr ""
1886 1885
1887 #: rhodecode/lib/action_parser.py:155
1886 #: rhodecode/lib/action_parser.py:160
1888 1887 msgid "[started following] repository"
1889 1888 msgstr ""
1890 1889
1891 #: rhodecode/lib/action_parser.py:158
1890 #: rhodecode/lib/action_parser.py:163
1892 1891 msgid "[stopped following] repository"
1893 1892 msgstr ""
1894 1893
1895 #: rhodecode/lib/action_parser.py:166
1894 #: rhodecode/lib/action_parser.py:172
1896 1895 #, python-format
1897 1896 msgid "fork name %s"
1898 1897 msgstr ""
1899 1898
1900 #: rhodecode/lib/action_parser.py:183
1899 #: rhodecode/lib/action_parser.py:190
1901 1900 #: rhodecode/templates/pullrequests/pullrequest_show.mako:51
1902 1901 #, python-format
1903 1902 msgid "Pull request #%s"
1904 1903 msgstr ""
1905 1904
1906 #: rhodecode/lib/action_parser.py:216
1905 #: rhodecode/lib/action_parser.py:223
1907 1906 #, python-format
1908 1907 msgid "Show all combined commits %s->%s"
1909 1908 msgstr ""
1910 1909
1911 #: rhodecode/lib/action_parser.py:220
1912 msgid "compare view"
1913 msgstr ""
1914
1915 1910 #: rhodecode/lib/action_parser.py:227
1911 msgid "compare view"
1912 msgstr ""
1913
1914 #: rhodecode/lib/action_parser.py:234
1916 1915 #, python-format
1917 1916 msgid " and %(num)s more commits"
1918 1917 msgstr ""
1919 1918
1920 #: rhodecode/lib/action_parser.py:279
1919 #: rhodecode/lib/action_parser.py:286
1921 1920 #, python-format
1922 1921 msgid "Deleted branch: %s"
1923 1922 msgstr ""
1924 1923
1925 #: rhodecode/lib/action_parser.py:282
1924 #: rhodecode/lib/action_parser.py:289
1926 1925 #, python-format
1927 1926 msgid "Created tag: %s"
1928 1927 msgstr ""
1929 1928
1930 #: rhodecode/lib/action_parser.py:295
1929 #: rhodecode/lib/action_parser.py:302
1931 1930 msgid "Commit not found"
1932 1931 msgstr ""
1933 1932
1934 #: rhodecode/lib/auth.py:1197
1933 #: rhodecode/lib/auth.py:1220
1935 1934 #, python-format
1936 1935 msgid "IP %s not allowed"
1937 1936 msgstr ""
1938 1937
1939 #: rhodecode/lib/auth.py:1281
1938 #: rhodecode/lib/auth.py:1309
1940 1939 msgid "You need to be a registered user to perform this action"
1941 1940 msgstr ""
1942 1941
1943 #: rhodecode/lib/auth.py:1329
1942 #: rhodecode/lib/auth.py:1366
1944 1943 #, python-format
1945 1944 msgid "Action not supported for %s."
1946 1945 msgstr ""
1947 1946
1948 #: rhodecode/lib/auth.py:1379
1947 #: rhodecode/lib/auth.py:1412
1949 1948 msgid "You need to be signed in to view this page"
1950 1949 msgstr ""
1951 1950
1952 #: rhodecode/lib/base.py:549
1951 #: rhodecode/lib/base.py:561
1953 1952 #, python-format
1954 1953 msgid "The repository at %(repo_name)s cannot be located."
1955 1954 msgstr ""
@@ -1974,20 +1973,21 b' msgstr ""'
1974 1973 msgid "Click to select line"
1975 1974 msgstr ""
1976 1975
1977 #: rhodecode/lib/helpers.py:1517
1976 #: rhodecode/lib/helpers.py:1527
1978 1977 #, python-format
1979 1978 msgid " and %s more"
1980 1979 msgstr ""
1981 1980
1982 #: rhodecode/lib/helpers.py:1521
1981 #: rhodecode/lib/helpers.py:1531
1983 1982 msgid "No Files"
1984 1983 msgstr ""
1985 1984
1986 #: rhodecode/lib/helpers.py:1800
1985 #: rhodecode/lib/helpers.py:1836
1987 1986 msgid ""
1988 1987 "Example filter terms:\n"
1989 1988 " repository:vcs\n"
1990 1989 " username:marcin\n"
1990 " username:(NOT marcin)\n"
1991 1991 " action:*push*\n"
1992 1992 " ip:127.0.0.1\n"
1993 1993 " date:20120101\n"
@@ -2002,7 +2002,21 b' msgid ""'
2002 2002 " \"username:test AND repository:test*\"\n"
2003 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 2020 #, python-format
2007 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 2022 msgstr ""
@@ -2042,7 +2056,7 b' msgstr ""'
2042 2056 #: rhodecode/lib/utils2.py:515
2043 2057 #: rhodecode/public/js/rhodecode-components.js:33659
2044 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 2060 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:174
2047 2061 msgid "just now"
2048 2062 msgstr ""
@@ -2076,7 +2090,7 b' msgstr ""'
2076 2090 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2289
2077 2091 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2289
2078 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 2094 msgid "Repository no access"
2081 2095 msgstr ""
2082 2096
@@ -2109,7 +2123,7 b' msgstr ""'
2109 2123 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2290
2110 2124 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2290
2111 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 2127 msgid "Repository read access"
2114 2128 msgstr ""
2115 2129
@@ -2142,7 +2156,7 b' msgstr ""'
2142 2156 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2291
2143 2157 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2291
2144 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 2160 msgid "Repository write access"
2147 2161 msgstr ""
2148 2162
@@ -2175,7 +2189,7 b' msgstr ""'
2175 2189 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2292
2176 2190 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2292
2177 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 2193 msgid "Repository admin access"
2180 2194 msgstr ""
2181 2195
@@ -2248,7 +2262,7 b' msgstr ""'
2248 2262 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2310
2249 2263 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2310
2250 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 2266 msgid "Repository creation disabled"
2253 2267 msgstr ""
2254 2268
@@ -2281,7 +2295,7 b' msgstr ""'
2281 2295 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2311
2282 2296 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2311
2283 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 2299 msgid "Repository creation enabled"
2286 2300 msgstr ""
2287 2301
@@ -2314,7 +2328,7 b' msgstr ""'
2314 2328 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2315
2315 2329 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2315
2316 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 2332 msgid "Repository forking disabled"
2319 2333 msgstr ""
2320 2334
@@ -2347,7 +2361,7 b' msgstr ""'
2347 2361 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2316
2348 2362 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2316
2349 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 2365 msgid "Repository forking enabled"
2352 2366 msgstr ""
2353 2367
@@ -2401,7 +2415,7 b' msgstr ""'
2401 2415 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2950
2402 2416 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2950
2403 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 2419 msgid "Not Reviewed"
2406 2420 msgstr ""
2407 2421
@@ -2434,7 +2448,7 b' msgstr ""'
2434 2448 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2951
2435 2449 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2951
2436 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 2452 msgid "Approved"
2439 2453 msgstr ""
2440 2454
@@ -2467,7 +2481,7 b' msgstr ""'
2467 2481 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2952
2468 2482 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2952
2469 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 2485 msgid "Rejected"
2472 2486 msgstr ""
2473 2487
@@ -2500,7 +2514,7 b' msgstr ""'
2500 2514 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2953
2501 2515 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2953
2502 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 2518 msgid "Under Review"
2505 2519 msgstr ""
2506 2520
@@ -2530,7 +2544,7 b' msgstr ""'
2530 2544 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2294
2531 2545 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2294
2532 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 2548 msgid "Repository group no access"
2535 2549 msgstr ""
2536 2550
@@ -2560,7 +2574,7 b' msgstr ""'
2560 2574 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2295
2561 2575 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2295
2562 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 2578 msgid "Repository group read access"
2565 2579 msgstr ""
2566 2580
@@ -2590,7 +2604,7 b' msgstr ""'
2590 2604 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2296
2591 2605 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2296
2592 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 2608 msgid "Repository group write access"
2595 2609 msgstr ""
2596 2610
@@ -2620,7 +2634,7 b' msgstr ""'
2620 2634 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2297
2621 2635 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2297
2622 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 2638 msgid "Repository group admin access"
2625 2639 msgstr ""
2626 2640
@@ -2649,7 +2663,7 b' msgstr ""'
2649 2663 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2299
2650 2664 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2299
2651 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 2667 msgid "User group no access"
2654 2668 msgstr ""
2655 2669
@@ -2678,7 +2692,7 b' msgstr ""'
2678 2692 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2300
2679 2693 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2300
2680 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 2696 msgid "User group read access"
2683 2697 msgstr ""
2684 2698
@@ -2707,7 +2721,7 b' msgstr ""'
2707 2721 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2301
2708 2722 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2301
2709 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 2725 msgid "User group write access"
2712 2726 msgstr ""
2713 2727
@@ -2736,7 +2750,7 b' msgstr ""'
2736 2750 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2302
2737 2751 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2302
2738 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 2754 msgid "User group admin access"
2741 2755 msgstr ""
2742 2756
@@ -2765,7 +2779,7 b' msgstr ""'
2765 2779 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2304
2766 2780 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2304
2767 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 2783 msgid "Repository Group creation disabled"
2770 2784 msgstr ""
2771 2785
@@ -2794,7 +2808,7 b' msgstr ""'
2794 2808 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2305
2795 2809 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2305
2796 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 2812 msgid "Repository Group creation enabled"
2799 2813 msgstr ""
2800 2814
@@ -2823,7 +2837,7 b' msgstr ""'
2823 2837 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2307
2824 2838 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2307
2825 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 2841 msgid "User Group creation disabled"
2828 2842 msgstr ""
2829 2843
@@ -2852,7 +2866,7 b' msgstr ""'
2852 2866 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2308
2853 2867 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2308
2854 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 2870 msgid "User Group creation enabled"
2857 2871 msgstr ""
2858 2872
@@ -2881,7 +2895,7 b' msgstr ""'
2881 2895 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2318
2882 2896 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2318
2883 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 2899 msgid "Registration disabled"
2886 2900 msgstr ""
2887 2901
@@ -2910,7 +2924,7 b' msgstr ""'
2910 2924 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2319
2911 2925 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2319
2912 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 2928 msgid "User Registration with manual account activation"
2915 2929 msgstr ""
2916 2930
@@ -2939,7 +2953,7 b' msgstr ""'
2939 2953 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2320
2940 2954 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2320
2941 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 2957 msgid "User Registration with automatic account activation"
2944 2958 msgstr ""
2945 2959
@@ -2968,7 +2982,7 b' msgstr ""'
2968 2982 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2322
2969 2983 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2322
2970 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 2986 #: rhodecode/model/permission.py:95
2973 2987 msgid "Manual activation of external account"
2974 2988 msgstr ""
@@ -2998,7 +3012,7 b' msgstr ""'
2998 3012 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2323
2999 3013 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2323
3000 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 3016 #: rhodecode/model/permission.py:96
3003 3017 msgid "Automatic activation of external account"
3004 3018 msgstr ""
@@ -3022,7 +3036,7 b' msgstr ""'
3022 3036 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2312
3023 3037 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2312
3024 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 3040 msgid "Repository creation enabled with write permission to a repository group"
3027 3041 msgstr ""
3028 3042
@@ -3045,7 +3059,7 b' msgstr ""'
3045 3059 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2313
3046 3060 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2313
3047 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 3063 msgid "Repository creation disabled with write permission to a repository group"
3050 3064 msgstr ""
3051 3065
@@ -3065,7 +3079,7 b' msgstr ""'
3065 3079 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2287
3066 3080 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2287
3067 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 3083 msgid "RhodeCode Super Administrator"
3070 3084 msgstr ""
3071 3085
@@ -3083,7 +3097,7 b' msgstr ""'
3083 3097 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2325
3084 3098 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2325
3085 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 3101 msgid "Inherit object permissions from default user disabled"
3088 3102 msgstr ""
3089 3103
@@ -3101,7 +3115,7 b' msgstr ""'
3101 3115 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2326
3102 3116 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2326
3103 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 3119 msgid "Inherit object permissions from default user enabled"
3106 3120 msgstr ""
3107 3121
@@ -3111,7 +3125,7 b' msgstr ""'
3111 3125 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:912
3112 3126 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:912
3113 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 3129 msgid "all"
3116 3130 msgstr ""
3117 3131
@@ -3121,7 +3135,7 b' msgstr ""'
3121 3135 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:913
3122 3136 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:913
3123 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 3139 msgid "http/web interface"
3126 3140 msgstr ""
3127 3141
@@ -3131,7 +3145,7 b' msgstr ""'
3131 3145 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:914
3132 3146 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:914
3133 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 3149 msgid "vcs (git/hg/svn protocol)"
3136 3150 msgstr ""
3137 3151
@@ -3141,7 +3155,7 b' msgstr ""'
3141 3155 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:915
3142 3156 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:915
3143 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 3159 msgid "api calls"
3146 3160 msgstr ""
3147 3161
@@ -3151,7 +3165,7 b' msgstr ""'
3151 3165 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:916
3152 3166 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:916
3153 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 3169 msgid "feed access"
3156 3170 msgstr ""
3157 3171
@@ -3161,62 +3175,62 b' msgstr ""'
3161 3175 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2065
3162 3176 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2065
3163 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 3179 msgid "No parent"
3166 3180 msgstr ""
3167 3181
3168 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 3184 msgid "Password reset enabled"
3171 3185 msgstr ""
3172 3186
3173 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 3189 msgid "Password reset hidden"
3176 3190 msgstr ""
3177 3191
3178 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 3194 msgid "Password reset disabled"
3181 3195 msgstr ""
3182 3196
3183 #: rhodecode/lib/index/whoosh.py:149
3197 #: rhodecode/lib/index/whoosh.py:150
3184 3198 msgid "Invalid search query. Try quoting it."
3185 3199 msgstr ""
3186 3200
3187 #: rhodecode/lib/index/whoosh.py:151
3201 #: rhodecode/lib/index/whoosh.py:152
3188 3202 msgid "There is no index to search in. Please run whoosh indexer"
3189 3203 msgstr ""
3190 3204
3191 #: rhodecode/lib/index/whoosh.py:156
3205 #: rhodecode/lib/index/whoosh.py:157
3192 3206 msgid "An error occurred during this search operation"
3193 3207 msgstr ""
3194 3208
3195 #: rhodecode/lib/index/whoosh.py:164
3196 msgid "Index Type"
3197 msgstr ""
3198
3199 3209 #: rhodecode/lib/index/whoosh.py:165
3210 msgid "Index Type"
3211 msgstr ""
3212
3213 #: rhodecode/lib/index/whoosh.py:166
3200 3214 msgid "File Index"
3201 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 3218 msgid "Indexed documents"
3205 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 3222 msgid "Last update"
3209 3223 msgstr ""
3210 3224
3211 #: rhodecode/lib/index/whoosh.py:170
3225 #: rhodecode/lib/index/whoosh.py:171
3212 3226 msgid "Commit index"
3213 3227 msgstr ""
3214 3228
3215 #: rhodecode/model/comment.py:368
3229 #: rhodecode/model/comment.py:374
3216 3230 msgid "made a comment"
3217 3231 msgstr ""
3218 3232
3219 #: rhodecode/model/comment.py:369
3233 #: rhodecode/model/comment.py:375
3220 3234 msgid "Show it now"
3221 3235 msgstr ""
3222 3236
@@ -3302,7 +3316,7 b' msgstr ""'
3302 3316 #: rhodecode/model/permission.py:79
3303 3317 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:11
3304 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 3320 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:11
3307 3321 msgid "None"
3308 3322 msgstr ""
@@ -3310,7 +3324,7 b' msgstr ""'
3310 3324 #: rhodecode/model/permission.py:68 rhodecode/model/permission.py:74
3311 3325 #: rhodecode/model/permission.py:80
3312 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 3328 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:12
3315 3329 msgid "Read"
3316 3330 msgstr ""
@@ -3318,10 +3332,10 b' msgstr ""'
3318 3332 #: rhodecode/model/permission.py:69 rhodecode/model/permission.py:75
3319 3333 #: rhodecode/model/permission.py:81
3320 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 3336 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:13
3323 #: rhodecode/templates/changeset/changeset_file_comment.mako:266
3324 #: rhodecode/templates/changeset/changeset_file_comment.mako:316
3337 #: rhodecode/templates/changeset/changeset_file_comment.mako:271
3338 #: rhodecode/templates/changeset/changeset_file_comment.mako:321
3325 3339 msgid "Write"
3326 3340 msgstr ""
3327 3341
@@ -3344,7 +3358,7 b' msgstr ""'
3344 3358 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:13
3345 3359 #: rhodecode/templates/admin/repos/repo_add.mako:13
3346 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 3362 #: rhodecode/templates/admin/repos/repos.mako:13
3349 3363 #: rhodecode/templates/admin/settings/settings.mako:12
3350 3364 #: rhodecode/templates/admin/user_groups/user_group_add.mako:11
@@ -3354,9 +3368,9 b' msgstr ""'
3354 3368 #: rhodecode/templates/admin/users/user_add.mako:11
3355 3369 #: rhodecode/templates/admin/users/user_edit.mako:12
3356 3370 #: rhodecode/templates/admin/users/users.mako:13
3357 #: rhodecode/templates/admin/users/users.mako:75
3358 #: rhodecode/templates/base/base.mako:409
3359 #: rhodecode/templates/base/base.mako:416
3371 #: rhodecode/templates/admin/users/users.mako:76
3372 #: rhodecode/templates/base/base.mako:412
3373 #: rhodecode/templates/base/base.mako:419
3360 3374 msgid "Admin"
3361 3375 msgstr ""
3362 3376
@@ -3387,79 +3401,79 b' msgstr ""'
3387 3401 msgid "Disable password recovery"
3388 3402 msgstr ""
3389 3403
3390 #: rhodecode/model/pull_request.py:78
3404 #: rhodecode/model/pull_request.py:80
3391 3405 msgid "This pull request can be automatically merged."
3392 3406 msgstr ""
3393 3407
3394 #: rhodecode/model/pull_request.py:80
3408 #: rhodecode/model/pull_request.py:82
3395 3409 msgid "This pull request cannot be merged because of an unhandled exception."
3396 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 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 3417 msgid "This pull request could not be merged because push to target failed."
3404 3418 msgstr ""
3405 3419
3406 #: rhodecode/model/pull_request.py:88
3420 #: rhodecode/model/pull_request.py:90
3407 3421 msgid "This pull request cannot be merged because the target is not a head."
3408 3422 msgstr ""
3409 3423
3410 #: rhodecode/model/pull_request.py:91
3424 #: rhodecode/model/pull_request.py:93
3411 3425 msgid "This pull request cannot be merged because the source contains more branches than the target."
3412 3426 msgstr ""
3413 3427
3414 #: rhodecode/model/pull_request.py:94
3428 #: rhodecode/model/pull_request.py:96
3415 3429 msgid "This pull request cannot be merged because the target has multiple heads."
3416 3430 msgstr ""
3417 3431
3418 #: rhodecode/model/pull_request.py:97
3432 #: rhodecode/model/pull_request.py:99
3419 3433 msgid "This pull request cannot be merged because the target repository is locked."
3420 3434 msgstr ""
3421 3435
3422 #: rhodecode/model/pull_request.py:100
3436 #: rhodecode/model/pull_request.py:102
3423 3437 msgid "This pull request cannot be merged because the target or the source reference is missing."
3424 3438 msgstr ""
3425 3439
3426 #: rhodecode/model/pull_request.py:103
3440 #: rhodecode/model/pull_request.py:105
3427 3441 msgid "This pull request cannot be merged because the target reference is missing."
3428 3442 msgstr ""
3429 3443
3430 #: rhodecode/model/pull_request.py:106
3444 #: rhodecode/model/pull_request.py:108
3431 3445 msgid "This pull request cannot be merged because the source reference is missing."
3432 3446 msgstr ""
3433 3447
3434 #: rhodecode/model/pull_request.py:109
3448 #: rhodecode/model/pull_request.py:111
3435 3449 msgid "This pull request cannot be merged because of conflicts related to sub repositories."
3436 3450 msgstr ""
3437 3451
3438 #: rhodecode/model/pull_request.py:115
3439 msgid "Pull request update successful."
3440 msgstr ""
3441
3442 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 3454 msgstr ""
3445 3455
3446 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 3458 msgstr ""
3449 3459
3450 3460 #: rhodecode/model/pull_request.py:121
3451 msgid "Pull request cannot be updated because the reference type is not supported for an update."
3452 msgstr ""
3453
3454 #: rhodecode/model/pull_request.py:124
3461 msgid "No update needed because the source and target have not changed."
3462 msgstr ""
3463
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 3469 msgid "This pull request cannot be updated because the target reference is missing."
3456 3470 msgstr ""
3457 3471
3458 #: rhodecode/model/pull_request.py:127
3472 #: rhodecode/model/pull_request.py:129
3459 3473 msgid "This pull request cannot be updated because the source reference is missing."
3460 3474 msgstr ""
3461 3475
3462 #: rhodecode/model/pull_request.py:524
3476 #: rhodecode/model/pull_request.py:544
3463 3477 #, python-format
3464 3478 msgid ""
3465 3479 "Merge pull request #%(pr_id)s from %(source_repo)s %(source_ref_name)s\n"
@@ -3467,86 +3481,90 b' msgid ""'
3467 3481 " %(pr_title)s"
3468 3482 msgstr ""
3469 3483
3470 #: rhodecode/model/pull_request.py:556
3484 #: rhodecode/model/pull_request.py:576
3471 3485 msgid "Pull request merged and closed"
3472 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 3493 msgid "Server-side pull request merging is disabled."
3476 3494 msgstr ""
3477 3495
3478 #: rhodecode/model/pull_request.py:1089
3496 #: rhodecode/model/pull_request.py:1154
3479 3497 msgid "This pull request is closed."
3480 3498 msgstr ""
3481 3499
3482 #: rhodecode/model/pull_request.py:1101
3500 #: rhodecode/model/pull_request.py:1166
3483 3501 msgid "Pull request merging is not supported."
3484 3502 msgstr ""
3485 3503
3486 #: rhodecode/model/pull_request.py:1119
3504 #: rhodecode/model/pull_request.py:1184
3487 3505 msgid "Target repository large files support is disabled."
3488 3506 msgstr ""
3489 3507
3490 #: rhodecode/model/pull_request.py:1122
3508 #: rhodecode/model/pull_request.py:1187
3491 3509 msgid "Source repository large files support is disabled."
3492 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 3513 msgid "Bookmarks"
3496 3514 msgstr ""
3497 3515
3498 #: rhodecode/model/pull_request.py:1284
3516 #: rhodecode/model/pull_request.py:1349
3499 3517 msgid "Commit IDs"
3500 3518 msgstr ""
3501 3519
3502 #: rhodecode/model/pull_request.py:1287
3520 #: rhodecode/model/pull_request.py:1352
3503 3521 msgid "Closed Branches"
3504 3522 msgstr ""
3505 3523
3506 #: rhodecode/model/pull_request.py:1411
3524 #: rhodecode/model/pull_request.py:1493
3507 3525 msgid "User `{}` not allowed to perform merge."
3508 3526 msgstr ""
3509 3527
3510 #: rhodecode/model/pull_request.py:1424
3528 #: rhodecode/model/pull_request.py:1506
3511 3529 msgid "Pull request reviewer approval is pending."
3512 3530 msgstr ""
3513 3531
3514 #: rhodecode/model/pull_request.py:1439
3532 #: rhodecode/model/pull_request.py:1521
3515 3533 msgid "Cannot merge, {} TODO still not resolved."
3516 3534 msgstr ""
3517 3535
3518 #: rhodecode/model/pull_request.py:1442
3536 #: rhodecode/model/pull_request.py:1524
3519 3537 msgid "Cannot merge, {} TODOs still not resolved."
3520 3538 msgstr ""
3521 3539
3522 #: rhodecode/model/scm.py:768
3540 #: rhodecode/model/scm.py:761
3523 3541 msgid "latest tip"
3524 3542 msgstr ""
3525 3543
3526 #: rhodecode/model/user.py:126
3544 #: rhodecode/model/user.py:166
3527 3545 msgid "You can't Edit this user since it's crucial for entire application"
3528 3546 msgstr ""
3529 3547
3530 #: rhodecode/model/user.py:292
3548 #: rhodecode/model/user.py:332
3531 3549 #, python-format
3532 3550 msgid "You can't edit this user (`%(username)s`) since it's crucial for entire application"
3533 3551 msgstr ""
3534 3552
3535 #: rhodecode/model/user.py:462
3553 #: rhodecode/model/user.py:502
3536 3554 msgid "You can't remove this user since it's crucial for entire application"
3537 3555 msgstr ""
3538 3556
3539 #: rhodecode/model/user.py:470
3557 #: rhodecode/model/user.py:510
3540 3558 #, python-format
3541 3559 msgid "user \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories:%s"
3542 3560 msgstr ""
3543 3561
3544 #: rhodecode/model/user.py:479
3562 #: rhodecode/model/user.py:519
3545 3563 #, python-format
3546 3564 msgid "user \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups:%s"
3547 3565 msgstr ""
3548 3566
3549 #: rhodecode/model/user.py:488
3567 #: rhodecode/model/user.py:528
3550 3568 #, python-format
3551 3569 msgid "user \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups:%s"
3552 3570 msgstr ""
@@ -3570,6 +3588,7 b' msgid "Username \\"%(username)s\\" is forb'
3570 3588 msgstr ""
3571 3589
3572 3590 #: rhodecode/model/validators.py:164
3591 #: rhodecode/model/validation_schema/schemas/user_schema.py:69
3573 3592 msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character or underscore"
3574 3593 msgstr ""
3575 3594
@@ -3675,94 +3694,107 b' msgstr ""'
3675 3694 msgid "Repository group with name \"%(repo)s\" exists in group \"%(group)s\""
3676 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 3703 #, python-format
3680 3704 msgid "invalid clone url for %(rtype)s repository"
3681 3705 msgstr ""
3682 3706
3683 #: rhodecode/model/validators.py:621
3707 #: rhodecode/model/validators.py:641
3684 3708 #, python-format
3685 3709 msgid "Invalid clone url, provide a valid clone url starting with one of %(allowed_prefixes)s"
3686 3710 msgstr ""
3687 3711
3688 #: rhodecode/model/validators.py:650
3712 #: rhodecode/model/validators.py:670
3689 3713 msgid "Fork have to be the same type as parent"
3690 3714 msgstr ""
3691 3715
3692 #: rhodecode/model/validators.py:665
3716 #: rhodecode/model/validators.py:685
3693 3717 msgid "You do not have the permission to create repositories in this group."
3694 3718 msgstr ""
3695 3719
3696 #: rhodecode/model/validators.py:668
3697 #: rhodecode/model/validation_schema/schemas/repo_schema.py:102
3720 #: rhodecode/model/validators.py:688
3721 #: rhodecode/model/validation_schema/schemas/repo_schema.py:125
3698 3722 msgid "You do not have the permission to store repositories in the root location."
3699 3723 msgstr ""
3700 3724
3701 #: rhodecode/model/validators.py:728
3725 #: rhodecode/model/validators.py:748
3702 3726 msgid "This username or user group name is not valid"
3703 3727 msgstr ""
3704 3728
3705 #: rhodecode/model/validators.py:846
3729 #: rhodecode/model/validators.py:879
3706 3730 msgid "This is not a valid path"
3707 3731 msgstr ""
3708 3732
3709 #: rhodecode/model/validators.py:861
3733 #: rhodecode/model/validators.py:894
3710 3734 msgid "This e-mail address is already taken"
3711 3735 msgstr ""
3712 3736
3713 #: rhodecode/model/validators.py:881
3737 #: rhodecode/model/validators.py:914
3714 3738 #, python-format
3715 3739 msgid "e-mail \"%(email)s\" does not exist."
3716 3740 msgstr ""
3717 3741
3718 #: rhodecode/model/validators.py:902
3742 #: rhodecode/model/validators.py:935
3719 3743 #, python-format
3720 3744 msgid "Revisions %(revs)s are already part of pull request or have set status"
3721 3745 msgstr ""
3722 3746
3723 #: rhodecode/model/validators.py:933
3724 #: rhodecode/model/validation_schema/validators.py:16
3725 #: rhodecode/model/validation_schema/validators.py:29
3747 #: rhodecode/model/validators.py:966
3748 #: rhodecode/model/validation_schema/validators.py:40
3749 #: rhodecode/model/validation_schema/validators.py:53
3726 3750 msgid "Please enter a valid IPv4 or IpV6 address"
3727 3751 msgstr ""
3728 3752
3729 #: rhodecode/model/validators.py:934
3753 #: rhodecode/model/validators.py:967
3730 3754 #, python-format
3731 3755 msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
3732 3756 msgstr ""
3733 3757
3734 #: rhodecode/model/validators.py:961
3758 #: rhodecode/model/validators.py:994
3735 3759 msgid "Key name can only consist of letters, underscore, dash or numbers"
3736 3760 msgstr ""
3737 3761
3738 #: rhodecode/model/validators.py:976
3762 #: rhodecode/model/validators.py:1009
3739 3763 #, python-format
3740 3764 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
3741 3765 msgstr ""
3742 3766
3743 #: rhodecode/model/validators.py:979
3767 #: rhodecode/model/validators.py:1012
3744 3768 #, python-format
3745 3769 msgid "The plugin \"%(plugin_id)s\" is missing an includeme function."
3746 3770 msgstr ""
3747 3771
3748 #: rhodecode/model/validators.py:982
3772 #: rhodecode/model/validators.py:1015
3749 3773 #, python-format
3750 3774 msgid "Can not load plugin \"%(plugin_id)s\""
3751 3775 msgstr ""
3752 3776
3753 #: rhodecode/model/validators.py:984
3777 #: rhodecode/model/validators.py:1017
3754 3778 #, python-format
3755 3779 msgid "No plugin available with ID \"%(plugin_id)s\""
3756 3780 msgstr ""
3757 3781
3758 #: rhodecode/model/validation_schema/validators.py:37
3782 #: rhodecode/model/validation_schema/validators.py:61
3759 3783 msgid "Invalid glob pattern"
3760 3784 msgstr ""
3761 3785
3762 #: rhodecode/model/validation_schema/validators.py:46
3786 #: rhodecode/model/validation_schema/validators.py:70
3763 3787 msgid "Name must start with a letter or number. Got `{}`"
3764 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 3798 #: rhodecode/model/validation_schema/schemas/comment_schema.py:42
3767 3799 #: rhodecode/model/validation_schema/schemas/gist_schema.py:89
3768 3800 msgid "Gist with name {} already exists"
@@ -3831,253 +3863,261 b' msgid "Repo group owner with id `{}` doe'
3831 3863 msgstr ""
3832 3864
3833 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 3867 msgid "Repository with name `{}` already exists"
3836 3868 msgstr ""
3837 3869
3838 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 3872 msgid "Repository group with name `{}` already exists"
3841 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 3876 msgid "Repo owner with id `{}` does not exists"
3845 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 3880 msgid "Fork with id `{}` does not exists"
3849 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 3884 msgid "Cannot set fork of parameter of this repository to itself"
3853 3885 msgstr ""
3854 3886
3855 #: rhodecode/model/validation_schema/schemas/repo_schema.py:96
3856 #: rhodecode/model/validation_schema/schemas/repo_schema.py:100
3887 #: rhodecode/model/validation_schema/schemas/repo_schema.py:119
3888 #: rhodecode/model/validation_schema/schemas/repo_schema.py:123
3857 3889 msgid "Repository group `{}` does not exist"
3858 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 3901 msgid "Password is incorrect"
3862 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 3905 msgid "New password must be different to old password"
3866 3906 msgstr ""
3867 3907
3868 3908 #: rhodecode/public/js/rhodecode-components.js:31663
3869 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 3911 #: rhodecode/public/js/src/plugins/jquery.autocomplete.js:87
3872 3912 msgid "No results"
3873 3913 msgstr ""
3874 3914
3875 3915 #: rhodecode/public/js/rhodecode-components.js:33594
3876 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 3918 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:109
3879 3919 msgid "{0} year"
3880 3920 msgstr ""
3881 3921
3882 3922 #: rhodecode/public/js/rhodecode-components.js:33595
3883 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 3925 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:110
3886 3926 msgid "{0} month"
3887 3927 msgstr ""
3888 3928
3889 3929 #: rhodecode/public/js/rhodecode-components.js:33596
3890 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 3932 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:111
3893 3933 msgid "{0} day"
3894 3934 msgstr ""
3895 3935
3896 3936 #: rhodecode/public/js/rhodecode-components.js:33597
3897 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 3939 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:112
3900 3940 msgid "{0} hour"
3901 3941 msgstr ""
3902 3942
3903 3943 #: rhodecode/public/js/rhodecode-components.js:33598
3904 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 3946 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:113
3907 3947 msgid "{0} min"
3908 3948 msgstr ""
3909 3949
3910 3950 #: rhodecode/public/js/rhodecode-components.js:33599
3911 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 3953 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:114
3914 3954 msgid "{0} sec"
3915 3955 msgstr ""
3916 3956
3917 3957 #: rhodecode/public/js/rhodecode-components.js:33619
3918 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 3960 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:134
3921 3961 msgid "in {0}"
3922 3962 msgstr ""
3923 3963
3924 3964 #: rhodecode/public/js/rhodecode-components.js:33627
3925 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 3967 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:142
3928 3968 msgid "{0} ago"
3929 3969 msgstr ""
3930 3970
3931 3971 #: rhodecode/public/js/rhodecode-components.js:33639
3932 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 3974 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:154
3935 3975 msgid "{0}, {1} ago"
3936 3976 msgstr ""
3937 3977
3938 3978 #: rhodecode/public/js/rhodecode-components.js:33641
3939 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 3981 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:156
3942 3982 msgid "in {0}, {1}"
3943 3983 msgstr ""
3944 3984
3945 3985 #: rhodecode/public/js/rhodecode-components.js:33645
3946 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 3988 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:160
3949 3989 msgid "{0} and {1}"
3950 3990 msgstr ""
3951 3991
3952 3992 #: rhodecode/public/js/rhodecode-components.js:33647
3953 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 3995 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:162
3956 3996 msgid "{0} and {1} ago"
3957 3997 msgstr ""
3958 3998
3959 3999 #: rhodecode/public/js/rhodecode-components.js:33649
3960 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 4002 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:164
3963 4003 msgid "in {0} and {1}"
3964 4004 msgstr ""
3965 4005
3966 4006 #: rhodecode/public/js/rhodecode-components.js:47492
3967 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 4009 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:4
3970 4010 msgid "Loading more results..."
3971 4011 msgstr ""
3972 4012
3973 4013 #: rhodecode/public/js/rhodecode-components.js:47495
3974 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 4016 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:7
3977 4017 msgid "Searching..."
3978 4018 msgstr ""
3979 4019
3980 4020 #: rhodecode/public/js/rhodecode-components.js:47498
3981 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 4023 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:10
3984 4024 msgid "No matches found"
3985 4025 msgstr ""
3986 4026
3987 4027 #: rhodecode/public/js/rhodecode-components.js:47501
3988 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 4030 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:13
3991 4031 msgid "Loading failed"
3992 4032 msgstr ""
3993 4033
3994 4034 #: rhodecode/public/js/rhodecode-components.js:47505
3995 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 4037 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:17
3998 4038 msgid "One result is available, press enter to select it."
3999 4039 msgstr ""
4000 4040
4001 4041 #: rhodecode/public/js/rhodecode-components.js:47507
4002 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 4044 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:19
4005 4045 msgid "{0} results are available, use up and down arrow keys to navigate."
4006 4046 msgstr ""
4007 4047
4008 4048 #: rhodecode/public/js/rhodecode-components.js:47512
4009 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 4051 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:24
4012 4052 msgid "Please enter {0} or more character"
4013 4053 msgstr ""
4014 4054
4015 4055 #: rhodecode/public/js/rhodecode-components.js:47514
4016 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 4058 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:26
4019 4059 msgid "Please enter {0} or more characters"
4020 4060 msgstr ""
4021 4061
4022 4062 #: rhodecode/public/js/rhodecode-components.js:47519
4023 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 4065 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:31
4026 4066 msgid "Please delete {0} character"
4027 4067 msgstr ""
4028 4068
4029 4069 #: rhodecode/public/js/rhodecode-components.js:47521
4030 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 4072 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:33
4033 4073 msgid "Please delete {0} characters"
4034 4074 msgstr ""
4035 4075
4036 4076 #: rhodecode/public/js/rhodecode-components.js:47525
4037 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 4079 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:37
4040 4080 msgid "You can only select {0} item"
4041 4081 msgstr ""
4042 4082
4043 4083 #: rhodecode/public/js/rhodecode-components.js:47527
4044 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 4086 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:39
4047 4087 msgid "You can only select {0} items"
4048 4088 msgstr ""
4049 4089
4050 4090 #: rhodecode/public/js/rhodecode-components.js:48456
4051 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 4093 #: rhodecode/public/js/src/rhodecode/changelog.js:35
4054 4094 msgid "showing {0} out of {1} commit"
4055 4095 msgstr ""
4056 4096
4057 4097 #: rhodecode/public/js/rhodecode-components.js:48458
4058 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 4100 #: rhodecode/public/js/src/rhodecode/changelog.js:37
4061 4101 msgid "showing {0} out of {1} commits"
4062 4102 msgstr ""
4063 4103
4064 4104 #: rhodecode/public/js/rhodecode-components.js:48891
4065 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 4107 #: rhodecode/public/js/src/rhodecode/codemirror.js:296
4068 4108 msgid "Set status to Approved"
4069 4109 msgstr ""
4070 4110
4071 4111 #: rhodecode/public/js/rhodecode-components.js:48910
4072 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 4114 #: rhodecode/public/js/src/rhodecode/codemirror.js:315
4075 4115 msgid "Set status to Rejected"
4076 4116 msgstr ""
4077 4117
4078 4118 #: rhodecode/public/js/rhodecode-components.js:48929
4079 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 4121 #: rhodecode/public/js/src/rhodecode/codemirror.js:334
4082 4122 #: rhodecode/templates/email_templates/commit_comment.mako:99
4083 4123 #: rhodecode/templates/email_templates/pull_request_comment.mako:107
@@ -4086,42 +4126,42 b' msgstr ""'
4086 4126
4087 4127 #: rhodecode/public/js/rhodecode-components.js:48949
4088 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 4130 #: rhodecode/public/js/src/rhodecode/codemirror.js:354
4091 4131 msgid "Note Comment"
4092 4132 msgstr ""
4093 4133
4094 4134 #: rhodecode/public/js/rhodecode-components.js:49315
4095 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 4137 #: rhodecode/public/js/src/rhodecode/comments.js:125
4098 4138 msgid "resolve comment"
4099 4139 msgstr ""
4100 4140
4101 4141 #: rhodecode/public/js/rhodecode-components.js:49399
4102 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 4144 #: rhodecode/public/js/src/rhodecode/comments.js:209
4105 4145 msgid "Status Review"
4106 4146 msgstr ""
4107 4147
4108 4148 #: rhodecode/public/js/rhodecode-components.js:49414
4109 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 4151 #: rhodecode/public/js/src/rhodecode/comments.js:224
4112 4152 msgid "Comment text will be set automatically based on currently selected status ({0}) ..."
4113 4153 msgstr ""
4114 4154
4115 4155 #: rhodecode/public/js/rhodecode-components.js:49571
4116 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 4158 #: rhodecode/public/js/src/rhodecode/comments.js:381
4119 4159 msgid "Submitting..."
4120 4160 msgstr ""
4121 4161
4122 4162 #: rhodecode/public/js/rhodecode-components.js:49622
4123 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 4165 #: rhodecode/public/js/src/rhodecode/comments.js:432
4126 4166 #: rhodecode/templates/files/files_browser_tree.mako:51
4127 4167 msgid "Loading ..."
@@ -4129,105 +4169,170 b' msgstr ""'
4129 4169
4130 4170 #: rhodecode/public/js/rhodecode-components.js:49727
4131 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 4173 #: rhodecode/public/js/src/rhodecode/comments.js:537
4134 4174 msgid "Delete this comment?"
4135 4175 msgstr ""
4136 4176
4137 4177 #: rhodecode/public/js/rhodecode-components.js:49798
4138 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 4180 #: rhodecode/public/js/src/rhodecode/comments.js:608
4141 4181 msgid "Leave a comment, or click resolve button to resolve TODO comment #{0}"
4142 4182 msgstr ""
4143 4183
4144 4184 #: rhodecode/public/js/rhodecode-components.js:49875
4145 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 4187 #: rhodecode/public/js/src/rhodecode/comments.js:685
4148 4188 msgid "Leave a comment on line {0}."
4149 4189 msgstr ""
4150 4190
4151 4191 #: rhodecode/public/js/rhodecode-components.js:49989
4152 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 4194 #: rhodecode/public/js/src/rhodecode/comments.js:799
4155 4195 msgid "TODO from comment {0} was fixed."
4156 4196 msgstr ""
4157 4197
4158 4198 #: rhodecode/public/js/rhodecode-components.js:50195
4159 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 4201 #: rhodecode/public/js/src/rhodecode/files.js:150
4162 4202 msgid "truncated result"
4163 4203 msgstr ""
4164 4204
4165 4205 #: rhodecode/public/js/rhodecode-components.js:50197
4166 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 4208 #: rhodecode/public/js/src/rhodecode/files.js:152
4169 4209 msgid "truncated results"
4170 4210 msgstr ""
4171 4211
4172 4212 #: rhodecode/public/js/rhodecode-components.js:50206
4173 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 4215 #: rhodecode/public/js/src/rhodecode/files.js:161
4176 4216 msgid "No matching files"
4177 4217 msgstr ""
4178 4218
4179 4219 #: rhodecode/public/js/rhodecode-components.js:50341
4180 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 4222 #: rhodecode/public/js/src/rhodecode/files.js:296
4183 4223 msgid "Selection link"
4184 4224 msgstr ""
4185 4225
4186 4226 #: rhodecode/public/js/rhodecode-components.js:50381
4187 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 4229 #: rhodecode/public/js/src/rhodecode/followers.js:26
4190 4230 msgid "Stop following this repository"
4191 4231 msgstr ""
4192 4232
4193 4233 #: rhodecode/public/js/rhodecode-components.js:50382
4194 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 4236 #: rhodecode/public/js/src/rhodecode/followers.js:27
4197 4237 msgid "Unfollow"
4198 4238 msgstr ""
4199 4239
4200 4240 #: rhodecode/public/js/rhodecode-components.js:50391
4201 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 4243 #: rhodecode/public/js/src/rhodecode/followers.js:36
4204 4244 msgid "Start following this repository"
4205 4245 msgstr ""
4206 4246
4207 4247 #: rhodecode/public/js/rhodecode-components.js:50392
4208 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 4250 #: rhodecode/public/js/src/rhodecode/followers.js:37
4211 4251 msgid "Follow"
4212 4252 msgstr ""
4213 4253
4214 #: rhodecode/public/js/rhodecode-components.js:50849
4215 #: rhodecode/public/js/scripts.js:42697
4216 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:58
4217 #: rhodecode/public/js/src/rhodecode/pullrequests.js:213
4254 #: rhodecode/public/js/rhodecode-components.js:50770
4255 #: rhodecode/public/js/rhodecode-components.js:50779
4256 #: rhodecode/public/js/scripts.js:42618 rhodecode/public/js/scripts.js:42627
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 4316 msgid "added manually by \"{0}\""
4219 4317 msgstr ""
4220 4318
4221 #: rhodecode/public/js/rhodecode-components.js:51420
4222 #: rhodecode/public/js/scripts.js:43268
4223 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:61
4319 #: rhodecode/public/js/rhodecode-components.js:51101
4320 #: rhodecode/public/js/scripts.js:42949
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 4329 #: rhodecode/public/js/src/rhodecode.js:142
4225 4330 msgid "file"
4226 4331 msgstr ""
4227 4332
4228 #: rhodecode/public/js/rhodecode-components.js:51440
4229 #: rhodecode/public/js/scripts.js:43288
4230 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:42
4333 #: rhodecode/public/js/rhodecode-components.js:51702
4334 #: rhodecode/public/js/scripts.js:43550
4335 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:49
4231 4336 #: rhodecode/public/js/src/rhodecode.js:162
4232 4337 msgid "Show more"
4233 4338 msgstr ""
@@ -4242,150 +4347,150 b' msgstr ""'
4242 4347 msgid "Add another comment"
4243 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 4351 #: rhodecode/public/js/src/i18n_messages.js:5
4247 4352 #: rhodecode/templates/pullrequests/pullrequest_show.mako:325
4248 4353 msgid "Close"
4249 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 4357 msgid "Diff to Commit "
4253 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 4360 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:15
4261 msgid "No bookmarks available yet."
4262 msgstr ""
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."
4361 #: rhodecode/public/js/src/i18n_messages.js:4
4362 msgid "Invite reviewers to this discussion"
4274 4363 msgstr ""
4275 4364
4276 4365 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:21
4277 msgid "No repositories available yet."
4366 msgid "No bookmarks available yet."
4278 4367 msgstr ""
4279 4368
4280 4369 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:22
4281 msgid "No repository groups available yet."
4282 msgstr ""
4283
4284 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:24
4285 msgid "No tags available yet."
4286 msgstr ""
4287
4288 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:25
4289 msgid "No user groups available yet."
4370 msgid "No branches available yet."
4371 msgstr ""
4372
4373 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:23
4374 msgid "No gists available yet."
4290 4375 msgstr ""
4291 4376
4292 4377 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:26
4293 msgid "No users available yet."
4294 msgstr ""
4295
4296 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:29
4297 #: rhodecode/templates/changelog/changelog.mako:61
4298 msgid "Open new pull request"
4378 msgid "No pull requests available yet."
4379 msgstr ""
4380
4381 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:27
4382 msgid "No repositories available yet."
4383 msgstr ""
4384
4385 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:28
4386 msgid "No repository groups available yet."
4299 4387 msgstr ""
4300 4388
4301 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 4399 msgstr ""
4304 4400
4305 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 4411 msgid "Saving..."
4307 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 4415 #: rhodecode/public/js/src/i18n_messages.js:6
4311 4416 #: rhodecode/templates/admin/settings/settings_email.mako:48
4312 4417 msgid "Send"
4313 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 4421 msgid "Show at Commit "
4317 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 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 4438 #: rhodecode/public/js/src/i18n_messages.js:8
4334 4439 msgid "Switch to comment"
4335 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 4442 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:60
4351 #: rhodecode/templates/admin/auth/auth_settings.mako:71
4352 msgid "enabled"
4443 msgid "There are currently no open pull requests requiring your participation."
4353 4444 msgstr ""
4354 4445
4355 4446 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:62
4356 msgid "files"
4447 msgid "Updating..."
4357 4448 msgstr ""
4358 4449
4359 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 4466 msgid "loading..."
4362 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 4470 msgid "specify commit"
4366 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 4474 msgid "{0} active out of {1} users"
4370 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 4478 msgid "{0} days"
4374 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 4482 msgid "{0} hours"
4378 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 4486 msgid "{0} months"
4382 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 4490 msgid "{0} out of {1} users"
4386 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 4494 msgid "{0} years"
4390 4495 msgstr ""
4391 4496
@@ -4467,7 +4572,7 b' msgstr ""'
4467 4572 #: rhodecode/templates/admin/my_account/my_account_watched.mako:31
4468 4573 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:53
4469 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 4576 #: rhodecode/templates/admin/repos/repos.mako:54
4472 4577 #: rhodecode/templates/admin/user_groups/user_groups.mako:55
4473 4578 #: rhodecode/templates/admin/users/user_edit_groups.mako:54
@@ -4475,7 +4580,7 b' msgstr ""'
4475 4580 #: rhodecode/templates/bookmarks/bookmarks.mako:59
4476 4581 #: rhodecode/templates/branches/branches.mako:58
4477 4582 #: rhodecode/templates/files/files_browser_tree.mako:5
4478 #: rhodecode/templates/pullrequests/pullrequests.mako:100
4583 #: rhodecode/templates/pullrequests/pullrequests.mako:110
4479 4584 #: rhodecode/templates/tags/tags.mako:59
4480 4585 msgid "Name"
4481 4586 msgstr ""
@@ -4490,7 +4595,7 b' msgstr ""'
4490 4595 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:56
4491 4596 #: rhodecode/templates/admin/repos/repo_add_base.mako:43
4492 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 4599 #: rhodecode/templates/admin/repos/repos.mako:57
4495 4600 #: rhodecode/templates/admin/user_groups/user_group_add.mako:43
4496 4601 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:42
@@ -4498,9 +4603,10 b' msgstr ""'
4498 4603 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:15
4499 4604 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:67
4500 4605 #: rhodecode/templates/admin/users/user_edit_groups.mako:59
4606 #: rhodecode/templates/admin/users/user_edit_ips.mako:12
4501 4607 #: rhodecode/templates/base/issue_tracker_settings.mako:10
4502 #: rhodecode/templates/changeset/changeset.mako:53
4503 #: rhodecode/templates/compare/compare_commits.mako:20
4608 #: rhodecode/templates/changeset/changeset.mako:73
4609 #: rhodecode/templates/compare/compare_commits.mako:21
4504 4610 #: rhodecode/templates/email_templates/commit_comment.mako:89
4505 4611 #: rhodecode/templates/email_templates/pull_request_review.mako:41
4506 4612 #: rhodecode/templates/email_templates/pull_request_review.mako:75
@@ -4508,9 +4614,9 b' msgstr ""'
4508 4614 #: rhodecode/templates/files/file_tree_detail.mako:12
4509 4615 #: rhodecode/templates/forks/fork.mako:48
4510 4616 #: rhodecode/templates/forks/forks_data.mako:9
4511 #: rhodecode/templates/pullrequests/pullrequest.mako:47
4617 #: rhodecode/templates/pullrequests/pullrequest.mako:54
4512 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 4620 #: rhodecode/templates/summary/components.mako:73
4515 4621 msgid "Description"
4516 4622 msgstr ""
@@ -4521,7 +4627,7 b' msgstr ""'
4521 4627 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:24
4522 4628 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:60
4523 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 4631 #: rhodecode/templates/admin/repos/repos.mako:65
4526 4632 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:5
4527 4633 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:24
@@ -4544,13 +4650,13 b' msgstr ""'
4544 4650 #: rhodecode/templates/bookmarks/bookmarks.mako:66
4545 4651 #: rhodecode/templates/branches/branches.mako:65
4546 4652 #: rhodecode/templates/changelog/changelog.mako:107
4547 #: rhodecode/templates/changelog/changelog_summary_data.mako:8
4548 4653 #: rhodecode/templates/changeset/changeset.mako:36
4549 #: rhodecode/templates/compare/compare_commits.mako:18
4654 #: rhodecode/templates/compare/compare_commits.mako:19
4550 4655 #: rhodecode/templates/email_templates/commit_comment.mako:49
4551 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 4658 #: rhodecode/templates/search/search_commit.mako:6
4659 #: rhodecode/templates/summary/summary_commits.mako:8
4554 4660 #: rhodecode/templates/tags/tags.mako:66
4555 4661 msgid "Commit"
4556 4662 msgstr ""
@@ -4565,7 +4671,7 b' msgid "Home"'
4565 4671 msgstr ""
4566 4672
4567 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 4675 #: rhodecode/templates/debug_style/login.html:60
4570 4676 msgid "Sign In"
4571 4677 msgstr ""
@@ -4588,13 +4694,13 b' msgstr ""'
4588 4694
4589 4695 #: rhodecode/templates/login.mako:68 rhodecode/templates/password_reset.mako:37
4590 4696 #: rhodecode/templates/base/base.mako:46
4591 #: rhodecode/templates/errors/error_document.mako:64
4697 #: rhodecode/templates/errors/error_document.mako:63
4592 4698 msgid "Support"
4593 4699 msgstr ""
4594 4700
4595 4701 #: rhodecode/templates/login.mako:69 rhodecode/templates/password_reset.mako:38
4596 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 4704 msgid "or"
4599 4705 msgstr ""
4600 4706
@@ -4653,7 +4759,7 b' msgstr ""'
4653 4759 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:79
4654 4760 #: rhodecode/templates/admin/users/user_add.mako:68
4655 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 4763 msgid "First Name"
4658 4764 msgstr ""
4659 4765
@@ -4663,7 +4769,7 b' msgstr ""'
4663 4769 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:88
4664 4770 #: rhodecode/templates/admin/users/user_add.mako:77
4665 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 4773 msgid "Last Name"
4668 4774 msgstr ""
4669 4775
@@ -4675,36 +4781,32 b' msgstr ""'
4675 4781 msgid "Create Account"
4676 4782 msgstr ""
4677 4783
4678 #: rhodecode/templates/admin/admin.mako:5
4679 #: rhodecode/templates/admin/admin.mako:15
4784 #: rhodecode/templates/admin/admin_audit_logs.mako:5
4680 4785 #: rhodecode/templates/base/base.mako:75
4681 msgid "Admin journal"
4682 msgstr ""
4683
4684 #: rhodecode/templates/admin/admin.mako:13
4685 msgid "journal filter..."
4686 msgstr ""
4687
4688 #: rhodecode/templates/admin/admin.mako:14
4786 msgid "Admin audit logs"
4787 msgstr ""
4788
4789 #: rhodecode/templates/admin/admin_audit_logs.mako:13
4790 msgid "filter..."
4791 msgstr ""
4792
4793 #: rhodecode/templates/admin/admin_audit_logs.mako:14
4689 4794 #: rhodecode/templates/admin/users/user_edit_audit.mako:15
4690 4795 msgid "filter"
4691 4796 msgstr ""
4692 4797
4693 #: rhodecode/templates/admin/admin.mako:15
4694 #: rhodecode/templates/journal/journal.mako:14
4695 #, python-format
4696 msgid "%s entry"
4697 msgid_plural "%s entries"
4698 msgstr[0] ""
4699 msgstr[1] ""
4700
4701 #: rhodecode/templates/admin/admin.mako:17
4798 #: rhodecode/templates/admin/admin_audit_logs.mako:15
4799 msgid "Audit logs"
4800 msgstr ""
4801
4802 #: rhodecode/templates/admin/admin_audit_logs.mako:17
4702 4803 #: rhodecode/templates/admin/users/user_edit_audit.mako:17
4703 4804 #: rhodecode/templates/journal/journal.mako:17
4805 #: rhodecode/templates/search/search.mako:76
4704 4806 msgid "Example Queries"
4705 4807 msgstr ""
4706 4808
4707 #: rhodecode/templates/admin/admin_log.mako:8
4809 #: rhodecode/templates/admin/admin_log_base.mako:7
4708 4810 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:18
4709 4811 #: rhodecode/templates/admin/my_account/my_account_repos.mako:37
4710 4812 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:62
@@ -4712,38 +4814,45 b' msgstr ""'
4712 4814 #: rhodecode/templates/admin/repos/repos.mako:69
4713 4815 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:71
4714 4816 #: rhodecode/templates/admin/user_groups/user_groups.mako:68
4715 #: rhodecode/templates/admin/users/user_edit_audit.mako:23
4716 4817 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:18
4717 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 4820 #: rhodecode/templates/files/files_detail.mako:58
4720 4821 msgid "Action"
4721 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 4829 #: rhodecode/templates/admin/defaults/defaults.mako:31
4725 4830 #: rhodecode/templates/admin/permissions/permissions_objects.mako:13
4726 #: rhodecode/templates/admin/users/user_edit_audit.mako:24
4727 4831 #: rhodecode/templates/search/search_commit.mako:5
4728 4832 #: rhodecode/templates/search/search_path.mako:3
4729 4833 msgid "Repository"
4730 4834 msgstr ""
4731 4835
4732 #: rhodecode/templates/admin/admin_log.mako:10
4733 #: rhodecode/templates/admin/users/user_edit_audit.mako:25
4836 #: rhodecode/templates/admin/admin_log_base.mako:10
4734 4837 #: rhodecode/templates/bookmarks/bookmarks.mako:61
4735 4838 #: rhodecode/templates/branches/branches.mako:60
4736 4839 #: rhodecode/templates/tags/tags.mako:61
4737 4840 msgid "Date"
4738 4841 msgstr ""
4739 4842
4740 #: rhodecode/templates/admin/admin_log.mako:11
4741 #: rhodecode/templates/admin/users/user_edit_audit.mako:26
4742 msgid "From IP"
4743 msgstr ""
4744
4745 #: rhodecode/templates/admin/admin_log.mako:46
4746 #: rhodecode/templates/admin/users/user_edit_audit.mako:61
4843 #: rhodecode/templates/admin/admin_log_base.mako:11
4844 msgid "IP"
4845 msgstr ""
4846
4847 #: rhodecode/templates/admin/admin_log_base.mako:38
4848 msgid "toggle"
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 4856 msgid "No actions yet"
4748 4857 msgstr ""
4749 4858
@@ -4784,8 +4893,8 b' msgstr ""'
4784 4893 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:67
4785 4894 #: rhodecode/templates/admin/repos/repo_add_base.mako:101
4786 4895 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:79
4787 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:110
4788 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:160
4896 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:109
4897 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:194
4789 4898 #: rhodecode/templates/admin/settings/settings_hooks.mako:63
4790 4899 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:15
4791 4900 #: rhodecode/templates/admin/user_groups/user_group_add.mako:60
@@ -4826,7 +4935,7 b' msgstr ""'
4826 4935
4827 4936 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:27
4828 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 4939 #: rhodecode/templates/forks/fork.mako:87
4831 4940 msgid "Private repositories are only visible to people explicitly added as collaborators."
4832 4941 msgstr ""
@@ -4877,7 +4986,7 b' msgstr ""'
4877 4986
4878 4987 #: rhodecode/templates/admin/gists/edit.mako:56
4879 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 4990 #: rhodecode/templates/files/files_edit.mako:78
4882 4991 msgid "plain"
4883 4992 msgstr ""
@@ -4888,9 +4997,9 b' msgstr ""'
4888 4997
4889 4998 #: rhodecode/templates/admin/gists/edit.mako:102
4890 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 5001 #: rhodecode/templates/codeblocks/diffs.mako:76
4893 #: rhodecode/templates/files/files_add.mako:102
5002 #: rhodecode/templates/files/files_add.mako:108
4894 5003 #: rhodecode/templates/files/files_delete.mako:69
4895 5004 #: rhodecode/templates/files/files_edit.mako:105
4896 5005 #: rhodecode/templates/pullrequests/pullrequest_show.mako:64
@@ -4957,14 +5066,13 b' msgstr ""'
4957 5066 #: rhodecode/templates/bookmarks/bookmarks.mako:63
4958 5067 #: rhodecode/templates/branches/branches.mako:62
4959 5068 #: rhodecode/templates/changelog/changelog.mako:113
4960 #: rhodecode/templates/changelog/changelog_summary_data.mako:11
4961 #: rhodecode/templates/changeset/changeset.mako:180
4962 #: rhodecode/templates/compare/compare_commits.mako:17
5069 #: rhodecode/templates/changeset/changeset.mako:200
5070 #: rhodecode/templates/compare/compare_commits.mako:18
4963 5071 #: rhodecode/templates/files/files_browser_tree.mako:9
4964 #: rhodecode/templates/pullrequests/pullrequest_show.mako:309
4965 #: rhodecode/templates/pullrequests/pullrequest_show.mako:457
4966 #: rhodecode/templates/pullrequests/pullrequests.mako:102
5072 #: rhodecode/templates/pullrequests/pullrequest_show.mako:484
5073 #: rhodecode/templates/pullrequests/pullrequests.mako:112
4967 5074 #: rhodecode/templates/search/search_commit.mako:16
5075 #: rhodecode/templates/summary/summary_commits.mako:11
4968 5076 #: rhodecode/templates/tags/tags.mako:63
4969 5077 msgid "Author"
4970 5078 msgstr ""
@@ -5025,12 +5133,12 b' msgstr ""'
5025 5133 #: rhodecode/templates/data_table/_dt_elements.mako:193
5026 5134 #: rhodecode/templates/data_table/_dt_elements.mako:206
5027 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 5137 #: rhodecode/templates/files/files_edit.mako:165
5030 5138 #: rhodecode/templates/files/files_source.mako:48
5031 5139 #: rhodecode/templates/files/files_source.mako:51
5032 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 5142 #: rhodecode/templates/users/user_profile.mako:7
5035 5143 msgid "Edit"
5036 5144 msgstr ""
@@ -5071,10 +5179,10 b' msgstr ""'
5071 5179 #: rhodecode/templates/admin/integrations/new.mako:21
5072 5180 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:48
5073 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 5183 #: rhodecode/templates/admin/settings/settings.mako:14
5076 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 5186 msgid "Settings"
5079 5187 msgstr ""
5080 5188
@@ -5177,7 +5285,7 b' msgid "No description available"'
5177 5285 msgstr ""
5178 5286
5179 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 5289 msgid "My account"
5182 5290 msgstr ""
5183 5291
@@ -5201,7 +5309,7 b' msgid "OAuth Identities"'
5201 5309 msgstr ""
5202 5310
5203 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 5313 msgid "Emails"
5206 5314 msgstr ""
5207 5315
@@ -5218,7 +5326,7 b' msgstr ""'
5218 5326 #: rhodecode/templates/admin/my_account/my_account.mako:41
5219 5327 #: rhodecode/templates/admin/permissions/permissions.mako:14
5220 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 5330 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:34
5223 5331 #: rhodecode/templates/base/base.mako:80
5224 5332 msgid "Permissions"
@@ -5275,7 +5383,7 b' msgstr ""'
5275 5383 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:65
5276 5384 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:82
5277 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 5387 msgid "Add"
5280 5388 msgstr ""
5281 5389
@@ -5294,9 +5402,7 b' msgid "Primary"'
5294 5402 msgstr ""
5295 5403
5296 5404 #: rhodecode/templates/admin/my_account/my_account_emails.mako:31
5297 #: rhodecode/templates/admin/users/user_edit_emails.mako:30
5298 #, python-format
5299 msgid "Confirm to delete this email: %s"
5405 msgid "Confirm to delete this email: {}"
5300 5406 msgstr ""
5301 5407
5302 5408 #: rhodecode/templates/admin/my_account/my_account_emails.mako:42
@@ -5380,13 +5486,13 b' msgstr ""'
5380 5486 #: rhodecode/templates/admin/settings/settings_global.mako:9
5381 5487 #: rhodecode/templates/email_templates/pull_request_review.mako:39
5382 5488 #: rhodecode/templates/email_templates/pull_request_review.mako:72
5383 #: rhodecode/templates/pullrequests/pullrequest.mako:38
5384 #: rhodecode/templates/pullrequests/pullrequests.mako:104
5489 #: rhodecode/templates/pullrequests/pullrequest.mako:45
5490 #: rhodecode/templates/pullrequests/pullrequests.mako:114
5385 5491 msgid "Title"
5386 5492 msgstr ""
5387 5493
5388 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 5496 msgid "Last Update"
5391 5497 msgstr ""
5392 5498
@@ -5404,7 +5510,7 b' msgid "My Notifications"'
5404 5510 msgstr ""
5405 5511
5406 5512 #: rhodecode/templates/admin/notifications/notifications.mako:32
5407 #: rhodecode/templates/changeset/changeset.mako:140
5513 #: rhodecode/templates/changeset/changeset.mako:160
5408 5514 msgid "Comments"
5409 5515 msgstr ""
5410 5516
@@ -5425,6 +5531,10 b' msgstr ""'
5425 5531 msgid "Notifications"
5426 5532 msgstr ""
5427 5533
5534 #: rhodecode/templates/admin/notifications/show_notification.mako:40
5535 msgid "Subject"
5536 msgstr ""
5537
5428 5538 #: rhodecode/templates/admin/permissions/permissions.mako:5
5429 5539 msgid "Permissions Administration"
5430 5540 msgstr ""
@@ -5483,23 +5593,23 b' msgid "Default IP Whitelist For All User'
5483 5593 msgstr ""
5484 5594
5485 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 5597 #, python-format
5488 5598 msgid "Confirm to delete this ip: %s"
5489 5599 msgstr ""
5490 5600
5491 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 5603 msgid "All IP addresses are allowed"
5494 5604 msgstr ""
5495 5605
5496 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 5608 msgid "New IP Address"
5499 5609 msgstr ""
5500 5610
5501 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 5613 msgid "Description..."
5504 5614 msgstr ""
5505 5615
@@ -5583,9 +5693,9 b' msgid "Add Child Group"'
5583 5693 msgstr ""
5584 5694
5585 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 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 5699 msgid "Advanced"
5590 5700 msgstr ""
5591 5701
@@ -5642,26 +5752,26 b' msgid "Repository Group Permissions"'
5642 5752 msgstr ""
5643 5753
5644 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 5756 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:15
5647 5757 msgid "User/User Group"
5648 5758 msgstr ""
5649 5759
5650 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 5762 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:31
5653 5763 msgid "super admin"
5654 5764 msgstr ""
5655 5765
5656 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 5768 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:34
5659 5769 msgid "owner"
5660 5770 msgstr ""
5661 5771
5662 5772 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:52
5663 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 5775 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:52
5666 5776 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:76
5667 5777 msgid "permission for all other users"
@@ -5669,8 +5779,8 b' msgstr ""'
5669 5779
5670 5780 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:62
5671 5781 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.mako:109
5672 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:71
5673 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:99
5782 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:70
5783 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:98
5674 5784 msgid "Revoke"
5675 5785 msgstr ""
5676 5786
@@ -5680,7 +5790,7 b' msgid "delegated admin"'
5680 5790 msgstr ""
5681 5791
5682 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 5794 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:117
5685 5795 #: rhodecode/templates/base/issue_tracker_settings.mako:83
5686 5796 msgid "Add new"
@@ -5745,7 +5855,7 b' msgid "Clone from"'
5745 5855 msgstr ""
5746 5856
5747 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 5859 #: rhodecode/templates/forks/fork.mako:52
5750 5860 msgid "Keep it short and to the point. Use a README file for longer descriptions."
5751 5861 msgstr ""
@@ -5755,7 +5865,6 b' msgid "Repository Group"'
5755 5865 msgstr ""
5756 5866
5757 5867 #: rhodecode/templates/admin/repos/repo_add_base.mako:58
5758 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:62
5759 5868 #: rhodecode/templates/forks/fork.mako:64
5760 5869 #, python-format
5761 5870 msgid "Select my personal group (%(repo_group_name)s)"
@@ -5775,7 +5884,7 b' msgid "Set the type of repository to cre'
5775 5884 msgstr ""
5776 5885
5777 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 5888 #: rhodecode/templates/forks/fork.mako:73
5780 5889 msgid "Landing commit"
5781 5890 msgstr ""
@@ -5803,36 +5912,36 b' msgstr ""'
5803 5912 msgid "%s repository settings"
5804 5913 msgstr ""
5805 5914
5806 #: rhodecode/templates/admin/repos/repo_edit.mako:55
5915 #: rhodecode/templates/admin/repos/repo_edit.mako:58
5807 5916 msgid "Extra Fields"
5808 5917 msgstr ""
5809 5918
5810 #: rhodecode/templates/admin/repos/repo_edit.mako:61
5919 #: rhodecode/templates/admin/repos/repo_edit.mako:64
5811 5920 msgid "Caches"
5812 5921 msgstr ""
5813 5922
5814 #: rhodecode/templates/admin/repos/repo_edit.mako:65
5923 #: rhodecode/templates/admin/repos/repo_edit.mako:68
5815 5924 msgid "Remote"
5816 5925 msgstr ""
5817 5926
5818 #: rhodecode/templates/admin/repos/repo_edit.mako:69
5927 #: rhodecode/templates/admin/repos/repo_edit.mako:72
5819 5928 #: rhodecode/templates/summary/components.mako:135
5820 5929 msgid "Statistics"
5821 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 5937 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:3
5825 5938 msgid "Maintenance"
5826 5939 msgstr ""
5827 5940
5828 #: rhodecode/templates/admin/repos/repo_edit.mako:78
5941 #: rhodecode/templates/admin/repos/repo_edit.mako:86
5829 5942 msgid "Strip"
5830 5943 msgstr ""
5831 5944
5832 #: rhodecode/templates/admin/repos/repo_edit.mako:93
5833 msgid "Reviewers"
5834 msgstr ""
5835
5836 5945 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:7
5837 5946 msgid "Updated on"
5838 5947 msgstr ""
@@ -5867,84 +5976,77 b' msgstr ""'
5867 5976 msgid "Public Journal Visibility"
5868 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 5980 msgid "Remove from Public Journal"
5872 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 5984 msgid "Add to Public Journal"
5876 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 5988 msgid "All actions made on this repository will be visible to everyone following the public journal."
5880 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 5992 msgid "Locking state"
5884 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 5996 msgid "This Repository is not currently locked."
5888 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 6000 msgid "Confirm to unlock repository."
5892 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 6004 msgid "Unlock repository"
5896 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 6008 msgid "Confirm to lock repository."
5900 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 6012 msgid "Lock Repository"
5904 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 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 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 6020 msgid "Delete repository"
5912 6021 msgstr ""
5913 6022
5914 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:121
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
6023 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:124
5922 6024 msgid "Detach forks"
5923 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 6028 msgid "Delete forks"
5927 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 6032 #: rhodecode/templates/data_table/_dt_elements.mako:124
5931 6033 #, python-format
5932 6034 msgid "Confirm to delete this repository: %s"
5933 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 6038 msgid "Delete This Repository"
5937 6039 msgstr ""
5938 6040
5939 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:146
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."
5941 msgstr ""
5942
5943 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:180
6041 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:145
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."
6043 msgstr ""
6044
6045 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:179
5944 6046 msgid "Change repository"
5945 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 6050 msgid "Pick repository"
5949 6051 msgstr ""
5950 6052
@@ -5952,44 +6054,41 b' msgstr ""'
5952 6054 msgid "Invalidate Cache for Repository"
5953 6055 msgstr ""
5954 6056
5955 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:10
5956 msgid "Invalidate repository cache"
6057 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:7
6058 msgid "Manually invalidate the repository cache. On the next access a repository cache will be recreated."
5957 6059 msgstr ""
5958 6060
5959 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 6070 msgid "Confirm to invalidate repository cache"
5961 6071 msgstr ""
5962 6072
5963 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:14
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
6073 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:39
5975 6074 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:32
5976 6075 #: rhodecode/templates/base/issue_tracker_settings.mako:13
5977 6076 msgid "Prefix"
5978 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 6080 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:11
5982 6081 msgid "Key"
5983 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 6085 #: rhodecode/templates/admin/user_groups/user_group_add.mako:52
5987 6086 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:51
5988 6087 #: rhodecode/templates/admin/user_groups/user_groups.mako:64
5989 6088 #: rhodecode/templates/admin/users/user_add.mako:97
5990 6089 #: rhodecode/templates/admin/users/user_edit_groups.mako:64
5991 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 6092 msgid "Active"
5994 6093 msgstr ""
5995 6094
@@ -6063,19 +6162,27 b' msgstr ""'
6063 6162 msgid "Test Patterns"
6064 6163 msgstr ""
6065 6164
6066 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:9
6067 msgid "Perform maintenance tasks for this repo, following tasks will be performed"
6068 msgstr ""
6069
6070 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:16
6165 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:8
6166 msgid "Perform maintenance tasks for this repo"
6167 msgstr ""
6168
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 6178 msgid "No maintenance tasks for this repo available"
6072 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 6182 msgid "Run Maintenance"
6076 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 6186 msgid "Performing Maintenance"
6080 6187 msgstr ""
6081 6188
@@ -6083,11 +6190,11 b' msgstr ""'
6083 6190 msgid "Repository Permissions"
6084 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 6194 msgid "private repository"
6088 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 6198 msgid "only users/user groups explicitly added here will have access"
6092 6199 msgstr ""
6093 6200
@@ -6095,56 +6202,87 b' msgstr ""'
6095 6202 msgid "Remote url"
6096 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 6210 msgid "Remote mirror url"
6100 6211 msgstr ""
6101 6212
6102 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:12
6103 msgid "Pull can be automated by such api call called periodically (in crontab etc)"
6104 msgstr ""
6105
6106 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:22
6107 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:30
6213 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:15
6214 msgid "Pull can be automated by such api call. Can be called periodically in crontab etc."
6215 msgstr ""
6216
6217 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:25
6218 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:36
6108 6219 msgid "Pull changes from remote location"
6109 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 6223 msgid "Confirm to pull changes from remote side"
6113 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 6227 msgid "This repository does not have any remote mirror url set."
6117 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 6242 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:6
6120 6243 #, python-format
6121 6244 msgid "Settings for Repository: %s"
6122 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 6248 msgid "Non-changeable id"
6126 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 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 6260 msgid ""
6138 6261 "In case this repository is renamed or moved into another group the repository url changes.\n"
6139 6262 " Using above url guarantees that this repository will always be accessible under such url.\n"
6140 6263 " Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service."
6141 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 6282 msgid "Remote uri"
6145 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 6286 #: rhodecode/templates/base/perms_summary.mako:79
6149 6287 #: rhodecode/templates/base/perms_summary.mako:149
6150 6288 #: rhodecode/templates/base/perms_summary.mako:151
@@ -6152,63 +6290,56 b' msgstr ""'
6152 6290 msgid "edit"
6153 6291 msgstr ""
6154 6292
6155 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:39
6156 msgid "new value, leave empty to remove"
6157 msgstr ""
6158
6159 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:41
6293 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:65
6294 msgid "enter new value, or leave empty to remove"
6295 msgstr ""
6296
6297 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:75
6160 6298 msgid "cancel"
6161 6299 msgstr ""
6162 6300
6163 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:48
6164 msgid "http[s] url where from repository was imported, also used for doing remote pulls."
6165 msgstr ""
6166
6167 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:56
6168 #: rhodecode/templates/data_table/_dt_elements.mako:164
6169 #: rhodecode/templates/forks/fork.mako:58
6170 msgid "Repository group"
6171 msgstr ""
6172
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"
6301 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:87
6302 msgid "http[s] url where from repository was imported, this field can used for doing {pull_link}."
6303 msgstr ""
6304
6305 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:88
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."
6307 msgstr ""
6308
6309 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:103
6310 msgid "Default commit for files page, downloads, full text search index and readme"
6193 6311 msgstr ""
6194 6312
6195 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 6327 msgid "Enable statistics window on summary page."
6197 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 6331 msgid "Enable downloads"
6201 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 6335 msgid "Enable download menu on summary page."
6205 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 6339 msgid "Enable automatic locking"
6209 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 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 6344 msgstr ""
6214 6345
@@ -6272,32 +6403,24 b' msgstr ""'
6272 6403 msgid "Remove"
6273 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 6407 msgid "Checking commits"
6277 6408 msgstr ""
6278 6409
6279 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:127
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
6410 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:142
6288 6411 msgid " commit verified positive"
6289 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 6415 msgid " commit verified negative"
6293 6416 msgstr ""
6294 6417
6295 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:153
6296 msgid " commit striped successful"
6297 msgstr ""
6298
6299 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:156
6300 msgid " commit striped failed"
6418 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:179
6419 msgid " commit striped successfully"
6420 msgstr ""
6421
6422 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:182
6423 msgid " commit strip failed"
6301 6424 msgstr ""
6302 6425
6303 6426 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:13
@@ -6835,12 +6958,12 b' msgid "%s user group settings"'
6835 6958 msgstr ""
6836 6959
6837 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 6962 msgid "Global permissions"
6840 6963 msgstr ""
6841 6964
6842 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 6967 msgid "Permissions summary"
6845 6968 msgstr ""
6846 6969
@@ -6877,43 +7000,35 b' msgid "This group is set to be automatic'
6877 7000 msgstr ""
6878 7001
6879 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 7003 msgid "This group synchronization was set by"
6885 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 7007 msgid "This group is not set to be automatically synchronised"
6889 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 7011 msgid "Disable synchronization"
6893 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 7015 msgid "Enable synchronization"
6897 7016 msgstr ""
6898 7017
6899 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:60
6900 msgid "User group will no longer synchronize membership"
6901 msgstr ""
6902
6903 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:62
6904 msgid "User group will start to synchronize membership"
6905 msgstr ""
6906
6907 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:75
7018 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:58
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)"
7020 msgstr ""
7021
7022 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:71
6908 7023 msgid "Delete User Group"
6909 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 7027 #, python-format
6913 7028 msgid "Confirm to delete user group `%(ugroup)s` with all permission assignments"
6914 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 7032 msgid "Delete This User Group"
6918 7033 msgstr ""
6919 7034
@@ -7005,24 +7120,28 b' msgstr ""'
7005 7120 msgid "%s user settings"
7006 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 7128 #: rhodecode/templates/admin/users/user_edit_profile.mako:5
7010 7129 msgid "User Profile"
7011 7130 msgstr ""
7012 7131
7013 #: rhodecode/templates/admin/users/user_edit.mako:34
7014 msgid "Auth tokens"
7015 msgstr ""
7016
7017 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 7137 msgid "Ip Whitelist"
7019 7138 msgstr ""
7020 7139
7021 #: rhodecode/templates/admin/users/user_edit.mako:40
7140 #: rhodecode/templates/admin/users/user_edit.mako:45
7022 7141 msgid "User Groups Management"
7023 7142 msgstr ""
7024 7143
7025 #: rhodecode/templates/admin/users/user_edit.mako:41
7144 #: rhodecode/templates/admin/users/user_edit.mako:46
7026 7145 msgid "User audit"
7027 7146 msgstr ""
7028 7147
@@ -7036,7 +7155,7 b' msgid "Last login"'
7036 7155 msgstr ""
7037 7156
7038 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 7159 msgid "Last activity"
7041 7160 msgstr ""
7042 7161
@@ -7151,6 +7270,11 b' msgstr ""'
7151 7270 msgid "Additional Email Addresses"
7152 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 7278 #: rhodecode/templates/admin/users/user_edit_groups.mako:12
7155 7279 #, python-format
7156 7280 msgid "Add `%s` to user group"
@@ -7160,12 +7284,24 b' msgstr ""'
7160 7284 msgid "Custom IP Whitelist"
7161 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 7300 #, python-format
7165 7301 msgid "Inherited from %s"
7166 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 7305 msgid ""
7170 7306 "Enter comma separated list of ip addresses like 127.0.0.1,\n"
7171 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 7346 msgid "Users administration"
7211 7347 msgstr ""
7212 7348
7213 #: rhodecode/templates/admin/users/users.mako:77
7349 #: rhodecode/templates/admin/users/users.mako:78
7214 7350 msgid "Auth type"
7215 7351 msgstr ""
7216 7352
@@ -7278,110 +7414,110 b' msgstr ""'
7278 7414 msgid "Show Pull Requests for %s"
7279 7415 msgstr ""
7280 7416
7281 #: rhodecode/templates/base/base.mako:246
7417 #: rhodecode/templates/base/base.mako:247
7282 7418 msgid "Options"
7283 7419 msgstr ""
7284 7420
7285 #: rhodecode/templates/base/base.mako:253
7421 #: rhodecode/templates/base/base.mako:255
7286 7422 #: rhodecode/templates/forks/forks_data.mako:30
7287 7423 msgid "Compare fork"
7288 7424 msgstr ""
7289 7425
7290 #: rhodecode/templates/base/base.mako:256
7291 #: rhodecode/templates/base/base.mako:403
7426 #: rhodecode/templates/base/base.mako:258
7427 #: rhodecode/templates/base/base.mako:406
7292 7428 #: rhodecode/templates/search/search.mako:64
7293 7429 msgid "Search"
7294 7430 msgstr ""
7295 7431
7296 #: rhodecode/templates/base/base.mako:260
7297 msgid "Unlock"
7298 msgstr ""
7299
7300 7432 #: rhodecode/templates/base/base.mako:262
7433 msgid "Unlock"
7434 msgstr ""
7435
7436 #: rhodecode/templates/base/base.mako:264
7301 7437 msgid "Lock"
7302 7438 msgstr ""
7303 7439
7304 #: rhodecode/templates/base/base.mako:267
7440 #: rhodecode/templates/base/base.mako:269
7305 7441 #: rhodecode/templates/data_table/_dt_elements.mako:27
7306 7442 #: rhodecode/templates/data_table/_dt_elements.mako:28
7307 7443 #: rhodecode/templates/forks/forks_data.mako:8
7308 #: rhodecode/templates/summary/components.mako:103
7309 7444 msgid "Fork"
7310 msgid_plural "Forks"
7311 msgstr[0] ""
7312 msgstr[1] ""
7313
7314 #: rhodecode/templates/base/base.mako:268
7445 msgstr ""
7446
7447 #: rhodecode/templates/base/base.mako:270
7315 7448 msgid "Create Pull Request"
7316 7449 msgstr ""
7317 7450
7318 #: rhodecode/templates/base/base.mako:290
7451 #: rhodecode/templates/base/base.mako:292
7319 7452 msgid "Sign in"
7320 7453 msgstr ""
7321 7454
7322 #: rhodecode/templates/base/base.mako:298
7455 #: rhodecode/templates/base/base.mako:300
7323 7456 #: rhodecode/templates/debug_style/login.html:28
7324 7457 msgid "Sign in to your account"
7325 7458 msgstr ""
7326 7459
7327 #: rhodecode/templates/base/base.mako:315
7460 #: rhodecode/templates/base/base.mako:317
7328 7461 #: rhodecode/templates/debug_style/login.html:46
7329 7462 msgid "(Forgot password?)"
7330 7463 msgstr ""
7331 7464
7332 #: rhodecode/templates/base/base.mako:325
7333 #: rhodecode/templates/debug_style/login.html:56
7465 #: rhodecode/templates/base/base.mako:327
7334 7466 msgid "Don't have an account ?"
7335 7467 msgstr ""
7336 7468
7337 #: rhodecode/templates/base/base.mako:345
7469 #: rhodecode/templates/base/base.mako:329
7470 msgid "Using external auth? Sign In here."
7471 msgstr ""
7472
7473 #: rhodecode/templates/base/base.mako:348
7338 7474 msgid "My personal group"
7339 7475 msgstr ""
7340 7476
7341 #: rhodecode/templates/base/base.mako:349
7477 #: rhodecode/templates/base/base.mako:352
7342 7478 msgid "Sign Out"
7343 7479 msgstr ""
7344 7480
7345 #: rhodecode/templates/base/base.mako:385
7481 #: rhodecode/templates/base/base.mako:388
7346 7482 msgid "Show activity journal"
7347 7483 msgstr ""
7348 7484
7349 #: rhodecode/templates/base/base.mako:386
7485 #: rhodecode/templates/base/base.mako:389
7350 7486 #: rhodecode/templates/journal/journal.mako:4
7351 7487 #: rhodecode/templates/journal/journal.mako:14
7352 7488 msgid "Journal"
7353 7489 msgstr ""
7354 7490
7355 #: rhodecode/templates/base/base.mako:391
7491 #: rhodecode/templates/base/base.mako:394
7356 7492 msgid "Show Public activity journal"
7357 7493 msgstr ""
7358 7494
7359 #: rhodecode/templates/base/base.mako:392
7495 #: rhodecode/templates/base/base.mako:395
7360 7496 msgid "Public journal"
7361 7497 msgstr ""
7362 7498
7363 #: rhodecode/templates/base/base.mako:397
7499 #: rhodecode/templates/base/base.mako:400
7364 7500 msgid "Show Gists"
7365 7501 msgstr ""
7366 7502
7367 #: rhodecode/templates/base/base.mako:398
7503 #: rhodecode/templates/base/base.mako:401
7368 7504 msgid "Gists"
7369 7505 msgstr ""
7370 7506
7371 #: rhodecode/templates/base/base.mako:402
7507 #: rhodecode/templates/base/base.mako:405
7372 7508 msgid "Search in repositories you have access to"
7373 7509 msgstr ""
7374 7510
7375 #: rhodecode/templates/base/base.mako:408
7511 #: rhodecode/templates/base/base.mako:411
7376 7512 msgid "Admin settings"
7377 7513 msgstr ""
7378 7514
7379 #: rhodecode/templates/base/base.mako:415
7515 #: rhodecode/templates/base/base.mako:418
7380 7516 msgid "Delegated Admin settings"
7381 7517 msgstr ""
7382 7518
7383 #: rhodecode/templates/base/base.mako:425
7384 #: rhodecode/templates/base/base.mako:426
7519 #: rhodecode/templates/base/base.mako:428
7520 #: rhodecode/templates/base/base.mako:429
7385 7521 #: rhodecode/templates/debug_style/alerts.html:5
7386 7522 #: rhodecode/templates/debug_style/buttons.html:5
7387 7523 #: rhodecode/templates/debug_style/code-block.html:6
@@ -7403,15 +7539,15 b' msgstr ""'
7403 7539 msgid "Style"
7404 7540 msgstr ""
7405 7541
7406 #: rhodecode/templates/base/base.mako:483
7542 #: rhodecode/templates/base/base.mako:486
7407 7543 msgid "Go to"
7408 7544 msgstr ""
7409 7545
7410 #: rhodecode/templates/base/base.mako:536
7546 #: rhodecode/templates/base/base.mako:539
7411 7547 msgid "Keyboard shortcuts"
7412 7548 msgstr ""
7413 7549
7414 #: rhodecode/templates/base/base.mako:544
7550 #: rhodecode/templates/base/base.mako:547
7415 7551 msgid "Site-wide shortcuts"
7416 7552 msgstr ""
7417 7553
@@ -7503,10 +7639,10 b' msgid "Confirm to remove this pattern:"'
7503 7639 msgstr ""
7504 7640
7505 7641 #: rhodecode/templates/base/issue_tracker_settings.mako:191
7506 #: rhodecode/templates/changeset/changeset_file_comment.mako:269
7507 #: rhodecode/templates/changeset/changeset_file_comment.mako:319
7508 #: rhodecode/templates/files/files_add.mako:78
7509 #: rhodecode/templates/files/files_add.mako:224
7642 #: rhodecode/templates/changeset/changeset_file_comment.mako:274
7643 #: rhodecode/templates/changeset/changeset_file_comment.mako:324
7644 #: rhodecode/templates/files/files_add.mako:84
7645 #: rhodecode/templates/files/files_add.mako:228
7510 7646 #: rhodecode/templates/files/files_edit.mako:82
7511 7647 #: rhodecode/templates/files/files_edit.mako:185
7512 7648 msgid "Preview"
@@ -7586,7 +7722,7 b' msgstr ""'
7586 7722 msgid "No permission defined"
7587 7723 msgstr ""
7588 7724
7589 #: rhodecode/templates/base/root.mako:150
7725 #: rhodecode/templates/base/root.mako:155
7590 7726 msgid "Please enable JavaScript to use RhodeCode Enterprise"
7591 7727 msgstr ""
7592 7728
@@ -7682,95 +7818,107 b' msgstr ""'
7682 7818 msgid "Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type."
7683 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 7834 msgid "Mercurial Labs Settings"
7687 7835 msgstr ""
7688 7836
7689 #: rhodecode/templates/base/vcs_settings.mako:139
7837 #: rhodecode/templates/base/vcs_settings.mako:152
7690 7838 msgid "These features are considered experimental and may not work as expected."
7691 7839 msgstr ""
7692 7840
7693 #: rhodecode/templates/base/vcs_settings.mako:145
7841 #: rhodecode/templates/base/vcs_settings.mako:158
7694 7842 msgid "Use rebase as merge strategy"
7695 7843 msgstr ""
7696 7844
7697 #: rhodecode/templates/base/vcs_settings.mako:148
7845 #: rhodecode/templates/base/vcs_settings.mako:161
7698 7846 msgid "Use rebase instead of creating a merge commit when merging via web interface."
7699 7847 msgstr ""
7700 7848
7701 #: rhodecode/templates/base/vcs_settings.mako:160
7849 #: rhodecode/templates/base/vcs_settings.mako:173
7702 7850 msgid "Git Settings"
7703 7851 msgstr ""
7704 7852
7705 #: rhodecode/templates/base/vcs_settings.mako:165
7853 #: rhodecode/templates/base/vcs_settings.mako:178
7706 7854 msgid "Enable lfs extension"
7707 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 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 7866 msgid "Filesystem location where Git lfs objects should be stored."
7719 7867 msgstr ""
7720 7868
7721 #: rhodecode/templates/base/vcs_settings.mako:193
7869 #: rhodecode/templates/base/vcs_settings.mako:206
7722 7870 msgid "Global Subversion Settings"
7723 7871 msgstr ""
7724 7872
7725 #: rhodecode/templates/base/vcs_settings.mako:199
7873 #: rhodecode/templates/base/vcs_settings.mako:212
7726 7874 msgid "Proxy subversion HTTP requests"
7727 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 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 7890 msgid "Generate Apache Config"
7743 7891 msgstr ""
7744 7892
7745 #: rhodecode/templates/base/vcs_settings.mako:228
7893 #: rhodecode/templates/base/vcs_settings.mako:241
7746 7894 msgid "Subversion Settings"
7747 7895 msgstr ""
7748 7896
7749 #: rhodecode/templates/base/vcs_settings.mako:233
7897 #: rhodecode/templates/base/vcs_settings.mako:246
7750 7898 msgid "Repository patterns"
7751 7899 msgstr ""
7752 7900
7753 #: rhodecode/templates/base/vcs_settings.mako:237
7901 #: rhodecode/templates/base/vcs_settings.mako:250
7754 7902 msgid "Patterns for identifying SVN branches and tags. For recursive search, use \"*\". Eg.: \"/branches/*\""
7755 7903 msgstr ""
7756 7904
7757 #: rhodecode/templates/base/vcs_settings.mako:301
7905 #: rhodecode/templates/base/vcs_settings.mako:314
7758 7906 msgid "Pull Request Settings"
7759 7907 msgstr ""
7760 7908
7761 #: rhodecode/templates/base/vcs_settings.mako:306
7909 #: rhodecode/templates/base/vcs_settings.mako:319
7762 7910 msgid "Enable server-side merge for pull requests"
7763 7911 msgstr ""
7764 7912
7765 #: rhodecode/templates/base/vcs_settings.mako:309
7913 #: rhodecode/templates/base/vcs_settings.mako:322
7766 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 7915 msgstr ""
7768 7916
7769 #: rhodecode/templates/base/vcs_settings.mako:313
7917 #: rhodecode/templates/base/vcs_settings.mako:326
7770 7918 msgid "Invalidate and relocate inline comments during update"
7771 7919 msgstr ""
7772 7920
7773 #: rhodecode/templates/base/vcs_settings.mako:316
7921 #: rhodecode/templates/base/vcs_settings.mako:329
7774 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 7923 msgstr ""
7776 7924
@@ -7788,10 +7936,10 b' msgid "Compare Selected Bookmarks"'
7788 7936 msgstr ""
7789 7937
7790 7938 #: rhodecode/templates/bookmarks/bookmarks_data.mako:13
7791 #: rhodecode/templates/changelog/changelog_elements.mako:91
7792 #: rhodecode/templates/changelog/changelog_summary_data.mako:62
7793 #: rhodecode/templates/changeset/changeset.mako:92
7939 #: rhodecode/templates/changelog/changelog_elements.mako:111
7940 #: rhodecode/templates/changeset/changeset.mako:112
7794 7941 #: rhodecode/templates/files/base.mako:10
7942 #: rhodecode/templates/summary/summary_commits.mako:62
7795 7943 #, python-format
7796 7944 msgid "Bookmark %s"
7797 7945 msgstr ""
@@ -7810,10 +7958,10 b' msgid "Compare Selected Branches"'
7810 7958 msgstr ""
7811 7959
7812 7960 #: rhodecode/templates/branches/branches_data.mako:12
7813 #: rhodecode/templates/changelog/changelog_elements.mako:83
7814 #: rhodecode/templates/changelog/changelog_summary_data.mako:76
7815 #: rhodecode/templates/changeset/changeset.mako:105
7961 #: rhodecode/templates/changelog/changelog_elements.mako:103
7962 #: rhodecode/templates/changeset/changeset.mako:125
7816 7963 #: rhodecode/templates/files/base.mako:23
7964 #: rhodecode/templates/summary/summary_commits.mako:76
7817 7965 #, python-format
7818 7966 msgid "Branch %s"
7819 7967 msgstr ""
@@ -7851,19 +7999,19 b' msgstr[0] ""'
7851 7999 msgstr[1] ""
7852 8000
7853 8001 #: rhodecode/templates/changelog/changelog.mako:110
7854 #: rhodecode/templates/files/files_add.mako:93
8002 #: rhodecode/templates/files/files_add.mako:99
7855 8003 #: rhodecode/templates/files/files_delete.mako:60
7856 8004 #: rhodecode/templates/files/files_edit.mako:96
7857 8005 msgid "Commit Message"
7858 8006 msgstr ""
7859 8007
7860 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 8010 msgid "Age"
7863 8011 msgstr ""
7864 8012
7865 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 8015 msgid "Refs"
7868 8016 msgstr ""
7869 8017
@@ -7880,7 +8028,7 b' msgid "load previous"'
7880 8028 msgstr ""
7881 8029
7882 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 8032 #, python-format
7885 8033 msgid ""
7886 8034 "Commit status: %s\n"
@@ -7888,44 +8036,66 b' msgid ""'
7888 8036 msgstr ""
7889 8037
7890 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 8040 #, python-format
7893 8041 msgid "Commit status: %s"
7894 8042 msgstr ""
7895 8043
7896 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 8046 msgid "Commit status: Not Reviewed"
7899 8047 msgstr ""
7900 8048
7901 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 8051 msgid "Commit has comments"
7904 8052 msgstr ""
7905 8053
7906 8054 #: rhodecode/templates/changelog/changelog_elements.mako:53
7907 #: rhodecode/templates/compare/compare_commits.mako:46
7908 #: rhodecode/templates/pullrequests/pullrequest_show.mako:495
8055 #: rhodecode/templates/changeset/changeset.mako:40
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 8079 #: rhodecode/templates/search/search_commit.mako:36
7910 8080 msgid "Expand commit message"
7911 8081 msgstr ""
7912 8082
7913 #: rhodecode/templates/changelog/changelog_elements.mako:77
7914 #: rhodecode/templates/changeset/changeset.mako:86
8083 #: rhodecode/templates/changelog/changelog_elements.mako:97
8084 #: rhodecode/templates/changeset/changeset.mako:106
7915 8085 #: rhodecode/templates/files/base.mako:4
7916 8086 msgid "merge"
7917 8087 msgstr ""
7918 8088
7919 #: rhodecode/templates/changelog/changelog_elements.mako:99
7920 #: rhodecode/templates/changelog/changelog_summary_data.mako:69
7921 #: rhodecode/templates/changeset/changeset.mako:99
8089 #: rhodecode/templates/changelog/changelog_elements.mako:119
8090 #: rhodecode/templates/changeset/changeset.mako:119
7922 8091 #: rhodecode/templates/files/base.mako:17
8092 #: rhodecode/templates/summary/summary_commits.mako:69
7923 8093 #: rhodecode/templates/tags/tags_data.mako:12
7924 8094 #, python-format
7925 8095 msgid "Tag %s"
7926 8096 msgstr ""
7927 8097
7928 #: rhodecode/templates/changelog/changelog_elements.mako:113
8098 #: rhodecode/templates/changelog/changelog_elements.mako:133
7929 8099 msgid "load next"
7930 8100 msgstr ""
7931 8101
@@ -7933,100 +8103,78 b' msgstr ""'
7933 8103 msgid "Show File"
7934 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 8106 #: rhodecode/templates/changeset/changeset.mako:7
7959 8107 #, python-format
7960 8108 msgid "%s Commit"
7961 8109 msgstr ""
7962 8110
7963 #: rhodecode/templates/changeset/changeset.mako:43
8111 #: rhodecode/templates/changeset/changeset.mako:62
7964 8112 msgid "Parent Commit"
7965 8113 msgstr ""
7966 8114
7967 #: rhodecode/templates/changeset/changeset.mako:43
8115 #: rhodecode/templates/changeset/changeset.mako:62
7968 8116 msgid "Parent"
7969 8117 msgstr ""
7970 8118
7971 #: rhodecode/templates/changeset/changeset.mako:47
8119 #: rhodecode/templates/changeset/changeset.mako:66
7972 8120 msgid "Child Commit"
7973 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 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 8133 #: rhodecode/templates/changeset/changeset_file_comment.mako:81
7986 8134 #: rhodecode/templates/compare/compare_diff.mako:159
7987 8135 msgid "Commit status"
7988 8136 msgstr ""
7989 8137
7990 #: rhodecode/templates/changeset/changeset.mako:79
8138 #: rhodecode/templates/changeset/changeset.mako:99
7991 8139 #: rhodecode/templates/files/file_tree_detail.mako:21
7992 8140 #: rhodecode/templates/files/files_detail.mako:20
7993 8141 msgid "References"
7994 8142 msgstr ""
7995 8143
7996 #: rhodecode/templates/changeset/changeset.mako:115
8144 #: rhodecode/templates/changeset/changeset.mako:135
7997 8145 msgid "Diff options"
7998 8146 msgstr ""
7999 8147
8000 #: rhodecode/templates/changeset/changeset.mako:119
8148 #: rhodecode/templates/changeset/changeset.mako:139
8001 8149 #: rhodecode/templates/codeblocks/diffs.mako:445
8002 8150 #: rhodecode/templates/codeblocks/diffs.mako:448
8003 8151 msgid "Raw diff"
8004 8152 msgstr ""
8005 8153
8006 #: rhodecode/templates/changeset/changeset.mako:120
8154 #: rhodecode/templates/changeset/changeset.mako:140
8007 8155 msgid "Raw Diff"
8008 8156 msgstr ""
8009 8157
8010 #: rhodecode/templates/changeset/changeset.mako:123
8158 #: rhodecode/templates/changeset/changeset.mako:143
8011 8159 msgid "Patch diff"
8012 8160 msgstr ""
8013 8161
8014 #: rhodecode/templates/changeset/changeset.mako:124
8162 #: rhodecode/templates/changeset/changeset.mako:144
8015 8163 msgid "Patch Diff"
8016 8164 msgstr ""
8017 8165
8018 #: rhodecode/templates/changeset/changeset.mako:127
8166 #: rhodecode/templates/changeset/changeset.mako:147
8019 8167 #: rhodecode/templates/codeblocks/diffs.mako:452
8020 8168 #: rhodecode/templates/codeblocks/diffs.mako:455
8021 8169 msgid "Download diff"
8022 8170 msgstr ""
8023 8171
8024 #: rhodecode/templates/changeset/changeset.mako:128
8172 #: rhodecode/templates/changeset/changeset.mako:148
8025 8173 msgid "Download Diff"
8026 8174 msgstr ""
8027 8175
8028 #: rhodecode/templates/changeset/changeset.mako:145
8029 #: rhodecode/templates/changeset/changeset.mako:147
8176 #: rhodecode/templates/changeset/changeset.mako:165
8177 #: rhodecode/templates/changeset/changeset.mako:167
8030 8178 #: rhodecode/tests/functional/test_commit_comments.py:275
8031 8179 #, python-format
8032 8180 msgid "%d Commit comment"
@@ -8034,8 +8182,8 b' msgid_plural "%d Commit comments"'
8034 8182 msgstr[0] ""
8035 8183 msgstr[1] ""
8036 8184
8037 #: rhodecode/templates/changeset/changeset.mako:150
8038 #: rhodecode/templates/changeset/changeset.mako:152
8185 #: rhodecode/templates/changeset/changeset.mako:170
8186 #: rhodecode/templates/changeset/changeset.mako:172
8039 8187 #: rhodecode/tests/functional/test_commit_comments.py:282
8040 8188 #, python-format
8041 8189 msgid "%d Inline Comment"
@@ -8043,19 +8191,19 b' msgid_plural "%d Inline Comments"'
8043 8191 msgstr[0] ""
8044 8192 msgstr[1] ""
8045 8193
8046 #: rhodecode/templates/changeset/changeset.mako:160
8194 #: rhodecode/templates/changeset/changeset.mako:180
8047 8195 msgid "Unresolved TODOs"
8048 8196 msgstr ""
8049 8197
8050 #: rhodecode/templates/changeset/changeset.mako:169
8198 #: rhodecode/templates/changeset/changeset.mako:189
8051 8199 msgid "There are no unresolved TODOs"
8052 8200 msgstr ""
8053 8201
8054 #: rhodecode/templates/changeset/changeset.mako:249
8202 #: rhodecode/templates/changeset/changeset.mako:269
8055 8203 msgid "No Child Commits"
8056 8204 msgstr ""
8057 8205
8058 #: rhodecode/templates/changeset/changeset.mako:285
8206 #: rhodecode/templates/changeset/changeset.mako:305
8059 8207 msgid "No Parent Commits"
8060 8208 msgstr ""
8061 8209
@@ -8081,72 +8229,80 b' msgstr ""'
8081 8229 msgid "resolves comment #{}"
8082 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 8241 msgid "Outdated comment from pull request version {0}"
8086 8242 msgstr ""
8087 8243
8088 #: rhodecode/templates/changeset/changeset_file_comment.mako:104
8089 #: rhodecode/templates/changeset/changeset_file_comment.mako:119
8244 #: rhodecode/templates/changeset/changeset_file_comment.mako:109
8245 #: rhodecode/templates/changeset/changeset_file_comment.mako:124
8090 8246 msgid "Comment from pull request version {0}"
8091 8247 msgstr ""
8092 8248
8093 #: rhodecode/templates/changeset/changeset_file_comment.mako:116
8249 #: rhodecode/templates/changeset/changeset_file_comment.mako:121
8094 8250 msgid "Outdated comment from pull request version {}"
8095 8251 msgstr ""
8096 8252
8097 #: rhodecode/templates/changeset/changeset_file_comment.mako:146
8098 #: rhodecode/templates/changeset/changeset_file_comment.mako:149
8253 #: rhodecode/templates/changeset/changeset_file_comment.mako:151
8254 #: rhodecode/templates/changeset/changeset_file_comment.mako:154
8099 8255 msgid "Prev"
8100 8256 msgstr ""
8101 8257
8102 #: rhodecode/templates/changeset/changeset_file_comment.mako:147
8103 #: rhodecode/templates/changeset/changeset_file_comment.mako:150
8258 #: rhodecode/templates/changeset/changeset_file_comment.mako:152
8259 #: rhodecode/templates/changeset/changeset_file_comment.mako:155
8104 8260 msgid "Next"
8105 8261 msgstr ""
8106 8262
8107 #: rhodecode/templates/changeset/changeset_file_comment.mako:185
8263 #: rhodecode/templates/changeset/changeset_file_comment.mako:190
8108 8264 msgid "Leave a comment on this Pull Request."
8109 8265 msgstr ""
8110 8266
8111 #: rhodecode/templates/changeset/changeset_file_comment.mako:187
8267 #: rhodecode/templates/changeset/changeset_file_comment.mako:192
8112 8268 msgid "Leave a comment on {} commits in this range."
8113 8269 msgstr ""
8114 8270
8115 #: rhodecode/templates/changeset/changeset_file_comment.mako:189
8271 #: rhodecode/templates/changeset/changeset_file_comment.mako:194
8116 8272 msgid "Leave a comment on this Commit."
8117 8273 msgstr ""
8118 8274
8119 #: rhodecode/templates/changeset/changeset_file_comment.mako:277
8275 #: rhodecode/templates/changeset/changeset_file_comment.mako:282
8120 8276 #: rhodecode/templates/codeblocks/diffs.mako:71
8121 8277 msgid "You need to be logged in to leave comments."
8122 8278 msgstr ""
8123 8279
8124 #: rhodecode/templates/changeset/changeset_file_comment.mako:278
8280 #: rhodecode/templates/changeset/changeset_file_comment.mako:283
8125 8281 #: rhodecode/templates/codeblocks/diffs.mako:71
8126 8282 msgid "Login now"
8127 8283 msgstr ""
8128 8284
8129 #: rhodecode/templates/changeset/changeset_file_comment.mako:343
8285 #: rhodecode/templates/changeset/changeset_file_comment.mako:348
8130 8286 #, python-format
8131 8287 msgid "Comments parsed using %s syntax with %s, and %s actions support."
8132 8288 msgstr ""
8133 8289
8134 #: rhodecode/templates/changeset/changeset_file_comment.mako:345
8290 #: rhodecode/templates/changeset/changeset_file_comment.mako:350
8135 8291 msgid "Use @username inside this text to send notification to this RhodeCode user"
8136 8292 msgstr ""
8137 8293
8138 #: rhodecode/templates/changeset/changeset_file_comment.mako:346
8294 #: rhodecode/templates/changeset/changeset_file_comment.mako:351
8139 8295 msgid "Start typing with / for certain actions to be triggered via text box."
8140 8296 msgstr ""
8141 8297
8142 #: rhodecode/templates/changeset/changeset_file_comment.mako:363
8298 #: rhodecode/templates/changeset/changeset_file_comment.mako:368
8143 8299 #: rhodecode/templates/pullrequests/pullrequest_show.mako:15
8144 8300 #: rhodecode/templates/pullrequests/pullrequest_show.mako:153
8145 8301 #: rhodecode/templates/pullrequests/pullrequests.mako:52
8146 8302 msgid "Closed"
8147 8303 msgstr ""
8148 8304
8149 #: rhodecode/templates/changeset/changeset_file_comment.mako:393
8305 #: rhodecode/templates/changeset/changeset_file_comment.mako:398
8150 8306 #: rhodecode/templates/compare/compare_diff.mako:104
8151 8307 #: rhodecode/templates/compare/compare_diff.mako:112
8152 8308 #: rhodecode/templates/compare/compare_diff.mako:120
@@ -8185,7 +8341,7 b' msgstr ""'
8185 8341
8186 8342 #: rhodecode/templates/changeset/changeset_range.mako:99
8187 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 8345 #, python-format
8190 8346 msgid "Expand %s commit"
8191 8347 msgid_plural "Expand %s commits"
@@ -8194,7 +8350,7 b' msgstr[1] ""'
8194 8350
8195 8351 #: rhodecode/templates/changeset/changeset_range.mako:105
8196 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 8354 #, python-format
8199 8355 msgid "Collapse %s commit"
8200 8356 msgid_plural "Collapse %s commits"
@@ -8363,26 +8519,26 b' msgstr ""'
8363 8519 msgid "Compare was calculated based on this shared commit."
8364 8520 msgstr ""
8365 8521
8366 #: rhodecode/templates/compare/compare_commits.mako:16
8367 #: rhodecode/templates/pullrequests/pullrequest_show.mako:456
8522 #: rhodecode/templates/compare/compare_commits.mako:17
8523 #: rhodecode/templates/pullrequests/pullrequest_show.mako:483
8368 8524 msgid "Time"
8369 8525 msgstr ""
8370 8526
8371 #: rhodecode/templates/compare/compare_commits.mako:67
8527 #: rhodecode/templates/compare/compare_commits.mako:68
8372 8528 #, python-format
8373 8529 msgid "%s commit hidden"
8374 8530 msgid_plural "%s commits hidden"
8375 8531 msgstr[0] ""
8376 8532 msgstr[1] ""
8377 8533
8378 #: rhodecode/templates/compare/compare_commits.mako:68
8379 #: rhodecode/templates/pullrequests/pullrequest_show.mako:573
8534 #: rhodecode/templates/compare/compare_commits.mako:69
8535 #: rhodecode/templates/pullrequests/pullrequest_show.mako:600
8380 8536 msgid "show it"
8381 8537 msgid_plural "show them"
8382 8538 msgstr[0] ""
8383 8539 msgstr[1] ""
8384 8540
8385 #: rhodecode/templates/compare/compare_commits.mako:74
8541 #: rhodecode/templates/compare/compare_commits.mako:75
8386 8542 msgid "No commits in this compare"
8387 8543 msgstr ""
8388 8544
@@ -8420,6 +8576,7 b' msgstr ""'
8420 8576 #: rhodecode/templates/email_templates/pull_request_comment.mako:90
8421 8577 #: rhodecode/templates/email_templates/pull_request_review.mako:73
8422 8578 #: rhodecode/templates/files/files_source.mako:23
8579 #: rhodecode/templates/pullrequests/pullrequest_show.mako:71
8423 8580 msgid "Source"
8424 8581 msgstr ""
8425 8582
@@ -8633,6 +8790,10 b' msgstr ""'
8633 8790 msgid "Form vertical"
8634 8791 msgstr ""
8635 8792
8793 #: rhodecode/templates/debug_style/login.html:56
8794 msgid "Don't have an account ?"
8795 msgstr ""
8796
8636 8797 #: rhodecode/templates/email_templates/base.mako:32
8637 8798 #, python-format
8638 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 8907 msgstr ""
8747 8908
8748 8909 #: rhodecode/templates/email_templates/pull_request_comment.mako:49
8910 #: rhodecode/templates/pullrequests/pullrequest.mako:72
8749 8911 msgid "Source repository"
8750 8912 msgstr ""
8751 8913
@@ -8814,8 +8976,6 b' msgid "%(target_ref_type)s of %(target_r'
8814 8976 msgstr ""
8815 8977
8816 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 8979 #, python-format
8820 8980 msgid "%(num)s Commit"
8821 8981 msgid_plural "%(num)s Commits"
@@ -8839,7 +8999,7 b' msgstr ""'
8839 8999 msgid "Full Name"
8840 9000 msgstr ""
8841 9001
8842 #: rhodecode/templates/errors/error_document.mako:46
9002 #: rhodecode/templates/errors/error_document.mako:45
8843 9003 #, python-format
8844 9004 msgid "You will be redirected to %s in %s seconds"
8845 9005 msgstr ""
@@ -8910,6 +9070,7 b' msgid "Remove Custom Path"'
8910 9070 msgstr ""
8911 9071
8912 9072 #: rhodecode/templates/files/files_add.mako:50
9073 #: rhodecode/templates/files/files_add.mako:59
8913 9074 msgid "Filename"
8914 9075 msgstr ""
8915 9076
@@ -8917,34 +9078,34 b' msgstr ""'
8917 9078 msgid "Upload File"
8918 9079 msgstr ""
8919 9080
8920 #: rhodecode/templates/files/files_add.mako:59
8921 msgid "Upload file"
8922 msgstr ""
8923
8924 #: rhodecode/templates/files/files_add.mako:63
9081 #: rhodecode/templates/files/files_add.mako:62
8925 9082 msgid "No file selected"
8926 9083 msgstr ""
8927 9084
8928 9085 #: rhodecode/templates/files/files_add.mako:65
9086 msgid "Upload file"
9087 msgstr ""
9088
9089 #: rhodecode/templates/files/files_add.mako:71
8929 9090 msgid "Create New File"
8930 9091 msgstr ""
8931 9092
8932 #: rhodecode/templates/files/files_add.mako:75
9093 #: rhodecode/templates/files/files_add.mako:81
8933 9094 #: rhodecode/templates/files/files_edit.mako:79
8934 9095 msgid "line wraps"
8935 9096 msgstr ""
8936 9097
8937 #: rhodecode/templates/files/files_add.mako:76
9098 #: rhodecode/templates/files/files_add.mako:82
8938 9099 #: rhodecode/templates/files/files_edit.mako:80
8939 9100 msgid "on"
8940 9101 msgstr ""
8941 9102
8942 #: rhodecode/templates/files/files_add.mako:76
9103 #: rhodecode/templates/files/files_add.mako:82
8943 9104 #: rhodecode/templates/files/files_edit.mako:80
8944 9105 msgid "off"
8945 9106 msgstr ""
8946 9107
8947 #: rhodecode/templates/files/files_add.mako:103
9108 #: rhodecode/templates/files/files_add.mako:109
8948 9109 #: rhodecode/templates/files/files_edit.mako:106
8949 9110 msgid "Commit changes"
8950 9111 msgstr ""
@@ -8965,6 +9126,11 b' msgstr ""'
8965 9126 msgid "Close File List"
8966 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 9134 #: rhodecode/templates/files/files_browser.mako:27
8969 9135 msgid "Add File"
8970 9136 msgstr ""
@@ -9077,7 +9243,6 b' msgid "LargeFile"'
9077 9243 msgstr ""
9078 9244
9079 9245 #: rhodecode/templates/files/files_source.mako:10
9080 #: rhodecode/templates/search/search_content.mako:57
9081 9246 msgid "line"
9082 9247 msgid_plural "lines"
9083 9248 msgstr[0] ""
@@ -9141,6 +9306,10 b' msgstr ""'
9141 9306 msgid "Fork name"
9142 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 9313 #: rhodecode/templates/forks/fork.mako:93
9145 9314 msgid "Copy permissions"
9146 9315 msgstr ""
@@ -9178,6 +9347,13 b' msgstr ""'
9178 9347 msgid "Filter"
9179 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 9357 #: rhodecode/templates/journal/journal.mako:23
9182 9358 msgid "ATOM journal feed"
9183 9359 msgstr ""
@@ -9208,65 +9384,75 b' msgstr ""'
9208 9384 msgid "New pull request"
9209 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 9392 msgid "Write a short description on this pull request"
9213 9393 msgstr ""
9214 9394
9215 #: rhodecode/templates/pullrequests/pullrequest.mako:57
9395 #: rhodecode/templates/pullrequests/pullrequest.mako:64
9216 9396 msgid "Commit flow"
9217 9397 msgstr ""
9218 9398
9219 #: rhodecode/templates/pullrequests/pullrequest.mako:65
9220 msgid "Origin repository"
9221 msgstr ""
9222
9223 #: rhodecode/templates/pullrequests/pullrequest.mako:83
9399 #: rhodecode/templates/pullrequests/pullrequest.mako:90
9224 9400 msgid "Loading refs..."
9225 9401 msgstr ""
9226 9402
9227 #: rhodecode/templates/pullrequests/pullrequest.mako:94
9403 #: rhodecode/templates/pullrequests/pullrequest.mako:101
9228 9404 msgid "Submit Pull Request"
9229 9405 msgstr ""
9230 9406
9231 #: rhodecode/templates/pullrequests/pullrequest.mako:107
9232 #: rhodecode/templates/pullrequests/pullrequest_show.mako:322
9407 #: rhodecode/templates/pullrequests/pullrequest.mako:115
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 9419 msgid "Pull request reviewers"
9234 9420 msgstr ""
9235 9421
9236 #: rhodecode/templates/pullrequests/pullrequest.mako:118
9237 #: rhodecode/templates/pullrequests/pullrequest_show.mako:366
9238 msgid "Add reviewer"
9239 msgstr ""
9240
9241 #: rhodecode/templates/pullrequests/pullrequest.mako:297
9242 #: rhodecode/templates/pullrequests/pullrequest.mako:570
9243 msgid "Please select origin and destination"
9244 msgstr ""
9245
9246 #: rhodecode/templates/pullrequests/pullrequest.mako:303
9422 #: rhodecode/templates/pullrequests/pullrequest.mako:150
9423 #: rhodecode/templates/pullrequests/pullrequest_show.mako:392
9424 msgid "Add reviewer or reviewer group"
9425 msgstr ""
9426
9427 #: rhodecode/templates/pullrequests/pullrequest.mako:302
9428 #: rhodecode/templates/pullrequests/pullrequest.mako:504
9429 msgid "Please select source and target"
9430 msgstr ""
9431
9432 #: rhodecode/templates/pullrequests/pullrequest.mako:308
9247 9433 msgid "Loading compare ..."
9248 9434 msgstr ""
9249 9435
9250 #: rhodecode/templates/pullrequests/pullrequest.mako:350
9251 #: rhodecode/templates/pullrequests/pullrequest.mako:352
9436 #: rhodecode/templates/pullrequests/pullrequest.mako:356
9437 #: rhodecode/templates/pullrequests/pullrequest.mako:358
9252 9438 msgid "This pull request will consist of __COMMITS__ commit."
9253 9439 msgid_plural "This pull request will consist of __COMMITS__ commits."
9254 9440 msgstr[0] ""
9255 9441 msgstr[1] ""
9256 9442
9257 #: rhodecode/templates/pullrequests/pullrequest.mako:355
9443 #: rhodecode/templates/pullrequests/pullrequest.mako:361
9258 9444 msgid "Show detailed compare."
9259 9445 msgstr ""
9260 9446
9261 #: rhodecode/templates/pullrequests/pullrequest.mako:362
9447 #: rhodecode/templates/pullrequests/pullrequest.mako:368
9262 9448 msgid "There are no commits to merge."
9263 9449 msgstr ""
9264 9450
9265 #: rhodecode/templates/pullrequests/pullrequest.mako:462
9266 msgid "Destination repository"
9267 msgstr ""
9268
9269 #: rhodecode/templates/pullrequests/pullrequest.mako:473
9451 #: rhodecode/templates/pullrequests/pullrequest.mako:431
9452 msgid "Target repository"
9453 msgstr ""
9454
9455 #: rhodecode/templates/pullrequests/pullrequest.mako:441
9270 9456 msgid "Select commit reference"
9271 9457 msgstr ""
9272 9458
@@ -9314,10 +9500,6 b' msgstr ""'
9314 9500 msgid "Confirm to delete this pull request"
9315 9501 msgstr ""
9316 9502
9317 #: rhodecode/templates/pullrequests/pullrequest_show.mako:71
9318 msgid "Origin"
9319 msgstr ""
9320
9321 9503 #: rhodecode/templates/pullrequests/pullrequest_show.mako:88
9322 9504 msgid "Common ancestor"
9323 9505 msgstr ""
@@ -9416,69 +9598,69 b' msgid "Pull request versions not availab'
9416 9598 msgstr ""
9417 9599
9418 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 9602 msgid "Save Changes"
9421 9603 msgstr ""
9422 9604
9423 #: rhodecode/templates/pullrequests/pullrequest_show.mako:387
9605 #: rhodecode/templates/pullrequests/pullrequest_show.mako:414
9424 9606 msgid "Missing requirements:"
9425 9607 msgstr ""
9426 9608
9427 #: rhodecode/templates/pullrequests/pullrequest_show.mako:388
9609 #: rhodecode/templates/pullrequests/pullrequest_show.mako:415
9428 9610 msgid "These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled."
9429 9611 msgstr ""
9430 9612
9431 #: rhodecode/templates/pullrequests/pullrequest_show.mako:396
9613 #: rhodecode/templates/pullrequests/pullrequest_show.mako:423
9432 9614 msgid "Missing commits"
9433 9615 msgstr ""
9434 9616
9435 #: rhodecode/templates/pullrequests/pullrequest_show.mako:397
9617 #: rhodecode/templates/pullrequests/pullrequest_show.mako:424
9436 9618 msgid "This pull request cannot be displayed, because one or more commits no longer exist in the source repository."
9437 9619 msgstr ""
9438 9620
9439 #: rhodecode/templates/pullrequests/pullrequest_show.mako:398
9621 #: rhodecode/templates/pullrequests/pullrequest_show.mako:425
9440 9622 msgid "Please update this pull request, push the commits back into the source repository, or consider closing this pull request."
9441 9623 msgstr ""
9442 9624
9443 #: rhodecode/templates/pullrequests/pullrequest_show.mako:409
9625 #: rhodecode/templates/pullrequests/pullrequest_show.mako:436
9444 9626 #, python-format
9445 9627 msgid "Showing changes at v%d, commenting is disabled."
9446 9628 msgstr ""
9447 9629
9448 #: rhodecode/templates/pullrequests/pullrequest_show.mako:432
9449 #: rhodecode/templates/pullrequests/pullrequest_show.mako:434
9630 #: rhodecode/templates/pullrequests/pullrequest_show.mako:459
9631 #: rhodecode/templates/pullrequests/pullrequest_show.mako:461
9450 9632 msgid "Update commits"
9451 9633 msgstr ""
9452 9634
9453 #: rhodecode/templates/pullrequests/pullrequest_show.mako:434
9635 #: rhodecode/templates/pullrequests/pullrequest_show.mako:461
9454 9636 msgid "Update is disabled for current view"
9455 9637 msgstr ""
9456 9638
9457 #: rhodecode/templates/pullrequests/pullrequest_show.mako:445
9639 #: rhodecode/templates/pullrequests/pullrequest_show.mako:472
9458 9640 msgid "Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled"
9459 9641 msgstr ""
9460 9642
9461 #: rhodecode/templates/pullrequests/pullrequest_show.mako:449
9643 #: rhodecode/templates/pullrequests/pullrequest_show.mako:476
9462 9644 msgid "commits added: {}, removed: {}"
9463 9645 msgstr ""
9464 9646
9465 #: rhodecode/templates/pullrequests/pullrequest_show.mako:467
9647 #: rhodecode/templates/pullrequests/pullrequest_show.mako:494
9466 9648 msgid "Commit added in displayed changes"
9467 9649 msgstr ""
9468 9650
9469 #: rhodecode/templates/pullrequests/pullrequest_show.mako:469
9651 #: rhodecode/templates/pullrequests/pullrequest_show.mako:496
9470 9652 msgid "Commit removed in displayed changes"
9471 9653 msgstr ""
9472 9654
9473 #: rhodecode/templates/pullrequests/pullrequest_show.mako:572
9655 #: rhodecode/templates/pullrequests/pullrequest_show.mako:599
9474 9656 msgid "there is {num} general comment from older versions"
9475 9657 msgstr ""
9476 9658
9477 #: rhodecode/templates/pullrequests/pullrequest_show.mako:575
9659 #: rhodecode/templates/pullrequests/pullrequest_show.mako:602
9478 9660 msgid "there are {num} general comments from older versions"
9479 9661 msgstr ""
9480 9662
9481 #: rhodecode/templates/pullrequests/pullrequest_show.mako:576
9663 #: rhodecode/templates/pullrequests/pullrequest_show.mako:603
9482 9664 msgid "show them"
9483 9665 msgstr ""
9484 9666
@@ -9568,6 +9750,11 b' msgstr ""'
9568 9750 msgid "File names"
9569 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 9758 #: rhodecode/templates/search/search_commit.mako:11
9572 9759 msgid "Age (new first)"
9573 9760 msgstr ""
@@ -9599,34 +9786,6 b' msgstr ""'
9599 9786 msgid "%s RSS feed"
9600 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 9789 #: rhodecode/templates/summary/components.mako:49
9631 9790 msgid "Read-only url"
9632 9791 msgstr ""
@@ -9707,6 +9866,18 b' msgstr ""'
9707 9866 msgid "Readme file from commit %s:%s"
9708 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 9881 #: rhodecode/templates/tags/tags.mako:5
9711 9882 #, python-format
9712 9883 msgid "%s Tags"
@@ -35,12 +35,14 b' from rhodecode.integrations.types.base i'
35 35
36 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 39 WEBHOOK_URL_VARS = [
40 40 'repo_name',
41 41 'repo_type',
42 42 'repo_id',
43 43 'repo_url',
44 # extra repo fields
45 'extra:<extra_key_name>',
44 46
45 47 # special attrs below that we handle, using multi-call
46 48 'branch',
@@ -50,6 +52,10 b' WEBHOOK_URL_VARS = ['
50 52 'pull_request_id',
51 53 'pull_request_url',
52 54
55 # user who triggers the call
56 'username',
57 'user_id',
58
53 59 ]
54 60 URL_VARS = ', '.join('${' + x + '}' for x in WEBHOOK_URL_VARS)
55 61
@@ -70,7 +76,13 b' class WebhookHandler(object):'
70 76 'repo_type': data['repo']['repo_type'],
71 77 'repo_id': data['repo']['repo_id'],
72 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 87 return string.Template(
76 88 self.template_url).safe_substitute(**common_vars)
@@ -42,6 +42,11 b' def action_parser(user_log, feed=False, '
42 42 :param feed: use output for feeds (no html and fancy icons)
43 43 :param parse_cs: parse Changesets into VCS instances
44 44 """
45 if user_log.version == 'v2':
46 ap = AuditLogParser(user_log)
47 return ap.callbacks()
48 else:
49 # old style
45 50 ap = ActionParser(user_log, feed=False, parse_commits=False)
46 51 return ap.callbacks()
47 52
@@ -161,8 +166,9 b' class ActionParser(object):'
161 166 return action_map
162 167
163 168 def get_fork_name(self):
169 from rhodecode.lib import helpers as h
164 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 172 return _('fork name %s') % link_to(self.action_params, _url)
167 173
168 174 def get_user_name(self):
@@ -174,6 +180,7 b' class ActionParser(object):'
174 180 return group_name
175 181
176 182 def get_pull_request(self):
183 from rhodecode.lib import helpers as h
177 184 pull_request_id = self.action_params
178 185 if self.is_deleted():
179 186 repo_name = self.user_log.repository_name
@@ -181,7 +188,7 b' class ActionParser(object):'
181 188 repo_name = self.user_log.repository.repo_name
182 189 return link_to(
183 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 192 pull_request_id=pull_request_id))
186 193
187 194 def get_archive_name(self):
@@ -302,6 +309,34 b' class ActionParser(object):'
302 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 340 def _no_params_func():
306 341 return ""
307 342
@@ -34,10 +34,10 b' import traceback'
34 34 from functools import wraps
35 35
36 36 import ipaddress
37 from pyramid.httpexceptions import HTTPForbidden, HTTPFound
38 from pylons import url, request
39 from pylons.controllers.util import abort, redirect
37 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 38 from pylons.i18n.translation import _
39 # NOTE(marcink): this has to be removed only after pyramid migration,
40 # replace with _ = request.translate
41 41 from sqlalchemy.orm.exc import ObjectDeletedError
42 42 from sqlalchemy.orm import joinedload
43 43 from zope.cachedescriptors.property import Lazy as LazyProperty
@@ -138,7 +138,7 b' class _RhodeCodeCryptoBase(object):'
138 138
139 139
140 140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141 ENC_PREF = '$2a$10'
141 ENC_PREF = ('$2a$10', '$2b$10')
142 142
143 143 def hash_create(self, str_):
144 144 self._assert_bytes(str_)
@@ -302,7 +302,8 b' def _cached_perms_data(user_id, scope, u'
302 302 explicit, algo)
303 303 return permissions.calculate()
304 304
305 class PermOrigin:
305
306 class PermOrigin(object):
306 307 ADMIN = 'superadmin'
307 308
308 309 REPO_USER = 'user:%s'
@@ -341,7 +342,6 b' class PermOriginDict(dict):'
341 342 {'resource': [('read', 'default'), ('write', 'admin')]}
342 343 """
343 344
344
345 345 def __init__(self, *args, **kw):
346 346 dict.__init__(self, *args, **kw)
347 347 self.perm_origin_stack = {}
@@ -807,6 +807,8 b' class AuthUser(object):'
807 807 self.ip_addr = ip_addr
808 808 self.name = ''
809 809 self.lastname = ''
810 self.first_name = ''
811 self.last_name = ''
810 812 self.email = ''
811 813 self.is_authenticated = False
812 814 self.admin = False
@@ -1045,8 +1047,8 b' class AuthUser(object):'
1045 1047 default_ips = UserIpMap.query().filter(
1046 1048 UserIpMap.user == User.get_default_user(cache=True))
1047 1049 if cache:
1048 default_ips = default_ips.options(FromCache("sql_cache_short",
1049 "get_user_ips_default"))
1050 default_ips = default_ips.options(
1051 FromCache("sql_cache_short", "get_user_ips_default"))
1050 1052
1051 1053 # populate from default user
1052 1054 for ip in default_ips:
@@ -1059,8 +1061,8 b' class AuthUser(object):'
1059 1061
1060 1062 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1061 1063 if cache:
1062 user_ips = user_ips.options(FromCache("sql_cache_short",
1063 "get_user_ips_%s" % user_id))
1064 user_ips = user_ips.options(
1065 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1064 1066
1065 1067 for ip in user_ips:
1066 1068 try:
@@ -1114,6 +1116,17 b' def get_csrf_token(session=None, force_n'
1114 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 1130 # CHECK DECORATORS
1118 1131 class CSRFRequired(object):
1119 1132 """
@@ -1144,7 +1157,12 b' class CSRFRequired(object):'
1144 1157 supplied_token = self._get_csrf(_request)
1145 1158 return supplied_token and supplied_token == cur_token
1146 1159
1160 def _get_request(self):
1161 return get_request(self)
1162
1147 1163 def __wrapper(self, func, *fargs, **fkwargs):
1164 request = self._get_request()
1165
1148 1166 if request.method in self.except_methods:
1149 1167 return func(*fargs, **fkwargs)
1150 1168
@@ -1157,8 +1175,8 b' class CSRFRequired(object):'
1157 1175 reason = 'token-missing'
1158 1176 supplied_token = self._get_csrf(request)
1159 1177 if supplied_token and cur_token != supplied_token:
1160 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1161 supplied_token or ''[:6])
1178 reason = 'token-mismatch [%s:%s]' % (
1179 cur_token or ''[:6], supplied_token or ''[:6])
1162 1180
1163 1181 csrf_message = \
1164 1182 ("Cross-site request forgery detected, request denied. See "
@@ -1185,10 +1203,15 b' class LoginRequired(object):'
1185 1203 def __call__(self, func):
1186 1204 return get_cython_compat_decorator(self.__wrapper, func)
1187 1205
1206 def _get_request(self):
1207 return get_request(self)
1208
1188 1209 def __wrapper(self, func, *fargs, **fkwargs):
1189 1210 from rhodecode.lib import helpers as h
1190 1211 cls = fargs[0]
1191 1212 user = cls._rhodecode_user
1213 request = self._get_request()
1214
1192 1215 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1193 1216 log.debug('Starting login restriction checks for user: %s' % (user,))
1194 1217 # check if our IP is allowed
@@ -1255,22 +1278,27 b' class LoginRequired(object):'
1255 1278 # we preserve the get PARAM
1256 1279 came_from = request.path_qs
1257 1280 log.debug('redirecting to login page with %s' % (came_from,))
1258 return redirect(
1281 raise HTTPFound(
1259 1282 h.route_path('login', _query={'came_from': came_from}))
1260 1283
1261 1284
1262 1285 class NotAnonymous(object):
1263 1286 """
1264 1287 Must be logged in to execute this function else
1265 redirect to login page"""
1288 redirect to login page
1289 """
1266 1290
1267 1291 def __call__(self, func):
1268 1292 return get_cython_compat_decorator(self.__wrapper, func)
1269 1293
1294 def _get_request(self):
1295 return get_request(self)
1296
1270 1297 def __wrapper(self, func, *fargs, **fkwargs):
1271 1298 import rhodecode.lib.helpers as h
1272 1299 cls = fargs[0]
1273 1300 self.user = cls._rhodecode_user
1301 request = self._get_request()
1274 1302
1275 1303 log.debug('Checking if user is not anonymous @%s' % cls)
1276 1304
@@ -1281,19 +1309,28 b' class NotAnonymous(object):'
1281 1309 h.flash(_('You need to be a registered user to '
1282 1310 'perform this action'),
1283 1311 category='warning')
1284 return redirect(
1312 raise HTTPFound(
1285 1313 h.route_path('login', _query={'came_from': came_from}))
1286 1314 else:
1287 1315 return func(*fargs, **fkwargs)
1288 1316
1289 1317
1290 1318 class XHRRequired(object):
1319 # TODO(marcink): remove this in favor of the predicates in pyramid routes
1320
1291 1321 def __call__(self, func):
1292 1322 return get_cython_compat_decorator(self.__wrapper, func)
1293 1323
1324 def _get_request(self):
1325 return get_request(self)
1326
1294 1327 def __wrapper(self, func, *fargs, **fkwargs):
1328 from pylons.controllers.util import abort
1329 request = self._get_request()
1330
1295 1331 log.debug('Checking if request is XMLHttpRequest (XHR)')
1296 1332 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1333
1297 1334 if not request.is_xhr:
1298 1335 abort(400, detail=xhr_message)
1299 1336
@@ -1303,9 +1340,9 b' class XHRRequired(object):'
1303 1340 class HasAcceptedRepoType(object):
1304 1341 """
1305 1342 Check if requested repo is within given repo type aliases
1343 """
1306 1344
1307 TODO: anderson: not sure where to put this decorator
1308 """
1345 # TODO(marcink): remove this in favor of the predicates in pyramid routes
1309 1346
1310 1347 def __init__(self, *repo_type_list):
1311 1348 self.repo_type_list = set(repo_type_list)
@@ -1328,8 +1365,9 b' class HasAcceptedRepoType(object):'
1328 1365 h.flash(h.literal(
1329 1366 _('Action not supported for %s.' % rhodecode_repo.alias)),
1330 1367 category='warning')
1331 return redirect(
1332 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1368 raise HTTPFound(
1369 h.route_path('repo_summary',
1370 repo_name=cls.rhodecode_db_repo.repo_name))
1333 1371
1334 1372
1335 1373 class PermsDecorator(object):
@@ -1345,12 +1383,7 b' class PermsDecorator(object):'
1345 1383 return get_cython_compat_decorator(self.__wrapper, func)
1346 1384
1347 1385 def _get_request(self):
1348 from pyramid.threadlocal import get_current_request
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
1386 return get_request(self)
1354 1387
1355 1388 def _get_came_from(self):
1356 1389 _request = self._get_request()
@@ -1382,8 +1415,8 b' class PermsDecorator(object):'
1382 1415 h.route_path('login', _query={'came_from': came_from}))
1383 1416
1384 1417 else:
1385 # redirect with forbidden ret code
1386 raise HTTPForbidden()
1418 # redirect with 404 to prevent resource discovery
1419 raise HTTPNotFound()
1387 1420
1388 1421 def check_permissions(self, user):
1389 1422 """Dummy function for overriding"""
@@ -1429,10 +1462,16 b' class HasRepoPermissionAllDecorator(Perm'
1429 1462 def check_permissions(self, user):
1430 1463 perms = user.permissions
1431 1464 repo_name = self._get_repo_name()
1465
1432 1466 try:
1433 1467 user_perms = set([perms['repositories'][repo_name]])
1434 1468 except KeyError:
1469 log.debug('cannot locate repo with name: `%s` in permissions defs',
1470 repo_name)
1435 1471 return False
1472
1473 log.debug('checking `%s` permissions for repo `%s`',
1474 user_perms, repo_name)
1436 1475 if self.required_perms.issubset(user_perms):
1437 1476 return True
1438 1477 return False
@@ -1450,11 +1489,16 b' class HasRepoPermissionAnyDecorator(Perm'
1450 1489 def check_permissions(self, user):
1451 1490 perms = user.permissions
1452 1491 repo_name = self._get_repo_name()
1492
1453 1493 try:
1454 1494 user_perms = set([perms['repositories'][repo_name]])
1455 1495 except KeyError:
1496 log.debug('cannot locate repo with name: `%s` in permissions defs',
1497 repo_name)
1456 1498 return False
1457 1499
1500 log.debug('checking `%s` permissions for repo `%s`',
1501 user_perms, repo_name)
1458 1502 if self.required_perms.intersection(user_perms):
1459 1503 return True
1460 1504 return False
@@ -1476,8 +1520,12 b' class HasRepoGroupPermissionAllDecorator'
1476 1520 try:
1477 1521 user_perms = set([perms['repositories_groups'][group_name]])
1478 1522 except KeyError:
1523 log.debug('cannot locate repo group with name: `%s` in permissions defs',
1524 group_name)
1479 1525 return False
1480 1526
1527 log.debug('checking `%s` permissions for repo group `%s`',
1528 user_perms, group_name)
1481 1529 if self.required_perms.issubset(user_perms):
1482 1530 return True
1483 1531 return False
@@ -1496,11 +1544,16 b' class HasRepoGroupPermissionAnyDecorator'
1496 1544 def check_permissions(self, user):
1497 1545 perms = user.permissions
1498 1546 group_name = self._get_repo_group_name()
1547
1499 1548 try:
1500 1549 user_perms = set([perms['repositories_groups'][group_name]])
1501 1550 except KeyError:
1551 log.debug('cannot locate repo group with name: `%s` in permissions defs',
1552 group_name)
1502 1553 return False
1503 1554
1555 log.debug('checking `%s` permissions for repo group `%s`',
1556 user_perms, group_name)
1504 1557 if self.required_perms.intersection(user_perms):
1505 1558 return True
1506 1559 return False
@@ -1575,6 +1628,7 b' class PermsFunction(object):'
1575 1628 if not user:
1576 1629 log.debug('Using user attribute from global request')
1577 1630 # TODO: remove this someday,put as user as attribute here
1631 request = self._get_request()
1578 1632 user = request.user
1579 1633
1580 1634 # init auth user if not already given
@@ -1603,12 +1657,7 b' class PermsFunction(object):'
1603 1657 return False
1604 1658
1605 1659 def _get_request(self):
1606 from pyramid.threadlocal import get_current_request
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
1660 return get_request(self)
1612 1661
1613 1662 def _get_check_scope(self, cls_name):
1614 1663 return {
@@ -1673,7 +1722,8 b' class HasRepoPermissionAny(PermsFunction'
1673 1722
1674 1723 def _get_repo_name(self):
1675 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 1727 return self.repo_name
1678 1728
1679 1729 def check_permissions(self, user):
@@ -162,6 +162,10 b' def get_access_path(environ):'
162 162 return path
163 163
164 164
165 def get_user_agent(environ):
166 return environ.get('HTTP_USER_AGENT')
167
168
165 169 def vcs_operation_context(
166 170 environ, repo_name, username, action, scm, check_locking=True,
167 171 is_shadow_repo=False):
@@ -200,6 +204,7 b' def vcs_operation_context('
200 204 'make_lock': make_lock,
201 205 'locked_by': locked_by,
202 206 'server_url': utils2.get_server_url(environ),
207 'user_agent': get_user_agent(environ),
203 208 'hooks': get_enabled_hook_classes(ui_settings),
204 209 'is_shadow_repo': is_shadow_repo,
205 210 }
@@ -260,7 +265,7 b' class BasicAuth(AuthBasicAuthenticator):'
260 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 270 Attach variables into template context called `c`, please note that
266 271 request could be pylons or pyramid request in here.
@@ -302,7 +307,7 b' def attach_context_attributes(context, r'
302 307 'rhodecode_markup_renderer', 'rst')
303 308 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
304 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 312 context.pre_code = rc_config.get('rhodecode_pre_code')
308 313 context.post_code = rc_config.get('rhodecode_post_code')
@@ -325,6 +330,11 b' def attach_context_attributes(context, r'
325 330
326 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 338 # AppEnlight
329 339 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
330 340 context.appenlight_api_public_key = config.get(
@@ -382,10 +392,12 b' def attach_context_attributes(context, r'
382 392 context.csrf_token = auth.get_csrf_token()
383 393 context.backends = rhodecode.BACKENDS.keys()
384 394 context.backends.sort()
385 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
386 context.rhodecode_user.user_id)
395 context.unread_notifications = NotificationModel().get_unread_cnt_for_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 403 def get_auth_user(environ):
@@ -435,7 +447,7 b' class BaseController(WSGIController):'
435 447 """
436 448 # on each call propagate settings calls into global settings.
437 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 452 # TODO: Remove this when fixed in attach_context_attributes()
441 453 c.repo_name = get_repo_slug(request) # can be empty
@@ -550,7 +562,7 b' class BaseRepoController(BaseController)'
550 562 "The repository at %(repo_name)s cannot be located.") %
551 563 {'repo_name': c.repo_name},
552 564 category='error', ignore_duplicate=True)
553 redirect(url('home'))
565 redirect(h.route_path('home'))
554 566
555 567 # update last change according to VCS data
556 568 if not missing_requirements:
@@ -577,7 +589,7 b' class BaseRepoController(BaseController)'
577 589 'Requirements are missing for repository %s: %s',
578 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 593 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
582 594 settings_update_url = url('repo', repo_name=c.repo_name)
583 595 path = request.path
@@ -31,12 +31,13 b' from celery.task import task'
31 31 from pylons import config
32 32
33 33 import rhodecode
34 from rhodecode.lib import audit_logger
34 35 from rhodecode.lib.celerylib import (
35 36 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 37 get_session, vcsconnection, RhodecodeCeleryTask)
37 38 from rhodecode.lib.hooks_base import log_create_repository
38 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 41 from rhodecode.lib.utils2 import safe_int, str2bool
41 42 from rhodecode.model.db import Repository, User
42 43
@@ -141,7 +142,7 b' def create_repo(form_data, cur_user):'
141 142 'enable_downloads', defs.get('repo_enable_downloads'))
142 143
143 144 try:
144 RepoModel(DBS)._create_repo(
145 repo = RepoModel(DBS)._create_repo(
145 146 repo_name=repo_name_full,
146 147 repo_type=repo_type,
147 148 description=description,
@@ -158,8 +159,6 b' def create_repo(form_data, cur_user):'
158 159 enable_downloads=enable_downloads,
159 160 state=state
160 161 )
161
162 action_logger(cur_user, 'user_created_repo', repo_name_full, '', DBS)
163 162 DBS.commit()
164 163
165 164 # now create this repo on Filesystem
@@ -177,6 +176,14 b' def create_repo(form_data, cur_user):'
177 176
178 177 # set new created state
179 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 187 DBS.commit()
181 188 except Exception:
182 189 log.warning('Exception occurred when creating repository, '
@@ -240,8 +247,7 b' def create_repo_fork(form_data, cur_user'
240 247 fork_of=fork_of,
241 248 copy_fork_permissions=copy_fork_permissions
242 249 )
243 action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
244 fork_of.repo_name, '', DBS)
250
245 251 DBS.commit()
246 252
247 253 base_path = Repository.base_path()
@@ -264,6 +270,14 b' def create_repo_fork(form_data, cur_user'
264 270
265 271 # set new created state
266 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 281 DBS.commit()
268 282 except Exception as e:
269 283 log.warning('Exception %s occurred when forking repository, '
@@ -77,8 +77,8 b' def get_user_data(user_id):'
77 77 return {
78 78 'id': user.user_id,
79 79 'username': user.username,
80 'first_name': user.name,
81 'last_name': user.lastname,
80 'first_name': user.first_name,
81 'last_name': user.last_name,
82 82 'icon_link': h.gravatar_url(user.email, 60),
83 83 'display_name': h.person(user, 'username_or_name_or_email'),
84 84 'display_link': h.link_to_user(user),
@@ -471,31 +471,29 b' class DiffSet(object):'
471 471 source_file_type = source_lexer.name
472 472 target_file_type = target_lexer.name
473 473
474 op_hunks = patch['chunks'][0]
475 hunks = patch['chunks'][1:]
476
477 474 filediff = AttributeDict({
478 475 'source_file_path': source_file_path,
479 476 'target_file_path': target_file_path,
480 477 'source_filenode': source_filenode,
481 478 'target_filenode': target_filenode,
482 'hunks': [],
483 479 'source_file_type': target_file_type,
484 480 'target_file_type': source_file_type,
485 'patch': patch,
481 'patch': {'filename': patch['filename'], 'stats': patch['stats']},
482 'operation': patch['operation'],
486 483 'source_mode': patch['stats']['old_mode'],
487 484 'target_mode': patch['stats']['new_mode'],
488 485 'limited_diff': isinstance(patch, LimitedDiffContainer),
486 'hunks': [],
489 487 'diffset': self,
490 488 })
491 489
492 for hunk in hunks:
490 for hunk in patch['chunks'][1:]:
493 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 494 filediff.hunks.append(hunkbit)
496 495
497 496 left_comments = {}
498
499 497 if source_file_path in self.comments_store:
500 498 for lineno, comments in self.comments_store[source_file_path].items():
501 499 left_comments[lineno] = comments
@@ -503,8 +501,8 b' class DiffSet(object):'
503 501 if target_file_path in self.comments_store:
504 502 for lineno, comments in self.comments_store[target_file_path].items():
505 503 left_comments[lineno] = comments
504 filediff.left_comments = left_comments
506 505
507 filediff.left_comments = left_comments
508 506 return filediff
509 507
510 508 def parse_hunk(self, hunk, source_file, target_file):
@@ -519,6 +517,7 b' class DiffSet(object):'
519 517 before, after = [], []
520 518
521 519 for line in hunk['lines']:
520
522 521 if line['action'] == 'unmod':
523 522 result.lines.extend(
524 523 self.parse_lines(before, after, source_file, target_file))
@@ -567,7 +566,8 b' class DiffSet(object):'
567 566 before_tokens = [('nonl', before['line'])]
568 567 else:
569 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 571 file=source_file)
572 572 original.lineno = before['old_lineno']
573 573 original.content = before['line']
@@ -319,6 +319,7 b' class DbManage(object):'
319 319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 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 364 hgsubversion.ui_active = False
364 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 375 # enable hggit disabled by default
367 376 hggit = RhodeCodeUi()
368 377 hggit.ui_section = 'extensions'
@@ -36,6 +36,8 b' import urlparse'
36 36 import time
37 37 import string
38 38 import hashlib
39 from collections import OrderedDict
40
39 41 import pygments
40 42 import itertools
41 43 import fnmatch
@@ -891,8 +893,9 b' def author_string(email):'
891 893 if email:
892 894 user = User.get_by_email(email, case_insensitive=True, cache=True)
893 895 if user:
894 if user.firstname or user.lastname:
895 return '%s %s &lt;%s&gt;' % (user.firstname, user.lastname, email)
896 if user.first_name or user.last_name:
897 return '%s %s &lt;%s&gt;' % (
898 user.first_name, user.last_name, email)
896 899 else:
897 900 return email
898 901 else:
@@ -1141,14 +1144,14 b' class InitialsGravatar(object):'
1141 1144 # first push the email initials
1142 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 1148 _dot_split = prefix.rsplit('.', 1)
1146 1149 if len(_dot_split) == 2:
1147 1150 initials = [_dot_split[0][0], _dot_split[1][0]]
1148 1151 else:
1149 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 1155 fn_letter = (first_name or " ")[0].strip()
1153 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 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):
1252 request = get_current_request()
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:
1249 1257 # doh, we need to re-import those to mock it later
1250 1258 from pylons import tmpl_context as c
1251 1259
1252 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 1265 email_address = email_address or User.DEFAULT_USER_EMAIL
1256 1266 if isinstance(email_address, unicode):
@@ -1532,10 +1542,10 b' def breadcrumb_repo_link(repo):'
1532 1542 """
1533 1543
1534 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 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 1551 return literal(' &raquo; '.join(path))
@@ -1602,16 +1612,24 b' def urlify_commits(text_, repository):'
1602 1612
1603 1613
1604 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 1616 pref = ''
1607 1617 if match_obj.group().startswith(' '):
1608 1618 pref = ' '
1609 1619
1610 1620 issue_id = ''.join(match_obj.groups())
1621
1622 if link_format == 'html':
1611 1623 tmpl = (
1612 1624 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1613 1625 '%(issue-prefix)s%(id-repr)s'
1614 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 1634 (repo_name_cleaned,
1617 1635 parent_group_name) = RepoGroupModel().\
@@ -1644,7 +1662,12 b' def _process_url_func(match_obj, repo_na'
1644 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 1671 repo = None
1649 1672 if repo_name:
1650 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 1680 issues_data = []
1658 1681 newtext = text_string
1682
1659 1683 for uid, entry in active_entries.items():
1660 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 1706 issues_data.append(data_func(match_obj))
1683 1707
1684 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 1712 newtext = pattern.sub(url_func, newtext)
1688 1713 log.debug('processed prefix:uid `%s`' % (uid,))
@@ -1750,7 +1775,8 b' def renderer_from_filename(filename, exc'
1750 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 1781 def maybe_convert_relative_links(html_source):
1756 1782 if relative_url:
@@ -1758,11 +1784,21 b" def render(source, renderer='rst', menti"
1758 1784 return html_source
1759 1785
1760 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 1792 return literal(
1762 1793 '<div class="rst-block">%s</div>' %
1763 1794 maybe_convert_relative_links(
1764 1795 MarkupRenderer.rst(source, mentions=mentions)))
1765 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 1802 return literal(
1767 1803 '<div class="markdown-block">%s</div>' %
1768 1804 maybe_convert_relative_links(
@@ -1801,6 +1837,7 b' def journal_filter_help():'
1801 1837 'Example filter terms:\n' +
1802 1838 ' repository:vcs\n' +
1803 1839 ' username:marcin\n' +
1840 ' username:(NOT marcin)\n' +
1804 1841 ' action:*push*\n' +
1805 1842 ' ip:127.0.0.1\n' +
1806 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 1874 def not_mapped_error(repo_name):
1820 1875 flash(_('%s repository is not mapped to db perhaps'
1821 1876 ' it was created or renamed from the filesystem'
@@ -1914,24 +1969,24 b' def get_last_path_part(file_node):'
1914 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
1920 URLs from within pylons views or templates. This will be removed when
1921 pyramid migration if finished.
1974 Wrapper around pyramids `route_url` (fully qualified url) function.
1975 It is used to generate URLs from within pylons views or templates.
1976 This will be removed when pyramid migration if finished.
1922 1977 """
1923 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 1984 Wrapper around pyramids `route_path` function. It is used to generate
1930 1985 URLs from within pylons views or templates. This will be removed when
1931 1986 pyramid migration if finished.
1932 1987 """
1933 1988 req = get_current_request()
1934 return req.route_path(*args, **kwds)
1989 return req.route_path(*args, **kwargs)
1935 1990
1936 1991
1937 1992 def route_path_or_none(*args, **kwargs):
@@ -1959,3 +2014,23 b' def resource_path(*args, **kwds):'
1959 2014 """
1960 2015 req = get_current_request()
1961 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 30 import rhodecode
31 31 from rhodecode import events
32 32 from rhodecode.lib import helpers as h
33 from rhodecode.lib.utils import action_logger
33 from rhodecode.lib import audit_logger
34 34 from rhodecode.lib.utils2 import safe_str
35 35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
36 36 from rhodecode.model.db import Repository, User
@@ -152,9 +152,15 b' def pre_pull(extras):'
152 152
153 153 def post_pull(extras):
154 154 """Hook executed after client pulls the code."""
155 user = User.get_by_username(extras.username)
156 action = 'pull'
157 action_logger(user, action, extras.repository, extras.ip, commit=True)
155
156 audit_user = audit_logger.UserWrap(
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 165 # Propagate to external components.
160 166 if not is_shadow_repo(extras):
@@ -165,6 +171,7 b' def post_pull(extras):'
165 171 output = ''
166 172 # make lock is a tri state False, True, None. We only make lock on True
167 173 if extras.make_lock is True and not is_shadow_repo(extras):
174 user = User.get_by_username(extras.username)
168 175 Repository.lock(Repository.get_by_repo_name(extras.repository),
169 176 user.user_id,
170 177 lock_reason=Repository.LOCK_PULL)
@@ -185,12 +192,17 b' def post_pull(extras):'
185 192
186 193 def post_push(extras):
187 194 """Hook executed after user pushes to the repository."""
188 action_tmpl = extras.action + ':%s'
189 commit_ids = extras.commit_ids[:29000]
195 commit_ids = extras.commit_ids
190 196
191 action = action_tmpl % ','.join(commit_ids)
192 action_logger(
193 extras.username, action, extras.repository, extras.ip, commit=True)
197 # log the push call
198 audit_user = audit_logger.UserWrap(
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 207 # Propagate to external components.
196 208 if not is_shadow_repo(extras):
@@ -220,8 +232,20 b' def post_push(extras):'
220 232 # 2xx Codes don't raise exceptions
221 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 248 output += 'RhodeCode: push completed\n'
224
225 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 35 class BaseSearch(object):
36 query_lang_doc = ''
37
36 38 def __init__(self):
37 39 pass
38 40
@@ -61,7 +61,8 b' log = logging.getLogger(__name__)'
61 61
62 62
63 63 class Search(BaseSearch):
64
64 # this also shows in UI
65 query_lang_doc = 'http://whoosh.readthedocs.io/en/latest/querylang.html'
65 66 name = 'whoosh'
66 67
67 68 def __init__(self, config):
@@ -34,6 +34,8 b' from mako.template import Template as Ma'
34 34
35 35 from docutils.core import publish_parts
36 36 from docutils.parsers.rst import directives
37 from docutils import writers
38 from docutils.writers import html4css1
37 39 import markdown
38 40
39 41 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
@@ -46,6 +48,31 b' log = logging.getLogger(__name__)'
46 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 76 def relative_links(html_source, server_path):
50 77 if not html_source:
51 78 return html_source
@@ -56,12 +83,12 b' def relative_links(html_source, server_p'
56 83 return html_source
57 84
58 85 for el in doc.cssselect('img, video'):
59 src = el.attrib['src']
86 src = el.attrib.get('src')
60 87 if src:
61 88 el.attrib['src'] = relative_path(src, server_path)
62 89
63 90 for el in doc.cssselect('a:not(.gfm)'):
64 src = el.attrib['href']
91 src = el.attrib.get('href')
65 92 if src:
66 93 el.attrib['href'] = relative_path(src, server_path)
67 94
@@ -341,7 +368,7 b' class MarkupRenderer(object):'
341 368 directives.register_directive(k, v)
342 369
343 370 parts = publish_parts(source=source,
344 writer_name="html4css1",
371 writer=RhodeCodeWriter(),
345 372 settings_overrides=docutils_settings)
346 373
347 374 return parts['html_title'] + parts["fragment"]
@@ -36,7 +36,8 b' from webob.exc import ('
36 36 import rhodecode
37 37 from rhodecode.authentication.base import authenticate, VCS_TYPE
38 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 41 from rhodecode.lib.exceptions import (
41 42 HTTPLockedRC, HTTPRequirementError, UserCreationError,
42 43 NotAllowedToCreateUserError)
@@ -310,6 +311,7 b' class SimpleVCS(object):'
310 311 log.debug('Extracted repo name is %s', self.url_repo_name)
311 312
312 313 ip_addr = get_ip_addr(environ)
314 user_agent = get_user_agent(environ)
313 315 username = None
314 316
315 317 # skip passing error to error controller
@@ -429,9 +431,9 b' class SimpleVCS(object):'
429 431 fix_PATH()
430 432
431 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 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 438 return self._generate_vcs_response(
437 439 environ, start_response, repo_path, extras, action)
@@ -114,14 +114,18 b' class Message(object):'
114 114
115 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 123 def is_bad_headers(self):
118 124 """
119 125 Checks for bad headers i.e. newlines in subject, sender or recipients.
120 126 """
121 127
122 headers = [self.subject, self.sender]
123 headers += list(self.send_to)
124 headers += self.extra_headers.values()
128 headers = self._get_headers()
125 129
126 130 for val in headers:
127 131 for c in '\r\n':
@@ -144,7 +148,8 b' class Message(object):'
144 148 raise InvalidMessage("No sender address has been set")
145 149
146 150 if self.is_bad_headers():
147 raise BadHeaders
151 headers = self._get_headers()
152 raise BadHeaders(headers)
148 153
149 154 def add_recipient(self, recipient):
150 155 """
@@ -83,6 +83,15 b' class HGVerify(MaintenanceTask):'
83 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 95 class RepoMaintenance(object):
87 96 """
88 97 Performs maintenance of repository based on it's type
@@ -90,7 +99,7 b' class RepoMaintenance(object):'
90 99 tasks = {
91 100 'hg': [HGVerify],
92 101 'git': [GitGC],
93 'svn': [],
102 'svn': [SVNVerify],
94 103 }
95 104
96 105 def get_tasks_for_repo(self, db_repo):
@@ -570,6 +570,10 b' def rhodecode_config():'
570 570 log.exception('Failed to read .ini file for display')
571 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 577 rhodecode_ini_safe['server:main'] = parsed_ini
574 578
575 579 blacklist = [
@@ -608,7 +612,8 b' def rhodecode_config():'
608 612 rhodecode_ini_safe.pop(k, None)
609 613
610 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 619 def database_info():
@@ -23,10 +23,10 b' import logging'
23 23 from whoosh.qparser.default import QueryParser, query
24 24 from whoosh.qparser.dateparse import DateParserPlugin
25 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 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 31 # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh
32 32 # querylang to build sql queries and filter journals
@@ -54,7 +54,7 b' def user_log_filter(user_log, search_ter'
54 54 if search_term:
55 55 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
56 56 qp.add_plugin(DateParserPlugin())
57 qry = qp.parse(unicode(search_term))
57 qry = qp.parse(safe_unicode(search_term))
58 58 log.debug('Filtering using parsed query %r' % qry)
59 59
60 60 def wildcard_handler(col, wc_term):
@@ -89,16 +89,27 b' def user_log_filter(user_log, search_ter'
89 89 return func.lower(field).startswith(func.lower(val))
90 90 elif isinstance(term, query.DateRange):
91 91 return and_(field >= val[0], field <= val[1])
92 elif isinstance(term, query.Not):
93 return not_(field == val)
92 94 return func.lower(field) == func.lower(val)
93 95
94 if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
95 query.DateRange)):
96 if isinstance(qry, (query.And, query.Not, query.Term, query.Prefix,
97 query.Wildcard, query.DateRange)):
96 98 if not isinstance(qry, query.And):
97 99 qry = [qry]
100
98 101 for term in qry:
102 if isinstance(term, query.Not):
103 not_term = [z for z in term.leaves()][0]
104 field = not_term.fieldname
105 val = not_term.text
106 elif isinstance(term, query.DateRange):
99 107 field = term.fieldname
100 val = (term.text if not isinstance(term, query.DateRange)
101 else [term.startdate, term.enddate])
108 val = [term.startdate, term.enddate]
109 else:
110 field = term.fieldname
111 val = term.text
112
102 113 user_log = user_log.filter(get_filterion(field, val, term))
103 114 elif isinstance(qry, query.Or):
104 115 filters = []
@@ -96,10 +96,11 b' def repo_name_slug(value):'
96 96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 97 #==============================================================================
98 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 100 # pyramid
101 _repo = request.matchdict.get('repo_name')
101 _repo = request.db_repo.repo_name
102 102 else:
103 # TODO(marcink): remove after pylons migration...
103 104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 105
105 106 if _repo:
@@ -110,7 +111,7 b' def get_repo_slug(request):'
110 111 def get_repo_group_slug(request):
111 112 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 113 # pyramid
113 _group = request.matchdict.get('group_name')
114 _group = request.matchdict.get('repo_group_name')
114 115 else:
115 116 _group = request.environ['pylons.routes_dict'].get('group_name')
116 117
@@ -138,68 +139,6 b' def get_user_group_slug(request):'
138 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 142 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
204 143 """
205 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 367 if 'push' not in enabled_hook_classes:
429 368 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
430 369 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
370 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
431 371
432 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 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 577 parsed_url = urlobject.URLObject(qualifed_home_url)
577 578 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
578 579 args = {
@@ -130,7 +130,7 b' class UpdateFailureReason(object):'
130 130 NO_CHANGE = 2
131 131
132 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 135 # Update failed because the target reference is missing.
136 136 MISSING_TARGET_REF = 4
@@ -1065,8 +1065,8 b' class BaseCommit(object):'
1065 1065
1066 1066 def no_node_at_path(self, path):
1067 1067 return NodeDoesNotExistError(
1068 "There is no file nor directory at the given path: "
1069 "'%s' at commit %s" % (path, self.short_id))
1068 u"There is no file nor directory at the given path: "
1069 u"`%s` at commit %s" % (safe_unicode(path), self.short_id))
1070 1070
1071 1071 def _fix_path(self, path):
1072 1072 """
@@ -160,6 +160,27 b' class MercurialCommit(base.BaseCommit):'
160 160 return self._make_commits(parents)
161 161
162 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 184 def children(self):
164 185 """
165 186 Returns list of child commits.
@@ -158,6 +158,12 b' class SubversionRepository(base.BaseRepo'
158 158 return commit_id1
159 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 167 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
162 168 # TODO: johbo: Implement better comparison, this is a very naive
163 169 # version which does not allow to compare branches, tags or folders
@@ -59,20 +59,22 b' class AuthTokenModel(BaseModel):'
59 59
60 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 64 Deletes given api_key, if user is set it also filters the object for
65 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 70 if user:
70 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 76 try:
75 Session().delete(api_key)
77 Session().delete(auth_token)
76 78 except Exception:
77 79 log.error(traceback.format_exc())
78 80 raise
@@ -80,7 +80,7 b' class ChangesetStatusModel(BaseModel):'
80 80 """
81 81 votes = defaultdict(int)
82 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 84 if statuses:
85 85 ver, latest = statuses[0]
86 86 votes[latest.status] += 1
@@ -248,13 +248,14 b' class ChangesetStatusModel(BaseModel):'
248 248 for o in pull_request.reviewers:
249 249 if not o.user:
250 250 continue
251 st = commit_statuses.get(o.user.username, None)
252 if st:
253 st = [(x, list(y)[0])
254 for x, y in (itertools.groupby(sorted(st, key=version),
255 version))]
251 statuses = commit_statuses.get(o.user.username, None)
252 if statuses:
253 statuses = [(x, list(y)[0])
254 for x, y in (itertools.groupby(
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 259 return pull_request_reviewers
259 260
260 261 def calculated_review_status(self, pull_request, reviewers_statuses=None):
@@ -29,14 +29,14 b' import collections'
29 29 from datetime import datetime
30 30
31 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 33 from sqlalchemy.sql.expression import null
34 34 from sqlalchemy.sql.functions import coalesce
35 35
36 36 from rhodecode.lib import helpers as h, diffs
37 from rhodecode.lib import audit_logger
37 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
39 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
40 40 from rhodecode.model import BaseModel
41 41 from rhodecode.model.db import (
42 42 ChangesetComment, User, Notification, PullRequest, AttributeDict)
@@ -163,6 +163,13 b' class CommentsModel(BaseModel):'
163 163
164 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 173 def create(self, text, repo, user, commit_id=None, pull_request=None,
167 174 f_path=None, line_no=None, status_change=None,
168 175 status_change_type=None, comment_type=None,
@@ -268,8 +275,7 b' class CommentsModel(BaseModel):'
268 275
269 276 target_repo_url = h.link_to(
270 277 repo.repo_name,
271 h.url('summary_home',
272 repo_name=repo.repo_name, qualified=True))
278 h.route_url('repo_summary', repo_name=repo.repo_name))
273 279
274 280 # commit specifics
275 281 kwargs.update({
@@ -300,13 +306,11 b' class CommentsModel(BaseModel):'
300 306 qualified=True,)
301 307
302 308 # set some variables for email notification
303 pr_target_repo_url = h.url(
304 'summary_home', repo_name=pr_target_repo.repo_name,
305 qualified=True)
309 pr_target_repo_url = h.route_url(
310 'repo_summary', repo_name=pr_target_repo.repo_name)
306 311
307 pr_source_repo_url = h.url(
308 'summary_home', repo_name=pr_source_repo.repo_name,
309 qualified=True)
312 pr_source_repo_url = h.route_url(
313 'repo_summary', repo_name=pr_source_repo.repo_name)
310 314
311 315 # pull request specifics
312 316 kwargs.update({
@@ -340,13 +344,15 b' class CommentsModel(BaseModel):'
340 344 email_kwargs=kwargs,
341 345 )
342 346
343 action = (
344 'user_commented_pull_request:{}'.format(
345 comment.pull_request.pull_request_id)
346 if comment.pull_request
347 else 'user_commented_revision:{}'.format(comment.revision)
348 )
349 action_logger(user, action, comment.repo)
347 Session().flush()
348 if comment.pull_request:
349 action = 'repo.pull_request.comment.create'
350 else:
351 action = 'repo.commit.comment.create'
352
353 comment_data = comment.get_api_data()
354 self._log_audit_action(
355 action, {'data': comment_data}, user, comment)
350 356
351 357 registry = get_current_registry()
352 358 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
@@ -388,15 +394,22 b' class CommentsModel(BaseModel):'
388 394
389 395 return comment
390 396
391 def delete(self, comment):
397 def delete(self, comment, user):
392 398 """
393 399 Deletes given comment
394
395 :param comment_id:
396 400 """
397 401 comment = self.__get_commit_comment(comment)
402 old_data = comment.get_api_data()
398 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 413 return comment
401 414
402 415 def get_all_comments(self, repo_id, revision=None, pull_request=None):
@@ -412,22 +425,39 b' class CommentsModel(BaseModel):'
412 425 q = q.order_by(ChangesetComment.created_on)
413 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 432 comment = self.__get_commit_comment(comment)
417 433 if comment.pull_request:
418 return h.url(
419 'pullrequest_show',
420 repo_name=comment.pull_request.target_repo.repo_name,
421 pull_request_id=comment.pull_request.pull_request_id,
422 anchor='comment-%s' % comment.comment_id,
423 qualified=True,)
434 pull_request = comment.pull_request
435 if permalink:
436 return request.route_url(
437 'pull_requests_global',
438 pull_request_id=pull_request.pull_request_id,
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 446 else:
425 return h.url(
426 'changeset_home',
427 repo_name=comment.repo.repo_name,
428 revision=comment.revision,
429 anchor='comment-%s' % comment.comment_id,
430 qualified=True,)
447 repo = comment.repo
448 commit_id = comment.revision
449
450 if permalink:
451 return request.route_url(
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 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 44 from beaker.cache import cache_region
45 45 from zope.cachedescriptors.property import Lazy as LazyProperty
46 46
47 from pylons import url
48 47 from pylons.i18n.translation import lazy_ugettext as _
48 from pyramid.threadlocal import get_current_request
49 49
50 50 from rhodecode.lib.vcs import get_vcs_instance
51 51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
@@ -358,6 +358,7 b' class RhodeCodeUi(Base, BaseModel):'
358 358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 360 HOOK_PUSH = 'changegroup.push_logger'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
361 362
362 363 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 364 # git part is currently hardcoded.
@@ -571,6 +572,20 b' class User(Base, BaseModel):'
571 572 self._email = val.lower() if val else None
572 573
573 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 589 def api_key(self):
575 590 """
576 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 705 @property
691 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 709 @property
695 710 def username_or_name_or_email(self):
@@ -698,20 +713,20 b' class User(Base, BaseModel):'
698 713
699 714 @property
700 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 718 @property
704 719 def full_name_or_username(self):
705 return ('%s %s' % (self.firstname, self.lastname)
706 if (self.firstname and self.lastname) else self.username)
720 return ('%s %s' % (self.first_name, self.last_name)
721 if (self.first_name and self.last_name) else self.username)
707 722
708 723 @property
709 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 727 @property
713 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 731 @property
717 732 def is_admin(self):
@@ -761,9 +776,9 b' class User(Base, BaseModel):'
761 776 if val:
762 777 return val
763 778 else:
779 cache_key = "get_user_by_name_%s" % _hash_key(username)
764 780 q = q.options(
765 FromCache("sql_cache_short",
766 "get_user_by_name_%s" % _hash_key(username)))
781 FromCache("sql_cache_short", cache_key))
767 782
768 783 return q.scalar()
769 784
@@ -774,8 +789,8 b' class User(Base, BaseModel):'
774 789 .filter(or_(UserApiKeys.expires == -1,
775 790 UserApiKeys.expires >= time.time()))
776 791 if cache:
777 q = q.options(FromCache("sql_cache_short",
778 "get_auth_token_%s" % auth_token))
792 q = q.options(
793 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
779 794
780 795 match = q.first()
781 796 if match:
@@ -790,9 +805,10 b' class User(Base, BaseModel):'
790 805 else:
791 806 q = cls.query().filter(cls.email == email)
792 807
808 email_key = _hash_key(email)
793 809 if cache:
794 q = q.options(FromCache("sql_cache_short",
795 "get_email_key_%s" % _hash_key(email)))
810 q = q.options(
811 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
796 812
797 813 ret = q.scalar()
798 814 if ret is None:
@@ -804,8 +820,8 b' class User(Base, BaseModel):'
804 820 q = q.filter(UserEmailMap.email == email)
805 821 q = q.options(joinedload(UserEmailMap.user))
806 822 if cache:
807 q = q.options(FromCache("sql_cache_short",
808 "get_email_map_key_%s" % email))
823 q = q.options(
824 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
809 825 ret = getattr(q.scalar(), 'user', None)
810 826
811 827 return ret
@@ -872,10 +888,16 b' class User(Base, BaseModel):'
872 888 .order_by(User.username.asc()).all()
873 889
874 890 @classmethod
875 def get_default_user(cls, cache=False):
891 def get_default_user(cls, cache=False, refresh=False):
876 892 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
877 893 if user is None:
878 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 901 return user
880 902
881 903 def _get_default_perms(self, user, suffix=''):
@@ -996,6 +1018,19 b' class UserApiKeys(Base, BaseModel):'
996 1018 }
997 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 1034 @property
1000 1035 def expired(self):
1001 1036 if self.expires == -1:
@@ -1027,6 +1062,11 b' class UserApiKeys(Base, BaseModel):'
1027 1062 def scope_humanized(self):
1028 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 1071 class UserEmailMap(Base, BaseModel):
1032 1072 __tablename__ = 'user_email_map'
@@ -1076,6 +1116,11 b' class UserIpMap(Base, BaseModel):'
1076 1116 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1077 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 1124 @classmethod
1080 1125 def _get_ip_range(cls, ip_addr):
1081 1126 net = ipaddress.ip_network(ip_addr, strict=False)
@@ -1098,6 +1143,10 b' class UserLog(Base, BaseModel):'
1098 1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1099 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 1150 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1102 1151 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1103 1152 username = Column("username", String(255), nullable=True, unique=None, default=None)
@@ -1107,6 +1156,10 b' class UserLog(Base, BaseModel):'
1107 1156 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1108 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 1163 def __unicode__(self):
1111 1164 return u"<%s('id:%s:%s')>" % (
1112 1165 self.__class__.__name__, self.repository_name, self.action)
@@ -1156,6 +1209,11 b' class UserGroup(Base, BaseModel):'
1156 1209 user = relationship('User')
1157 1210
1158 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 1217 def group_data(self):
1160 1218 if not self._group_data:
1161 1219 return {}
@@ -1187,17 +1245,16 b' class UserGroup(Base, BaseModel):'
1187 1245 else:
1188 1246 q = cls.query().filter(cls.users_group_name == group_name)
1189 1247 if cache:
1190 q = q.options(FromCache(
1191 "sql_cache_short",
1192 "get_group_%s" % _hash_key(group_name)))
1248 q = q.options(
1249 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1193 1250 return q.scalar()
1194 1251
1195 1252 @classmethod
1196 1253 def get(cls, user_group_id, cache=False):
1197 1254 user_group = cls.query()
1198 1255 if cache:
1199 user_group = user_group.options(FromCache("sql_cache_short",
1200 "get_users_group_%s" % user_group_id))
1256 user_group = user_group.options(
1257 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1201 1258 return user_group.get(user_group_id)
1202 1259
1203 1260 def permissions(self, with_admins=True, with_owner=True):
@@ -1454,6 +1511,11 b' class Repository(Base, BaseModel):'
1454 1511 safe_unicode(self.repo_name))
1455 1512
1456 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 1519 def landing_rev(self):
1458 1520 # always should return [rev_type, rev]
1459 1521 if self._landing_revision:
@@ -1538,9 +1600,9 b' class Repository(Base, BaseModel):'
1538 1600 if val:
1539 1601 return val
1540 1602 else:
1603 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1541 1604 q = q.options(
1542 FromCache("sql_cache_short",
1543 "get_repo_by_name_%s" % _hash_key(repo_name)))
1605 FromCache("sql_cache_short", cache_key))
1544 1606
1545 1607 return q.scalar()
1546 1608
@@ -1750,6 +1812,7 b' class Repository(Base, BaseModel):'
1750 1812 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1751 1813 # move this methods on models level.
1752 1814 from rhodecode.model.settings import SettingsModel
1815 from rhodecode.model.repo import RepoModel
1753 1816
1754 1817 repo = self
1755 1818 _user_id, _time, _reason = self.locked
@@ -1759,13 +1822,14 b' class Repository(Base, BaseModel):'
1759 1822 'repo_name': repo.repo_name,
1760 1823 'repo_type': repo.repo_type,
1761 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 1826 'private': repo.private,
1764 1827 'created_on': repo.created_on,
1765 'description': repo.description,
1828 'description': repo.description_safe,
1766 1829 'landing_rev': repo.landing_rev,
1767 1830 'owner': repo.user.username,
1768 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 1833 'enable_statistics': repo.enable_statistics,
1770 1834 'enable_locking': repo.enable_locking,
1771 1835 'enable_downloads': repo.enable_downloads,
@@ -1893,7 +1957,6 b' class Repository(Base, BaseModel):'
1893 1957 return clone_uri
1894 1958
1895 1959 def clone_url(self, **override):
1896 qualified_home_url = url('home', qualified=True)
1897 1960
1898 1961 uri_tmpl = None
1899 1962 if 'with_id' in override:
@@ -1915,8 +1978,9 b' class Repository(Base, BaseModel):'
1915 1978 # ie, not having tmpl_context set up
1916 1979 pass
1917 1980
1918 return get_clone_url(uri_tmpl=uri_tmpl,
1919 qualifed_home_url=qualified_home_url,
1981 request = get_current_request()
1982 return get_clone_url(request=request,
1983 uri_tmpl=uri_tmpl,
1920 1984 repo_name=self.repo_name,
1921 1985 repo_id=self.repo_id, **override)
1922 1986
@@ -2160,8 +2224,13 b' class RepoGroup(Base, BaseModel):'
2160 2224 self.parent_group = parent_group
2161 2225
2162 2226 def __unicode__(self):
2163 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2164 self.group_name)
2227 return u"<%s('id:%s:%s')>" % (
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 2235 @classmethod
2167 2236 def _generate_choice(cls, repo_group):
@@ -2176,7 +2245,7 b' class RepoGroup(Base, BaseModel):'
2176 2245
2177 2246 repo_groups = []
2178 2247 if show_empty_group:
2179 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2248 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2180 2249
2181 2250 repo_groups.extend([cls._generate_choice(x) for x in groups])
2182 2251
@@ -2196,14 +2265,17 b' class RepoGroup(Base, BaseModel):'
2196 2265 else:
2197 2266 gr = cls.query().filter(cls.group_name == group_name)
2198 2267 if cache:
2199 gr = gr.options(FromCache(
2200 "sql_cache_short",
2201 "get_group_%s" % _hash_key(group_name)))
2268 name_key = _hash_key(group_name)
2269 gr = gr.options(
2270 FromCache("sql_cache_short", "get_group_%s" % name_key))
2202 2271 return gr.scalar()
2203 2272
2204 2273 @classmethod
2205 2274 def get_user_personal_repo_group(cls, user_id):
2206 2275 user = User.get(user_id)
2276 if user.username == User.DEFAULT_USER:
2277 return None
2278
2207 2279 return cls.query()\
2208 2280 .filter(cls.personal == true())\
2209 2281 .filter(cls.user == user).scalar()
@@ -2389,7 +2461,7 b' class RepoGroup(Base, BaseModel):'
2389 2461 data = {
2390 2462 'group_id': group.group_id,
2391 2463 'group_name': group.group_name,
2392 'group_description': group.group_description,
2464 'group_description': group.description_safe,
2393 2465 'parent_group': group.parent_group.group_name if group.parent_group else None,
2394 2466 'repositories': [x.repo_name for x in group.repositories],
2395 2467 'owner': group.user.username,
@@ -3088,20 +3160,39 b' class ChangesetComment(Base, BaseModel):'
3088 3160 def is_todo(self):
3089 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 3167 def get_index_version(self, versions):
3092 3168 return self.get_index_from_version(
3093 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 3171 def __repr__(self):
3100 3172 if self.comment_id:
3101 3173 return '<DB:Comment #%s>' % self.comment_id
3102 3174 else:
3103 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 3197 class ChangesetStatus(Base, BaseModel):
3107 3198 __tablename__ = 'changeset_statuses'
@@ -3153,6 +3244,19 b' class ChangesetStatus(Base, BaseModel):'
3153 3244 def status_lbl(self):
3154 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 3261 class _PullRequestBase(BaseModel):
3158 3262 """
@@ -3215,6 +3319,19 b' class _PullRequestBase(BaseModel):'
3215 3319 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3216 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 3335 @hybrid_property
3219 3336 def revisions(self):
3220 3337 return self._revisions.split(':') if self._revisions else []
@@ -3276,14 +3393,19 b' class _PullRequestBase(BaseModel):'
3276 3393 else:
3277 3394 return None
3278 3395
3279 def get_api_data(self):
3396 def get_api_data(self, with_merge_state=True):
3280 3397 from rhodecode.model.pull_request import PullRequestModel
3398
3281 3399 pull_request = self
3400 if with_merge_state:
3282 3401 merge_status = PullRequestModel().merge_status(pull_request)
3283
3284 pull_request_url = url(
3285 'pullrequest_show', repo_name=self.target_repo.repo_name,
3286 pull_request_id=self.pull_request_id, qualified=True)
3402 merge_state = {
3403 'status': merge_status[0],
3404 'message': safe_unicode(merge_status[1]),
3405 }
3406 else:
3407 merge_state = {'status': 'not_available',
3408 'message': 'not_available'}
3287 3409
3288 3410 merge_data = {
3289 3411 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
@@ -3294,7 +3416,7 b' class _PullRequestBase(BaseModel):'
3294 3416
3295 3417 data = {
3296 3418 'pull_request_id': pull_request.pull_request_id,
3297 'url': pull_request_url,
3419 'url': PullRequestModel().get_url(pull_request),
3298 3420 'title': pull_request.title,
3299 3421 'description': pull_request.description,
3300 3422 'status': pull_request.status,
@@ -3302,10 +3424,7 b' class _PullRequestBase(BaseModel):'
3302 3424 'updated_on': pull_request.updated_on,
3303 3425 'commit_ids': pull_request.revisions,
3304 3426 'review_status': pull_request.calculated_review_status(),
3305 'mergeable': {
3306 'status': merge_status[0],
3307 'message': unicode(merge_status[1]),
3308 },
3427 'mergeable': merge_state,
3309 3428 'source': {
3310 3429 'clone_url': pull_request.source_repo.clone_url(),
3311 3430 'repository': pull_request.source_repo.repo_name,
@@ -3334,7 +3453,8 b' class _PullRequestBase(BaseModel):'
3334 3453 'reasons': reasons,
3335 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 3480 reviewers = relationship('PullRequestReviewers',
3361 3481 cascade="all, delete, delete-orphan")
3362 statuses = relationship('ChangesetStatus')
3482 statuses = relationship('ChangesetStatus',
3483 cascade="all, delete, delete-orphan")
3363 3484 comments = relationship('ChangesetComment',
3364 3485 cascade="all, delete, delete-orphan")
3365 3486 versions = relationship('PullRequestVersion',
@@ -3424,6 +3545,8 b' class PullRequest(Base, _PullRequestBase'
3424 3545 attrs.revisions = pull_request_obj.revisions
3425 3546
3426 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 3551 return PullRequestDisplay(attrs, internal=internal_methods)
3429 3552
@@ -3502,11 +3625,6 b' class PullRequestReviewers(Base, BaseMod'
3502 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 3628 @hybrid_property
3511 3629 def reasons(self):
3512 3630 if not self._reasons:
@@ -3531,7 +3649,7 b' class PullRequestReviewers(Base, BaseMod'
3531 3649 _reasons = Column(
3532 3650 'reason', MutationList.as_mutable(
3533 3651 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3534
3652 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3535 3653 user = relationship('User')
3536 3654 pull_request = relationship('PullRequest')
3537 3655
@@ -3651,6 +3769,11 b' class Gist(Base, BaseModel):'
3651 3769 def __repr__(self):
3652 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 3777 @classmethod
3655 3778 def get_or_404(cls, id_, pyramid_exc=False):
3656 3779
@@ -3670,6 +3793,8 b' class Gist(Base, BaseModel):'
3670 3793
3671 3794 def gist_url(self):
3672 3795 import rhodecode
3796 from pylons import url
3797
3673 3798 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3674 3799 if alias_url:
3675 3800 return alias_url.replace('{gistid}', self.gist_access_id)
@@ -3835,14 +3960,17 b' class RepoReviewRuleUser(Base, BaseModel'
3835 3960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3836 3961 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3837 3962 )
3838 repo_review_rule_user_id = Column(
3839 'repo_review_rule_user_id', Integer(), primary_key=True)
3840 repo_review_rule_id = Column("repo_review_rule_id",
3841 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3842 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3843 nullable=False)
3963 repo_review_rule_user_id = Column('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'))
3965 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3966 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3844 3967 user = relationship('User')
3845 3968
3969 def rule_data(self):
3970 return {
3971 'mandatory': self.mandatory
3972 }
3973
3846 3974
3847 3975 class RepoReviewRuleUserGroup(Base, BaseModel):
3848 3976 __tablename__ = 'repo_review_rules_users_groups'
@@ -3850,14 +3978,17 b' class RepoReviewRuleUserGroup(Base, Base'
3850 3978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3851 3979 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3852 3980 )
3853 repo_review_rule_users_group_id = Column(
3854 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3855 repo_review_rule_id = Column("repo_review_rule_id",
3856 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3857 users_group_id = Column("users_group_id", Integer(),
3858 ForeignKey('users_groups.users_group_id'), nullable=False)
3981 repo_review_rule_users_group_id = Column('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'))
3983 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3984 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3859 3985 users_group = relationship('UserGroup')
3860 3986
3987 def rule_data(self):
3988 return {
3989 'mandatory': self.mandatory
3990 }
3991
3861 3992
3862 3993 class RepoReviewRule(Base, BaseModel):
3863 3994 __tablename__ = 'repo_review_rules'
@@ -3872,13 +4003,14 b' class RepoReviewRule(Base, BaseModel):'
3872 4003 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3873 4004 repo = relationship('Repository', backref='review_rules')
3874 4005
3875 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3876 default=u'*') # glob
3877 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3878 default=u'*') # glob
3879
3880 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3881 nullable=False, default=False)
4006 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4007 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4008
4009 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4010 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4011 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4012 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4013
3882 4014 rule_users = relationship('RepoReviewRuleUser')
3883 4015 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3884 4016
@@ -3934,16 +4066,32 b' class RepoReviewRule(Base, BaseModel):'
3934 4066 def review_users(self):
3935 4067 """ Returns the users which this rule applies to """
3936 4068
3937 users = set()
3938 users |= set([
3939 rule_user.user for rule_user in self.rule_users
3940 if rule_user.user.active])
3941 users |= set(
3942 member.user
3943 for rule_user_group in self.rule_user_groups
3944 for member in rule_user_group.users_group.members
3945 if member.user.active
3946 )
4069 users = collections.OrderedDict()
4070
4071 for rule_user in self.rule_users:
4072 if rule_user.user.active:
4073 if rule_user.user not in users:
4074 users[rule_user.user.username] = {
4075 'user': rule_user.user,
4076 'source': 'user',
4077 'source_data': {},
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 4095 return users
3948 4096
3949 4097 def __repr__(self):
@@ -242,7 +242,7 b' def RepoForm(edit=False, old_data=None, '
242 242 allow_extra_fields = True
243 243 filter_extra_fields = False
244 244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
245 v.SlugifyName())
245 v.SlugifyName(), v.CannotHaveGitSuffix())
246 246 repo_group = All(v.CanWriteGroup(old_data),
247 247 v.OneOf(repo_groups, hideList=True))
248 248 repo_type = v.OneOf(supported_backends, required=False,
@@ -384,6 +384,7 b' class _BaseVcsSettingsForm(formencode.Sc'
384 384
385 385 # hg
386 386 extensions_largefiles = v.StringBoolean(if_missing=False)
387 extensions_evolve = v.StringBoolean(if_missing=False)
387 388 phases_publish = v.StringBoolean(if_missing=False)
388 389 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
389 390
@@ -534,12 +535,13 b' def PullRequestForm(repo_id):'
534 535 class ReviewerForm(formencode.Schema):
535 536 user_id = v.Int(not_empty=True)
536 537 reasons = All()
538 mandatory = v.StringBoolean()
537 539
538 540 class _PullRequestForm(formencode.Schema):
539 541 allow_extra_fields = True
540 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 545 source_repo = v.UnicodeString(strip=True, required=True)
544 546 source_ref = v.UnicodeString(strip=True, required=True)
545 547 target_repo = v.UnicodeString(strip=True, required=True)
@@ -96,6 +96,9 b' class IntegrationModel(BaseModel):'
96 96 """ Send an event to an integration """
97 97 handler = self.get_integration_handler(integration)
98 98 if handler:
99 log.debug(
100 'events: sending event %s on integration %s using handler %s',
101 event, integration, handler)
99 102 handler.send_event(event)
100 103
101 104 def get_integrations(self, scope, IntegrationType=None):
@@ -200,14 +203,14 b' class IntegrationModel(BaseModel):'
200 203 query = query.filter(or_(*clauses))
201 204
202 205 if cache:
203 query = query.options(FromCache(
204 "sql_cache_short",
205 "get_enabled_repo_integrations_%i" % event.repo.repo_id))
206 cache_key = "get_enabled_repo_integrations_%i" % event.repo.repo_id
207 query = query.options(
208 FromCache("sql_cache_short", cache_key))
206 209 else: # only global integrations
207 210 query = query.filter(global_integrations_filter)
208 211 if cache:
209 query = query.options(FromCache(
210 "sql_cache_short", "get_enabled_global_integrations"))
212 query = query.options(
213 FromCache("sql_cache_short", "get_enabled_global_integrations"))
211 214
212 215 result = query.all()
213 216 return result No newline at end of file
@@ -332,14 +332,19 b' class EmailNotificationModel(BaseModel):'
332 332
333 333 :param kwargs:
334 334 """
335
335 336 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
336
337 instance_url = h.route_url('home')
337 338 _kwargs = {
338 'instance_url': h.url('home', qualified=True),
339 'instance_url': instance_url,
340 'whitespace_filter': self.whitespace_filter
339 341 }
340 342 _kwargs.update(kwargs)
341 343 return _kwargs
342 344
345 def whitespace_filter(self, text):
346 return text.replace('\n', '').replace('\t', '')
347
343 348 def get_renderer(self, type_):
344 349 template_name = self.email_types[type_]
345 350 return PartialRenderer(template_name)
@@ -31,14 +31,16 b' import urllib'
31 31
32 32 from pylons.i18n.translation import _
33 33 from pylons.i18n.translation import lazy_ugettext
34 from pyramid.threadlocal import get_current_request
34 35 from sqlalchemy import or_
35 36
37 from rhodecode import events
36 38 from rhodecode.lib import helpers as h, hooks_utils, diffs
39 from rhodecode.lib import audit_logger
37 40 from rhodecode.lib.compat import OrderedDict
38 41 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
39 42 from rhodecode.lib.markup_renderer import (
40 43 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
41 from rhodecode.lib.utils import action_logger
42 44 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe
43 45 from rhodecode.lib.vcs.backends.base import (
44 46 Reference, MergeResponse, MergeFailureReason, UpdateFailureReason)
@@ -118,9 +120,9 b' class PullRequestModel(BaseModel):'
118 120 'Pull request update failed because of an unknown error.'),
119 121 UpdateFailureReason.NO_CHANGE: lazy_ugettext(
120 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 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 126 UpdateFailureReason.MISSING_TARGET_REF: lazy_ugettext(
125 127 'This pull request cannot be updated because the target '
126 128 'reference is missing.'),
@@ -415,7 +417,9 b' class PullRequestModel(BaseModel):'
415 417 .all()
416 418
417 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 423 created_by_user = self._get_user(created_by)
420 424 source_repo = self._get_repo(source_repo)
421 425 target_repo = self._get_repo(target_repo)
@@ -429,6 +433,7 b' class PullRequestModel(BaseModel):'
429 433 pull_request.title = title
430 434 pull_request.description = description
431 435 pull_request.author = created_by_user
436 pull_request.reviewer_data = reviewer_data
432 437
433 438 Session().add(pull_request)
434 439 Session().flush()
@@ -436,15 +441,20 b' class PullRequestModel(BaseModel):'
436 441 reviewer_ids = set()
437 442 # members / reviewers
438 443 for reviewer_object in reviewers:
439 if isinstance(reviewer_object, tuple):
440 user_id, reasons = reviewer_object
441 else:
442 user_id, reasons = reviewer_object, []
444 user_id, reasons, mandatory = reviewer_object
445 user = self._get_user(user_id)
443 446
444 user = self._get_user(user_id)
447 # skip duplicates
448 if user.user_id in reviewer_ids:
449 continue
450
445 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 458 Session().add(reviewer)
449 459
450 460 # Set approval status to "Under Review" for all commits which are
@@ -460,6 +470,11 b' class PullRequestModel(BaseModel):'
460 470 self._trigger_pull_request_hook(
461 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 478 return pull_request
464 479
465 480 def _trigger_pull_request_hook(self, pull_request, user, action):
@@ -510,7 +525,12 b' class PullRequestModel(BaseModel):'
510 525 log.debug(
511 526 "Merge was successful, updating the pull request comments.")
512 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 534 else:
515 535 log.warn("Merge failed, not updating the pull request.")
516 536 return merge_state
@@ -594,7 +614,7 b' class PullRequestModel(BaseModel):'
594 614 pull_request, source_ref_type)
595 615 return UpdateResponse(
596 616 executed=False,
597 reason=UpdateFailureReason.WRONG_REF_TPYE,
617 reason=UpdateFailureReason.WRONG_REF_TYPE,
598 618 old=pull_request, new=None, changes=None,
599 619 source_changed=False, target_changed=False)
600 620
@@ -763,6 +783,7 b' class PullRequestModel(BaseModel):'
763 783 version._last_merge_status = pull_request._last_merge_status
764 784 version.shadow_merge_ref = pull_request.shadow_merge_ref
765 785 version.merge_rev = pull_request.merge_rev
786 version.reviewer_data = pull_request.reviewer_data
766 787
767 788 version.revisions = pull_request.revisions
768 789 version.pull_request = pull_request
@@ -806,13 +827,15 b' class PullRequestModel(BaseModel):'
806 827
807 828 """
808 829 pull_request = pull_request_version.pull_request
809 comments = ChangesetComment.query().filter(
830 comments = ChangesetComment.query()\
831 .filter(
810 832 # TODO: johbo: Should we query for the repo at all here?
811 833 # Pending decision on how comments of PRs are to be related
812 834 # to either the source repo, the target repo or no repo at all.
813 835 ChangesetComment.repo_id == pull_request.target_repo.repo_id,
814 836 ChangesetComment.pull_request == pull_request,
815 ChangesetComment.pull_request_version == None)
837 ChangesetComment.pull_request_version == None)\
838 .order_by(ChangesetComment.comment_id.asc())
816 839
817 840 # TODO: johbo: Find out why this breaks if it is done in a bulk
818 841 # operation.
@@ -886,8 +909,9 b' class PullRequestModel(BaseModel):'
886 909 renderer = RstTemplateRenderer()
887 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 913 pull_request = self.__get_pull_request(pull_request)
914 old_data = pull_request.get_api_data(with_merge_state=False)
891 915 if pull_request.is_closed():
892 916 raise ValueError('This pull request is closed')
893 917 if title:
@@ -895,22 +919,27 b' class PullRequestModel(BaseModel):'
895 919 pull_request.description = description
896 920 pull_request.updated_on = datetime.datetime.now()
897 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 928 Update the reviewers in the pull request
902 929
903 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 = {}
908 for user_id, reasons in reviewer_data:
935 reviewers = {}
936 for user_id, reasons, mandatory in reviewer_data:
909 937 if isinstance(user_id, (int, basestring)):
910 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 943 pull_request = self.__get_pull_request(pull_request)
915 944 current_reviewers = PullRequestReviewers.query()\
916 945 .filter(PullRequestReviewers.pull_request ==
@@ -926,9 +955,16 b' class PullRequestModel(BaseModel):'
926 955 for uid in ids_to_add:
927 956 changed = True
928 957 _usr = self._get_user(uid)
929 reasons = reviewers_reasons[uid]
930 reviewer = PullRequestReviewers(_usr, pull_request, reasons)
958 reviewer = PullRequestReviewers()
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 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 969 for uid in ids_to_remove:
934 970 changed = True
@@ -939,7 +975,11 b' class PullRequestModel(BaseModel):'
939 975 # use .all() in case we accidentally added the same person twice
940 976 # this CAN happen due to the lack of DB checks
941 977 for obj in reviewers:
978 old_data = obj.get_dict()
942 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 984 if changed:
945 985 pull_request.updated_on = datetime.datetime.now()
@@ -948,11 +988,18 b' class PullRequestModel(BaseModel):'
948 988 self.notify_reviewers(pull_request, ids_to_add)
949 989 return ids_to_add, ids_to_remove
950 990
951 def get_url(self, pull_request):
952 return h.url('pullrequest_show',
991 def get_url(self, pull_request, request=None, permalink=False):
992 if not request:
993 request = get_current_request()
994
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',
953 1001 repo_name=safe_str(pull_request.target_repo.repo_name),
954 pull_request_id=pull_request.pull_request_id,
955 qualified=True)
1002 pull_request_id=pull_request.pull_request_id,)
956 1003
957 1004 def get_shadow_clone_url(self, pull_request):
958 1005 """
@@ -979,22 +1026,16 b' class PullRequestModel(BaseModel):'
979 1026 pr_source_repo = pull_request_obj.source_repo
980 1027 pr_target_repo = pull_request_obj.target_repo
981 1028
982 pr_url = h.url(
983 'pullrequest_show',
1029 pr_url = h.route_url('pullrequest_show',
984 1030 repo_name=pr_target_repo.repo_name,
985 pull_request_id=pull_request_obj.pull_request_id,
986 qualified=True,)
1031 pull_request_id=pull_request_obj.pull_request_id,)
987 1032
988 1033 # set some variables for email notification
989 pr_target_repo_url = h.url(
990 'summary_home',
991 repo_name=pr_target_repo.repo_name,
992 qualified=True)
1034 pr_target_repo_url = h.route_url(
1035 'repo_summary', repo_name=pr_target_repo.repo_name)
993 1036
994 pr_source_repo_url = h.url(
995 'summary_home',
996 repo_name=pr_source_repo.repo_name,
997 qualified=True)
1037 pr_source_repo_url = h.route_url(
1038 'repo_summary', repo_name=pr_source_repo.repo_name)
998 1039
999 1040 # pull request specifics
1000 1041 pull_request_commits = [
@@ -1031,9 +1072,13 b' class PullRequestModel(BaseModel):'
1031 1072 email_kwargs=kwargs,
1032 1073 )
1033 1074
1034 def delete(self, pull_request):
1075 def delete(self, pull_request, user):
1035 1076 pull_request = self.__get_pull_request(pull_request)
1077 old_data = pull_request.get_api_data(with_merge_state=False)
1036 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 1082 Session().delete(pull_request)
1038 1083
1039 1084 def close_pull_request(self, pull_request, user):
@@ -1044,44 +1089,64 b' class PullRequestModel(BaseModel):'
1044 1089 Session().add(pull_request)
1045 1090 self._trigger_pull_request_hook(
1046 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,
1050 message=None):
1051 status = ChangesetStatus.STATUS_REJECTED
1095 def close_pull_request_with_comment(
1096 self, pull_request, user, repo, message=None):
1097
1098 pull_request_review_status = pull_request.calculated_review_status()
1052 1099
1053 if not message:
1054 message = (
1055 _('Status change %(transition_icon)s %(status)s') % {
1056 'transition_icon': '>',
1057 'status': ChangesetStatus.get_status_lbl(status)})
1100 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
1101 # approved only if we have voting consent
1102 status = ChangesetStatus.STATUS_APPROVED
1103 else:
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(
1062 text=internal_message,
1112 # create a comment, and link it to new status
1113 comment = CommentsModel().create(
1114 text=text,
1063 1115 repo=repo.repo_id,
1064 1116 user=user.user_id,
1065 1117 pull_request=pull_request.pull_request_id,
1066 f_path=None,
1067 line_no=None,
1068 status_change=ChangesetStatus.get_status_lbl(status),
1118 status_change=status_lbl,
1069 1119 status_change_type=status,
1070 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 1125 ChangesetStatusModel().set_status(
1074 1126 repo.repo_id,
1075 1127 status,
1076 1128 user.user_id,
1077 comm,
1129 comment=comment,
1078 1130 pull_request=pull_request.pull_request_id
1079 1131 )
1132
1080 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 1145 PullRequestModel().close_pull_request(
1083 1146 pull_request.pull_request_id, user)
1084 1147
1148 return comment, status
1149
1085 1150 def merge_status(self, pull_request):
1086 1151 if not self._is_merge_enabled(pull_request):
1087 1152 return False, _('Server-side pull request merging is disabled.')
@@ -1226,11 +1291,11 b' class PullRequestModel(BaseModel):'
1226 1291 'user': {
1227 1292 'user_id': repo.user.user_id,
1228 1293 'username': repo.user.username,
1229 'firstname': repo.user.firstname,
1230 'lastname': repo.user.lastname,
1294 'firstname': repo.user.first_name,
1295 'lastname': repo.user.last_name,
1231 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 1299 'refs': {
1235 1300 'all_refs': all_refs,
1236 1301 'selected_ref': selected_ref,
@@ -1360,12 +1425,29 b' class PullRequestModel(BaseModel):'
1360 1425 settings = settings_model.get_general_settings()
1361 1426 return settings.get('rhodecode_hg_use_rebase_for_merging', False)
1362 1427
1363 def _log_action(self, action, user, pull_request):
1364 action_logger(
1365 user,
1366 '{action}:{pr_id}'.format(
1367 action=action, pr_id=pull_request.pull_request_id),
1368 pull_request.target_repo)
1428 def _log_audit_action(self, action, action_data, user, pull_request):
1429 audit_logger.store(
1430 action=action,
1431 action_data=action_data,
1432 user=user,
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 1453 class MergeCheck(object):
@@ -30,8 +30,7 b' import time'
30 30 import traceback
31 31 from datetime import datetime, timedelta
32 32
33 from sqlalchemy.sql import func
34 from sqlalchemy.sql.expression import true, or_
33 from pyramid.threadlocal import get_current_request
35 34 from zope.cachedescriptors.property import Lazy as LazyProperty
36 35
37 36 from rhodecode import events
@@ -40,19 +39,17 b' from rhodecode.lib.auth import HasUserGr'
40 39 from rhodecode.lib.caching_query import FromCache
41 40 from rhodecode.lib.exceptions import AttachedForksError
42 41 from rhodecode.lib.hooks_base import log_delete_repository
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 42 from rhodecode.lib.utils import make_db_config
45 43 from rhodecode.lib.utils2 import (
46 44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
47 45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
48 46 from rhodecode.lib.vcs.backends import get_backend
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
50 47 from rhodecode.model import BaseModel
51 from rhodecode.model.db import (
48 from rhodecode.model.db import (_hash_key,
52 49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
53 50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
54 51 RepoGroup, RepositoryField)
55 from rhodecode.model.scm import UserGroupList
52
56 53 from rhodecode.model.settings import VcsSettingsModel
57 54
58 55
@@ -103,8 +100,8 b' class RepoModel(BaseModel):'
103 100 .filter(Repository.repo_id == repo_id)
104 101
105 102 if cache:
106 repo = repo.options(FromCache("sql_cache_short",
107 "get_repo_%s" % repo_id))
103 repo = repo.options(
104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
108 105 return repo.scalar()
109 106
110 107 def get_repo(self, repository):
@@ -115,8 +112,9 b' class RepoModel(BaseModel):'
115 112 .filter(Repository.repo_name == repo_name)
116 113
117 114 if cache:
118 repo = repo.options(FromCache("sql_cache_short",
119 "get_repo_%s" % repo_name))
115 name_key = _hash_key(repo_name)
116 repo = repo.options(
117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
120 118 return repo.scalar()
121 119
122 120 def _extract_id_from_repo_name(self, repo_name):
@@ -134,6 +132,7 b' class RepoModel(BaseModel):'
134 132 :param repo_name:
135 133 :return: repo object if matched else None
136 134 """
135
137 136 try:
138 137 _repo_id = self._extract_id_from_repo_name(repo_name)
139 138 if _repo_id:
@@ -156,86 +155,36 b' class RepoModel(BaseModel):'
156 155 repos = Repository.query().filter(Repository.group == root).all()
157 156 return repos
158 157
159 def get_url(self, repo):
160 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
161 qualified=True)
158 def get_url(self, repo, request=None, permalink=False):
159 if not request:
160 request = get_current_request()
162 161
163 def get_users(self, name_contains=None, limit=20, only_active=True):
164
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())
162 if not request:
163 return
169 164
170 if name_contains:
171 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
172 query = query.filter(
173 or_(
174 User.name.ilike(ilike_expression),
175 User.lastname.ilike(ilike_expression),
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
165 if permalink:
166 return request.route_url(
167 'repo_summary', repo_name=safe_str(repo.repo_id))
168 else:
169 return request.route_url(
170 'repo_summary', repo_name=safe_str(repo.repo_name))
198 171
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
200
201 # TODO: mikhail: move this method to the UserGroupModel.
202 query = self.sa.query(UserGroup)
203 if only_active:
204 query = query.filter(UserGroup.users_group_active == true())
172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
173 if not request:
174 request = get_current_request()
205 175
206 if name_contains:
207 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
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)
176 if not request:
177 return
217 178
218 _groups = [
219 {
220 'id': group.users_group_id,
221 # TODO: marcink figure out a way to generate the url for the
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,
179 if permalink:
180 return request.route_url(
181 'repo_commit', repo_name=safe_str(repo.repo_id),
182 commit_id=commit_id)
229 183
230 'owner_icon': h.gravatar_url(group.user.email, 30),
231 'value_display_owner': h.person(group.user.email),
232
233 'value_type': 'user_group',
234 'active': group.users_group_active,
235 }
236 for group in user_groups
237 ]
238 return _groups
184 else:
185 return request.route_url(
186 'repo_commit', repo_name=safe_str(repo.repo_name),
187 commit_id=commit_id)
239 188
240 189 @classmethod
241 190 def update_repoinfo(cls, repositories=None):
@@ -308,7 +257,7 b' class RepoModel(BaseModel):'
308 257 "last_changeset": last_rev(repo.repo_name, cs_cache),
309 258 "last_changeset_raw": cs_cache.get('revision'),
310 259
311 "desc": desc(repo.description),
260 "desc": desc(repo.description_safe),
312 261 "owner": user_profile(repo.user.username),
313 262
314 263 "state": state(repo.repo_state),
@@ -376,16 +325,6 b' class RepoModel(BaseModel):'
376 325 replacement_user = User.get_first_super_admin().username
377 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 328 return defaults
390 329
391 330 def update(self, repo, **kwargs):
@@ -414,12 +353,6 b' class RepoModel(BaseModel):'
414 353 val = kwargs[k]
415 354 if strip:
416 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 357 setattr(cur_repo, k, val)
425 358
@@ -594,10 +527,16 b' class RepoModel(BaseModel):'
594 527
595 528 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
596 529
530 changes = {
531 'added': [],
532 'updated': [],
533 'deleted': []
534 }
597 535 # update permissions
598 536 for member_id, perm, member_type in perm_updates:
599 537 member_id = int(member_id)
600 538 if member_type == 'user':
539 member_name = User.get(member_id).username
601 540 # this updates also current one if found
602 541 self.grant_user_permission(
603 542 repo=repo, user=member_id, perm=perm)
@@ -609,10 +548,14 b' class RepoModel(BaseModel):'
609 548 self.grant_user_group_permission(
610 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 554 # set new permissions
613 555 for member_id, perm, member_type in perm_additions:
614 556 member_id = int(member_id)
615 557 if member_type == 'user':
558 member_name = User.get(member_id).username
616 559 self.grant_user_permission(
617 560 repo=repo, user=member_id, perm=perm)
618 561 else: # set for user group
@@ -622,11 +565,13 b' class RepoModel(BaseModel):'
622 565 *req_perms)(member_name, user=cur_user):
623 566 self.grant_user_group_permission(
624 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 570 # delete permissions
627 571 for member_id, perm, member_type in perm_deletions:
628 572 member_id = int(member_id)
629 573 if member_type == 'user':
574 member_name = User.get(member_id).username
630 575 self.revoke_user_permission(repo=repo, user=member_id)
631 576 else: # set for user group
632 577 # check if we have permissions to alter this usergroup
@@ -636,6 +581,10 b' class RepoModel(BaseModel):'
636 581 self.revoke_user_group_permission(
637 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 588 def create_fork(self, form_data, cur_user):
640 589 """
641 590 Simple wrapper into executing celery task for fork creation
@@ -35,7 +35,7 b' from zope.cachedescriptors.property impo'
35 35
36 36 from rhodecode import events
37 37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (_hash_key,
39 39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 40 UserGroup, Repository)
41 41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
@@ -73,8 +73,9 b' class RepoGroupModel(BaseModel):'
73 73 .filter(RepoGroup.group_name == repo_group_name)
74 74
75 75 if cache:
76 repo = repo.options(FromCache(
77 "sql_cache_short", "get_repo_group_%s" % repo_group_name))
76 name_key = _hash_key(repo_group_name)
77 repo = repo.options(
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
78 79 return repo.scalar()
79 80
80 81 def get_default_create_personal_repo_group(self):
@@ -339,6 +340,12 b' class RepoGroupModel(BaseModel):'
339 340
340 341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
341 342
343 changes = {
344 'added': [],
345 'updated': [],
346 'deleted': []
347 }
348
342 349 def _set_perm_user(obj, user, perm):
343 350 if isinstance(obj, RepoGroup):
344 351 self.grant_user_permission(
@@ -381,7 +388,6 b' class RepoGroupModel(BaseModel):'
381 388 repo=obj, group_name=user_group)
382 389
383 390 # start updates
384 updates = []
385 391 log.debug('Now updating permissions for %s in recursive mode:%s',
386 392 repo_group, recursive)
387 393
@@ -407,10 +413,13 b' class RepoGroupModel(BaseModel):'
407 413 # in recursive mode
408 414 obj = repo_group
409 415
416 change_obj = obj.get_api_data()
417
410 418 # update permissions
411 419 for member_id, perm, member_type in perm_updates:
412 420 member_id = int(member_id)
413 421 if member_type == 'user':
422 member_name = User.get(member_id).username
414 423 # this updates also current one if found
415 424 _set_perm_user(obj, user=member_id, perm=perm)
416 425 else: # set for user group
@@ -419,10 +428,15 b' class RepoGroupModel(BaseModel):'
419 428 user=cur_user):
420 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 435 # set new permissions
423 436 for member_id, perm, member_type in perm_additions:
424 437 member_id = int(member_id)
425 438 if member_type == 'user':
439 member_name = User.get(member_id).username
426 440 _set_perm_user(obj, user=member_id, perm=perm)
427 441 else: # set for user group
428 442 # check if we have permissions to alter this usergroup
@@ -431,10 +445,15 b' class RepoGroupModel(BaseModel):'
431 445 user=cur_user):
432 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 452 # delete permissions
435 453 for member_id, perm, member_type in perm_deletions:
436 454 member_id = int(member_id)
437 455 if member_type == 'user':
456 member_name = User.get(member_id).username
438 457 _revoke_perm_user(obj, user=member_id)
439 458 else: # set for user group
440 459 # check if we have permissions to alter this usergroup
@@ -443,13 +462,16 b' class RepoGroupModel(BaseModel):'
443 462 user=cur_user):
444 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 469 # if it's not recursive call for all,repos,groups
448 470 # break the loop and don't proceed with other changes
449 471 if recursive not in ['all', 'repos', 'groups']:
450 472 break
451 473
452 return updates
474 return changes
453 475
454 476 def update(self, repo_group, form_data):
455 477 try:
@@ -689,7 +711,7 b' class RepoGroupModel(BaseModel):'
689 711 "menu": quick_menu(group.group_name),
690 712 "name": repo_group_lnk(group.group_name),
691 713 "name_raw": group.group_name,
692 "desc": desc(group.group_description, group.personal),
714 "desc": desc(group.description_safe, group.personal),
693 715 "top_level_repos": 0,
694 716 "owner": user_profile(group.user.username)
695 717 }
@@ -47,7 +47,7 b' from rhodecode.lib.auth import ('
47 47 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
48 48 from rhodecode.lib import hooks_utils, caches
49 49 from rhodecode.lib.utils import (
50 get_filesystem_repos, action_logger, make_db_config)
50 get_filesystem_repos, make_db_config)
51 51 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
52 52 from rhodecode.lib.system_info import get_system_info
53 53 from rhodecode.model import BaseModel
@@ -289,9 +289,6 b' class ScmModel(BaseModel):'
289 289 if f is not None:
290 290 try:
291 291 self.sa.delete(f)
292 action_logger(UserTemp(user_id),
293 'stopped_following_repo',
294 RepoTemp(follow_repo_id))
295 292 return
296 293 except Exception:
297 294 log.error(traceback.format_exc())
@@ -302,10 +299,6 b' class ScmModel(BaseModel):'
302 299 f.user_id = user_id
303 300 f.follows_repo_id = follow_repo_id
304 301 self.sa.add(f)
305
306 action_logger(UserTemp(user_id),
307 'started_following_repo',
308 RepoTemp(follow_repo_id))
309 302 except Exception:
310 303 log.error(traceback.format_exc())
311 304 raise
@@ -503,7 +496,7 b' class ScmModel(BaseModel):'
503 496
504 497 if not flat:
505 498 _data = {
506 "name": f.unicode_path,
499 "name": h.escape(f.unicode_path),
507 500 "type": "file",
508 501 }
509 502 if extended_info:
@@ -529,7 +522,7 b' class ScmModel(BaseModel):'
529 522 _data = d.unicode_path
530 523 if not flat:
531 524 _data = {
532 "name": d.unicode_path,
525 "name": h.escape(d.unicode_path),
533 526 "type": "dir",
534 527 }
535 528 if extended_info:
@@ -18,6 +18,7 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import os
21 22 import hashlib
22 23 import logging
23 24 from collections import namedtuple
@@ -51,7 +52,8 b' class SettingsModel(BaseModel):'
51 52 BUILTIN_HOOKS = (
52 53 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 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 57 HOOKS_SECTION = 'hooks'
56 58
57 59 def __init__(self, sa=None, repo=None):
@@ -207,6 +209,7 b' class SettingsModel(BaseModel):'
207 209 caches.clear_cache_manager(cache_manager)
208 210
209 211 def get_all_settings(self, cache=False):
212
210 213 def _compute():
211 214 q = self._get_settings_query()
212 215 if not q:
@@ -413,15 +416,16 b' class VcsSettingsModel(object):'
413 416 ('hooks', 'outgoing.pull_logger'),)
414 417 HG_SETTINGS = (
415 418 ('extensions', 'largefiles'),
416 ('phases', 'publish'),)
419 ('phases', 'publish'),
420 ('extensions', 'evolve'),)
417 421 GIT_SETTINGS = (
418 422 ('vcs_git_lfs', 'enabled'),)
419
420 423 GLOBAL_HG_SETTINGS = (
421 424 ('extensions', 'largefiles'),
422 425 ('largefiles', 'usercache'),
423 426 ('phases', 'publish'),
424 ('extensions', 'hgsubversion'))
427 ('extensions', 'hgsubversion'),
428 ('extensions', 'evolve'),)
425 429 GLOBAL_GIT_SETTINGS = (
426 430 ('vcs_git_lfs', 'enabled'),
427 431 ('vcs_git_lfs', 'store_location'))
@@ -544,22 +548,26 b' class VcsSettingsModel(object):'
544 548
545 549 @assert_repo_settings
546 550 def create_or_update_repo_hg_settings(self, data):
547 largefiles, phases = \
551 largefiles, phases, evolve = \
548 552 self.HG_SETTINGS
549 largefiles_key, phases_key = \
553 largefiles_key, phases_key, evolve_key = \
550 554 self._get_settings_keys(self.HG_SETTINGS, data)
551 555
552 556 self._create_or_update_ui(
553 557 self.repo_settings, *largefiles, value='',
554 558 active=data[largefiles_key])
555 559 self._create_or_update_ui(
560 self.repo_settings, *evolve, value='',
561 active=data[evolve_key])
562 self._create_or_update_ui(
556 563 self.repo_settings, *phases, value=safe_str(data[phases_key]))
557 564
558 565 def create_or_update_global_hg_settings(self, data):
559 largefiles, largefiles_store, phases, hgsubversion \
566 largefiles, largefiles_store, phases, hgsubversion, evolve \
560 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 569 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
570
563 571 self._create_or_update_ui(
564 572 self.global_settings, *largefiles, value='',
565 573 active=data[largefiles_key])
@@ -570,6 +578,9 b' class VcsSettingsModel(object):'
570 578 self.global_settings, *phases, value=safe_str(data[phases_key]))
571 579 self._create_or_update_ui(
572 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 585 def create_or_update_repo_git_settings(self, data):
575 586 # NOTE(marcink): # comma make unpack work properly
@@ -774,3 +785,27 b' class VcsSettingsModel(object):'
774 785 raise ValueError(
775 786 'The given data does not contain {} key'.format(data_key))
776 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 31 import ipaddress
32 32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.sql.expression import true, false
34 33
35 34 from rhodecode import events
36 35 from rhodecode.lib.user_log_filter import user_log_filter
37 36 from rhodecode.lib.utils2 import (
38 37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
39 38 AttributeDict, str2bool)
39 from rhodecode.lib.exceptions import (
40 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
40 42 from rhodecode.lib.caching_query import FromCache
41 43 from rhodecode.model import BaseModel
42 44 from rhodecode.model.auth_token import AuthTokenModel
43 45 from rhodecode.model.db import (
44 or_, joinedload, User, UserToPerm, UserEmailMap, UserIpMap, UserLog)
45 from rhodecode.lib.exceptions import (
46 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
47 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
46 _hash_key, true, false, or_, joinedload, User, UserToPerm,
47 UserEmailMap, UserIpMap, UserLog)
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo_group import RepoGroupModel
50 50
@@ -58,13 +58,52 b' class UserModel(BaseModel):'
58 58 def get(self, user_id, cache=False):
59 59 user = self.sa.query(User)
60 60 if cache:
61 user = user.options(FromCache("sql_cache_short",
62 "get_user_%s" % user_id))
61 user = user.options(
62 FromCache("sql_cache_short", "get_user_%s" % user_id))
63 63 return user.get(user_id)
64 64
65 65 def get_user(self, user):
66 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 107 def get_by_username(self, username, cache=False, case_insensitive=False):
69 108
70 109 if case_insensitive:
@@ -73,8 +112,9 b' class UserModel(BaseModel):'
73 112 user = self.sa.query(User)\
74 113 .filter(User.username == username)
75 114 if cache:
76 user = user.options(FromCache("sql_cache_short",
77 "get_user_%s" % username))
115 name_key = _hash_key(username)
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
78 118 return user.scalar()
79 119
80 120 def get_by_email(self, email, cache=False, case_insensitive=False):
@@ -630,7 +670,8 b' class UserModel(BaseModel):'
630 670 user_id, api_key, username)
631 671 return False
632 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 675 return False
635 676
636 677 log.debug('filling user:%s data', dbuser)
@@ -638,6 +679,11 b' class UserModel(BaseModel):'
638 679 # TODO: johbo: Think about this and find a clean solution
639 680 user_data = dbuser.get_dict()
640 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 688 for k, v in user_data.iteritems():
643 689 # properties of auth user we dont update
@@ -726,7 +772,7 b' class UserModel(BaseModel):'
726 772 """
727 773 user = self._get_user(user)
728 774 obj = UserEmailMap.query().get(email_id)
729 if obj:
775 if obj and obj.user_id == user.user_id:
730 776 self.sa.delete(obj)
731 777
732 778 def parse_ip_range(self, ip_range):
@@ -783,7 +829,7 b' class UserModel(BaseModel):'
783 829 """
784 830 user = self._get_user(user)
785 831 obj = UserIpMap.query().get(ip_id)
786 if obj:
832 if obj and obj.user_id == user.user_id:
787 833 self.sa.delete(obj)
788 834
789 835 def get_accounts_in_creation_order(self, current_user=None):
@@ -27,14 +27,18 b' user group model for RhodeCode'
27 27 import logging
28 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 35 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
36 RepoGroupAssignmentError
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
36 from rhodecode.model.scm import UserGroupList
37 from rhodecode.model.db import (
38 true, func, User, UserGroupMember, UserGroup,
39 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
40 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
41
38 42
39 43 log = logging.getLogger(__name__)
40 44
@@ -171,7 +175,7 b' class UserGroupModel(BaseModel):'
171 175 user_id for user_id in current_members_ids
172 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 180 def _set_users_as_members(self, user_group, user_ids):
177 181 user_group.members = []
@@ -187,6 +191,7 b' class UserGroupModel(BaseModel):'
187 191 self._set_users_as_members(user_group, user_ids)
188 192 self._log_user_changes('added to', user_group, added)
189 193 self._log_user_changes('removed from', user_group, removed)
194 return added, removed
190 195
191 196 def _clean_members_data(self, members_data):
192 197 if not members_data:
@@ -221,12 +226,16 b' class UserGroupModel(BaseModel):'
221 226
222 227 user_group.user = owner
223 228
229 added_user_ids = []
230 removed_user_ids = []
224 231 if 'users_group_members' in form_data:
225 232 members_id_list = self._clean_members_data(
226 233 form_data['users_group_members'])
234 added_user_ids, removed_user_ids = \
227 235 self._update_members_from_user_ids(user_group, members_id_list)
228 236
229 237 self.sa.add(user_group)
238 return user_group, added_user_ids, removed_user_ids
230 239
231 240 def delete(self, user_group, force=False):
232 241 """
@@ -539,6 +548,59 b' class UserGroupModel(BaseModel):'
539 548 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
540 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 604 @staticmethod
543 605 def get_user_groups_as_dict(user_group):
544 606 import rhodecode.lib.helpers as h
@@ -550,7 +612,9 b' class UserGroupModel(BaseModel):'
550 612 'active': user_group.users_group_active,
551 613 "owner": user_group.user.username,
552 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 619 return data
556 620
@@ -19,8 +19,10 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import colander
22 import deform.widget
22 23
23 24 from rhodecode.translation import _
25 from rhodecode.model.validation_schema.utils import convert_to_optgroup
24 26 from rhodecode.model.validation_schema import validators, preparers, types
25 27
26 28 DEFAULT_LANDING_REF = 'rev:tip'
@@ -32,6 +34,11 b' def get_group_and_repo(repo_name):'
32 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 42 @colander.deferred
36 43 def deferred_repo_type_validator(node, kw):
37 44 options = kw.get('repo_type_options', [])
@@ -53,11 +60,27 b' def deferred_repo_owner_validator(node, '
53 60
54 61 @colander.deferred
55 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 65 return colander.OneOf([x for x in options])
58 66
59 67
60 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 84 def deferred_fork_of_validator(node, kw):
62 85 old_values = kw.get('old_values') or {}
63 86
@@ -191,7 +214,25 b' def deferred_unique_name_validator(node,'
191 214
192 215 @colander.deferred
193 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 238 class GroupType(colander.Mapping):
@@ -215,8 +256,10 b' class GroupType(colander.Mapping):'
215 256 parent_group_name,
216 257 parent_group) = get_group_and_repo(validated_name)
217 258
259 appstruct['repo_name_with_group'] = validated_name
218 260 appstruct['repo_name_without_group'] = repo_name_without_group
219 261 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
262
220 263 if parent_group:
221 264 appstruct['repo_group_id'] = parent_group.group_id
222 265
@@ -260,16 +303,19 b' class RepoSchema(colander.MappingSchema)'
260 303
261 304 repo_owner = colander.SchemaNode(
262 305 colander.String(),
263 validator=deferred_repo_owner_validator)
306 validator=deferred_repo_owner_validator,
307 widget=deform.widget.TextInputWidget())
264 308
265 309 repo_description = colander.SchemaNode(
266 colander.String(), missing='')
310 colander.String(), missing='',
311 widget=deform.widget.TextAreaWidget())
267 312
268 313 repo_landing_commit_ref = colander.SchemaNode(
269 314 colander.String(),
270 315 validator=deferred_landing_ref_validator,
271 316 preparers=[preparers.strip_preparer],
272 missing=DEFAULT_LANDING_REF)
317 missing=DEFAULT_LANDING_REF,
318 widget=deferred_landing_ref_widget)
273 319
274 320 repo_clone_uri = colander.SchemaNode(
275 321 colander.String(),
@@ -284,19 +330,19 b' class RepoSchema(colander.MappingSchema)'
284 330
285 331 repo_private = colander.SchemaNode(
286 332 types.StringBooleanType(),
287 missing=False)
333 missing=False, widget=deform.widget.CheckboxWidget())
288 334 repo_copy_permissions = colander.SchemaNode(
289 335 types.StringBooleanType(),
290 missing=False)
336 missing=False, widget=deform.widget.CheckboxWidget())
291 337 repo_enable_statistics = colander.SchemaNode(
292 338 types.StringBooleanType(),
293 missing=False)
339 missing=False, widget=deform.widget.CheckboxWidget())
294 340 repo_enable_downloads = colander.SchemaNode(
295 341 types.StringBooleanType(),
296 missing=False)
342 missing=False, widget=deform.widget.CheckboxWidget())
297 343 repo_enable_locking = colander.SchemaNode(
298 344 types.StringBooleanType(),
299 missing=False)
345 missing=False, widget=deform.widget.CheckboxWidget())
300 346
301 347 def deserialize(self, cstruct):
302 348 """
@@ -319,3 +365,50 b' class RepoSchema(colander.MappingSchema)'
319 365 third.deserialize({'unique_repo_name': validated_name})
320 366
321 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 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import re
21 22 import colander
22 23
23 24 from rhodecode import forms
24 25 from rhodecode.model.db import User
26 from rhodecode.model.validation_schema import types, validators
25 27 from rhodecode.translation import _
26 28 from rhodecode.lib.auth import check_password
27 29
@@ -52,10 +54,72 b' class ChangePasswordSchema(colander.Sche'
52 54 widget=forms.widget.CheckedPasswordWidget(redisplay=True),
53 55 validator=colander.Length(min=6))
54 56
55
56 57 def validator(self, form, values):
57 58 if values['current_password'] == values['new_password']:
58 59 exc = colander.Invalid(form)
59 60 exc['new_password'] = _('New password must be different '
60 61 'to old password')
61 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 191 class StrOrIntType(colander.String):
192 192 def deserialize(self, node, cstruct):
193 if isinstance(node, basestring):
193 if isinstance(cstruct, basestring):
194 194 return super(StrOrIntType, self).deserialize(node, cstruct)
195 195 else:
196 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 21 import os
2 22 import re
23 import logging
24
3 25
4 26 import ipaddress
5 27 import colander
@@ -7,6 +29,8 b' import colander'
7 29 from rhodecode.translation import _
8 30 from rhodecode.lib.utils2 import glob2re
9 31
32 log = logging.getLogger(__name__)
33
10 34
11 35 def ip_addr_validator(node, value):
12 36 try:
@@ -46,3 +70,71 b' def valid_name_validator(node, value):'
46 70 msg = _('Name must start with a letter or number. Got `{}`').format(value)
47 71 if not re.match(r'^[a-zA-z0-9]{1,}', value):
48 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 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 597 def ValidCloneUri():
578 598 class InvalidCloneUrl(Exception):
579 599 allowed_prefixes = ()
@@ -764,7 +784,8 b" def ValidPerms(type_='repo'):"
764 784 del_member = perm_dict.get('id')
765 785 del_type = perm_dict.get('type')
766 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 790 # store additions in order of how they were added in web form
770 791 for k in sorted(new_perms_group.keys()):
@@ -773,36 +794,48 b" def ValidPerms(type_='repo'):"
773 794 new_type = perm_dict.get('type')
774 795 new_perm = perm_dict.get('perm')
775 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 800 # get updates of permissions
779 801 # (read the existing radio button states)
802 default_user_id = User.get_default_user().user_id
780 803 for k, update_value in value.iteritems():
781 804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
782 805 member = k[7:]
783 806 update_type = {'u': 'user',
784 807 'g': 'users_group'}[k[0]]
785 if member == User.DEFAULT_USER:
808
809 if safe_int(member) == default_user_id:
786 810 if str2bool(value.get('repo_private')):
787 # set none for default when updating to
788 # private repo protects agains form manipulation
811 # prevent from updating default user permissions
812 # when this repository is marked as private
789 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 819 value['perm_updates'] = list(perm_updates)
795 820 value['perm_deletions'] = list(perm_deletions)
796 821
797 # validate users they exist and they are active !
798 for member_id, _perm, member_type in perm_additions:
822 updates_map = dict(
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 832 try:
800 833 if member_type == 'user':
801 self.user_db = User.query()\
834 User.query()\
802 835 .filter(User.active == true())\
803 836 .filter(User.user_id == member_id).one()
804 837 if member_type == 'users_group':
805 self.user_db = UserGroup.query()\
838 UserGroup.query()\
806 839 .filter(UserGroup.users_group_active == true())\
807 840 .filter(UserGroup.users_group_id == member_id)\
808 841 .one()
@@ -62,6 +62,10 b' input[type="button"] {'
62 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 274 font-size: inherit;
271 275 color: @rcblue;
272 276 border: none;
273 .border-radius (0);
277 border-radius: 0;
274 278 background-color: transparent;
275 279
280 &.last-item {
281 border: none;
282 padding: 0 0 0 0;
283 }
284
276 285 &:last-child {
277 286 border: none;
287 padding: 0 0 0 0;
278 288 }
279 289
280 290 &:hover {
@@ -12,7 +12,7 b''
12 12
13 13 .control-label {
14 14 width: 200px;
15 padding: 10px;
15 padding: 10px 0px;
16 16 float: left;
17 17 }
18 18 .control-inputs {
@@ -165,6 +165,7 b' div.markdown-block h6 {'
165 165 div.markdown-block hr {
166 166 border: 0;
167 167 color: #e6e5e5;
168 background-color: #e6e5e5;
168 169 height: 3px;
169 170 margin-bottom: 13px;
170 171 }
@@ -200,6 +201,12 b' div.markdown-block img {'
200 201 background-color: #fff;
201 202 }
202 203
204
205 div.markdown-block strong {
206 font-weight: 600;
207 margin: 0;
208 }
209
203 210 div.markdown-block ul,
204 211 div.markdown-block ol {
205 212 padding-left: 30px !important;
@@ -210,7 +217,7 b' div.markdown-block ol {'
210 217 div.markdown-block ul li,
211 218 div.markdown-block ol li {
212 219 list-style: disc !important;
213 margin: 13px !important;
220 margin: 6px !important;
214 221 padding: 0 !important;
215 222 }
216 223
@@ -11,6 +11,7 b''
11 11 @import 'form-bootstrap';
12 12 @import 'codemirror';
13 13 @import 'legacy_code_styles';
14 @import 'readme-box';
14 15 @import 'progress-bar';
15 16
16 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 373 .pr-pullinfo {
362 374 clear: both;
363 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 384 #pr-title-input {
@@ -1297,6 +1315,11 b' table.integrations {'
1297 1315 width: 100%;
1298 1316 margin-bottom: 8px;
1299 1317 }
1318
1319 .reviewer_entry {
1320 min-height: 55px;
1321 }
1322
1300 1323 .reviewers_member {
1301 1324 width: 100%;
1302 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 1359 .reviewer_member_remove {
1335 1360 position: absolute;
1336 1361 right: 0;
@@ -1340,6 +1365,15 b' table.integrations {'
1340 1365 padding: 0;
1341 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 1377 .reviewer_member_status {
1344 1378 margin-top: 5px;
1345 1379 }
@@ -1369,6 +1403,11 b' table.integrations {'
1369 1403 .pr-description {
1370 1404 white-space:pre-wrap;
1371 1405 }
1406
1407 .pr-reviewer-rules {
1408 padding: 10px 0px 20px 0px;
1409 }
1410
1372 1411 .group_members {
1373 1412 margin-top: 0;
1374 1413 padding: 0;
@@ -2008,6 +2047,10 b' BIN_FILENODE = 7'
2008 2047 input {
2009 2048 display: none;
2010 2049 }
2050 margin-top: 10px;
2051 }
2052 .file-upload-label {
2053 margin-top: 10px;
2011 2054 }
2012 2055 p {
2013 2056 margin-top: 5px;
@@ -558,6 +558,8 b' ul#context-pages {'
558 558 .dataTables_processing {
559 559 text-align: center;
560 560 font-size: 1.1em;
561 position: relative;
562 top: 95px;
561 563 }
562 564
563 565 .dataTables_paginate, .pagination-wh {
@@ -42,6 +42,10 b''
42 42
43 43 .panel-body {
44 44 padding: @panel-padding;
45
46 &.panel-body-min-height {
47 min-height: 150px
48 }
45 49 }
46 50
47 51 .panel-footer {
@@ -57,9 +57,13 b''
57 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 65 #clone_url_id {
62 min-width: 29em;
66 width: ~"calc(100% - 118px)";
63 67 padding: @padding/4;
64 68 }
65 69
@@ -211,6 +215,7 b''
211 215 float: left;
212 216 display: block;
213 217 position: relative;
218 width: 100%;
214 219
215 220 // adds some space to make copy and paste easier
216 221 .left-label,
@@ -108,6 +108,11 b' table.dataTable {'
108 108 &.td-hash {
109 109 min-width: 80px;
110 110 width: 200px;
111
112 .obsolete {
113 text-decoration: line-through;
114 color: lighten(@grey2,25%);
115 }
111 116 }
112 117
113 118 &.td-time {
@@ -98,4 +98,12 b''
98 98 &.admin {
99 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 62 // nav in repo context
63 63 Mousetrap.bind(['g s'], function(e) {
64 64 window.location = pyroutes.url(
65 'summary_home', {'repo_name': repoName});
65 'repo_summary', {'repo_name': repoName});
66 66 });
67 67 Mousetrap.bind(['g c'], function(e) {
68 68 window.location = pyroutes.url(
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Unfollow',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'just now',
73 81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Bitte {0} Zeichen löschen',
39 45 'Please enter {0} or more character': 'Bitte {0} oder mehr Zeichen eingeben',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Unfollow',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'jetzt gerade',
73 81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Unfollow',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'just now',
73 81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Selection link',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Unfollow',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'just now',
73 81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Lien vers la sélection',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Unfollow',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'à l’instant',
73 81 'loading...': 'Chargement…',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Segui',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Collegamento selezione',
@@ -59,6 +66,7 b' var _TM = {'
59 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 67 'Unfollow': 'Smetti di seguire',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'proprio ora',
73 81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'フォロー',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': '{0} 文字削除してください',
39 45 'Please enter {0} or more character': '{0} 文字以上入力してください',
40 46 'Please enter {0} or more characters': '{0} 文字以上入力してください',
47 'Reviewers picked from source code changes.': 'Reviewers picked from source code changes.',
41 48 'Saving...': 'Saving...',
42 49 'Searching...': '検索中...',
43 50 'Selection link': 'セレクション・リンク',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'アンフォロー',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': '{0} 件のみ選択できます',
63 71 'You can only select {0} items': '{0} 件のみ選択できます',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'たったいま',
73 81 'loading...': '読み込み中...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -1,8 +1,14 b''
1 1 // AUTO GENERATED FILE FOR Babel JS-GETTEXT EXTRACTORS, DO NOT CHANGE
2 2 _gettext('(from usergroup {0})');
3 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 9 _gettext('Close');
5 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 12 _gettext('Delete this comment?');
7 13 _gettext('Diff to Commit ');
8 14 _gettext('Follow');
@@ -32,6 +38,7 b''
32 38 _gettext('Please delete {0} characters');
33 39 _gettext('Please enter {0} or more character');
34 40 _gettext('Please enter {0} or more characters');
41 _gettext('Reviewers picked from source code changes.');
35 42 _gettext('Saving...');
36 43 _gettext('Searching...');
37 44 _gettext('Selection link');
@@ -53,6 +60,7 b''
53 60 _gettext('There are currently no open pull requests requiring your participation.');
54 61 _gettext('Unfollow');
55 62 _gettext('Updating...');
63 _gettext('User `{0}` not allowed to be a reviewer');
56 64 _gettext('You can only select {0} item');
57 65 _gettext('You can only select {0} items');
58 66 _gettext('added manually by "{0}"');
@@ -65,6 +73,7 b''
65 73 _gettext('in {0}, {1}');
66 74 _gettext('just now');
67 75 _gettext('loading...');
76 _gettext('member of "{0}"');
68 77 _gettext('resolve comment');
69 78 _gettext('showing {0} out of {1} commit');
70 79 _gettext('showing {0} out of {1} commits');
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Zamknij',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Obserwuj',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Wybór linku',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Nie obserwuj',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'przed chwilą',
73 81 'loading...': 'wczytywanie...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Seguir',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Link da seleção',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Parar de seguir',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'agora há pouco',
73 81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Наблюдать',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': 'Ссылка выбора',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Не наблюдать',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': 'прямо сейчас',
73 81 'loading...': 'загрузка...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -7,8 +7,14 b''
7 7 var _TM = {
8 8 '(from usergroup {0})': '(from usergroup {0})',
9 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 15 'Close': 'Close',
11 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 18 'Delete this comment?': 'Delete this comment?',
13 19 'Diff to Commit ': 'Diff to Commit ',
14 20 'Follow': 'Follow',
@@ -38,6 +44,7 b' var _TM = {'
38 44 'Please delete {0} characters': 'Please delete {0} characters',
39 45 'Please enter {0} or more character': 'Please enter {0} or more character',
40 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 48 'Saving...': 'Saving...',
42 49 'Searching...': 'Searching...',
43 50 'Selection link': '选择链接',
@@ -59,6 +66,7 b' var _TM = {'
59 66 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
60 67 'Unfollow': 'Unfollow',
61 68 'Updating...': 'Updating...',
69 'User `{0}` not allowed to be a reviewer': 'User `{0}` not allowed to be a reviewer',
62 70 'You can only select {0} item': 'You can only select {0} item',
63 71 'You can only select {0} items': 'You can only select {0} items',
64 72 'added manually by "{0}"': 'added manually by "{0}"',
@@ -71,6 +79,7 b' var _TM = {'
71 79 'in {0}, {1}': 'in {0}, {1}',
72 80 'just now': '刚才',
73 81 'loading...': 'loading...',
82 'member of "{0}"': 'member of "{0}"',
74 83 'resolve comment': 'resolve comment',
75 84 'showing {0} out of {1} commit': 'showing {0} out of {1} commit',
76 85 'showing {0} out of {1} commits': 'showing {0} out of {1} commits',
@@ -12,22 +12,13 b''
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 pyroutes.register('home', '/', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
17 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
18 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
19 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
20 17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
21 18 pyroutes.register('gists', '/_admin/gists', []);
22 19 pyroutes.register('new_gist', '/_admin/gists/new', []);
23 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 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 22 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
32 23 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
33 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 30 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
40 31 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
41 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 33 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
44 34 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
45 35 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
@@ -53,8 +43,6 b' function registerRCRoutes() {'
53 43 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
54 44 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
55 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 46 pyroutes.register('favicon', '/favicon.ico', []);
59 47 pyroutes.register('robots', '/robots.txt', []);
60 48 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
@@ -73,17 +61,30 b' function registerRCRoutes() {'
73 61 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
74 62 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
75 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 70 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
77 71 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
78 72 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
79 73 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
80 74 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
81 75 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
76 pyroutes.register('admin_permissions_ips', '_admin/permissions/ips', []);
82 77 pyroutes.register('users', '_admin/users', []);
83 78 pyroutes.register('users_data', '_admin/users_data', []);
84 79 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
85 80 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
86 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 88 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
88 89 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
89 90 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
@@ -95,11 +96,44 b' function registerRCRoutes() {'
95 96 pyroutes.register('register', '/_admin/register', []);
96 97 pyroutes.register('reset_password', '/_admin/password_reset', []);
97 98 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
98 pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']);
99 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']);
100 pyroutes.register('strip', '/%(repo_name)s/strip', ['repo_name']);
101 pyroutes.register('strip_check', '/%(repo_name)s/strip_check', ['repo_name']);
102 pyroutes.register('strip_execute', '/%(repo_name)s/strip_execute', ['repo_name']);
99 pyroutes.register('home', '/', []);
100 pyroutes.register('user_autocomplete_data', '/_users', []);
101 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
102 pyroutes.register('repo_list_data', '/_repos', []);
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 137 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
104 138 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
105 139 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
@@ -107,5 +141,14 b' function registerRCRoutes() {'
107 141 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
108 142 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
109 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 153 pyroutes.register('apiv2', '/_admin/api', []);
111 154 }
@@ -16,10 +16,230 b''
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 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
31 */
32 var prButtonLock = function(lockEnabled, msg, scope) {
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 }
49
50 if (msg) {
51 $('#pr_open_message').html(msg);
52 }
53 };
54
55
56 /**
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
61 - commitN
62 - commitN+1
63 ...
64
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
67
68 * @param sourceRef
69 * @param elements
70 * @param limit
71 * @returns {*[]}
21 72 */
22 var removeReviewMember = function(reviewer_id, mark_delete){
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]
91 };
92
93
94
95 ReviewersController = function () {
96 var self = this;
97 this.$reviewRulesContainer = $('#review_rules');
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
99 this.forbidReviewUsers = undefined;
100 this.$reviewMembers = $('#review_members');
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();
202 }
203
204 $('.calculate-reviewers').show();
205 // reset reviewer members
206 self.$reviewMembers.empty();
207
208 prButtonLock(true, null, 'reviewers');
209 $('#user').hide(); // hide user autocomplete before load
210
211 var url = pyroutes.url('repo_default_reviewers_data',
212 {
213 'repo_name': templateContext.repo_name,
214 'source_repo': sourceRepo,
215 'source_ref': sourceRef[2],
216 'target_repo': targetRepo,
217 'target_ref': targetRef[2]
218 });
219
220 self.currentRequest = $.get(url)
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) {
23 243 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
24 244
25 245 if(typeof(mark_delete) === undefined){
@@ -41,18 +261,21 b' var removeReviewMember = function(review'
41 261 }
42 262 };
43 263
44 var addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons) {
45 var members = $('#review_members').get(0);
264 this.addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons, mandatory) {
265 var members = self.$reviewMembers.get(0);
46 266 var reasons_html = '';
47 267 var reasons_inputs = '';
48 268 var reasons = reasons || [];
269 var mandatory = mandatory || false;
270
49 271 if (reasons) {
50 272 for (var i = 0; i < reasons.length; i++) {
51 273 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]);
52 274 reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">';
53 275 }
54 276 }
55 var tmpl = '<li id="reviewer_{2}">'+
277 var tmpl = '' +
278 '<li id="reviewer_{2}" class="reviewer_entry">'+
56 279 '<input type="hidden" name="__start__" value="reviewer:mapping">'+
57 280 '<div class="reviewer_status">'+
58 281 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
@@ -63,11 +286,27 b' var addReviewMember = function(id, fname'
63 286 '<input type="hidden" name="user_id" value="{2}">'+
64 287 '<input type="hidden" name="__start__" value="reasons:sequence">'+
65 288 '{3}'+
66 '<input type="hidden" name="__end__" value="reasons:sequence">'+
67 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
289 '<input type="hidden" name="__end__" value="reasons:sequence">';
290
291 if (mandatory) {
292 tmpl += ''+
293 '<div class="reviewer_member_mandatory_remove">' +
68 294 '<i class="icon-remove-sign"></i>'+
69 295 '</div>'+
70 '</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 += ''+
71 310 '<input type="hidden" name="__end__" value="reviewer:mapping">'+
72 311 '</li>' ;
73 312
@@ -76,17 +315,43 b' var addReviewMember = function(id, fname'
76 315 var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs);
77 316 // check if we don't have this ID already in
78 317 var ids = [];
79 var _els = $('#review_members li').toArray();
318 var _els = self.$reviewMembers.find('li').toArray();
80 319 for (el in _els){
81 320 ids.push(_els[el].id)
82 321 }
83 if(ids.indexOf('reviewer_'+id) == -1){
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) {
84 341 // only add if it's not there
85 342 members.innerHTML += element;
86 343 }
87 344
88 345 };
89 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 };
351
352 };
353
354
90 355 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
91 356 var url = pyroutes.url(
92 357 'pullrequest_update',
@@ -102,23 +367,6 b' var _updatePullRequest = function(repo_n'
102 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 371 * PULL REQUEST update commits
124 372 */
@@ -198,8 +446,8 b' var initPullRequestsCodeMirror = functio'
198 446 /**
199 447 * Reviewer autocomplete
200 448 */
201 var ReviewerAutoComplete = function(input_id) {
202 $('#'+input_id).autocomplete({
449 var ReviewerAutoComplete = function(inputId) {
450 $(inputId).autocomplete({
203 451 serviceUrl: pyroutes.url('user_autocomplete_data'),
204 452 minChars:2,
205 453 maxHeight:400,
@@ -207,14 +455,28 b' var ReviewerAutoComplete = function(inpu'
207 455 showNoSuggestionNotice: true,
208 456 tabDisabled: true,
209 457 autoSelectFirst: true,
458 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
210 459 formatResult: autocompleteFormatResult,
211 460 lookupFilter: autocompleteFilterResult,
212 onSelect: function(suggestion, data){
213 var msg = _gettext('added manually by "{0}"');
214 var reasons = [msg.format(templateContext.rhodecode_user.username)];
215 addReviewMember(data.id, data.first_name, data.last_name,
461 onSelect: function(element, data) {
462
463 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
464 if (data.value_type == 'user_group') {
465 reasons.push(_gettext('member of "{0}"').format(data.value_display));
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,
216 476 data.username, data.icon_link, reasons);
217 $('#'+input_id).val('');
477 }
478
479 $(inputId).val('');
218 480 }
219 481 });
220 482 };
@@ -121,7 +121,7 b' def add_pylons_context(event):'
121 121 # Setup the pylons context object ('c')
122 122 context = ContextObj()
123 123 context.rhodecode_user = auth_user
124 attach_context_attributes(context, request)
124 attach_context_attributes(context, request, request.user.user_id)
125 125 pylons.tmpl_context._push_object(context)
126 126
127 127
@@ -130,12 +130,12 b' def scan_repositories_if_enabled(event):'
130 130 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
131 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 133 settings = event.app.registry.settings
136 134 vcs_server_enabled = settings['vcs.server.enable']
137 135 import_on_startup = settings['startup.import_repos']
138 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 139 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
140 140 repo2db_mapper(repositories, remove_obsolete=False)
141 141
@@ -172,6 +172,10 b' def write_metadata_if_needed(event):'
172 172 with open(metadata_destination, 'wb') as f:
173 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 179 try:
176 180 write()
177 181 except Exception:
@@ -2,7 +2,7 b''
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 ${_('Admin journal')}
5 ${_('Admin audit logs')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
@@ -10,9 +10,9 b''
10 10
11 11 <%def name="breadcrumbs_links()">
12 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 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 16 ${h.end_form()}
17 17 <p class="tooltip filterexample" title="${h.tooltip(h.journal_filter_help())}">${_('Example Queries')}</p>
18 18 </%def>
@@ -29,7 +29,7 b''
29 29 <!-- end box / title -->
30 30 <div class="table">
31 31 <div id="user_log">
32 ${c.log_data}
32 <%include file="/admin/admin_log_base.mako" />
33 33 </div>
34 34 </div>
35 35 </div>
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${_('Authentication Plugins')}
15 15 </%def>
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
15 15 &raquo;
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${_('Repositories defaults')}
15 15 </%def>
@@ -73,7 +73,7 b''
73 73 </div>
74 74
75 75 <div class="author">
76 <div title="${c.file_last_commit.author}">
76 <div title="${h.tooltip(c.file_last_commit.author)}">
77 77 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
78 78 </div>
79 79
@@ -18,7 +18,7 b''
18 18 </%def>
19 19
20 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 22 &raquo;
23 23 ${_('Integrations')}
24 24 </%def>
@@ -3,7 +3,7 b''
3 3
4 4 <%def name="breadcrumbs_links()">
5 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 7 &raquo;
8 8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 9 &raquo;
@@ -12,7 +12,7 b''
12 12 repo_name=c.repo.repo_name,
13 13 integration=current_IntegrationType.key))}
14 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 16 &raquo;
17 17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
18 18 &raquo;
@@ -25,7 +25,7 b''
25 25 repo_group_name=c.repo_group.group_name,
26 26 integration=current_IntegrationType.key))}
27 27 %else:
28 ${h.link_to(_('Admin'),h.url('admin_home'))}
28 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
29 29 &raquo;
30 30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
31 31 &raquo;
@@ -3,15 +3,15 b''
3 3
4 4 <%def name="breadcrumbs_links()">
5 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 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 9 &raquo;
10 10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
13 13 %else:
14 ${h.link_to(_('Admin'),h.url('admin_home'))}
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 15 &raquo;
16 16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 17 %endif
@@ -160,11 +160,11 b''
160 160 </td>
161 161 <td class="td-scope">
162 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 164 ${_('repo')}:${integration.repo.repo_name}
165 165 </a>
166 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 168 ${_('repogroup')}:${integration.repo_group.group_name}
169 169 %if integration.child_repos_only:
170 170 ${_('child repos only')}
@@ -4,11 +4,11 b''
4 4
5 5 <%def name="breadcrumbs_links()">
6 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 8 &raquo;
9 9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 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 12 &raquo;
13 13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 14 &raquo;
@@ -16,7 +16,7 b''
16 16 &raquo;
17 17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 18 %else:
19 ${h.link_to(_('Admin'),h.url('admin_home'))}
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 20 &raquo;
21 21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 22 &raquo;
@@ -34,12 +34,12 b''
34 34 % if my_account_oauth_url:
35 35 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
36 36 % endif
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('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>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</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.route_path('my_account_repos')}">${_('Repositories')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li>
40 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>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('my_account_notifications')}">${_('Live Notifications')}</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.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
43 43 </ul>
44 44 </div>
45 45
@@ -43,9 +43,9 b''
43 43 </td>
44 44 <td class="td-action">
45 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 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 49 ${_('Delete')}
50 50 </button>
51 51 ${h.end_form()}
@@ -139,7 +139,7 b' var repoFilter = function(data) {'
139 139 query.callback({results: cachedData.results});
140 140 } else {
141 141 $.ajax({
142 url: "${h.url('repo_list_data')}",
142 url: pyroutes.url('repo_list_data'),
143 143 data: {'query': query.term},
144 144 dataType: 'json',
145 145 type: 'GET',
@@ -25,10 +25,10 b''
25 25 <span class="user email">${em.email}</span>
26 26 </td>
27 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 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
31 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
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: {}').format(em.email)}');">
32 32 ${_('Delete')}
33 33 </button>
34 34 ${h.end_form()}
@@ -48,7 +48,7 b''
48 48 </div>
49 49
50 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 52 <div class="form">
53 53 <!-- fields -->
54 54 <div class="fields">
@@ -1,7 +1,7 b''
1 1 <template is="dom-bind" id="notificationsPage">
2 2 <iron-ajax id="toggleNotifications"
3 3 method="post"
4 url="${url('my_account_notifications_toggle_visibility')}"
4 url="${h.route_path('my_account_notifications_toggle_visibility')}"
5 5 content-type="application/json"
6 6 loading="{{changeNotificationsLoading}}"
7 7 on-response="handleNotifications"
@@ -10,7 +10,7 b''
10 10
11 11 <iron-ajax id="sendTestNotification"
12 12 method="post"
13 url="${url('my_account_notifications_test_channelstream')}"
13 url="${h.route_path('my_account_notifications_test_channelstream')}"
14 14 content-type="application/json"
15 15 on-response="handleTestNotification"
16 16 handle-as="json">
@@ -32,7 +32,7 b''
32 32 ${_('First Name')}:
33 33 </div>
34 34 <div class="right-content">
35 ${c.user.firstname}
35 ${c.user.first_name}
36 36 </div>
37 37 </div>
38 38 <div class="fieldset">
@@ -40,7 +40,7 b''
40 40 ${_('Last Name')}:
41 41 </div>
42 42 <div class="right-content">
43 ${c.user.lastname}
43 ${c.user.last_name}
44 44 </div>
45 45 </div>
46 46 <div class="fieldset">
@@ -23,11 +23,6 b''
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 ##<ul class="links">
27 ## <li>
28 ## <span ><a href="#">${_('Compose message')}</a></span>
29 ## </li>
30 ##</ul>
31 26 </div>
32 27 <div class="table">
33 28 <div id="notification_${c.notification.notification_id}" class="main-content-full">
@@ -41,7 +36,9 b''
41 36 </div>
42 37 </div>
43 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 42 %if c.notification.body:
46 43 ${h.render(c.notification.body, renderer=c.visual.default_renderer, mentions=True)}
47 44 %endif
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${_('Permissions')}
15 15 </%def>
@@ -20,7 +20,7 b''
20 20 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
21 21 <td class="td-description"><div class="ip">${ip.description}</div></td>
22 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 24 ${h.hidden('del_ip_id',ip.ip_id)}
25 25 ${h.hidden('default_user', 'True')}
26 26 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
@@ -40,7 +40,7 b''
40 40 </table>
41 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 44 <div class="form">
45 45 <!-- fields -->
46 46 <div class="fields">
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
15 15 &raquo;
@@ -9,11 +9,11 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
15 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 17 %endif
18 18 &raquo; ${c.repo_group.name}
19 19 </%def>
@@ -10,7 +10,7 b''
10 10
11 11 <%def name="breadcrumbs_links()">
12 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 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
@@ -10,7 +10,7 b''
10 10
11 11 <%def name="breadcrumbs_links()">
12 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 14 &raquo;
15 15 ${h.link_to(_('Repositories'),h.url('repos'))}
16 16 %else:
@@ -48,12 +48,12 b''
48 48 if (jsonResponse === undefined) {
49 49 setTimeout(function () {
50 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 52 }, 3000);
53 53 } else {
54 54 if (skipCheck || jsonResponse.result === true) {
55 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 57 } else {
58 58 // Schedule the next request when the current one's complete
59 59 setTimeout(worker, 1000);
@@ -61,7 +61,7 b''
61 61 }
62 62 }
63 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 24 </%def>
25 25
26 26 <%def name="main_content()">
27 % if hasattr(c, 'repo_edit_template'):
28 <%include file="${c.repo_edit_template}"/>
29 % else:
27 30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
31 % endif
28 32 </%def>
29 33
30 34
@@ -36,17 +40,16 b''
36 40 </div>
37 41
38 42 <div class="sidebar-col-wrapper scw-small">
39 ##main
40 43 <div class="sidebar">
41 44 <ul class="nav nav-pills nav-stacked">
42 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 47 </li>
45 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 50 </li>
48 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 53 </li>
51 54 <li class="${'active' if c.active=='vcs' else ''}">
52 55 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
@@ -58,7 +61,7 b''
58 61 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
59 62 </li>
60 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 65 </li>
63 66 %if c.repo_info.repo_type != 'svn':
64 67 <li class="${'active' if c.active=='remote' else ''}">
@@ -71,28 +74,18 b''
71 74 <li class="${'active' if c.active=='integrations' else ''}">
72 75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
73 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 82 <li class="${'active' if c.active=='maintenance' else ''}">
75 83 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
76 84 </li>
77 85 <li class="${'active' if c.active=='strip' else ''}">
78 86 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
79 87 </li>
80 ## TODO: dan: replace repo navigation with navlist registry like with
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
88
96 89 </ul>
97 90 </div>
98 91
@@ -10,8 +10,8 b''
10 10 %>
11 11
12 12 <div class="panel panel-default">
13 <div class="panel-heading">
14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name}</h3>
13 <div class="panel-heading" id="advanced-info" >
14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name} <a class="permalink" href="#advanced-info"></a></h3>
15 15 </div>
16 16 <div class="panel-body">
17 17 ${base.dt_info_panel(elems)}
@@ -20,14 +20,14 b''
20 20
21 21
22 22 <div class="panel panel-default">
23 <div class="panel-heading">
24 <h3 class="panel-title">${_('Fork Reference')}</h3>
23 <div class="panel-heading" id="advanced-fork">
24 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"></a></h3>
25 25 </div>
26 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 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 31 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
32 32 % endif
33 33
@@ -44,15 +44,14 b''
44 44
45 45
46 46 <div class="panel panel-default">
47 <div class="panel-heading">
48 <h3 class="panel-title">${_('Public Journal Visibility')}</h3>
47 <div class="panel-heading" id="advanced-journal">
48 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"></a></h3>
49 49 </div>
50 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 52 <div class="field">
53 53 %if c.in_public_journal:
54 54 <button class="btn btn-small" type="submit">
55 <i class="icon-minus"></i>
56 55 ${_('Remove from Public Journal')}
57 56 </button>
58 57 %else:
@@ -70,11 +69,11 b''
70 69
71 70
72 71 <div class="panel panel-default">
73 <div class="panel-heading">
74 <h3 class="panel-title">${_('Locking state')}</h3>
72 <div class="panel-heading" id="advanced-locking">
73 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"></a></h3>
75 74 </div>
76 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 78 %if c.repo_info.locked[0]:
80 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 109 </div>
111 110
112 111 <div class="panel panel-danger">
113 <div class="panel-heading">
114 <h3 class="panel-title">${_('Delete repository')}</h3>
112 <div class="panel-heading" id="advanced-delete">
113 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"></a></h3>
115 114 </div>
116 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 117 <table class="display">
119 118 <tr>
120 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 121 </td>
123 122 <td>
124 123 %if c.repo_info.forks.count():
@@ -143,7 +142,7 b''
143 142 </div>
144 143 <div class="field">
145 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 146 </span>
148 147 </div>
149 148
@@ -191,7 +190,7 b' var repoTypeFilter = function(data) {'
191 190 query.callback({results: cachedData.results});
192 191 } else {
193 192 $.ajax({
194 url: "${h.url('repo_list_data')}",
193 url: pyroutes.url('repo_list_data'),
195 194 data: {'query': query.term, repo_type: '${c.repo_info.repo_type}'},
196 195 dataType: 'json',
197 196 type: 'GET',
@@ -3,21 +3,25 b''
3 3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 ${h.secure_form(url('edit_repo_caches', repo_name=c.repo_name), method='put')}
7 <div>
8 <div class="fields">
6
7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
8
9 9 <p>
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')+"');")}
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>
11 15 </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 16
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), method='POST')}
18 <div class="form">
19 <div class="fields">
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')+"');")}
18 21 </div>
19 22 </div>
20 23 ${h.end_form()}
24
21 25 </div>
22 26 </div>
23 27
@@ -25,7 +29,7 b''
25 29 <div class="panel panel-default">
26 30 <div class="panel-heading">
27 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 33 </h3>
30 34 </div>
31 35 <div class="panel-body">
@@ -47,5 +51,3 b''
47 51 </div>
48 52 </div>
49 53 </div>
50
51
@@ -4,18 +4,26 b''
4 4 </div>
5 5 <div class="panel-body">
6 6
7 <p>
8 7 % if c.executable_tasks:
9 ${_('Perform maintenance tasks for this repo, following tasks will be performed')}:
8 <h4>${_('Perform maintenance tasks for this repo')}</h4>
9
10 <span>${_('Following tasks will be performed')}:</span>
10 11 <ol>
11 12 % for task in c.executable_tasks:
12 13 <li>${task}</li>
13 14 % endfor
14 15 </ol>
16 <p>
17 ${_('Maintenance can be automated by such api call. Can be called periodically in crontab etc.')}
18 <br/>
19 <code>
20 ${h.api_call_example(method='maintenance', args={"repoid": c.repo_info.repo_name})}
21 </code>
22 </p>
23
15 24 % else:
16 ${_('No maintenance tasks for this repo available')}
25 <h4>${_('No maintenance tasks for this repo available')}</h4>
17 26 % endif
18 </p>
19 27
20 28 <div id="results" style="display:none; padding: 10px 0px;"></div>
21 29
@@ -5,8 +5,7 b''
5 5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 ${h.secure_form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')}
9 ${h.hidden('repo_private')}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST')}
10 9 <table id="permissions_manage" class="rctable permissions">
11 10 <tr>
12 11 <th class="td-radio">${_('None')}</th>
@@ -40,7 +39,7 b''
40 39 <tr>
41 40 <td colspan="4">
42 41 <span class="private_repo_msg">
43 <strong>${_('private repository')}</strong>
42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
44 43 </span>
45 44 </td>
46 45 <td class="private_repo_msg">
@@ -50,10 +49,10 b''
50 49 </tr>
51 50 %else:
52 51 <tr>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write')}</td>
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin')}</td>
52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
57 56 <td class="td-user">
58 57 ${base.gravatar(_user.email, 16)}
59 58 <span class="user">
@@ -79,10 +78,10 b''
79 78 ## USER GROUPS
80 79 %for _user_group in c.repo_info.permission_user_groups():
81 80 <tr>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none')}</td>
83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read')}</td>
84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write')}</td>
85 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin')}</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>
82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</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>
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 85 <td class="td-componentname">
87 86 <i class="icon-group" ></i>
88 87 %if h.HasPermissionAny('hg.admin')():
@@ -4,17 +4,20 b''
4 4 </div>
5 5 <div class="panel-body">
6 6
7 <h4>${_('Manually pull changes from external repository.')}</h4>
8
7 9 %if c.repo_info.clone_uri:
8 10
9 <div class="panel-body-title-text">${_('Remote mirror url')}:
11 ${_('Remote mirror url')}:
10 12 <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a>
13
11 14 <p>
12 ${_('Pull can be automated by such api call called periodically (in crontab etc)')}
15 ${_('Pull can be automated by such api call. Can be called periodically in crontab etc.')}
16 <br/>
17 <code>
18 ${h.api_call_example(method='pull', args={"repoid": c.repo_info.repo_name})}
19 </code>
13 20 </p>
14 <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}"}}'
16 </code>
17 </div>
18 21
19 22 ${h.secure_form(url('edit_repo_remote', repo_name=c.repo_name), method='put')}
20 23 <div class="form">
@@ -24,14 +27,14 b''
24 27 </div>
25 28 ${h.end_form()}
26 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 35 <button class="btn disabled" type="submit" disabled="disabled">
30 36 ${_('Pull changes from remote location')}
31 37 </button>
32 38 %endif
33 39 </div>
34 40 </div>
35
36
37
@@ -3,92 +3,121 b''
3 3
4 4 <div class="panel panel-default">
5 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 7 </div>
8 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 10 <div class="form">
11 11 <!-- fields -->
12 12 <div class="fields">
13
13 14 <div class="field">
14 15 <div class="label">
15 16 <label for="repo_name">${_('Name')}:</label>
16 17 </div>
17 18 <div class="input">
18 ${h.text('repo_name',class_="medium")}
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>
19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
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 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 25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
23 26 Using above url guarantees that this repository will always be accessible under such url.
24 27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
25 28 </div>
26 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 49 <div class="field">
29 50 <div class="label">
30 51 <label for="clone_uri">${_('Remote uri')}:</label>
31 52 </div>
32 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 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 59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
37 60 </div>
38 <div id="alter_clone_uri" style="display: none">
39 ${h.text('clone_uri',class_="medium", placeholder=_('new value, leave empty to remove'))}
40 ${h.hidden('clone_uri_change', 'OLD')}
61 % endif
62
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 75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
76 % endif
77
42 78 </div>
43 79 %else:
44 80 ## not set yet, display form to set it
45 ${h.text('clone_uri',class_="medium")}
46 ${h.hidden('clone_uri_change', 'NEW')}
81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
83 ${h.hidden('repo_clone_uri_change', 'NEW')}
47 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 90 </div>
50 91 </div>
51 92 % else:
52 ${h.hidden('clone_uri', '')}
93 ${h.hidden('repo_clone_uri', '')}
53 94 % endif
95
54 96 <div class="field">
55 97 <div class="label">
56 <label for="repo_group">${_('Repository group')}:</label>
98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
57 99 </div>
58 100 <div class="select">
59 ${h.select('repo_group','',c.repo_groups,class_="medium")}
60 % if c.personal_repo_group:
61 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
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>
101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
75 104 </div>
76 105 </div>
77 106
78 107 <div class="field badged-field">
79 108 <div class="label">
80 <label for="user">${_('Owner')}:</label>
109 <label for="repo_owner">${_('Owner')}:</label>
81 110 </div>
82 111 <div class="input">
83 112 <div class="badge-input-container">
84 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 115 </div>
87 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 118 </div>
90 119 </div>
91 <form:error name="user"/>
120 ${c.form.render_error(request, c.form['repo_owner'])|n}
92 121 <p class="help-block">${_('Change owner of this repository.')}</p>
93 122 </div>
94 123 </div>
@@ -98,44 +127,49 b''
98 127 <label for="repo_description">${_('Description')}:</label>
99 128 </div>
100 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 132 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
103 133 </div>
104 134 </div>
105 135
106 136 <div class="field">
107 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 139 </div>
110 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 143 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
113 144 </div>
114 145 </div>
115 146 <div class="field">
116 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 149 </div>
119 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 153 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
122 154 </div>
123 155 </div>
124 156 <div class="field">
125 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 159 </div>
128 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 163 <span class="help-block">${_('Enable download menu on summary page.')}</span>
131 164 </div>
132 165 </div>
133 166 <div class="field">
134 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 169 </div>
137 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 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 174 </div>
141 175 </div>
@@ -168,12 +202,6 b''
168 202
169 203 <script>
170 204 $(document).ready(function(){
171 var select2Options = {
172 'containerCssClass': "drop-menu",
173 'dropdownCssClass': "drop-menu-dropdown",
174 'dropdownAutoWidth': true
175 };
176
177 205 var cloneUrl = function() {
178 206 var alterButton = $('#alter_clone_uri');
179 207 var editButton = $('#edit_clone_uri');
@@ -182,7 +210,7 b''
182 210 var hiddenUrlValue = $('#clone_uri_hidden_value');
183 211 var input = $('#clone_uri');
184 212 var helpBlock = $('#alter_clone_uri_help_block');
185 var changedFlag = $('#clone_uri_change');
213 var changedFlag = $('#repo_clone_uri_change');
186 214 var originalText = helpBlock.html();
187 215 var obfuscatedUrl = hiddenUrlValue.html();
188 216
@@ -223,18 +251,10 b''
223 251 initEvents();
224 252 }();
225 253
226 $('#show_more_clone_id').on('click', function(e){
227 $('#clone_id').show();
228 e.preventDefault();
229 });
254 selectMyGroup = function(element) {
255 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
256 };
230 257
231 $('#repo_landing_rev').select2(select2Options);
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 });
258 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
239 259 });
240 260 </script>
@@ -23,7 +23,7 b''
23 23 <div id="results" style="display:none; padding: 10px 0px;"></div>
24 24
25 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 27 ${_('Check commits')}
28 28 </button>
29 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 111 checkCommits = function() {
110 112 var postData = $('form').serialize();
111 113 $('#results').show();
112 114 $('#results').html('<h4>${_('Checking commits')}...</h4>');
113 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 117 btn.attr('disabled', 'disabled');
116 118 btn.addClass('disabled');
117 119
118 120 var success = function (data) {
119 result_data = {};
121 resultData = {
122 'csrf_token': CSRF_TOKEN
123 };
120 124 var i = 0;
121 result ='';
125 var result = '<ol>';
122 126 $.each(data, function(index, value){
123 127 i= index;
124 128 var box = $('#box-'+index);
125 129 if (value.rev){
126 result_data[index] = JSON.stringify(value);
127 msg = '${_("author")}: ' + value.author + ' ${_("comment")}: ' + value.comment;
128 result += '<h4><code>' +value.rev+ '</code>${_(' commit verified positive')}</br> '+ msg + '</h4>';
130 resultData[index] = JSON.stringify(value);
131
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 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 158 box.remove();
134 159 });
160 result += '</ol>';
135 161 var box = $('#box-'+(parseInt(i)+1));
136 162 box.remove();
137 163 $('#results').html(result);
@@ -147,22 +173,25 b' checkCommits = function() {'
147 173 strip = function(){
148 174 var url = "${h.route_path('strip_execute', repo_name=c.repo_info.repo_name)}";
149 175 var success = function(data){
150 result = '';
176 var result = '<h4>Strip executed</h4><ol>';
151 177 $.each(data, function(index, value){
152 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 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 189 $('#results').html(result);
160 190
161 191 };
162 ajaxPOST(url, result_data, success, null);
163 var btn = $('button');
164 btn.attr('disabled', 'disabled');
165 btn.addClass('disabled');
192 ajaxPOST(url, resultData, success, null);
193 var btn = $('#strip_action');
194 btn.remove();
166 195
167 196 };
168 197 </script>
@@ -10,7 +10,7 b''
10 10
11 11 <%def name="breadcrumbs_links()">
12 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 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${_('Settings')}
15 15 </%def>
@@ -23,7 +23,7 b''
23 23 <dl class="dl-horizontal settings">
24 24 %for dt, dd, tt in elems:
25 25 <dt >${dt}:</dt>
26 <dd title="${tt}">${dd}</dd>
26 <dd title="${h.tooltip(tt)}">${dd}</dd>
27 27 %endfor
28 28 </dl>
29 29 </div>
@@ -16,7 +16,7 b''
16 16 <dl class="dl-horizontal settings">
17 17 %for dt, dd, tt in elems:
18 18 <dt>${dt}:</dt>
19 <dd title="${tt}">${dd}</dd>
19 <dd title="${h.tooltip(tt)}">${dd}</dd>
20 20 %endfor
21 21 </dl>
22 22 </div>
@@ -52,6 +52,6 b''
52 52 <script>
53 53 $('#check_for_update').click(function(e){
54 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 57 </script>
@@ -8,7 +8,7 b''
8 8 %endif
9 9 </%def>
10 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 12 &raquo;
13 13 ${h.link_to(_('User groups'),h.url('users_groups'))}
14 14 &raquo;
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
15 15 &raquo;
@@ -34,7 +34,6 b''
34 34 % if sync_type:
35 35 <p>
36 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 37 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
39 38 </p>
40 39 % else:
@@ -56,11 +55,8 b''
56 55 </div>
57 56 <div class="field">
58 57 <span class="help-block">
59 %if sync_type:
60 ${_('User group will no longer synchronize membership')}
61 %else:
62 ${_('User group will start to synchronize membership')}
63 %endif
58 ${_('Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. '
59 'This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)')}
64 60 </span>
65 61 </div>
66 62 ${h.end_form()}
@@ -10,7 +10,7 b''
10 10
11 11 <%def name="breadcrumbs_links()">
12 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 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
@@ -8,7 +8,7 b''
8 8 %endif
9 9 </%def>
10 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 12 &raquo;
13 13 ${h.link_to(_('Users'),h.route_path('users'))}
14 14 &raquo;
@@ -9,11 +9,16 b''
9 9 </%def>
10 10
11 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 13 &raquo;
14 14 ${h.link_to(_('Users'),h.route_path('users'))}
15 15 &raquo;
16 % if c.user.active:
16 17 ${c.user.username}
18 % else:
19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
20 % endif
21
17 22 </%def>
18 23
19 24 <%def name="menu_bar_nav()">
@@ -35,8 +40,8 b''
35 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 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 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>
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>
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>
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 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 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 47 </ul>
@@ -59,7 +59,7 b''
59 59 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
60 60
61 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 63 %else:
64 64 <div class="panel-body-title-text">
65 65 ${_('This user currently does not have a personal repository group')}
@@ -5,7 +5,7 b''
5 5 <div class="panel panel-default">
6 6 <div class="panel-heading">
7 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 9 </h3>
10 10 </div>
11 11 <div class="panel-body">
@@ -16,50 +16,7 b''
16 16 ${h.end_form()}
17 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:
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
19 <%include file="/admin/admin_log_base.mako" />
63 20
64 21 </div>
65 22 </div>
@@ -38,10 +38,10 b''
38 38 %endif
39 39 </td>
40 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')}
42 ${h.hidden('del_auth_token',auth_token.api_key)}
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.user_api_key_id)}
43 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 45 ${_('Delete')}
46 46 </button>
47 47 ${h.end_form()}
@@ -55,7 +55,7 b''
55 55 </div>
56 56
57 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 59 <div class="form form-vertical">
60 60 <!-- fields -->
61 61 <div class="fields">
@@ -136,7 +136,7 b' var repoFilter = function(data) {'
136 136 query.callback({results: cachedData.results});
137 137 } else {
138 138 $.ajax({
139 url: "${h.url('repo_list_data')}",
139 url: pyroutes.url('repo_list_data'),
140 140 data: {'query': query.term},
141 141 dataType: 'json',
142 142 type: 'GET',
@@ -24,7 +24,7 b''
24 24 <span class="user email">${em.email}</span>
25 25 </td>
26 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 28 ${h.hidden('del_email_id',em.email_id)}
29 29 <button class="btn btn-link btn-danger" type="submit"
30 30 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
@@ -46,7 +46,7 b''
46 46 </table>
47 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 50 <div class="form">
51 51 <!-- fields -->
52 52 <div class="fields">
@@ -4,11 +4,12 b''
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="ips_wrap">
7 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
7 8 <table class="rctable ip-whitelist">
8 9 <tr>
9 <th>IP Address</th>
10 <th>IP Range</th>
11 <th>Description</th>
10 <th>${_('IP Address')}</th>
11 <th>${_('IP Range')}</th>
12 <th>${_('Description')}</th>
12 13 <th></th>
13 14 </tr>
14 15 %if c.default_user_ip_map and c.inherit_default_ips:
@@ -29,7 +30,7 b''
29 30 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
30 31 <td class="td-description"><div class="ip">${ip.description}</div></td>
31 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 34 ${h.hidden('del_ip_id',ip.ip_id)}
34 35 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
35 36 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
@@ -50,7 +51,7 b''
50 51 </div>
51 52
52 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 55 <div class="form">
55 56 <!-- fields -->
56 57 <div class="fields">
@@ -127,7 +127,7 b''
127 127 ## allowed_languages is defined in the users.py
128 128 ## c.language comes from base.py as a default language
129 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 131 </div>
132 132 </div>
133 133 <div class="buttons">
@@ -10,7 +10,7 b''
10 10
11 11 <%def name="breadcrumbs_links()">
12 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 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
@@ -38,9 +38,10 b''
38 38 <script type="text/javascript">
39 39
40 40 $(document).ready(function() {
41 var $userListTable = $('#user_list_table');
41 42
42 43 var getDatatableCount = function(){
43 var table = $('#user_list_table').dataTable();
44 var table = $userListTable.dataTable();
44 45 var page = table.api().page.info();
45 46 var active = page.recordsDisplay;
46 47 var total = page.recordsTotal;
@@ -50,7 +51,7 b''
50 51 };
51 52
52 53 // user list
53 $('#user_list_table').DataTable({
54 $userListTable.DataTable({
54 55 processing: true,
55 56 serverSide: true,
56 57 ajax: "${h.route_path('users_data')}",
@@ -76,7 +77,7 b''
76 77 { data: {"_": "extern_type",
77 78 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
78 79 { data: {"_": "action",
79 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
80 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
80 81 ],
81 82 language: {
82 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 $('#user_list_table').css('opacity', 1);
95 $userListTable.on('xhr.dt', function(e, settings, json, xhr){
96 $userListTable.css('opacity', 1);
96 97 });
97 98
98 $('#user_list_table').on('preXhr.dt', function(e, settings, data){
99 $('#user_list_table').css('opacity', 0.3);
99 $userListTable.on('preXhr.dt', function(e, settings, data){
100 $userListTable.css('opacity', 0.3);
100 101 });
101 102
102 103 // refresh counters on draw
103 $('#user_list_table').on('draw.dt', function(){
104 $userListTable.on('draw.dt', function(){
104 105 getDatatableCount();
105 106 });
106 107
107 108 // filter
108 109 $('#q_filter').on('keyup',
109 110 $.debounce(250, function() {
110 $('#user_list_table').DataTable().search(
111 $userListTable.DataTable().search(
111 112 $('#q_filter').val()
112 113 ).draw();
113 114 })
@@ -7,7 +7,7 b''
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 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 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
@@ -41,7 +41,7 b''
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 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 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
@@ -72,7 +72,7 b''
72 72
73 73 <%def name="admin_menu()">
74 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 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
@@ -90,7 +90,7 b''
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 <dd title="${title}">
93 <dd title="${h.tooltip(title)}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
@@ -134,7 +134,7 b''
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 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 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
@@ -186,7 +186,7 b''
186 186 %if repo_instance.fork:
187 187 <p>
188 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 190 </p>
191 191 %endif
192 192
@@ -225,7 +225,7 b''
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 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 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 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 231 <li class="${is_active('compare')}">
@@ -234,7 +234,7 b''
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 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 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
@@ -243,17 +243,19 b''
243 243 </li>
244 244 %endif
245 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 249 <ul class="submenu">
248 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 252 %endif
251 253 %if c.rhodecode_db_repo.fork:
252 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 255 ${_('Compare fork')}</a></li>
254 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 260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 261 %if c.rhodecode_db_repo.locked[0]:
@@ -322,8 +324,9 b''
322 324 <div class="buttons">
323 325 <div class="register">
324 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 328 %endif
329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
327 330 </div>
328 331 <div class="submit">
329 332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
@@ -342,7 +345,7 b''
342 345 <ol class="links">
343 346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
344 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 349 % endif
347 350 <li class="logout">
348 351 ${h.secure_form(h.route_path('logout'))}
@@ -399,7 +402,7 b''
399 402 </a>
400 403 </li>
401 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 406 <div class="menulabel">${_('Search')}</div>
404 407 </a>
405 408 </li>
@@ -503,7 +506,7 b''
503 506 query.callback({results: cachedData.results});
504 507 } else {
505 508 $.ajax({
506 url: "${h.url('goto_switcher_data')}",
509 url: pyroutes.url('goto_switcher_data'),
507 510 data: {'query': query.term},
508 511 dataType: 'json',
509 512 type: 'GET',
@@ -18,7 +18,7 b''
18 18 <td class="td-regex issue-tracker-example">${'(?:#)(?P<issue_id>\d+)'}</td>
19 19 <td class="td-url issue-tracker-example">${'https://myissueserver.com/${repo}/issue/${issue_id}'}</td>
20 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 22 </tr>
23 23 %for uid, entry in patterns:
24 24 <tr id="entry_${uid}">
@@ -124,9 +124,9 b''
124 124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
125 125 <td class="td-componentname">
126 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 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 130 %elif section == 'user_groups':
131 131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
132 132 ${k}
@@ -146,7 +146,7 b''
146 146 %if actions:
147 147 <td class="td-action">
148 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 150 %elif section == 'repositories_groups':
151 151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 152 %elif section == 'user_groups':
@@ -12,10 +12,15 b" if getattr(c, 'rhodecode_user', None) an"
12 12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
13 13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
14 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
16 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.lastname
15 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
16 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
17 17
18 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 25 <html xmlns="http://www.w3.org/1999/xhtml">
21 26 <head>
@@ -79,7 +84,7 b" c.template_context['visual']['default_re"
79 84 // register templateContext to pass template variables to JS
80 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 88 var ASSET_URL = "${h.asset('')}";
84 89 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
85 90 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
@@ -130,6 +130,19 b''
130 130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
131 131 </div>
132 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 146 </div>
134 147 </div>
135 148 ## LABS for HG
@@ -201,7 +214,7 b''
201 214 <div class="label">
202 215 <span class="help-block">
203 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 218 </span>
206 219 </div>
207 220 </div>
@@ -10,7 +10,7 b''
10 10
11 11
12 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 14 <a href="${files_url}">
15 15 <i class="icon-bookmark"></i>
16 16 ${name}
@@ -23,11 +23,11 b''
23 23 </%def>
24 24
25 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 27 </%def>
28 28
29 29 <%def name="commit(message, commit_id, commit_idx)">
30 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 32 </div>
33 33 </%def>
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 <a href="${files_url}"><i class="icon-code-fork"></i>${name}
14 14 %if name in c.closed_branches:
15 15 [closed]
@@ -23,11 +23,11 b''
23 23 </%def>
24 24
25 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 27 </%def>
28 28
29 29 <%def name="commit(message, commit_id, commit_idx)">
30 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 32 </div>
33 33 </%def>
@@ -37,7 +37,7 b''
37 37 %if c.rhodecode_db_repo.fork:
38 38 <span>
39 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 41 class="btn btn-small"
42 42 href="${h.url('compare_url',
43 43 repo_name=c.rhodecode_db_repo.fork.repo_name,
@@ -23,7 +23,7 b''
23 23 %if c.statuses.get(commit.raw_id):
24 24 <div class="changeset-status-ico">
25 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 27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
28 28 </a>
29 29 %else:
@@ -46,8 +46,28 b''
46 46 <td class="td-hash">
47 47 <code>
48 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 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 71 </code>
52 72 </td>
53 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 101 ## branch
82 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 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 105 </span>
86 106 %endif
@@ -88,7 +108,7 b''
88 108 ## bookmarks
89 109 %if h.is_hg(c.rhodecode_repo):
90 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 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 113 </span>
94 114 %endfor
@@ -96,7 +116,7 b''
96 116
97 117 ## tags
98 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 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 121 </span>
102 122 %endfor
@@ -14,7 +14,7 b''
14 14 </td>
15 15 <td class="td-message">
16 16 <div class="log-container">
17 <div class="message_history" title="${cs.message}">
17 <div class="message_history" title="${h.tooltip(cs.message)}">
18 18 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
19 19 ${h.shorter(cs.message, 75)}
20 20 </a>
@@ -36,9 +36,28 b''
36 36 <h4>${_('Commit')}
37 37 <code>
38 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 57 </code>
40 58 </h4>
41 59 </span>
60 <div class="pull-right">
42 61 <span id="parent_link">
43 62 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
44 63 </span>
@@ -47,6 +66,7 b''
47 66 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
48 67 </span>
49 68 </div>
69 </div>
50 70
51 71 <div class="fieldset">
52 72 <div class="left-label">
@@ -89,20 +109,20 b''
89 109
90 110 %if h.is_hg(c.rhodecode_repo):
91 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 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 114 </span>
95 115 %endfor
96 116 %endif
97 117
98 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 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 121 </span>
102 122 %endfor
103 123
104 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 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 127 </span>
108 128 %endif
@@ -1,4 +1,4 b''
1 1 ## this is a dummy html file for partial rendering on server and sending
2 2 ## generated output via ajax after comment submit
3 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 61 % else:
62 62 <div class="status-change">
63 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 65 % if comment.status_change:
66 66 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
67 67 % else:
@@ -92,7 +92,12 b''
92 92 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
93 93
94 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 101 % if inline:
97 102 <div class="pr-version-inline">
98 103 <a href="${h.url.current(version=comment.pull_request_version_id, anchor='comment-{}'.format(comment.comment_id))}">
@@ -117,7 +122,7 b''
117 122 </a>
118 123 % else:
119 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 126 <code class="pr-version-num">
122 127 ${'v{}'.format(pr_index_ver)}
123 128 </code>
@@ -153,7 +158,7 b''
153 158 </div>
154 159 </div>
155 160 <div class="text">
156 ${comment.render(mentions=True)|n}
161 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
157 162 </div>
158 163
159 164 </div>
@@ -341,7 +346,7 b''
341 346 <div class="toolbar">
342 347 <div class="toolbar-text">
343 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 350 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
346 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 114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
115 115 %if commit:
116 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 118 ${_('Browse Files')}
119 119 </a>
120 120 </div>
@@ -147,14 +147,14 b' collapse_all = len(diffset.files) > coll'
147 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 151 over_lines_changed_limit = lines_changed > lines_changed_limit
152 152 %>
153 153 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
154 154 <div
155 155 class="filediff"
156 data-f-path="${filediff['patch']['filename']}"
157 id="a_${h.FID('', filediff['patch']['filename'])}">
156 data-f-path="${filediff.patch['filename']}"
157 id="a_${h.FID('', filediff.patch['filename'])}">
158 158 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
159 159 <div class="filediff-collapse-indicator"></div>
160 160 ${diff_ops(filediff)}
@@ -162,7 +162,7 b' collapse_all = len(diffset.files) > coll'
162 162 ${diff_menu(filediff, use_comments=use_comments)}
163 163 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
164 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 166 <tr>
167 167 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
168 168 %if op_id == DEL_FILENODE:
@@ -176,7 +176,7 b' collapse_all = len(diffset.files) > coll'
176 176 </tr>
177 177 %endfor
178 178 %endif
179 %if filediff.patch['is_limited_diff']:
179 %if filediff.limited_diff:
180 180 <tr class="cb-warning cb-collapser">
181 181 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
182 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 323 <%def name="diff_ops(filediff)">
324 324 <%
325 stats = filediff['patch']['stats']
326 325 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
327 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 329 %if filediff.source_file_path and filediff.target_file_path:
331 330 %if filediff.source_file_path != filediff.target_file_path:
332 331 ## file was renamed, or copied
333 %if RENAMED_FILENODE in stats['ops']:
332 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
334 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 335 <strong>${filediff.target_file_path}</strong>${filediff.source_file_path}
337 336 %endif
338 337 %else:
@@ -350,19 +349,19 b' from rhodecode.lib.diffs import NEW_FILE'
350 349 %endif
351 350 </span>
352 351 <span class="pill-group" style="float: left">
353 %if filediff.patch['is_limited_diff']:
352 %if filediff.limited_diff:
354 353 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
355 354 %endif
356 355
357 %if RENAMED_FILENODE in stats['ops']:
356 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
358 357 <span class="pill" op="renamed">renamed</span>
359 358 %endif
360 359
361 %if COPIED_FILENODE in stats['ops']:
360 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
362 361 <span class="pill" op="copied">copied</span>
363 362 %endif
364 363
365 %if NEW_FILENODE in stats['ops']:
364 %if NEW_FILENODE in filediff.patch['stats']['ops']:
366 365 <span class="pill" op="created">created</span>
367 366 %if filediff['target_mode'].startswith('120'):
368 367 <span class="pill" op="symlink">symlink</span>
@@ -371,11 +370,11 b' from rhodecode.lib.diffs import NEW_FILE'
371 370 %endif
372 371 %endif
373 372
374 %if DEL_FILENODE in stats['ops']:
373 %if DEL_FILENODE in filediff.patch['stats']['ops']:
375 374 <span class="pill" op="removed">removed</span>
376 375 %endif
377 376
378 %if CHMOD_FILENODE in stats['ops']:
377 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
379 378 <span class="pill" op="mode">
380 379 ${nice_mode(filediff['source_mode'])}${nice_mode(filediff['target_mode'])}
381 380 </span>
@@ -385,17 +384,17 b' from rhodecode.lib.diffs import NEW_FILE'
385 384 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}"></a>
386 385
387 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 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 390 <span class="pill" op="modified">modified</span>
392 391 %endif
393 392 %endif
394 %if stats['added']:
395 <span class="pill" op="added">+${stats['added']}</span>
393 %if filediff.patch['stats']['added']:
394 <span class="pill" op="added">+${filediff.patch['stats']['added']}</span>
396 395 %endif
397 %if stats['deleted']:
398 <span class="pill" op="deleted">-${stats['deleted']}</span>
396 %if filediff.patch['stats']['deleted']:
397 <span class="pill" op="deleted">-${filediff.patch['stats']['deleted']}</span>
399 398 %endif
400 399 </span>
401 400
@@ -408,7 +407,7 b' from rhodecode.lib.diffs import NEW_FILE'
408 407 <%def name="diff_menu(filediff, use_comments=False)">
409 408 <div class="filediff-menu">
410 409 %if filediff.diffset.source_ref:
411 %if filediff.patch['operation'] in ['D', 'M']:
410 %if filediff.operation in ['D', 'M']:
412 411 <a
413 412 class="tooltip"
414 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 423 ${_('Show file before')}
425 424 </span> |
426 425 %endif
427 %if filediff.patch['operation'] in ['A', 'M']:
426 %if filediff.operation in ['A', 'M']:
428 427 <a
429 428 class="tooltip"
430 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 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 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 463 %endif
465 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 466 %endif
468 467
469 468 %if use_comments:
@@ -503,9 +502,9 b' from rhodecode.lib.diffs import NEW_FILE'
503 502 <%
504 503 old_line_anchor, new_line_anchor = None, None
505 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 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 510 <tr class="cb-line">
@@ -579,9 +578,9 b' from rhodecode.lib.diffs import NEW_FILE'
579 578 <%
580 579 old_line_anchor, new_line_anchor = None, None
581 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 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 585 <tr class="cb-line">
587 586 <td class="cb-data ${action_class(action)}">
@@ -640,13 +639,13 b' from rhodecode.lib.diffs import NEW_FILE'
640 639
641 640 <a
642 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 643 href="${h.url_replace(diffmode='sideside')}">
645 644 <span>${_('Side by Side')}</span>
646 645 </a>
647 646 <a
648 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 649 <span>${_('Unified')}</span>
651 650 </a>
652 651 </div>
@@ -5,6 +5,7 b''
5 5 from rhodecode.lib.codeblocks import render_tokenstream
6 6 # avoid module lookup for performance
7 7 html_escape = h.html_escape
8 tooltip = h.tooltip
8 9 %>
9 10 <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}"
10 11 %if annotation:
@@ -15,13 +16,13 b''
15 16 % if annotation:
16 17 % if show_annotation:
17 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 21 ${h.gravatar_with_user(annotation.author, 16) | n}
21 22 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
22 23 </td>
23 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 26 <i class="icon-left"></i>
26 27 </a>
27 28 </td>
@@ -6,6 +6,7 b''
6 6 <a href="${h.url('changeset_home', repo_name=c.repo_name, revision=c.ancestor)}">
7 7 ${h.short_id(c.ancestor)}
8 8 </a>. ${_('Compare was calculated based on this shared commit.')}
9 <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}">
9 10 </div>
10 11 %endif
11 12
@@ -9,7 +9,7 b''
9 9 <div class="menu_items_container hidden">
10 10 <ul class="menu_items">
11 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 13 <span>${_('Summary')}</span>
14 14 </a>
15 15 </li>
@@ -42,7 +42,7 b''
42 42 %>
43 43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 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 47 ##TYPE OF REPO
48 48 %if h.is_hg(rtype):
@@ -64,7 +64,7 b''
64 64 ${get_name(name)}
65 65 </a>
66 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 68 %endif
69 69 %if rstate == 'repo_state_pending':
70 70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
@@ -92,22 +92,22 b''
92 92
93 93 <%def name="rss(name)">
94 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 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 98 %endif
99 99 </%def>
100 100
101 101 <%def name="atom(name)">
102 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 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 106 %endif
107 107 </%def>
108 108
109 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 111 ${base.gravatar(email, 16)}
112 112 </div>
113 113 </%def>
@@ -115,11 +115,11 b''
115 115 <%def name="repo_actions(repo_name, super_user=True)">
116 116 <div>
117 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 119 <i class="icon-pencil"></i>Edit</a>
120 120 </div>
121 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 123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 125 ${h.end_form()}
@@ -134,7 +134,7 b''
134 134 %elif repo_state == 'repo_state_created':
135 135 <div class="tag tag1">${_('Created')}</div>
136 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 138 %endif
139 139 </div>
140 140 </%def>
@@ -146,7 +146,7 b''
146 146 <div class="menu_items_container hidden">
147 147 <ul class="menu_items">
148 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 150 <span class="icon">
151 151 <i class="icon-file-text"></i>
152 152 </span>
@@ -160,7 +160,7 b''
160 160
161 161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 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 164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 165 %if children_groups:
166 166 ${h.literal(' &raquo; '.join(children_groups))}
@@ -282,7 +282,7 b''
282 282
283 283 <%def name="pullrequest_target_repo(repo_name)">
284 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 286 </div>
287 287 </%def>
288 288 <%def name="pullrequest_status(status)">
@@ -299,7 +299,7 b''
299 299 </%def>
300 300
301 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 303 % if short:
304 304 #${pull_request_id}
305 305 % else:
@@ -886,7 +886,7 b''
886 886 AJAX_COMMENT_DELETE_URL = "/rhodecode-momentum/pull-request-comment/__COMMENT_ID__/delete";
887 887
888 888 $(function(){
889 ReviewerAutoComplete('user');
889 ReviewerAutoComplete('#user');
890 890
891 891 $('#open_edit_reviewers').on('click', function(e){
892 892 $('#open_edit_reviewers').hide();
@@ -949,9 +949,6 b''
949 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 953 </script>
957 954
@@ -3,7 +3,7 b''
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'user': h.person(user),
@@ -1,7 +1,7 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 5 RhodeCode test email: ${h.format_date(date)}
6 6 </%def>
7 7
@@ -1,7 +1,7 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 5 </%def>
6 6
7 7
@@ -1,7 +1,7 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 5 RhodeCode Password reset
6 6 </%def>
7 7
@@ -1,7 +1,7 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 5 Your new RhodeCode password
6 6 </%def>
7 7
@@ -3,7 +3,7 b''
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim">
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'user': h.person(user),
@@ -2,7 +2,7 b''
2 2 <%inherit file="base.mako"/>
3 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 7 data = {
8 8 'user': h.person(user),
@@ -1,7 +1,7 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 5 Test "Subject" ${_('hello "world"')|n}
6 6 </%def>
7 7
@@ -1,7 +1,7 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 <%def name="subject()" filter="n,trim">
4 <%def name="subject()" filter="n,trim,whitespace_filter">
5 5 RhodeCode new user registration: ${user.username}
6 6 </%def>
7 7
@@ -10,18 +10,18 b' RhodeCode new user registration: ${user.'
10 10 A new user `${user.username}` has registered on ${h.format_date(date)}
11 11
12 12 - Username: ${user.username}
13 - Full Name: ${user.firstname} ${user.lastname}
13 - Full Name: ${user.first_name} ${user.last_name}
14 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 17 ${self.plaintext_footer()}
18 18 </%def>
19 19
20 20 ## BODY GOES BELOW
21 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 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 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 27 </table> No newline at end of file
@@ -24,11 +24,10 b''
24 24 <script type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
25 25 </head>
26 26 <body>
27 <% messages = h.flash.pop_messages() %>
28 27
29 28 <div class="wrapper error_page">
30 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 31 </div>
33 32 <div class="main-content">
34 33 <h1>
@@ -37,8 +36,8 b''
37 36 </span><br/>
38 37 ${c.error_message} | <span class="error_message">${c.error_explanation}</span>
39 38 </h1>
40 % if messages:
41 % for message in messages:
39 % if c.messages:
40 % for message in c.messages:
42 41 <div class="alert alert-${message.category}">${message}</div>
43 42 % endfor
44 43 % endif
@@ -62,12 +61,12 b''
62 61 <div class="inner-column">
63 62 <h4>Support</h4>
64 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 65 </p>
67 66 </div>
68 67 <div class="inner-column">
69 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 70 </div>
72 71 </div>
73 72 </div>
@@ -7,20 +7,20 b''
7 7
8 8 %if h.is_hg(c.rhodecode_repo):
9 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 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 12 </span>
13 13 %endfor
14 14 %endif
15 15
16 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 18 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
19 19 </span>
20 20 %endfor
21 21
22 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 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 25 </span>
26 26 %endif
@@ -15,7 +15,7 b''
15 15 <ul class="sidebar-right-content">
16 16 % for email, user in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]):
17 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 19 ${base.gravatar(email, 16)}
20 20 <span class="user">${h.link_to_user(user)}</span>
21 21 </div>
@@ -56,11 +56,17 b''
56 56 </div>
57 57 <div id="upload_file_container" class="fieldset" style="display: none;">
58 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 65 ${_('Upload file')}:
60 66 </div>
61 67 <div class="right-content file-upload-input">
62 68 <label for="upload_file" class="btn btn-default">Browse</label>
63 <span id="selected-file">${_('No file selected')}</span>
69
64 70 <input type="file" name="upload_file" id="upload_file">
65 71 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
66 72 </div>
@@ -155,10 +161,8 b''
155 161 });
156 162
157 163 $('#upload_file').on('change', function() {
158 if (detectIE() && detectIE() <= 9) {
159 164 if (this.files && this.files[0]) {
160 $('#selected-file').html(this.files[0].name);
161 }
165 $('#filename_upload').val(this.files[0].name);
162 166 }
163 167 });
164 168
@@ -58,14 +58,14 b''
58 58 </td>
59 59 <td class="td-hash" data-attr-name="commit_id">
60 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 62 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre>
63 63 </div>
64 64 % endif
65 65 </td>
66 66 <td class="td-user" data-attr-name="author">
67 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 69 % endif
70 70 </td>
71 71 %else:
@@ -16,7 +16,7 b''
16 16 ${base.gravatar_with_user(f.user.email, 16)}
17 17 </td>
18 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 20 </td>
21 21 <td class="td-description">
22 22 <div class="truncate">${f.description}</div>
@@ -25,7 +25,7 b''
25 25 ${h.age_component(f.created_on, time_is_local=True)}
26 26 </td>
27 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 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 30 class="btn-link"><i class="icon-loop"></i> ${_('Compare fork')}</a>
31 31 </td>
@@ -21,7 +21,7 b''
21 21 tal:attributes="label item.label">
22 22 <option tal:repeat="(value, description) item.options"
23 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 25 class css_class;
26 26 label field.widget.long_label_generator and description;
27 27 value value"
@@ -29,7 +29,7 b''
29 29 </optgroup>
30 30 <option tal:condition="not isinstance(item, optgroup_class)"
31 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 33 class css_class;
34 34 value item[0]">${item[1]}</option>
35 35 </tal:loop>
@@ -37,7 +37,7 b''
37 37
38 38 <script type="text/javascript">
39 39 deform.addCallback(
40 '${field.oid}',
40 '${oid}',
41 41 function(oid) {
42 42 $('#' + oid).select2({
43 43 containerCssClass: 'form-control drop-menu',
@@ -32,21 +32,21 b''
32 32 tal:attributes="prototype prototype"/>
33 33
34 34 <div class="panel panel-default">
35 <div class="panel-heading">${title}</div>
36 35 <div class="panel-body">
37 36
38 37 <div class="deform-seq-container"
39 38 id="${oid}-orderable">
40 39 <div tal:define="subfields [ x[1] for x in subfields ]"
41 40 tal:repeat="subfield subfields"
42 tal:replace="structure subfield.render_template(item_tmpl,
43 parent=field)" />
41 tal:replace="structure subfield.render_template(item_tmpl,parent=field)" />
44 42 <span class="deform-insert-before"
45 43 tal:attributes="
46 44 min_len min_len;
47 45 max_len max_len;
48 46 now_len now_len;
49 orderable orderable;"></span>
47 orderable orderable;">
48
49 </span>
50 50 </div>
51 51
52 52 <div style="clear: both"></div>
@@ -78,12 +78,12 b''
78 78 placeholder: '<span class="glyphicon glyphicon-arrow-right placeholder"></span>',
79 79 onDragStart: function ($item, container, _super) {
80 80 var offset = $item.offset(),
81 pointer = container.rootGroup.pointer
81 pointer = container.rootGroup.pointer;
82 82
83 83 adjustment = {
84 84 left: pointer.left - offset.left,
85 85 top: pointer.top - offset.top
86 }
86 };
87 87
88 88 _super($item, container)
89 89 },
@@ -6,6 +6,7 b''
6 6 oid oid|field.oid"
7 7 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
8 8 i18n:domain="deform">
9
9 10 <div class="deform-seq-item-group">
10 11 <span tal:replace="structure field.serialize(cstruct)"/>
11 12 <tal:errors condition="field.error and not hidden"
@@ -17,13 +18,8 b''
17 18 i18n:translate="">${msg}</p>
18 19 </tal:errors>
19 20 </div>
21
20 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 23 <a class="deform-close-button close"
28 24 id="${oid}-close"
29 25 tal:condition="not field.widget.hidden"
@@ -31,5 +27,5 b''
31 27 i18n:attributes="title"
32 28 onclick="javascript:deform.removeSequenceItem(this);">&times;</a>
33 29 </div>
34 <!-- /sequence_item -->
30
35 31 </div>
@@ -10,9 +10,9 b''
10 10
11 11 <%def name="breadcrumbs()">
12 12 <span class="groups_breadcrumbs">
13 ${h.link_to(_(u'Home'),h.url('/'))}
13 ${h.link_to(_(u'Home'), h.route_path('home'))}
14 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 16 %endif
17 17 &raquo; ${c.repo_group.name}
18 18 </span>
@@ -20,7 +20,7 b''
20 20 <span class="journal_repo_name">
21 21 %if entry.repository is not None:
22 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 24 %else:
25 25 ${entry.repository_name}
26 26 %endif
@@ -14,7 +14,7 b''
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 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 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -14,7 +14,7 b''
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 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 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -20,15 +20,22 b''
20 20 <div class="box">
21 21 <div class="title">
22 22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 ${self.breadcrumbs()}
24 23 </div>
25 24
26 25 ${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
26
27 ${self.breadcrumbs()}
28
27 29 <div class="box pr-summary">
28 30
29 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 39 <!-- fields -->
33 40
34 41 <div class="fields" >
@@ -62,7 +69,7 b''
62 69
63 70 ##ORG
64 71 <div class="content">
65 <strong>${_('Origin repository')}:</strong>
72 <strong>${_('Source repository')}:</strong>
66 73 ${c.rhodecode_db_repo.description}
67 74 </div>
68 75 <div class="content">
@@ -102,6 +109,31 b''
102 109 </div>
103 110 </div>
104 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 137 <div class="reviewers-title block-right">
106 138 <div class="pr-details-title">
107 139 ${_('Pull request reviewers')}
@@ -115,7 +147,7 b''
115 147 <input type="hidden" name="__end__" value="review_members:sequence">
116 148 <div id="add_reviewer_input" class='ac'>
117 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 151 <div id="reviewers_container"></div>
120 152 </div>
121 153 </div>
@@ -137,7 +169,6 b''
137 169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
138 170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
139 171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
140 var targetRepoName = '${c.repo_name}';
141 172
142 173 var $pullRequestForm = $('#pull_request_form');
143 174 var $sourceRepo = $('#source_repo', $pullRequestForm);
@@ -145,6 +176,12 b''
145 176 var $sourceRef = $('#source_ref', $pullRequestForm);
146 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 185 var calculateContainerWidth = function() {
149 186 var maxWidth = 0;
150 187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
@@ -204,6 +241,8 b''
204 241 // custom code mirror
205 242 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
206 243
244 reviewersController = new ReviewersController();
245
207 246 var queryTargetRepo = function(self, query) {
208 247 // cache ALL results if query is empty
209 248 var cacheKey = query.term || '__';
@@ -213,7 +252,7 b''
213 252 query.callback({results: cachedData.results});
214 253 } else {
215 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 256 data: {query: query.term},
218 257 dataType: 'json',
219 258 type: 'GET',
@@ -246,55 +285,21 b''
246 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 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 290 var url_data = {
286 'repo_name': targetRepo,
287 'target_repo': sourceRepo,
288 'source_ref': targetRef[2],
291 'repo_name': targetRepo(),
292 'target_repo': sourceRepo(),
293 'source_ref': targetRef()[2],
289 294 'source_ref_type': 'rev',
290 'target_ref': sourceRef[2],
295 'target_ref': sourceRef()[2],
291 296 'target_ref_type': 'rev',
292 297 'merge': true,
293 298 '_': Date.now() // bypass browser caching
294 299 }; // gather the source/target ref and repo here
295 300
296 if (sourceRef.length !== 3 || targetRef.length !== 3) {
297 prButtonLock(true, "${_('Please select origin and destination')}");
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
302 prButtonLock(true, "${_('Please select source and target')}");
298 303 return;
299 304 }
300 305 var url = pyroutes.url('compare_url', url_data);
@@ -315,10 +320,11 b''
315 320 .done(function(data) {
316 321 loadRepoRefDiffPreview._currentRequest = null;
317 322 $('#pull_request_overview').html(data);
323
318 324 var commitElements = $(data).find('tr[commit_id]');
319 325
320 var prTitleAndDesc = getTitleAndDescription(sourceRef[1],
321 commitElements, 5);
326 var prTitleAndDesc = getTitleAndDescription(
327 sourceRef()[1], commitElements, 5);
322 328
323 329 var title = prTitleAndDesc[0];
324 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 375 var Select2Box = function(element, overrides) {
407 376 var globalDefaults = {
408 377 dropdownAutoWidth: true,
@@ -459,7 +428,7 b''
459 428 var targetRepoChanged = function(repoData) {
460 429 // generate new DESC of target repo displayed next to select
461 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 434 // generate dynamic select2 for refs.
@@ -468,8 +437,7 b''
468 437
469 438 };
470 439
471 var sourceRefSelect2 = Select2Box(
472 $sourceRef, {
440 var sourceRefSelect2 = Select2Box($sourceRef, {
473 441 placeholder: "${_('Select commit reference')}",
474 442 query: function(query) {
475 443 var initialData = defaultSourceRepoData['refs']['select2_refs'];
@@ -499,12 +467,14 b''
499 467
500 468 $sourceRef.on('change', function(e){
501 469 loadRepoRefDiffPreview();
502 loadDefaultReviewers();
470 reviewersController.loadDefaultReviewers(
471 sourceRepo(), sourceRef(), targetRepo(), targetRef());
503 472 });
504 473
505 474 $targetRef.on('change', function(e){
506 475 loadRepoRefDiffPreview();
507 loadDefaultReviewers();
476 reviewersController.loadDefaultReviewers(
477 sourceRepo(), sourceRef(), targetRepo(), targetRef());
508 478 });
509 479
510 480 $targetRepo.on('change', function(e){
@@ -515,7 +485,7 b''
515 485
516 486 $.ajax({
517 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 489 data: {},
520 490 dataType: 'json',
521 491 type: 'GET',
@@ -531,43 +501,7 b''
531 501
532 502 });
533 503
534 var loadDefaultReviewers = function() {
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');
504 prButtonLock(true, "${_('Please select source and target')}", 'all');
571 505
572 506 // auto-load on init, the target refs select2
573 507 calculateContainerWidth();
@@ -581,10 +515,11 b''
581 515 // in case we have a pre-selected value, use it now
582 516 $sourceRef.select2('val', '${c.default_source_ref}');
583 517 loadRepoRefDiffPreview();
584 loadDefaultReviewers();
518 reviewersController.loadDefaultReviewers(
519 sourceRepo(), sourceRef(), targetRepo(), targetRef());
585 520 % endif
586 521
587 ReviewerAutoComplete('user');
522 ReviewerAutoComplete('#user');
588 523 });
589 524 </script>
590 525
@@ -48,13 +48,13 b''
48 48 <div class="summary-details block-left">
49 49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 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 52 %if c.allowed_to_update:
53 53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 54 % if c.allowed_to_delete:
55 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 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 58 ${h.end_form()}
59 59 % else:
60 60 ${_('Delete')}
@@ -68,7 +68,7 b''
68 68 <div id="summary" class="fields pr-details-content">
69 69 <div class="field">
70 70 <div class="label-summary">
71 <label>${_('Origin')}:</label>
71 <label>${_('Source')}:</label>
72 72 </div>
73 73 <div class="input">
74 74 <div class="pr-origininfo">
@@ -81,7 +81,7 b''
81 81 %endif
82 82 </span>
83 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 85 </span>
86 86 <br/>
87 87 % if c.ancestor_commit:
@@ -113,7 +113,7 b''
113 113 %endif
114 114 </span>
115 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 117 </span>
118 118 </div>
119 119 </div>
@@ -297,7 +297,7 b''
297 297 <div id="pr-save" class="field" style="display: none;">
298 298 <div class="label-summary"></div>
299 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 301 </div>
302 302 </div>
303 303 </div>
@@ -306,7 +306,7 b''
306 306 ## AUTHOR
307 307 <div class="reviewers-title block-right">
308 308 <div class="pr-details-title">
309 ${_('Author')}
309 ${_('Author of this pull request')}
310 310 </div>
311 311 </div>
312 312 <div class="block-right pr-details-content reviewers">
@@ -316,13 +316,27 b''
316 316 </li>
317 317 </ul>
318 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 334 ## REVIEWERS
320 335 <div class="reviewers-title block-right">
321 336 <div class="pr-details-title">
322 337 ${_('Pull request reviewers')}
323 338 %if c.allowed_to_update:
324 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
325 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
339 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
326 340 %endif
327 341 </div>
328 342 </div>
@@ -330,8 +344,8 b''
330 344 ## members goes here !
331 345 <input type="hidden" name="__start__" value="review_members:sequence">
332 346 <ul id="review_members" class="group_members">
333 %for member,reasons,status in c.pull_request_reviewers:
334 <li id="reviewer_${member.user_id}">
347 %for member,reasons,mandatory,status in c.pull_request_reviewers:
348 <li id="reviewer_${member.user_id}" class="reviewer_entry">
335 349 <div class="reviewers_member">
336 350 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
337 351 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
@@ -348,26 +362,39 b''
348 362 %endfor
349 363 <input type="hidden" name="__end__" value="reasons:sequence">
350 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 366 <input type="hidden" name="__end__" value="reviewer:mapping">
352 %if c.allowed_to_update:
353 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
367 % if mandatory:
368 <div class="reviewer_member_mandatory_remove">
354 369 <i class="icon-remove-sign" ></i>
355 370 </div>
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
356 380 %endif
357 381 </div>
358 382 </li>
359 383 %endfor
360 384 </ul>
361 385 <input type="hidden" name="__end__" value="review_members:sequence">
386
362 387 %if not c.pull_request.is_closed():
363 <div id="add_reviewer_input" class='ac' style="display: none;">
388 <div id="add_reviewer" class="ac" style="display: none;">
364 389 %if c.allowed_to_update:
365 <div class="reviewer_ac">
366 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
390 % if not c.forbid_adding_reviewers:
391 <div id="add_reviewer_input" class="reviewer_ac">
392 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
367 393 <div id="reviewers_container"></div>
368 394 </div>
369 <div>
370 <button id="update_pull_request" class="btn btn-small">${_('Save Changes')}</button>
395 % endif
396 <div class="pull-right">
397 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
371 398 </div>
372 399 %endif
373 400 </div>
@@ -429,7 +456,7 b''
429 456
430 457 <div class="pull-right">
431 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 460 % else:
434 461 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
435 462 % endif
@@ -473,7 +500,7 b''
473 500 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
474 501 <td>
475 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 504 ${c_type.upper()}
478 505 </div>
479 506 </div>
@@ -615,9 +642,10 b''
615 642 versionController = new VersionController();
616 643 versionController.init();
617 644
645 reviewersController = new ReviewersController();
618 646
619 647 $(function(){
620 ReviewerAutoComplete('user');
648
621 649 // custom code mirror
622 650 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
623 651
@@ -655,13 +683,13 b''
655 683 var ReviewersPanel = {
656 684 editButton: $('#open_edit_reviewers'),
657 685 closeButton: $('#close_edit_reviewers'),
658 addButton: $('#add_reviewer_input'),
659 removeButtons: $('.reviewer_member_remove'),
686 addButton: $('#add_reviewer'),
687 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
660 688
661 689 init: function() {
662 var that = this;
663 this.editButton.on('click', function(e) { that.edit(); });
664 this.closeButton.on('click', function(e) { that.close(); });
690 var self = this;
691 this.editButton.on('click', function(e) { self.edit(); });
692 this.closeButton.on('click', function(e) { self.close(); });
665 693 },
666 694
667 695 edit: function(event) {
@@ -669,6 +697,9 b''
669 697 this.closeButton.show();
670 698 this.addButton.show();
671 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 705 close: function(event) {
@@ -676,6 +707,8 b''
676 707 this.closeButton.hide();
677 708 this.addButton.hide();
678 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 807 $(this).attr('disabled', 'disabled');
775 808 $(this).addClass('disabled');
776 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 814 $('#update_commits').on('click', function(e){
@@ -784,16 +818,13 b''
784 818 $(e.currentTarget).removeClass('btn-primary');
785 819 $(e.currentTarget).text(_gettext('Updating...'));
786 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 825 // fixing issue with caches on firefox
791 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 828 $('.show-inline-comments').on('click', function(e){
798 829 var boxid = $(this).attr('data-comment-id');
799 830 var button = $(this);
@@ -818,6 +849,8 b''
818 849 // initial injection
819 850 injectCloseAction();
820 851
852 ReviewerAutoComplete('#user');
853
821 854 })
822 855 </script>
823 856
@@ -45,12 +45,12 b''
45 45 ##main
46 46 <div class="sidebar">
47 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>
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>
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>
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>
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>
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>
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.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.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.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.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.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':1})}">${_('From this repo')}</a></li>
54 54 </ul>
55 55 </div>
56 56
@@ -73,7 +73,7 b''
73 73 %endif
74 74 </h3>
75 75 </div>
76 <div class="panel-body">
76 <div class="panel-body panel-body-min-height">
77 77 <table id="pull_request_list_table" class="display"></table>
78 78 </div>
79 79 </div>
@@ -83,14 +83,24 b''
83 83
84 84 <script type="text/javascript">
85 85 $(document).ready(function() {
86
87 var $pullRequestListTable = $('#pull_request_list_table');
88
86 89 // object list
87 $('#pull_request_list_table').DataTable({
88 data: ${c.data|n},
89 processing: false,
90 $pullRequestListTable.DataTable({
91 processing: true,
90 92 serverSide: true,
91 deferLoading: ${c.records_total},
92 ajax: "",
93 dom: 'tp',
93 ajax: {
94 "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}",
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 104 pageLength: ${c.visual.dashboard_items},
95 105 order: [[ 1, "desc" ]],
96 106 columns: [
@@ -109,6 +119,7 b''
109 119 ],
110 120 language: {
111 121 paginate: DEFAULT_GRID_PAGINATION,
122 sProcessing: _gettext('loading...'),
112 123 emptyTable: _gettext("No pull requests available yet.")
113 124 },
114 125 "drawCallback": function( settings, json ) {
@@ -120,13 +131,16 b''
120 131 }
121 132 }
122 133 });
123 });
124 $('#pull_request_list_table').on('xhr.dt', function(e, settings, json, xhr){
125 $('#pull_request_list_table').css('opacity', 1);
134
135 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
136 $pullRequestListTable.css('opacity', 1);
126 137 });
127 138
128 $('#pull_request_list_table').on('preXhr.dt', function(e, settings, data){
129 $('#pull_request_list_table').css('opacity', 0.3);
139 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
140 $pullRequestListTable.css('opacity', 0.3);
130 141 });
142
143 });
144
131 145 </script>
132 146 </%def>
@@ -14,7 +14,7 b''
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 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 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -45,7 +45,7 b''
45 45 <div class="title">
46 46 ${self.repo_page_title(c.rhodecode_db_repo)}
47 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 49 %else:
50 50 <!-- box / title -->
51 51 <div class="title">
@@ -53,7 +53,7 b''
53 53 <ul class="links">&nbsp;</ul>
54 54 </div>
55 55 <!-- end box / title -->
56 ${h.form(h.url('search'),method='get')}
56 ${h.form(h.route_path('search'), method='get')}
57 57 %endif
58 58 <div class="form search-form">
59 59 <div class="fields">
@@ -63,6 +63,7 b''
63 63 ${h.select('type',c.search_type,[('content',_('File contents')), ('commit',_('Commit messages')), ('path',_('File names')),],id='id_search_type')}
64 64 <input type="submit" value="${_('Search')}" class="btn"/>
65 65 <br/>
66
66 67 <div class="search-feedback-items">
67 68 % for error in c.errors:
68 69 <span class="error-message">
@@ -71,10 +72,16 b''
71 72 % endfor
72 73 </span>
73 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 80 <div class="field">${c.runtime}</div>
75 81 </div>
76 82 </div>
77 83 </div>
84
78 85 ${h.end_form()}
79 86 <div class="search">
80 87 % if c.search_type == 'content':
@@ -27,7 +27,7 b''
27 27 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
28 28 <i class="icon-svn"></i>
29 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 31 </td>
32 32 <td class="td-commit">
33 33 ${h.link_to(h._shorten_commit_id(entry['commit_id']),
@@ -49,12 +49,12 b' for line_number in matching_lines:'
49 49 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
50 50 <i class="icon-svn"></i>
51 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 53 </h2>
54 54 <div class="stats">
55 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 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 58 %endif
59 59 %if entry.get('size'):
60 60 | ${h.format_byte_size_binary(entry['size'])}
@@ -16,7 +16,7 b''
16 16 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
17 17 <i class="icon-svn"></i>
18 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 20 </td>
21 21 <td class="td-componentname">
22 22 ${h.link_to(h.literal(entry['f_path']),
@@ -10,8 +10,8 b''
10 10
11 11
12 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" />
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" />
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="${h.tooltip(_('%s RSS feed') % c.repo_name)}" type="application/rss+xml" />
15 15 </%def>
16 16
17 17
@@ -1,28 +1,28 b''
1 1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 2 <span class="branchtag tag">
3 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${ungettext(
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${_ungettext(
5 5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 6 </span>
7 7
8 8 %if closed_branches:
9 9 <span class="branchtag tag">
10 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${ungettext(
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${_ungettext(
12 12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 13 </span>
14 14 %endif
15 15
16 16 <span class="tagtag tag">
17 <a href="${h.url('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${ungettext(
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${_ungettext(
19 19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 20 </span>
21 21
22 22 %if bookmarks:
23 23 <span class="booktag tag">
24 <a href="${h.url('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${ungettext(
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${_ungettext(
26 26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 27 </span>
28 28 %endif
@@ -53,7 +53,7 b''
53 53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
54 54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
55 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 57 </div>
58 58 %else:
59 59 <div class="left-label">
@@ -92,15 +92,15 b''
92 92
93 93 ## commits
94 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 96 % else:
97 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 99 % endif
100 100
101 101 ## forks
102 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 105 ## repo size
106 106 % if commit_rev == -1:
@@ -145,7 +145,7 b''
145 145 ${_('Statistics are disabled for this repository')}
146 146 </span>
147 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 149 % endif
150 150 % endif
151 151 </div>
@@ -169,7 +169,7 b''
169 169 ${_('Downloads are disabled for this repository')}
170 170 </span>
171 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 173 % endif
174 174 % else:
175 175 <span class="enabled">
@@ -14,8 +14,14 b''
14 14
15 15 <div class="alert alert-dismissable alert-warning">
16 16 <strong>Missing requirements</strong>
17 These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.
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.
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 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 25 </div>
20 26
21 27 </%def>
@@ -36,7 +36,7 b''
36 36 %endif
37 37 <div class="table">
38 38 <div id="shortlog_data">
39 <%include file='../changelog/changelog_summary_data.mako'/>
39 <%include file='summary_commits.mako'/>
40 40 </div>
41 41 </div>
42 42 </div>
@@ -44,13 +44,13 b''
44 44 %if c.readme_data:
45 45 <div id="readme" class="anchor">
46 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 48 <h3 class="breadcrumbs">
49 49 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
50 50 </h3>
51 51 </div>
52 52 <div class="readme codeblock">
53 <div class="readme_box markdown-block">
53 <div class="readme_box">
54 54 ${c.readme_data|n}
55 55 </div>
56 56 </div>
@@ -110,8 +110,7 b''
110 110
111 111 var callback = function (data) {
112 112 % if c.show_stats:
113 showRepoStats(
114 'lang_stats', data);
113 showRepoStats('lang_stats', data);
115 114 % endif
116 115 };
117 116
@@ -9,7 +9,7 b''
9 9 </%def>
10 10
11 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 13 <a href="${files_url}"><i class="icon-tag"></i>${name}</a>
14 14 </span>
15 15 </%def>
@@ -19,11 +19,11 b''
19 19 </%def>
20 20
21 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 23 </%def>
24 24
25 25 <%def name="commit(message, commit_id, commit_idx)">
26 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 28 </div>
29 29 </%def>
@@ -35,7 +35,7 b''
35 35 ${_('First name')}:
36 36 </div>
37 37 <div class="right-content">
38 ${c.user.firstname}
38 ${c.user.first_name}
39 39 </div>
40 40 </div>
41 41 <div class="fieldset">
@@ -43,7 +43,7 b''
43 43 ${_('Last name')}:
44 44 </div>
45 45 <div class="right-content">
46 ${c.user.lastname}
46 ${c.user.last_name}
47 47 </div>
48 48 </div>
49 49 <div class="fieldset">
@@ -48,7 +48,6 b' from rhodecode.model.db import User'
48 48 from rhodecode.lib import auth
49 49 from rhodecode.lib.helpers import flash, link_to
50 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 53 log = logging.getLogger(__name__)
@@ -56,7 +55,7 b' log = logging.getLogger(__name__)'
56 55 __all__ = [
57 56 'get_new_dir', 'TestController', 'SkipTest',
58 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 59 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
61 60 'NEW_HG_REPO', 'NEW_GIT_REPO',
62 61 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
@@ -178,7 +177,7 b' def login_user_session('
178 177 response = response.follow()
179 178 assert response.status == '200 OK'
180 179
181 session = get_session_from_response(response)
180 session = response.get_session_from_response()
182 181 assert 'rhodecode_user' in session
183 182 rc_user = session['rhodecode_user']
184 183 assert rc_user.get('username') == username
@@ -249,3 +248,17 b' def assert_session_flash_is_empty(respon'
249 248 msg = 'flash messages are present in session:%s' % \
250 249 response.session['flash'][0]
251 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 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import mock
22
21 import pytest
22 import requests
23 23 from rhodecode.config import routing_links
24 24
25 25
26 def test_connect_redirection_links():
27 link_config = [
28 {"name": "example_link",
29 "external_target": "http://example.com",
30 "target": "https://rhodecode.com/r/v1/enterprise/example",
31 },
32 ]
26 def check_connection():
27 try:
28 response = requests.get('https://rhodecode.com')
29 return response.status_code == 200
30 except Exception as e:
31 print(e)
32
33 return False
34
33 35
34 rmap = mock.Mock()
35 with mock.patch.object(routing_links, 'link_config', link_config):
36 routing_links.connect_redirection_links(rmap)
36 connection_available = pytest.mark.skipif(
37 not check_connection(), reason="No outside internet connection available")
38
37 39
38 rmap.connect.assert_called_with(
39 link_config[0]['name'], link_config[0]['target'],
40 _static=True)
40 @connection_available
41 def test_connect_redirection_links():
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 22 import pytest
23 23
24 from rhodecode.tests import no_newline_id_generator
24 25 from rhodecode.config.middleware import (
25 26 _sanitize_vcs_settings, _bool_setting, _string_setting, _list_setting,
26 27 _int_setting)
@@ -70,7 +71,7 b' class TestHelperFunctions(object):'
70 71 (' hg\n git\n svn ', ['hg', 'git', 'svn']),
71 72 (', hg , git , svn , ', ['', 'hg', 'git', 'svn', '']),
72 73 ('cheese,free node,other', ['cheese', 'free node', 'other']),
73 ])
74 ], ids=no_newline_id_generator)
74 75 def test_list_setting_helper(self, raw, expected):
75 76 key = 'dummy-key'
76 77 settings = {key: raw}
@@ -64,7 +64,7 b' def test_repo_groups_load_defaults('
64 64 personal_group = personal_group_with_parent
65 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 68 returned_group_ids = [group[0] for group in c.repo_groups]
69 69 assert returned_group_ids == expected_list
70 70
@@ -74,6 +74,6 b' def test_repo_groups_load_defaults_with_'
74 74 personal_group = personal_group_with_parent
75 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 78 returned_group_ids = sorted([group[0] for group in c.repo_groups])
79 79 assert returned_group_ids == expected_list
@@ -42,7 +42,7 b' from rhodecode.events import ('
42 42 PullRequestMergeEvent,
43 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 46 pr = pr_util.create_pull_request()
47 47 event = EventClass(pr)
48 48 data = event.as_dict()
@@ -50,16 +50,19 b' def test_pullrequest_events_serialized(p'
50 50 assert data['repo']['repo_name'] == pr.target_repo.repo_name
51 51 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
52 52 assert data['pullrequest']['url']
53 assert data['pullrequest']['permalink_url']
54
53 55
54 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 58 with EventCatcher() as event_catcher:
57 59 pr_util.create_pull_request()
58 60
59 61 assert PullRequestCreateEvent in event_catcher.events_types
60 62
63
61 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 66 pr = pr_util.create_pull_request()
64 67 comment = CommentsModel().get_comments(
65 68 pr.target_repo.repo_id, pull_request=pr)[0]
@@ -69,11 +72,12 b' def test_pullrequest_comment_events_seri'
69 72 assert data['repo']['repo_name'] == pr.target_repo.repo_name
70 73 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
71 74 assert data['pullrequest']['url']
75 assert data['pullrequest']['permalink_url']
72 76 assert data['comment']['text'] == comment.text
73 77
74 78
75 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 81 pr = pr_util.create_pull_request()
78 82
79 83 with EventCatcher() as event_catcher:
@@ -83,7 +87,7 b' def test_close_pull_request_events(pr_ut'
83 87
84 88
85 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 91 pr = pr_util.create_pull_request()
88 92
89 93 with EventCatcher() as event_catcher:
@@ -43,6 +43,7 b' def scm_extras(user_regular, repo_stub):'
43 43 'config': '',
44 44 'server_url': 'http://example.com',
45 45 'make_lock': None,
46 'user-agent': 'some-client',
46 47 'locked_by': [None],
47 48 'commit_ids': ['a' * 40] * 3,
48 49 'is_shadow_repo': False,
@@ -55,28 +56,29 b' def scm_extras(user_regular, repo_stub):'
55 56 RepoPreCreateEvent, RepoCreateEvent,
56 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 60 event = EventClass(repo_stub)
60 61 data = event.as_dict()
61 62 assert data['name'] == EventClass.name
62 63 assert data['repo']['repo_name'] == repo_stub.repo_name
63 64 assert data['repo']['url']
65 assert data['repo']['permalink_url']
64 66
65 67
66 68 @pytest.mark.parametrize('EventClass', [
67 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 72 event = EventClass(repo_name=repo_stub.repo_name, extras=scm_extras)
71 73 data = event.as_dict()
72 74 assert data['name'] == EventClass.name
73 75 assert data['repo']['repo_name'] == repo_stub.repo_name
74 76 assert data['repo']['url']
75
77 assert data['repo']['permalink_url']
76 78
77 79
78 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 82 event = EventClass(repo_name=repo_stub.repo_name,
81 83 pushed_commit_ids=scm_extras['commit_ids'],
82 84 extras=scm_extras)
@@ -84,6 +86,7 b' def test_vcs_repo_push_event_serialize(r'
84 86 assert data['name'] == EventClass.name
85 87 assert data['repo']['repo_name'] == repo_stub.repo_name
86 88 assert data['repo']['url']
89 assert data['repo']['permalink_url']
87 90
88 91
89 92 def test_create_delete_repo_fires_events(backend):
@@ -286,6 +286,10 b' class Fixture(object):'
286 286 gr = UserGroup.get_by_group_name(group_name=name)
287 287 if gr:
288 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 293 form_data = self._get_user_group_create_params(name, **kwargs)
290 294 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
291 295 user_group = UserGroupModel().create(
@@ -28,7 +28,6 b' from rhodecode.model.meta import Session'
28 28 from rhodecode.tests import (
29 29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
30 30 TestController, assert_session_flash, url)
31 from rhodecode.tests.utils import AssertResponse
32 31
33 32
34 33 class GistUtility(object):
@@ -273,7 +272,7 b' class TestGistsController(TestController'
273 272
274 273 response.mustcontain('added file: gist-show-me<')
275 274
276 assert_response = AssertResponse(response)
275 assert_response = response.assert_response()
277 276 assert_response.element_equals_to(
278 277 'div.rc-user span.user',
279 278 '<a href="/_profiles/test_admin">test_admin</a></span>')
@@ -296,7 +295,7 b' class TestGistsController(TestController'
296 295 response = self.app.get(url('gist', gist_id=gist.gist_access_id))
297 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 299 assert_response.element_equals_to(
301 300 'div.rc-user span.user',
302 301 '<a href="/_profiles/test_admin">test_admin</a></span>')
@@ -337,9 +336,7 b' class TestGistsController(TestController'
337 336
338 337 def test_user_first_name_is_escaped(self, user_util, create_gist):
339 338 xss_atack_string = '"><script>alert(\'First Name\')</script>'
340 xss_escaped_string = (
341 '&#34;&gt;&lt;script&gt;alert(&#39;First Name&#39;)&lt;/script'
342 '&gt;')
339 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
343 340 password = 'test'
344 341 user = user_util.create_user(
345 342 firstname=xss_atack_string, password=password)
@@ -349,8 +346,7 b' class TestGistsController(TestController'
349 346
350 347 def test_user_last_name_is_escaped(self, user_util, create_gist):
351 348 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
352 xss_escaped_string = (
353 '&#34;&gt;&lt;script&gt;alert(&#39;Last Name&#39;)&lt;/script&gt;')
349 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
354 350 password = 'test'
355 351 user = user_util.create_user(
356 352 lastname=xss_atack_string, password=password)
@@ -23,14 +23,19 b' import pytest'
23 23 from rhodecode.lib import helpers as h
24 24 from rhodecode.model.db import User, UserFollowing, Repository
25 25 from rhodecode.tests import (
26 TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
27 assert_session_flash)
26 TestController, url, TEST_USER_ADMIN_LOGIN, assert_session_flash)
28 27 from rhodecode.tests.fixture import Fixture
29 28 from rhodecode.tests.utils import AssertResponse
30 29
31 30 fixture = Fixture()
32 31
33 32
33 def route_path(name, **kwargs):
34 return {
35 'home': '/',
36 }[name].format(**kwargs)
37
38
34 39 class TestMyAccountController(TestController):
35 40 test_user_1 = 'testme'
36 41 test_user_1_password = '0jd83nHNS/d23n'
@@ -41,7 +46,7 b' class TestMyAccountController(TestContro'
41 46 fixture.destroy_users(cls.destroy_users)
42 47
43 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 50 assert_response = AssertResponse(response)
46 51 element = assert_response.get_element('.logout #csrf_token')
47 52 assert element.value == csrf_token
@@ -52,26 +57,6 b' class TestMyAccountController(TestContro'
52 57
53 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 60 @pytest.mark.backends("git", "hg")
76 61 def test_my_account_my_pullrequests(self, pr_util):
77 62 self.log_user()
@@ -84,57 +69,6 b' class TestMyAccountController(TestContro'
84 69 response.mustcontain('"name_raw": %s' % pr.pull_request_id)
85 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 72 @pytest.mark.parametrize(
139 73 "name, attrs", [
140 74 ('firstname', {'firstname': 'new_username'}),
@@ -25,6 +25,24 b' from rhodecode.tests import ('
25 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 46 class TestAdminPermissionsController(TestController):
29 47
30 48 @pytest.fixture(scope='class', autouse=True)
@@ -181,10 +199,9 b' class TestAdminPermissionsController(Tes'
181 199
182 200 # ADD
183 201 default_user_id = User.get_default_user().user_id
184 response = self.app.post(
185 url('edit_user_ips', user_id=default_user_id),
186 params={'new_ip': '127.0.0.0/24', '_method': 'put',
187 'csrf_token': self.csrf_token})
202 self.app.post(
203 route_path('edit_user_ips_add', user_id=default_user_id),
204 params={'new_ip': '127.0.0.0/24', 'csrf_token': self.csrf_token})
188 205
189 206 response = self.app.get(url('admin_permissions_ips'))
190 207 response.mustcontain('127.0.0.0/24')
@@ -196,9 +213,11 b' class TestAdminPermissionsController(Tes'
196 213 default_user_id).first().ip_id
197 214
198 215 response = self.app.post(
199 url('edit_user_ips', user_id=default_user_id),
200 params={'_method': 'delete', 'del_ip_id': del_ip_id,
201 'csrf_token': self.csrf_token})
216 route_path('edit_user_ips_delete', user_id=default_user_id),
217 params={'del_ip_id': del_ip_id, 'csrf_token': self.csrf_token})
218
219 assert_session_flash(response, 'Removed ip address from user whitelist')
220
202 221 clear_all_caches()
203 222 response = self.app.get(url('admin_permissions_ips'))
204 223 response.mustcontain('All IP addresses are allowed')
@@ -31,6 +31,7 b' from rhodecode.tests.fixture import Fixt'
31 31 fixture = Fixture()
32 32
33 33
34
34 35 def test_update(app, csrf_token, autologin_user, user_util):
35 36 repo_group = user_util.create_repo_group()
36 37 description = 'description for newly created repo group'
@@ -123,11 +124,13 b' class _BaseTest(TestController):'
123 124 # run the check page that triggers the flash message
124 125 # response = self.app.get(url('repo_check_home', repo_name=repo_name))
125 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 130 assert_session_flash(
127 131 response,
128 u'Created repository group <a href="%s">%s</a>' % (
129 h.url('repo_group_home', group_name=repo_group_name),
130 repo_group_name_unicode))
132 'Created repository group <a href="%s">%s</a>' % (
133 repo_gr_url, repo_group_name_unicode))
131 134
132 135 # # test if the repo group was created in the database
133 136 new_repo_group = RepoGroupModel()._get_repo_group(
@@ -138,8 +141,7 b' class _BaseTest(TestController):'
138 141 assert new_repo_group.group_description == description
139 142
140 143 # test if the repository is visible in the list ?
141 response = self.app.get(
142 url('repo_group_home', group_name=repo_group_name))
144 response = self.app.get(repo_gr_url)
143 145 response.mustcontain(repo_group_name)
144 146
145 147 # test if the repository group was created on filesystem
@@ -173,7 +175,7 b' class _BaseTest(TestController):'
173 175 assert_session_flash(
174 176 response,
175 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 179 expected_group_name_unicode))
178 180 finally:
179 181 RepoGroupModel().delete(expected_group_name_unicode)
@@ -199,6 +201,13 b' class TestRepoGroupsControllerGIT(_BaseT'
199 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 211 class TestRepoGroupsControllerHG(_BaseTest):
203 212 REPO_GROUP = None
204 213 NEW_REPO_GROUP = 'hg_repo'
@@ -25,9 +25,9 b' import pytest'
25 25
26 26 from rhodecode.lib import auth
27 27 from rhodecode.lib.utils2 import safe_str, str2bool
28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
30 Permission
28 from rhodecode.lib import helpers as h
29 from rhodecode.model.db import (
30 Repository, RepoGroup, UserRepoToPerm, User, Permission)
31 31 from rhodecode.model.meta import Session
32 32 from rhodecode.model.repo import RepoModel
33 33 from rhodecode.model.repo_group import RepoGroupModel
@@ -44,7 +44,7 b' fixture = Fixture()'
44 44
45 45
46 46 @pytest.mark.usefixtures("app")
47 class TestAdminRepos:
47 class TestAdminRepos(object):
48 48
49 49 def test_index(self):
50 50 self.app.get(url('repos'))
@@ -63,13 +63,14 b' class TestAdminRepos:'
63 63 assert_response.element_contains('#repo_type', 'svn')
64 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 68 def test_create(self, autologin_user, backend, suffix, csrf_token):
68 69 repo_name_unicode = backend.new_repo_name(suffix=suffix)
69 70 repo_name = repo_name_unicode.encode('utf8')
70 71 description_unicode = u'description for newly created repo' + suffix
71 72 description = description_unicode.encode('utf8')
72 self.app.post(
73 response = self.app.post(
73 74 url('repos'),
74 75 fixture._get_repo_create_params(
75 76 repo_private=False,
@@ -77,8 +78,7 b' class TestAdminRepos:'
77 78 repo_type=backend.alias,
78 79 repo_description=description,
79 80 csrf_token=csrf_token),
80 status=302
81 )
81 status=302)
82 82
83 83 self.assert_repository_is_created_correctly(
84 84 repo_name, description, backend)
@@ -368,85 +368,23 b' class TestAdminRepos:'
368 368 csrf_token=csrf_token))
369 369 response.mustcontain('invalid clone url')
370 370
371 @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii'])
372 def test_delete(self, autologin_user, backend, suffix, csrf_token):
373 repo = backend.create_repo(name_suffix=suffix)
374 repo_name = repo.repo_name
375
376 response = self.app.post(url('repo', repo_name=repo_name),
377 params={'_method': 'delete',
378 'csrf_token': csrf_token})
379 assert_session_flash(response, 'Deleted repository %s' % (repo_name))
380 response.follow()
381
382 # check if repo was deleted from db
383 assert RepoModel().get_by_repo_name(repo_name) is None
384 assert not repo_on_filesystem(repo_name)
371 def test_create_with_git_suffix(
372 self, autologin_user, backend, csrf_token):
373 repo_name = backend.new_repo_name() + ".git"
374 description = 'description for newly created repo'
375 response = self.app.post(
376 url('repos'),
377 fixture._get_repo_create_params(
378 repo_private=False,
379 repo_name=repo_name,
380 repo_type=backend.alias,
381 repo_description=description,
382 csrf_token=csrf_token))
383 response.mustcontain('Repository name cannot end with .git')
385 384
386 385 def test_show(self, autologin_user, backend):
387 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 388 def test_default_user_cannot_access_private_repo_in_a_group(
451 389 self, autologin_user, user_util, backend, csrf_token):
452 390
@@ -461,82 +399,6 b' class TestAdminRepos:'
461 399 assert permissions[0].permission.permission_name == 'repository.none'
462 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 402 def test_create_on_top_level_without_permissions(self, backend):
541 403 session = login_user_session(
542 404 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
@@ -596,15 +458,15 b' class TestAdminRepos:'
596 458
597 459 def assert_repository_is_created_correctly(
598 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 463 # run the check page that triggers the flash message
602 464 response = self.app.get(url('repo_check_home', repo_name=repo_name))
603 465 assert response.json == {u'result': True}
604 assert_session_flash(
605 response,
606 u'Created repository <a href="/%s">%s</a>'
607 % (urllib.quote(repo_name_utf8), repo_name))
466
467 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
468 urllib.quote(repo_name_utf8), repo_name)
469 assert_session_flash(response, flash_msg)
608 470
609 471 # test if the repo was created in the database
610 472 new_repo = RepoModel().get_by_repo_name(repo_name)
@@ -613,7 +475,7 b' class TestAdminRepos:'
613 475 assert new_repo.description == description
614 476
615 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 479 response.mustcontain(repo_name)
618 480 response.mustcontain(backend.alias)
619 481
@@ -628,6 +490,7 b' class TestVcsSettings(object):'
628 490 'hooks_changegroup_push_logger': False,
629 491 'hooks_outgoing_pull_logger': False,
630 492 'extensions_largefiles': False,
493 'extensions_evolve': False,
631 494 'phases_publish': 'False',
632 495 'rhodecode_pr_merge_enabled': False,
633 496 'rhodecode_use_outdated_comments': False,
@@ -783,13 +646,14 b' class TestVcsSettings(object):'
783 646 self, backend, user_util, settings_util, csrf_token):
784 647 repo = backend.create_repo()
785 648 repo_name = repo.repo_name
786 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
787 649
788 650 logout_user_session(self.app, csrf_token)
789 651 session = login_user_session(
790 652 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
791 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 657 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
794 658 data = self.FORM_DATA.copy()
795 659 data['csrf_token'] = new_csrf_token
@@ -1165,11 +1029,14 b' class TestVcsSettings(object):'
1165 1029 self, backend_svn, user_util, settings_util, csrf_token):
1166 1030 repo = backend_svn.create_repo()
1167 1031 repo_name = repo.repo_name
1168 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1032
1169 1033 logout_user_session(self.app, csrf_token)
1170 1034 session = login_user_session(
1171 1035 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1172 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 1040 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1174 1041 branch = settings_util.create_repo_rhodecode_ui(
1175 1042 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
@@ -275,6 +275,23 b' class TestAdminSettingsVcs(object):'
275 275 'value="True" checked="checked" />')
276 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 295 def test_has_a_section_for_pull_request_settings(self, app):
279 296 response = app.get(url('admin_settings_vcs'))
280 297 response.mustcontain('Pull Request Settings')
@@ -445,7 +462,7 b' class TestOpenSourceLicenses(object):'
445 462 '.panel-heading', 'Licenses of Third Party Packages')
446 463
447 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 468 @pytest.mark.usefixtures('app')
@@ -458,7 +475,7 b' class TestUserSessions(object):'
458 475 }[name]
459 476
460 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 480 def test_show_sessions_page(self, autologin_user):
464 481 response = self.app.get(self._get_url(), status=200)
@@ -485,7 +502,7 b' class TestAdminSystemInfo(object):'
485 502 }[name]
486 503
487 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 507 def test_system_info_page(self, autologin_user):
491 508 response = self.app.get(self._get_url())
@@ -510,66 +510,3 b' class TestAdminUsersController(TestContr'
510 510 element = assert_response.get_element(css_selector)
511 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 489 compare_page = ComparePage(response)
490 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 508 repo = backend.repo
494 509 badrepo = 'badrepo'
495 510
@@ -504,7 +519,8 b' class TestCompareController(object):'
504 519 merge='1',),
505 520 status=302)
506 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 525 def test_compare_not_in_preview_mode(self, backend_stub):
510 526 commit0 = backend_stub.repo.get_commit(commit_idx=0)
@@ -30,6 +30,12 b' from rhodecode.tests.fixture import Fixt'
30 30 fixture = Fixture()
31 31
32 32
33 def route_path(name, **kwargs):
34 return {
35 'home': '/',
36 }[name].format(**kwargs)
37
38
33 39 class TestAdminUsersGroupsController(TestController):
34 40
35 41 def test_regular_user_cannot_see_admin_interfaces(self, user_util):
@@ -37,7 +43,7 b' class TestAdminUsersGroupsController(Tes'
37 43 self.log_user(user.username, 'qweqwe')
38 44
39 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 48 assert_response = response.assert_response()
43 49
@@ -69,7 +75,7 b' class TestAdminUsersGroupsController(Tes'
69 75
70 76 self.log_user(username, 'qweqwe')
71 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 80 assert_response = response.assert_response()
75 81
@@ -109,7 +115,7 b' class TestAdminUsersGroupsController(Tes'
109 115
110 116 self.log_user(username, 'qweqwe')
111 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 120 assert_response = response.assert_response()
115 121
@@ -484,7 +484,7 b' class TestRawFileHandling(object):'
484 484
485 485 msg = (
486 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 488 response.mustcontain(msg)
489 489
490 490 def test_raw_ok(self, backend):
@@ -517,7 +517,7 b' class TestRawFileHandling(object):'
517 517 f_path=f_path), status=404)
518 518 msg = (
519 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 521 response.mustcontain(msg)
522 522
523 523 def test_raw_svg_should_not_be_rendered(self, backend):
@@ -758,9 +758,8 b' class TestChangingFiles:'
758 758 'csrf_token': csrf_token,
759 759 },
760 760 status=302)
761 assert_session_flash(
762 response, 'Successfully committed to %s'
763 % os.path.join(filename))
761 assert_session_flash(response,
762 'Successfully committed new file `{}`'.format(os.path.join(filename)))
764 763
765 764 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
766 765 response = self.app.post(
@@ -796,11 +795,12 b' class TestChangingFiles:'
796 795
797 796 # Not allowed, redirect to the summary
798 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 800 # As there are no commits, displays the summary page with the error of
802 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 805 @pytest.mark.parametrize("location, filename", [
806 806 ('/abs', 'foo'),
@@ -847,9 +847,9 b' class TestChangingFiles:'
847 847 'csrf_token': csrf_token,
848 848 },
849 849 status=302)
850 assert_session_flash(
851 response, 'Successfully committed to %s'
852 % os.path.join(location, filename))
850 assert_session_flash(response,
851 'Successfully committed new file `{}`'.format(
852 os.path.join(location, filename)))
853 853
854 854 def test_edit_file_view(self, backend):
855 855 response = self.app.get(
@@ -893,7 +893,7 b' class TestChangingFiles:'
893 893 },
894 894 status=302)
895 895 assert_session_flash(
896 response, 'Successfully committed to vcs/nodes.py')
896 response, 'Successfully committed changes to file `vcs/nodes.py`')
897 897 tip = repo.get_commit(commit_idx=-1)
898 898 assert tip.message == 'I committed'
899 899
@@ -920,7 +920,7 b' class TestChangingFiles:'
920 920 },
921 921 status=302)
922 922 assert_session_flash(
923 response, 'Successfully committed to vcs/nodes.py')
923 response, 'Successfully committed changes to file `vcs/nodes.py`')
924 924 tip = repo.get_commit(commit_idx=-1)
925 925 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
926 926
@@ -960,7 +960,7 b' class TestChangingFiles:'
960 960 },
961 961 status=302)
962 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 966 def assert_files_in_response(response, files, params):
@@ -22,6 +22,7 b' import pytest'
22 22
23 23 from rhodecode.tests import *
24 24 from rhodecode.tests.fixture import Fixture
25 from rhodecode.lib import helpers as h
25 26
26 27 from rhodecode.model.db import Repository
27 28 from rhodecode.model.repo import RepoModel
@@ -74,7 +75,7 b' class _BaseTest(TestController):'
74 75 repo_name = self.REPO
75 76 self.app.post(
76 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 80 def test_index_with_fork(self):
80 81 self.log_user()
@@ -106,9 +107,7 b' class _BaseTest(TestController):'
106 107 )
107 108
108 109 # remove this fork
109 response = self.app.post(
110 url('repo', repo_name=fork_name),
111 params={'_method': 'delete', 'csrf_token': self.csrf_token})
110 fixture.destroy_repo(fork_name)
112 111
113 112 def test_fork_create_into_group(self):
114 113 self.log_user()
@@ -149,7 +148,7 b' class _BaseTest(TestController):'
149 148 assert fork_repo.fork.repo_name == repo_name
150 149
151 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 152 response.mustcontain(fork_name_full)
154 153 response.mustcontain(self.REPO_TYPE)
155 154
@@ -195,7 +194,7 b' class _BaseTest(TestController):'
195 194 assert fork_repo.fork.repo_name == repo_name
196 195
197 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 198 response.mustcontain(fork_name)
200 199 response.mustcontain(self.REPO_TYPE)
201 200 response.mustcontain('Fork of')
@@ -256,7 +255,7 b' class TestHG(_BaseTest):'
256 255
257 256 @pytest.mark.usefixtures('app', 'autologin_user')
258 257 @pytest.mark.skip_backends('git','hg')
259 class TestSVNFork:
258 class TestSVNFork(object):
260 259
261 260 def test_fork_redirects(self, backend):
262 261 denied_actions = ['fork','fork_create']
@@ -268,7 +267,7 b' class TestSVNFork:'
268 267
269 268 # Not allowed, redirect to the summary
270 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 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
@@ -25,10 +25,11 b' import pytest'
25 25
26 26 from rhodecode.config.routing import ADMIN_PREFIX
27 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 30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 31 from rhodecode.lib.auth import check_password
32 from rhodecode.lib import helpers as h
32 33 from rhodecode.model.auth_token import AuthTokenModel
33 34 from rhodecode.model import validators
34 35 from rhodecode.model.db import User, Notification, UserApiKeys
@@ -71,7 +72,7 b' class TestLoginController(object):'
71 72 {'username': 'test_admin',
72 73 'password': 'test12'})
73 74 assert response.status == '302 Found'
74 session = get_session_from_response(response)
75 session = response.get_session_from_response()
75 76 username = session['rhodecode_user'].get('username')
76 77 assert username == 'test_admin'
77 78 response = response.follow()
@@ -83,7 +84,7 b' class TestLoginController(object):'
83 84 'password': 'test12'})
84 85
85 86 assert response.status == '302 Found'
86 session = get_session_from_response(response)
87 session = response.get_session_from_response()
87 88 username = session['rhodecode_user'].get('username')
88 89 assert username == 'test_regular'
89 90 response = response.follow()
@@ -105,8 +106,9 b' class TestLoginController(object):'
105 106 with fixture.anon_access(False):
106 107 kwargs = {'branch': 'stable'}
107 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 110 assert response.status == '302 Found'
111
110 112 response_query = urlparse.parse_qsl(response.location)
111 113 assert 'branch=stable' in response_query[0][1]
112 114
@@ -122,7 +124,7 b' class TestLoginController(object):'
122 124 'ftp://some.ftp.server',
123 125 'http://other.domain',
124 126 '/\r\nX-Forwarded-Host: http://example.org',
125 ])
127 ], ids=no_newline_id_generator)
126 128 def test_login_bad_came_froms(self, url_came_from):
127 129 _url = '{}?came_from={}'.format(login_url, url_came_from)
128 130 response = self.app.post(
@@ -183,7 +185,7 b' class TestLoginController(object):'
183 185 'password': 'test123'})
184 186
185 187 assert response.status == '302 Found'
186 session = get_session_from_response(response)
188 session = response.get_session_from_response()
187 189 username = session['rhodecode_user'].get('username')
188 190 assert username == temp_user
189 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 218 msg = validators.ValidUsername()._messages['username_exists']
217 219 msg = msg % {'username': uname}
218 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 236 msg = validators.UniqSystemEmail()()._messages['email_taken']
235 237 assertr.element_contains('#email+.error-message', msg)
236 238
@@ -246,7 +248,7 b' class TestLoginController(object):'
246 248 'lastname': 'test'
247 249 }
248 250 )
249 assertr = AssertResponse(response)
251 assertr = response.assert_response()
250 252 msg = validators.UniqSystemEmail()()._messages['email_taken']
251 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 306 msg = validators.ValidUsername()._messages['username_exists']
305 307 msg = msg % {'username': usr}
306 308 assertr.element_contains('#username+.error-message', msg)
@@ -24,20 +24,21 b' from webob.exc import HTTPNotFound'
24 24
25 25 import rhodecode
26 26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.lib import helpers as h
27 28 from rhodecode.model.changeset_status import ChangesetStatusModel
28 29 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification)
30 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
30 31 from rhodecode.model.meta import Session
31 32 from rhodecode.model.pull_request import PullRequestModel
32 33 from rhodecode.model.user import UserModel
33 from rhodecode.model.repo import RepoModel
34 from rhodecode.tests import assert_session_flash, url, TEST_USER_ADMIN_LOGIN
34 from rhodecode.tests import (
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 36 from rhodecode.tests.utils import AssertResponse
36 37
37 38
38 39 @pytest.mark.usefixtures('app', 'autologin_user')
39 40 @pytest.mark.backends("git", "hg")
40 class TestPullrequestsController:
41 class TestPullrequestsController(object):
41 42
42 43 def test_index(self, backend):
43 44 self.app.get(url(
@@ -46,25 +47,12 b' class TestPullrequestsController:'
46 47
47 48 def test_option_menu_create_pull_request_exists(self, backend):
48 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 52 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
52 53 'pullrequest', repo_name=repo_name)
53 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 56 def test_create_pr_form_with_raw_commit_id(self, backend):
69 57 repo = backend.repo
70 58
@@ -97,7 +85,7 b' class TestPullrequestsController:'
97 85 'Server-side pull request merging is disabled.'
98 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 89 from rhodecode.tests.functional.test_login import login_url, logut_url
102 90 # Logout
103 91 response = self.app.post(
@@ -105,10 +93,11 b' class TestPullrequestsController:'
105 93 params={'csrf_token': csrf_token})
106 94 # Login as regular user
107 95 response = self.app.post(login_url,
108 {'username': 'test_regular',
96 {'username': TEST_USER_REGULAR_LOGIN,
109 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 102 response = self.app.get(url(
114 103 controller='pullrequests', action='show',
@@ -118,6 +107,22 b' class TestPullrequestsController:'
118 107 response.mustcontain('Server-side pull request merging is disabled.')
119 108
120 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 126 assert_response.one_element_exists('#close-pull-request-action')
122 127
123 128 def test_show_invalid_commit_id(self, pr_util):
@@ -232,7 +237,9 b' class TestPullrequestsController:'
232 237 assertr.element_contains(
233 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 243 pull_request = pr_util.create_pull_request(approved=True)
237 244 pull_request_id = pull_request.pull_request_id
238 245 author = pull_request.user_id
@@ -244,75 +251,82 b' class TestPullrequestsController:'
244 251 repo_name=pull_request.target_repo.scm_instance().name,
245 252 pull_request_id=str(pull_request_id)),
246 253 params={
247 'changeset_status': ChangesetStatus.STATUS_APPROVED,
248 254 'close_pull_request': '1',
249 255 'text': 'Closing a PR',
250 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 259 journal = UserLog.query()\
255 260 .filter(UserLog.user_id == author)\
256 261 .filter(UserLog.repository_id == repo)\
257 .filter(UserLog.action == action)\
262 .order_by('user_log_id') \
258 263 .all()
259 assert len(journal) == 1
264 assert journal[-1].action == 'repo.pull_request.close'
260 265
261 266 pull_request = PullRequest.get(pull_request_id)
262 267 assert pull_request.is_closed()
263 268
264 # check only the latest status, not the review status
265 269 status = ChangesetStatusModel().get_status(
266 270 pull_request.source_repo, pull_request=pull_request)
267 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 280 pull_request = pr_util.create_pull_request()
271 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 289 url(controller='pullrequests',
274 action='update',
290 action='comment',
275 291 repo_name=pull_request.target_repo.scm_instance().name,
276 pull_request_id=str(pull_request.pull_request_id)),
277 params={'close_pull_request': 'true', '_method': 'put',
278 'csrf_token': csrf_token})
292 pull_request_id=str(pull_request_id)),
293 params={
294 'close_pull_request': '1',
295 'csrf_token': csrf_token},
296 extra_environ=xhr_header)
279 297
280 298 pull_request = PullRequest.get(pull_request_id)
281 299
282 assert response.json is True
283 assert pull_request.is_closed()
300 journal = UserLog.query()\
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 306 # check only the latest status, not the review status
286 307 status = ChangesetStatusModel().get_status(
287 308 pull_request.source_repo, pull_request=pull_request)
288 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 313 pull_request = pr_util.create_pull_request()
292 314 pull_request_id = pull_request.pull_request_id
293 reviewers_data = [(1, ['reason']), (2, ['reason2'])]
294 PullRequestModel().update_reviewers(pull_request_id, reviewers_data)
295 author = pull_request.user_id
296 repo = pull_request.target_repo.repo_id
297 self.app.post(
315
316 response = self.app.post(
298 317 url(controller='pullrequests',
299 318 action='comment',
300 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 321 params={
303 'changeset_status': 'rejected',
304 'close_pull_request': '1',
322 'close_pull_request': 'true',
305 323 'csrf_token': csrf_token},
306 status=302)
324 extra_environ=xhr_header)
325
326 assert response.json
307 327
308 328 pull_request = PullRequest.get(pull_request_id)
309
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
329 assert pull_request.is_closed()
316 330
317 331 # check only the latest status, not the review status
318 332 status = ChangesetStatusModel().get_status(
@@ -340,6 +354,7 b' class TestPullrequestsController:'
340 354 ('source_ref', 'branch:default:' + commit_ids['change2']),
341 355 ('target_repo', target.repo_name),
342 356 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
357 ('common_ancestor', commit_ids['ancestor']),
343 358 ('pullrequest_desc', 'Description'),
344 359 ('pullrequest_title', 'Title'),
345 360 ('__start__', 'review_members:sequence'),
@@ -348,6 +363,7 b' class TestPullrequestsController:'
348 363 ('__start__', 'reasons:sequence'),
349 364 ('reason', 'Some reason'),
350 365 ('__end__', 'reasons:sequence'),
366 ('mandatory', 'False'),
351 367 ('__end__', 'reviewer:mapping'),
352 368 ('__end__', 'review_members:sequence'),
353 369 ('__start__', 'revisions:sequence'),
@@ -360,8 +376,9 b' class TestPullrequestsController:'
360 376 status=302)
361 377
362 378 location = response.headers['Location']
363 pull_request_id = int(location.rsplit('/', 1)[1])
364 pull_request = PullRequest.get(pull_request_id)
379 pull_request_id = location.rsplit('/', 1)[1]
380 assert pull_request_id != 'new'
381 pull_request = PullRequest.get(int(pull_request_id))
365 382
366 383 # check that we have now both revisions
367 384 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
@@ -398,6 +415,7 b' class TestPullrequestsController:'
398 415 ('source_ref', 'branch:default:' + commit_ids['change']),
399 416 ('target_repo', target.repo_name),
400 417 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
418 ('common_ancestor', commit_ids['ancestor']),
401 419 ('pullrequest_desc', 'Description'),
402 420 ('pullrequest_title', 'Title'),
403 421 ('__start__', 'review_members:sequence'),
@@ -406,6 +424,7 b' class TestPullrequestsController:'
406 424 ('__start__', 'reasons:sequence'),
407 425 ('reason', 'Some reason'),
408 426 ('__end__', 'reasons:sequence'),
427 ('mandatory', 'False'),
409 428 ('__end__', 'reviewer:mapping'),
410 429 ('__end__', 'review_members:sequence'),
411 430 ('__start__', 'revisions:sequence'),
@@ -417,21 +436,23 b' class TestPullrequestsController:'
417 436 status=302)
418 437
419 438 location = response.headers['Location']
420 pull_request_id = int(location.rsplit('/', 1)[1])
421 pull_request = PullRequest.get(pull_request_id)
439
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 444 # Check that a notification was made
424 445 notifications = Notification.query()\
425 446 .filter(Notification.created_by == pull_request.author.user_id,
426 447 Notification.type_ == Notification.TYPE_PULL_REQUEST,
427 Notification.subject.contains("wants you to review "
428 "pull request #%d"
429 % pull_request_id))
448 Notification.subject.contains(
449 "wants you to review pull request #%s" % pull_request_id))
430 450 assert len(notifications.all()) == 1
431 451
432 452 # Change reviewers and check that a notification was made
433 453 PullRequestModel().update_reviewers(
434 pull_request.pull_request_id, [(1, [])])
454 pull_request.pull_request_id, [(1, [], False)],
455 pull_request.author)
435 456 assert len(notifications.all()) == 2
436 457
437 458 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
@@ -462,6 +483,7 b' class TestPullrequestsController:'
462 483 ('source_ref', 'branch:default:' + commit_ids['change']),
463 484 ('target_repo', target.repo_name),
464 485 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
486 ('common_ancestor', commit_ids['ancestor']),
465 487 ('pullrequest_desc', 'Description'),
466 488 ('pullrequest_title', 'Title'),
467 489 ('__start__', 'review_members:sequence'),
@@ -470,6 +492,7 b' class TestPullrequestsController:'
470 492 ('__start__', 'reasons:sequence'),
471 493 ('reason', 'Some reason'),
472 494 ('__end__', 'reasons:sequence'),
495 ('mandatory', 'False'),
473 496 ('__end__', 'reviewer:mapping'),
474 497 ('__end__', 'review_members:sequence'),
475 498 ('__start__', 'revisions:sequence'),
@@ -481,8 +504,10 b' class TestPullrequestsController:'
481 504 status=302)
482 505
483 506 location = response.headers['Location']
484 pull_request_id = int(location.rsplit('/', 1)[1])
485 pull_request = PullRequest.get(pull_request_id)
507
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 512 # target_ref has to point to the ancestor's commit_id in order to
488 513 # show the correct diff
@@ -519,18 +544,21 b' class TestPullrequestsController:'
519 544 pull_request, ChangesetStatus.STATUS_APPROVED)
520 545
521 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 548 actions = [log.action for log in user_logs]
524 549 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
525 550 expected_actions = [
526 u'user_closed_pull_request:%d' % pull_request_id,
527 u'user_merged_pull_request:%d' % pull_request_id,
528 # The action below reflect that the post push actions were executed
529 u'user_commented_pull_request:%d' % pull_request_id,
530 u'push:%s' % ','.join(pr_commit_ids),
551 u'repo.pull_request.close',
552 u'repo.pull_request.merge',
553 u'repo.pull_request.comment.create'
531 554 ]
532 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 562 # Check post_push rcextension was really executed
535 563 push_calls = rhodecode.EXTENSIONS.calls['post_push']
536 564 assert len(push_calls) == 1
@@ -941,18 +969,9 b' class TestPullrequestsController:'
941 969 assert target.text.strip() == 'tag: target'
942 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 972 @pytest.mark.parametrize('mergeable', [True, False])
954 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 976 Check that the pull request summary page displays a link to the shadow
958 977 repository if the pull request is mergeable. If it is not mergeable
@@ -963,7 +982,7 b' class TestPullrequestsController:'
963 982 target_repo = pull_request.target_repo.scm_instance()
964 983 pr_id = pull_request.pull_request_id
965 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 987 response = self.app.get(url(
969 988 controller='pullrequests', action='show',
@@ -1041,6 +1060,17 b' class TestPullrequestsControllerDelete(o'
1041 1060 response.mustcontain('id="delete_pullrequest"')
1042 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 1075 def assert_pull_request_status(pull_request, expected_status):
1046 1076 status = ChangesetStatusModel().calculated_review_status(
@@ -1048,12 +1078,9 b' def assert_pull_request_status(pull_requ'
1048 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 1082 @pytest.mark.usefixtures("autologin_user")
1053 def test_redirects_to_repo_summary_for_svn_repositories(
1054 backend_svn, app, action):
1055 denied_actions = ['show_all', 'index', 'create']
1056 for action in denied_actions:
1083 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1057 1084 response = app.get(url(
1058 1085 controller='pullrequests', action=action,
1059 1086 repo_name=backend_svn.repo_name))
@@ -1061,17 +1088,7 b' def test_redirects_to_repo_summary_for_s'
1061 1088
1062 1089 # Not allowed, redirect to the summary
1063 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 1092
1066 1093 # URL adds leading slash and path doesn't have it
1067 assert redirected.req.path == summary_url
1068
1069
1070 def test_delete_comment_returns_404_if_comment_does_not_exist(pylonsapp):
1071 # TODO: johbo: Global import not possible because models.forms blows up
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)
1094 assert redirected.request.path == summary_url
@@ -21,8 +21,13 b''
21 21 import mock
22 22 import pytest
23 23
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, url
25 from rhodecode.tests.utils import AssertResponse
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25
26
27 def route_path(name, **kwargs):
28 return {
29 'home': '/',
30 }[name].format(**kwargs)
26 31
27 32
28 33 class TestSessionBehaviorOnPasswordChange(object):
@@ -39,18 +44,23 b' class TestSessionBehaviorOnPasswordChang'
39 44
40 45 def test_sessions_are_ok_when_password_is_not_changed(
41 46 self, app, autologin_user):
42 response = app.get(url('home'))
43 assert_response = AssertResponse(response)
47 response = app.get(route_path('home'))
48 assert_response = response.assert_response()
44 49 assert_response.element_contains(
45 50 '#quick_login_link .menu_link_user', TEST_USER_ADMIN_LOGIN)
46 assert 'rhodecode_user' in response.session
47 assert response.session.was_invalidated is False
51
52 session = response.get_session_from_response()
53
54 assert 'rhodecode_user' in session
55 assert session.was_invalidated is False
48 56
49 57 def test_sessions_invalidated_when_password_is_changed(
50 58 self, app, autologin_user):
51 59 self.password_changed_mock.return_value = True
52 response = app.get(url('home'))
53 assert_response = AssertResponse(response)
60 response = app.get(route_path('home'))
61 assert_response = response.assert_response()
54 62 assert_response.element_contains('#quick_login_link .user', 'Sign in')
55 assert 'rhodecode_user' not in response.session
56 assert response.session.was_invalidated is True
63
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 32 'repo_name': 'foo',
33 33 'repo_type': 'hg',
34 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 35 from rhodecode.tests import (
36 36 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
37 37 from rhodecode.tests.lib.middleware import mock_scm_app
38 from rhodecode.tests.utils import set_anonymous_access
39 38
40 39
41 40 class StubVCSController(simplevcs.SimpleVCS):
@@ -70,7 +69,7 b' def vcscontroller(pylonsapp, config_stub'
70 69 config_stub.testing_securitypolicy()
71 70 config_stub.include('rhodecode.authentication')
72 71
73 set_anonymous_access(True)
72 #set_anonymous_access(True)
74 73 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
75 74 app = HttpsFixup(controller, pylonsapp.config)
76 75 app = CustomTestApp(app)
@@ -87,19 +86,12 b' def vcscontroller(pylonsapp, config_stub'
87 86 def _remove_default_user_from_query_cache():
88 87 user = User.get_default_user(cache=True)
89 88 query = Session().query(User).filter(User.username == user.username)
90 query = query.options(FromCache(
91 "sql_cache_short", "get_user_%s" % _hash_key(user.username)))
89 query = query.options(
90 FromCache("sql_cache_short", "get_user_%s" % _hash_key(user.username)))
92 91 query.invalidate()
93 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 97 def test_handles_exceptions_during_permissions_checks(
@@ -275,7 +267,7 b' class TestShadowRepoExposure(object):'
275 267 controller.vcs_repo_name ==
276 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 272 Check that the set_repo_names method sets correct names on a request
281 273 to a shadow repo.
@@ -304,7 +296,7 b' class TestShadowRepoExposure(object):'
304 296 assert controller.is_shadow_repo
305 297
306 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 301 Checks that the set_repo_names method enforces matching target repos
310 302 and pull request IDs.
@@ -326,7 +318,7 b' class TestShadowRepoExposure(object):'
326 318
327 319
328 320 @pytest.mark.usefixtures('db')
329 class TestGenerateVcsResponse:
321 class TestGenerateVcsResponse(object):
330 322
331 323 def test_ensures_that_start_response_is_called_early_enough(self):
332 324 self.call_controller_with_response_body(iter(['a', 'b']))
@@ -418,7 +410,7 b' class TestGenerateVcsResponse:'
418 410 return self.controller._invalidate_cache.called
419 411
420 412
421 class TestInitializeGenerator:
413 class TestInitializeGenerator(object):
422 414
423 415 def test_drains_first_element(self):
424 416 gen = self.factory(['__init__', 1, 2])
@@ -20,10 +20,10 b''
20 20
21 21 import pytest
22 22 from mock import Mock, patch
23 from pylons import url
24 23
25 24 from rhodecode.lib import base
26 25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.lib import helpers as h
27 27 from rhodecode.model import db
28 28
29 29
@@ -34,8 +34,9 b' from rhodecode.model import db'
34 34 ('scm', 'stub_scm'),
35 35 ('hooks', ['stub_hook']),
36 36 ('config', 'stub_ini_filename'),
37 ('ip', 'fake_ip'),
37 ('ip', '1.2.3.4'),
38 38 ('server_url', 'https://example.com'),
39 ('user_agent', 'client-text-v1.1'),
39 40 # TODO: johbo: Commpare locking parameters with `_get_rc_scm_extras`
40 41 # in hooks_utils.
41 42 ('make_lock', None),
@@ -71,7 +72,6 b' def test_vcs_operation_context_can_skip_'
71 72
72 73 @patch.object(
73 74 base, 'get_enabled_hook_classes', Mock(return_value=['stub_hook']))
74 @patch.object(base, 'get_ip_addr', Mock(return_value="fake_ip"))
75 75 @patch('rhodecode.lib.utils2.get_server_url',
76 76 Mock(return_value='https://example.com'))
77 77 def call_vcs_operation_context(**kwargs_override):
@@ -87,7 +87,9 b' def call_vcs_operation_context(**kwargs_'
87 87 'rhodecode.CONFIG', {'__file__': 'stub_ini_filename'})
88 88 settings_patch = patch.object(base, 'VcsSettingsModel')
89 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 93 settings_mock.assert_called_once_with(repo='stub_repo_name')
92 94 return result
93 95
@@ -163,7 +165,7 b' class TestBaseRepoControllerHandleMissin'
163 165 context_mock.repo_name = repo_name
164 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 169 if should_redirect:
168 170 redirect_mock.assert_called_once_with(expected_url)
169 171 else:
@@ -19,11 +19,12 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 from pygments.lexers import get_lexer_by_name
22 23
24 from rhodecode.tests import no_newline_id_generator
23 25 from rhodecode.lib.codeblocks import (
24 26 tokenize_string, split_token_stream, rollup_tokenstream,
25 27 render_tokenstream)
26 from pygments.lexers import get_lexer_by_name
27 28
28 29
29 30 class TestTokenizeString(object):
@@ -298,7 +299,7 b' class TestRenderTokenStream(object):'
298 299 [('A', '', u'hel'), ('B', 'ins', u'lo')],
299 300 '<span class="A">hel</span><span class="B"><ins>lo</ins></span>',
300 301 ),
301 ])
302 ], ids=no_newline_id_generator)
302 303 def test_render_tokenstream_with_ops(self, tokenstream, output):
303 304 html = render_tokenstream(tokenstream)
304 305 assert html == output
@@ -27,6 +27,7 b' from pylons.util import ContextObj'
27 27 from rhodecode.lib import helpers
28 28 from rhodecode.lib.utils2 import AttributeDict
29 29 from rhodecode.model.settings import IssueTrackerSettingsModel
30 from rhodecode.tests import no_newline_id_generator
30 31
31 32
32 33 @pytest.mark.parametrize('url, expected_url', [
@@ -103,15 +104,25 b' def test_extract_issues(backend, text_st'
103 104 assert issues == expected
104 105
105 106
106 @pytest.mark.parametrize('text_string, pattern, expected_text', [
107 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
108 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'
109 ),
110 ('Fix #42', '(?:#)?<issue_id>\d+)', 'Fix #42'), # Broken regex
107 @pytest.mark.parametrize('text_string, pattern, link_format, expected_text', [
108 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'html',
109 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'),
110
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 121 repo = backend.create_repo()
114 config = {'123': {
122
123 def get_settings_mock(self, cache=True):
124 return {
125 '123': {
115 126 'uid': '123',
116 127 'pat': pattern,
117 128 'url': 'http://r.io/${repo}/i/${issue_id}',
@@ -119,25 +130,25 b' def test_process_patterns_repo(backend, '
119 130 }
120 131 }
121 132
122 def get_settings_mock(self, cache=True):
123 return config
124
125 133 with mock.patch.object(IssueTrackerSettingsModel,
126 134 'get_settings', get_settings_mock):
127 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 138 assert processed_text == expected_text.format(repo=repo.repo_name)
131 139
132 140
133 141 @pytest.mark.parametrize('text_string, pattern, expected_text', [
134 142 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
135 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'
136 ),
137 ('Fix #42', '(?:#)?<issue_id>\d+)', 'Fix #42'), # Broken regex
143 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'),
144 ('Fix #42', '(?:#)?<issue_id>\d+)',
145 'Fix #42'), # Broken regex
138 146 ])
139 147 def test_process_patterns_no_repo(text_string, pattern, expected_text):
140 config = {'123': {
148
149 def get_settings_mock(self, cache=True):
150 return {
151 '123': {
141 152 'uid': '123',
142 153 'pat': pattern,
143 154 'url': 'http://r.io/i/${issue_id}',
@@ -145,13 +156,10 b' def test_process_patterns_no_repo(text_s'
145 156 }
146 157 }
147 158
148 def get_settings_mock(self, cache=True):
149 return config
150
151 159 with mock.patch.object(IssueTrackerSettingsModel,
152 160 'get_global_settings', get_settings_mock):
153 161 processed_text, issues = helpers.process_patterns(
154 text_string, '', config)
162 text_string, '')
155 163
156 164 assert processed_text == expected_text
157 165
@@ -161,7 +169,10 b' def test_process_patterns_non_existent_r'
161 169 pattern = '(?:#)(?P<issue_id>\d+)'
162 170 expected_text = ('Fix <a class="issue-tracker-link" '
163 171 'href="http://r.io/do-not-exist/i/42">#42</a>')
164 config = {'123': {
172
173 def get_settings_mock(self, cache=True):
174 return {
175 '123': {
165 176 'uid': '123',
166 177 'pat': pattern,
167 178 'url': 'http://r.io/${repo}/i/${issue_id}',
@@ -169,13 +180,10 b' def test_process_patterns_non_existent_r'
169 180 }
170 181 }
171 182
172 def get_settings_mock(self, cache=True):
173 return config
174
175 183 with mock.patch.object(IssueTrackerSettingsModel,
176 184 'get_global_settings', get_settings_mock):
177 185 processed_text, issues = helpers.process_patterns(
178 text_string, 'do-not-exist', config)
186 text_string, 'do-not-exist')
179 187
180 188 assert processed_text == expected_text
181 189
@@ -197,7 +205,7 b' def test_get_visual_attr(pylonsapp):'
197 205 ('just a string\n', False, 'just a string'),
198 206 ('just a string\n next line', False, 'just a string...'),
199 207 ('just a string\n next line', True, 'just a string\n...'),
200 ])
208 ], ids=no_newline_id_generator)
201 209 def test_chop_at(test_text, inclusive, expected_text):
202 210 assert helpers.chop_at_smart(
203 211 test_text, '\n', inclusive, '...') == expected_text
@@ -20,15 +20,10 b''
20 20
21 21 import mock
22 22 import pytest
23
23 from rhodecode.model.db import Session, UserLog
24 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 27 def test_post_push_truncates_commits(user_regular, repo_stub):
33 28 extras = {
34 29 'ip': '127.0.0.1',
@@ -39,6 +34,7 b' def test_post_push_truncates_commits(use'
39 34 'config': '',
40 35 'server_url': 'http://example.com',
41 36 'make_lock': None,
37 'user_agent': 'some-client',
42 38 'locked_by': [None],
43 39 'commit_ids': ['abcde12345' * 4] * 30000,
44 40 'is_shadow_repo': False,
@@ -48,11 +44,13 b' def test_post_push_truncates_commits(use'
48 44 hooks_base.post_push(extras)
49 45
50 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(
54 extras.username, expected_action, extras.repository, extras.ip,
55 commit=True)
49 entry = UserLog.query().order_by('-user_log_id').first()
50 assert entry.action == 'user.push'
51 assert entry.action_data['commit_ids'] == commit_ids
52 Session().delete(entry)
53 Session().commit()
56 54
57 55
58 56 def assert_called_with_mock(callable_, expected_mock_name):
@@ -72,6 +70,7 b' def hook_extras(user_regular, repo_stub)'
72 70 'config': '',
73 71 'server_url': 'http://example.com',
74 72 'make_lock': None,
73 'user_agent': 'some-client',
75 74 'locked_by': [None],
76 75 'commit_ids': [],
77 76 'is_shadow_repo': False,
@@ -3,16 +3,17 b' import collections'
3 3 import pytest
4 4
5 5 from rhodecode.lib.utils import PartialRenderer
6 from rhodecode.lib.utils2 import AttributeDict
6 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 11 template = EmailNotificationModel().get_renderer(
11 12 EmailNotificationModel.TYPE_TEST)
12 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 17 kwargs = {}
17 18 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
18 19 EmailNotificationModel.TYPE_TEST, **kwargs)
@@ -27,12 +28,13 b' def test_render_email(pylonsapp):'
27 28 assert body_plaintext == 'Email Plaintext Body'
28 29
29 30 # body
30 assert 'This is a notification ' \
31 'from RhodeCode. http://test.example.com:80/' in body
31 notification_footer = 'This is a notification from RhodeCode. http://%s/' \
32 % http_host_only_stub
33 assert notification_footer in body
32 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 39 ref = collections.namedtuple('Ref',
38 40 'name, type')(
@@ -66,3 +68,57 b' def test_render_pr_email(pylonsapp, user'
66 68
67 69 # subject
68 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
@@ -86,22 +86,24 b' HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH'
86 86 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
87 87 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
88 88 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
89 HOOK_PUSH_KEY = db.RhodeCodeUi.HOOK_PUSH_KEY
89 90
90 91 HG_HOOKS = frozenset(
91 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 96 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
96 97 ([], HG_HOOKS),
97 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 102 # When a pull/push hook is disabled, its pre-pull/push counterpart should
102 103 # be disabled too.
103 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 108 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
107 109 expected_hooks):
@@ -219,11 +221,6 b' def platform_encodes_filenames():'
219 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 224 @pytest.fixture
228 225 def repo_groups(request):
229 226 session = meta.Session()
@@ -52,18 +52,18 b' class TestRepoGroupSchema(object):'
52 52 )
53 53
54 54 schema_data = schema.deserialize(dict(
55 repo_group_name='dupa',
55 repo_group_name='my_schema_group',
56 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 60 assert schema_data['repo_group'] == {
61 61 'repo_group_id': None,
62 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 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 67 ('', 'repo_group_name', 'Name must start with a letter or number. Got ``'),
68 68 ])
69 69 def test_deserialize_with_bad_group_name(
@@ -86,7 +86,7 b' class TestRepoGroupSchema(object):'
86 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 90 schema_data = schema.deserialize(dict(
91 91 repo_group_name=full_name,
92 92 repo_group_owner=user_admin.username
@@ -96,7 +96,7 b' class TestRepoGroupSchema(object):'
96 96 assert schema_data['repo_group'] == {
97 97 'repo_group_id': test_repo_group.group_id,
98 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 101 def test_deserialize_with_group_name_regular_user_no_perms(
102 102 self, app, user_regular, test_repo_group):
@@ -104,7 +104,7 b' class TestRepoGroupSchema(object):'
104 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 108 with pytest.raises(colander.Invalid) as excinfo:
109 109 schema.deserialize(dict(
110 110 repo_group_name=full_name,
@@ -57,19 +57,20 b' class TestRepoSchema(object):'
57 57 )
58 58
59 59 schema_data = schema.deserialize(dict(
60 repo_name='dupa',
60 repo_name='my_schema_repo',
61 61 repo_type='hg',
62 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 66 assert schema_data['repo_group'] == {
67 67 'repo_group_id': None,
68 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 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 74 ('', 'repo_name', 'Name must start with a letter or number. Got ``'),
74 75 ])
75 76 def test_deserialize_with_bad_group_name(
@@ -95,7 +96,7 b' class TestRepoSchema(object):'
95 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 100 schema_data = schema.deserialize(dict(
100 101 repo_name=full_name,
101 102 repo_type='hg',
@@ -106,7 +107,8 b' class TestRepoSchema(object):'
106 107 assert schema_data['repo_group'] == {
107 108 'repo_group_id': test_repo_group.group_id,
108 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 113 def test_deserialize_with_group_name_regular_user_no_perms(
112 114 self, app, user_regular, test_repo_group):
@@ -115,7 +117,7 b' class TestRepoSchema(object):'
115 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 121 with pytest.raises(colander.Invalid) as excinfo:
120 122 schema.deserialize(dict(
121 123 repo_name=full_name,
@@ -500,7 +500,8 b' class TestCreateOrUpdateUi(object):'
500 500 def test_update(self, repo_stub, settings_util):
501 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 505 section = 'test-section'
505 506 key = 'test-key'
506 507 settings_util.create_repo_rhodecode_ui(
@@ -519,6 +520,7 b' class TestCreateOrUpdateUi(object):'
519 520 class TestCreateOrUpdateRepoHgSettings(object):
520 521 FORM_DATA = {
521 522 'extensions_largefiles': False,
523 'extensions_evolve': False,
522 524 'phases_publish': False
523 525 }
524 526
@@ -529,6 +531,8 b' class TestCreateOrUpdateRepoHgSettings(o'
529 531 expected_calls = [
530 532 mock.call(model.repo_settings, 'extensions', 'largefiles',
531 533 active=False, value=''),
534 mock.call(model.repo_settings, 'extensions', 'evolve',
535 active=False, value=''),
532 536 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
533 537 ]
534 538 assert expected_calls == create_mock.call_args_list
@@ -574,7 +578,8 b' class TestCreateOrUpdateGlobalHgSettings'
574 578 'extensions_largefiles': False,
575 579 'largefiles_usercache': '/example/largefiles-store',
576 580 'phases_publish': False,
577 'extensions_hgsubversion': False
581 'extensions_hgsubversion': False,
582 'extensions_evolve': False
578 583 }
579 584
580 585 def test_creates_repo_hg_settings_when_data_is_correct(self):
@@ -589,7 +594,9 b' class TestCreateOrUpdateGlobalHgSettings'
589 594 mock.call(model.global_settings, 'phases', 'publish',
590 595 value='False'),
591 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 601 assert expected_calls == create_mock.call_args_list
595 602
@@ -957,6 +964,7 b' class TestCreateOrUpdateRepoSettings(obj'
957 964 'hooks_changegroup_push_logger': False,
958 965 'hooks_outgoing_pull_logger': False,
959 966 'extensions_largefiles': False,
967 'extensions_evolve': False,
960 968 'largefiles_usercache': '/example/largefiles-store',
961 969 'vcs_git_lfs_enabled': False,
962 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 34 pull_request = pr_util.create_pull_request()
35 35
36 36 # Expect that review status "Under Review"
@@ -44,7 +44,7 b' def test_new_pull_request_is_under_revie'
44 44 db.ChangesetStatus.STATUS_UNDER_REVIEW,
45 45 ])
46 46 def test_pull_request_under_review_if_one_reviewer_voted(
47 pr_util, voted_status):
47 pr_util, voted_status, config_stub):
48 48 pull_request = pr_util.create_pull_request()
49 49 pr_util.create_status_votes(
50 50 voted_status, pull_request.reviewers[0])
@@ -59,7 +59,7 b' def test_pull_request_under_review_if_on'
59 59 db.ChangesetStatus.STATUS_REJECTED,
60 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 63 pull_request = pr_util.create_pull_request()
64 64 pr_util.create_status_votes(
65 65 voted_status, *pull_request.reviewers)
@@ -74,7 +74,8 b' def test_pull_request_has_voted_status_i'
74 74 db.ChangesetStatus.STATUS_REJECTED,
75 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 79 pull_request = pr_util.create_pull_request()
79 80 pr_util.create_status_votes(
80 81 voted_status, *pull_request.reviewers)
@@ -92,7 +93,7 b' def test_pull_request_stays_if_update_wi'
92 93 db.ChangesetStatus.STATUS_REJECTED,
93 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 97 pull_request = pr_util.create_pull_request()
97 98 pr_util.create_status_votes(
98 99 voted_status, *pull_request.reviewers)
@@ -106,7 +107,7 b' def test_pull_request_under_review_if_up'
106 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 111 pull_request = pr_util.create_pull_request()
111 112 for commit_id in pull_request.revisions:
112 113 status = ChangesetStatusModel().get_status(
@@ -120,7 +121,7 b' def test_commit_under_review_if_part_of_'
120 121 db.ChangesetStatus.STATUS_UNDER_REVIEW,
121 122 ])
122 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 125 pull_request = pr_util.create_pull_request()
125 126 pr_util.create_status_votes(
126 127 voted_status, pull_request.reviewers[0])
@@ -130,7 +131,7 b' def test_commit_has_voted_status_after_v'
130 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 135 pull_request = pr_util.create_pull_request()
135 136 pr_util.create_status_votes(
136 137 db.ChangesetStatus.STATUS_APPROVED, pull_request.reviewers[0])
@@ -147,7 +148,7 b' def test_commit_under_review_if_added_to'
147 148 db.ChangesetStatus.STATUS_UNDER_REVIEW,
148 149 ])
149 150 def test_commit_keeps_status_if_removed_from_pull_request(
150 pr_util, voted_status):
151 pr_util, voted_status, config_stub):
151 152 pull_request = pr_util.create_pull_request()
152 153 pr_util.add_one_commit()
153 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 166 db.ChangesetStatus.STATUS_UNDER_REVIEW,
166 167 ])
167 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 170 pull_request = pr_util.create_pull_request()
170 171 commit_id = pull_request.revisions[-1]
171 172 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
@@ -30,7 +30,7 b' from rhodecode.model.user import UserMod'
30 30 fixture = Fixture()
31 31
32 32
33 class TestNotifications:
33 class TestNotifications(object):
34 34 destroy_users = set()
35 35
36 36 @classmethod
@@ -38,7 +38,7 b' class TestNotifications:'
38 38 fixture.destroy_users(cls.destroy_users)
39 39
40 40 @pytest.fixture(autouse=True)
41 def create_users(self, request, pylonsapp):
41 def create_users(self, request, app):
42 42 Session.remove()
43 43 self.u1 = UserModel().create_or_update(
44 44 username=u'u1', password=u'qweqwe',
@@ -62,7 +62,7 b' class TestNotifications:'
62 62 self.destroy_users.add('u3')
63 63
64 64 @pytest.fixture(autouse=True)
65 def _clean_notifications(self, request, pylonsapp):
65 def _clean_notifications(self, request, app):
66 66 for n in Notification.query().all():
67 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 47 @pytest.fixture
47 48 def pull_request(self, request, backend, pr_util):
@@ -49,7 +50,9 b' class TestPullRequestModel:'
49 50 A pull request combined with multiples patches.
50 51 """
51 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 56 self.workspace_remove_patcher = mock.patch.object(
54 57 BackendClass, 'cleanup_merge_workspace')
55 58
@@ -116,7 +119,8 b' class TestPullRequestModel:'
116 119
117 120 def test_get_awaiting_my_review(self, pull_request):
118 121 PullRequestModel().update_reviewers(
119 pull_request, [(pull_request.author, ['author'])])
122 pull_request, [(pull_request.author, ['author'], False)],
123 pull_request.author)
120 124 prs = PullRequestModel().get_awaiting_my_review(
121 125 pull_request.target_repo, user_id=pull_request.author.user_id)
122 126 assert isinstance(prs, list)
@@ -124,13 +128,14 b' class TestPullRequestModel:'
124 128
125 129 def test_count_awaiting_my_review(self, pull_request):
126 130 PullRequestModel().update_reviewers(
127 pull_request, [(pull_request.author, ['author'])])
131 pull_request, [(pull_request.author, ['author'], False)],
132 pull_request.author)
128 133 pr_count = PullRequestModel().count_awaiting_my_review(
129 134 pull_request.target_repo, user_id=pull_request.author.user_id)
130 135 assert pr_count == 1
131 136
132 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 140 self.workspace_remove_mock.assert_called_once_with(
136 141 self.workspace_id)
@@ -372,6 +377,7 b' class TestPullRequestModel:'
372 377 assert type(title) == unicode
373 378
374 379
380 @pytest.mark.usefixtures('config_stub')
375 381 class TestIntegrationMerge(object):
376 382 @pytest.mark.parametrize('extra_config', (
377 383 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
@@ -433,7 +439,7 b' class TestIntegrationMerge(object):'
433 439 (True, 0, 1),
434 440 ])
435 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 443 pull_request = pr_util.create_pull_request()
438 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 471 return extras
466 472
467 473
474 @pytest.mark.usefixtures('config_stub')
468 475 class TestUpdateCommentHandling(object):
469 476
470 477 @pytest.fixture(autouse=True, scope='class')
@@ -572,6 +579,7 b' class TestUpdateCommentHandling(object):'
572 579 assert_inline_comments(pull_request, visible=0, outdated=1)
573 580
574 581
582 @pytest.mark.usefixtures('config_stub')
575 583 class TestUpdateChangedFiles(object):
576 584
577 585 def test_no_changes_on_unchanged_diff(self, pr_util):
@@ -681,7 +689,7 b' class TestUpdateChangedFiles(object):'
681 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 693 model = PullRequestModel()
686 694 pull_request = pr_util.create_pull_request()
687 695 pr_util.update_source_repository()
@@ -692,7 +700,7 b' def test_update_writes_snapshot_into_pul'
692 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 704 pull_request = pr_util.create_pull_request()
697 705 model = PullRequestModel()
698 706 model.update_commits(pull_request)
@@ -701,7 +709,7 b' def test_update_skips_new_version_if_unc'
701 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 713 model = PullRequestModel()
706 714 pull_request = pr_util.create_pull_request()
707 715 comment = pr_util.create_comment()
@@ -713,7 +721,7 b' def test_update_assigns_comments_to_the_'
713 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 725 model = PullRequestModel()
718 726 pull_request = pr_util.create_pull_request()
719 727 pr_util.update_source_repository()
@@ -745,7 +753,7 b' def test_update_adds_a_comment_to_the_pu'
745 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 757 pull_request = pr_util.create_pull_request()
750 758
751 759 # Avoiding default values
@@ -784,7 +792,7 b' def test_create_version_from_snapshot_up'
784 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 796 version1 = pr_util.create_version_of_pull_request()
789 797 comment_linked = pr_util.create_comment(linked_to=version1)
790 798 comment_unlinked = pr_util.create_comment()
@@ -25,6 +25,7 b' from rhodecode.lib.vcs.exceptions import'
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26
27 27
28 @pytest.mark.usefixtures('config_stub')
28 29 @pytest.mark.backends('git')
29 30 class TestGetDiffForPrOrVersion(object):
30 31
@@ -29,10 +29,9 b' from rhodecode.model.db import Repositor'
29 29 from rhodecode.model.meta import Session
30 30 from rhodecode.model.repo import RepoModel
31 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 36 def test_remove_repo(self, backend):
38 37 repo = backend.create_repo()
@@ -170,141 +169,3 b' class TestRepoModel:'
170 169 repo.update_commit_cache(config=config)
171 170 scm.assert_called_with(
172 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 28 import rhodecode
29 29 from rhodecode.model import db, scm
30 from rhodecode.tests import no_newline_id_generator
30 31
31 32
32 33 def test_scm_instance_config(backend):
@@ -295,7 +296,7 b' class TestCheckRhodecodeHook(object):'
295 296 @pytest.mark.parametrize("file_content, expected_result", [
296 297 ("RC_HOOK_VER = '3.3.3'\n", True),
297 298 ("RC_HOOK = '3.3.3'\n", False),
298 ])
299 ], ids=no_newline_id_generator)
299 300 @patch('os.path.exists', Mock(return_value=True))
300 301 def test_signatures(self, file_content, expected_result):
301 302 hook_content_patcher = patch.object(
@@ -35,6 +35,64 b' def teardown_module(self):'
35 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 96 @pytest.mark.parametrize(
39 97 "pre_existing, regular_should_be, external_should_be, groups, "
40 98 "expected", [
@@ -19,10 +19,11 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 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,\
25 Permission, UserIpMap
24 from rhodecode.lib.utils2 import safe_unicode
25 from rhodecode.model.db import (
26 true, User, UserGroup, UserGroupMember, UserEmailMap, Permission, UserIpMap)
26 27 from rhodecode.model.meta import Session
27 28 from rhodecode.model.user import UserModel
28 29 from rhodecode.model.user_group import UserGroupModel
@@ -33,6 +34,86 b' from rhodecode.tests.fixture import Fixt'
33 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 117 @pytest.fixture
37 118 def test_user(request, pylonsapp):
38 119 usr = UserModel().create_or_update(
@@ -27,6 +27,8 b' import datetime'
27 27 import string
28 28 import mock
29 29 import pytest
30
31 from rhodecode.tests import no_newline_id_generator
30 32 from rhodecode.tests.utils import run_test_concurrently
31 33 from rhodecode.lib.helpers import InitialsGravatar
32 34
@@ -113,7 +115,7 b' def test_str2bool(str_bool, expected):'
113 115 (pref+"user.dot hej ! not-needed maril@domain.org", []),
114 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 119 def test_mention_extractor(text, expected):
118 120 from rhodecode.lib.utils2 import extract_mentioned_users
119 121 got = extract_mentioned_users(text)
@@ -330,7 +332,17 b' def test_initials_gravatar_mapping_algo('
330 332 ])
331 333 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
332 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
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,
334 346 repo_name=repo_name, repo_id=23, **overrides)
335 347 assert clone_url == expected
336 348
@@ -378,7 +390,7 b' def _quick_url(text, tmpl="""<a class="r'
378 390 some text url[123123123123]
379 391 sometimes !
380 392 """)
381 ])
393 ], ids=no_newline_id_generator)
382 394 def test_urlify_commits(sample, expected):
383 395 def fake_url(self, *args, **kwargs):
384 396 return '/some-url'
@@ -410,7 +422,7 b' def test_urlify_commits(sample, expected'
410 422 url[https://foo.bar.com]
411 423 some text lalala""",
412 424 "https://foo.bar.com")
413 ])
425 ], ids=no_newline_id_generator)
414 426 def test_urlify_test(sample, expected, url_):
415 427 from rhodecode.lib.helpers import urlify_text
416 428 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
@@ -131,6 +131,13 b' def _check_proper_git_push('
131 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 141 def _check_proper_clone(stdout, stderr, vcs):
135 142 if vcs == 'hg':
136 143 assert 'requesting all changes' in stdout
@@ -41,8 +41,7 b' from rhodecode.model.settings import Set'
41 41 from rhodecode.tests import (
42 42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
43 43 from rhodecode.tests.fixture import Fixture
44 from rhodecode.tests.utils import (
45 set_anonymous_access, is_url_reachable, wait_for_url)
44 from rhodecode.tests.utils import is_url_reachable, wait_for_url
46 45
47 46 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
48 47 REPO_GROUP = 'a_repo_group'
@@ -84,6 +83,9 b' class RcWebServer(object):'
84 83 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
85 84 return _url
86 85
86 def host_url(self):
87 return 'http://' + get_host_url(self.pylons_config)
88
87 89
88 90 @pytest.fixture(scope="module")
89 91 def rcextensions(request, pylonsapp, tmpdir_factory):
@@ -185,11 +187,6 b' def rc_web_server('
185 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 190 @pytest.fixture
194 191 def disable_locking(pylonsapp):
195 192 r = Repository.get_by_repo_name(GIT_REPO)
@@ -43,8 +43,8 b' def rc_web_server_config(testini_factory'
43 43 return testini_factory(CUSTOM_PARAMS)
44 44
45 45
46 @pytest.mark.usefixtures("disable_locking")
47 class TestVCSOperationsOnCustomIniConfig:
46 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
47 class TestVCSOperationsOnCustomIniConfig(object):
48 48
49 49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
50 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 43 return testini_factory(CUSTOM_PARAMS)
44 44
45 45
46 @pytest.mark.usefixtures("disable_locking")
47 class TestVCSOperationsOnCustomIniConfig:
46 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
47 class TestVCSOperationsOnCustomIniConfig(object):
48 48
49 49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
50 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 43 return testini_factory(CUSTOM_PARAMS)
44 44
45 45
46 @pytest.mark.usefixtures("disable_locking")
47 class TestVCSOperationsOnCustomIniConfig:
46 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
47 class TestVCSOperationsOnCustomIniConfig(object):
48 48
49 49 def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir):
50 50 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
@@ -34,6 +34,7 b' import time'
34 34 import pytest
35 35
36 36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
37 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
37 38 from rhodecode.lib.vcs.nodes import FileNode
38 39 from rhodecode.model.auth_token import AuthTokenModel
39 40 from rhodecode.model.db import Repository, UserIpMap, CacheKey
@@ -42,11 +43,12 b' from rhodecode.model.user import UserMod'
42 43 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
43 44
44 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 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 52 class TestVCSOperations(object):
51 53
52 54 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
@@ -62,6 +64,13 b' class TestVCSOperations(object):'
62 64 _check_proper_clone(stdout, stderr, 'git')
63 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 74 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
66 75 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
67 76 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
@@ -389,7 +398,6 b' class TestVCSOperations(object):'
389 398 'hg clone', clone_url, tmpdir.strpath)
390 399 assert 'abort: authorization failed' in stderr
391 400
392
393 401 def test_clone_by_auth_token_with_scope(
394 402 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
395 403 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
@@ -472,3 +480,176 b' def test_git_fetches_from_remote_reposit'
472 480 source_repo = backend_git['annotated-tag']
473 481 target_vcs_repo = backend_git.create_repo().scm_instance()
474 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 50 return testini_factory(CUSTOM_PARAMS)
51 51
52 52
53 @pytest.mark.usefixtures("disable_locking")
54 class TestVCSOperationsOnCustomIniConfig:
53 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
54 class TestVCSOperationsOnCustomIniConfig(object):
55 55
56 56 def test_clone_and_create_lock_hg(self, rc_web_server, tmpdir):
57 57 # enable locking
@@ -49,8 +49,8 b' def rc_web_server_config(testini_factory'
49 49 return testini_factory(CUSTOM_PARAMS)
50 50
51 51
52 @pytest.mark.usefixtures("disable_locking")
53 class TestVCSOperationsOnCustomIniConfig:
52 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
53 class TestVCSOperationsOnCustomIniConfig(object):
54 54
55 55 def test_clone_after_repo_was_locked_hg(self, rc_web_server, tmpdir):
56 56 # lock repo
@@ -62,7 +62,7 b' from rhodecode.tests import ('
62 62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
63 63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
64 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 66 from rhodecode.tests.fixture import Fixture
67 67
68 68
@@ -191,7 +191,15 b' def http_host_stub():'
191 191 """
192 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 205 @pytest.fixture
@@ -204,7 +212,7 b' def http_environ(http_host_stub):'
204 212 to override this for a specific test case.
205 213 """
206 214 return {
207 'SERVER_NAME': http_host_stub.split(':')[0],
215 'SERVER_NAME': http_host_only_stub(),
208 216 'SERVER_PORT': http_host_stub.split(':')[1],
209 217 'HTTP_HOST': http_host_stub,
210 218 'HTTP_USER_AGENT': 'rc-test-agent',
@@ -213,7 +221,7 b' def http_environ(http_host_stub):'
213 221
214 222
215 223 @pytest.fixture(scope='function')
216 def app(request, pylonsapp, http_environ):
224 def app(request, config_stub, pylonsapp, http_environ):
217 225 app = CustomTestApp(
218 226 pylonsapp,
219 227 extra_environ=http_environ)
@@ -884,7 +892,7 b' class RepoServer(object):'
884 892
885 893
886 894 @pytest.fixture
887 def pr_util(backend, request):
895 def pr_util(backend, request, config_stub):
888 896 """
889 897 Utility for tests of models and for functional tests around pull requests.
890 898
@@ -986,10 +994,9 b' class PRTestUtility(object):'
986 994 return reference
987 995
988 996 def _get_reviewers(self):
989 model = UserModel()
990 997 return [
991 model.get_by_username(TEST_USER_REGULAR_LOGIN),
992 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
998 (TEST_USER_REGULAR_LOGIN, ['default1'], False),
999 (TEST_USER_REGULAR2_LOGIN, ['default2'], False),
993 1000 ]
994 1001
995 1002 def update_source_repository(self, head=None):
@@ -1078,7 +1085,7 b' class PRTestUtility(object):'
1078 1085 # request will already be deleted.
1079 1086 pull_request = PullRequest().get(self.pull_request_id)
1080 1087 if pull_request:
1081 PullRequestModel().delete(pull_request)
1088 PullRequestModel().delete(pull_request, pull_request.author)
1082 1089 Session().commit()
1083 1090
1084 1091 if self.notification_patcher:
@@ -1641,14 +1648,6 b' def no_notifications(request):'
1641 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 1651 @pytest.fixture(scope='session')
1653 1652 def repeat(request):
1654 1653 """
@@ -1676,11 +1675,21 b' def request_stub():'
1676 1675
1677 1676
1678 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 1687 def config_stub(request, request_stub):
1680 1688 """
1681 1689 Set up pyramid.testing and return the Configurator.
1682 1690 """
1683 1691 config = pyramid.testing.setUp(request=request_stub)
1692 add_test_routes(config)
1684 1693
1685 1694 @request.addfinalizer
1686 1695 def cleanup():
@@ -1812,3 +1821,12 b' def local_dt_to_utc():'
1812 1821 return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
1813 1822 dateutil.tz.tzutc()).replace(tzinfo=None)
1814 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 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 141 def get_anonymous_access():
150 142 sa = get_session()
151 143 return sa.query(User).filter(User.username == 'default').one().active
@@ -94,6 +94,14 b' class CustomTestResponse(TestResponse):'
94 94 def assert_response(self):
95 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 106 class TestRequest(Request):
99 107
@@ -109,15 +117,13 b' class CustomTestApp(TestApp):'
109 117 RequestClass = TestRequest
110 118
111 119
112
113
114
115 120 def set_anonymous_access(enabled):
116 121 """(Dis)allows anonymous access depending on parameter `enabled`"""
117 122 user = User.get_default_user()
118 123 user.active = enabled
119 124 Session().add(user)
120 125 Session().commit()
126 time.sleep(1.5) # must sleep for cache (1s to expire)
121 127 log.info('anonymous access is now: %s', enabled)
122 128 assert enabled == User.get_default_user().active, (
123 129 'Cannot set anonymous access')
@@ -357,16 +363,6 b' def is_url_reachable(url):'
357 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 366 def repo_on_filesystem(repo_name):
371 367 from rhodecode.lib import vcs
372 368 from rhodecode.tests import TESTS_TMP_PATH
@@ -407,3 +403,21 b' def commit_change('
407 403 f_path=filename
408 404 )
409 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 114 return shadow_repo, source_ref, target_ref
115 115
116 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 119 This test is somewhat special. It does not really test the system
120 120 instead it is more or less a precondition for the
@@ -152,8 +152,7 b' class TestMercurialRemoteRepoInvalidatio'
152 152 shadow_repo.get_commit(source_ref.commit_id)
153 153
154 154 @pytest.mark.backends('hg')
155 def test_commit_does_not_exist_error_does_not_happen(
156 self, pr_util, pylonsapp):
155 def test_commit_does_not_exist_error_does_not_happen(self, pr_util, app):
157 156 """
158 157 This test simulates a pull request merge in which the pull operations
159 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