##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r1849:5440352d merge stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -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
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,6 +1,6 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 4.7.2
2 current_version = 4.8.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:rhodecode/VERSION]
5 [bumpversion:file:rhodecode/VERSION]
6
6
@@ -1,33 +1,28 b''
1 [DEFAULT]
1 [DEFAULT]
2 done = false
2 done = false
3
3
4 [task:bump_version]
4 [task:bump_version]
5 done = true
5 done = true
6
6
7 [task:rc_tools_pinned]
7 [task:rc_tools_pinned]
8 done = true
9
8
10 [task:fixes_on_stable]
9 [task:fixes_on_stable]
11 done = true
12
10
13 [task:pip2nix_generated]
11 [task:pip2nix_generated]
14 done = true
15
12
16 [task:changelog_updated]
13 [task:changelog_updated]
17 done = true
18
14
19 [task:generate_api_docs]
15 [task:generate_api_docs]
20 done = true
16
17 [task:updated_translation]
21
18
22 [release]
19 [release]
23 state = prepared
20 state = in_progress
24 version = 4.7.2
21 version = 4.8.0
25
26 [task:updated_translation]
27
22
28 [task:generate_js_routes]
23 [task:generate_js_routes]
29
24
30 [task:updated_trial_license]
25 [task:updated_trial_license]
31
26
32 [task:generate_oss_licenses]
27 [task:generate_oss_licenses]
33
28
@@ -1,53 +1,54 b''
1 # top level files
1 # top level files
2
2
3 include MANIFEST.in
3 include MANIFEST.in
4 include README.rst
4 include README.rst
5 include CHANGES.rst
5 include CHANGES.rst
6 include LICENSE.txt
6 include LICENSE.txt
7
7
8 include rhodecode/VERSION
8 include rhodecode/VERSION
9
9
10 # docs
10 # docs
11 recursive-include docs *
11 recursive-include docs *
12
12
13 # all config files
13 # all config files
14 recursive-include configs *
14 recursive-include configs *
15
15
16 # translations
16 # translations
17 recursive-include rhodecode/i18n *
17 recursive-include rhodecode/i18n *
18
18
19 # hook templates
19 # hook templates
20 recursive-include rhodecode/config/hook_templates *
20 recursive-include rhodecode/config/hook_templates *
21
21
22 # non-python core stuff
22 # non-python core stuff
23 recursive-include rhodecode *.cfg
23 recursive-include rhodecode *.cfg
24 recursive-include rhodecode *.json
24 recursive-include rhodecode *.json
25 recursive-include rhodecode *.ini_tmpl
25 recursive-include rhodecode *.ini_tmpl
26 recursive-include rhodecode *.sh
26 recursive-include rhodecode *.sh
27 recursive-include rhodecode *.mako
27 recursive-include rhodecode *.mako
28
28
29 # 502 page
29 # 502 page
30 include rhodecode/public/502.html
30 include rhodecode/public/502.html
31
31
32
32
33 # images, css
33 # images, css
34 include rhodecode/public/css/*.css
34 include rhodecode/public/css/*.css
35 include rhodecode/public/images/*.*
35 include rhodecode/public/images/*.*
36 include rhodecode/public/images/ee_features/*.*
36
37
37 # sound files
38 # sound files
38 include rhodecode/public/sounds/*.mp3
39 include rhodecode/public/sounds/*.mp3
39 include rhodecode/public/sounds/*.wav
40 include rhodecode/public/sounds/*.wav
40
41
41 # fonts
42 # fonts
42 recursive-include rhodecode/public/fonts/ProximaNova *
43 recursive-include rhodecode/public/fonts/ProximaNova *
43 recursive-include rhodecode/public/fonts/RCIcons *
44 recursive-include rhodecode/public/fonts/RCIcons *
44
45
45 # js
46 # js
46 recursive-include rhodecode/public/js *
47 recursive-include rhodecode/public/js *
47
48
48 # templates
49 # templates
49 recursive-include rhodecode/templates *
50 recursive-include rhodecode/templates *
50
51
51 # skip any tests files
52 # skip any tests files
52 recursive-exclude rhodecode/tests *
53 recursive-exclude rhodecode/tests *
53
54
@@ -1,241 +1,242 b''
1 # Nix environment for the community edition
1 # Nix environment for the community edition
2 #
2 #
3 # This shall be as lean as possible, just producing the Enterprise
3 # This shall be as lean as possible, just producing the Enterprise
4 # derivation. For advanced tweaks to pimp up the development environment we use
4 # derivation. For advanced tweaks to pimp up the development environment we use
5 # "shell.nix" so that it does not have to clutter this file.
5 # "shell.nix" so that it does not have to clutter this file.
6
6
7 args@
7 args@
8 { pythonPackages ? "python27Packages"
8 { pythonPackages ? "python27Packages"
9 , pythonExternalOverrides ? self: super: {}
9 , pythonExternalOverrides ? self: super: {}
10 , doCheck ? true
10 , doCheck ? true
11 , ...
11 , ...
12 }:
12 }:
13
13
14 let
14 let
15
15
16 # Use nixpkgs from args or import them. We use this indirect approach
16 # Use nixpkgs from args or import them. We use this indirect approach
17 # through args to be able to use the name `pkgs` for our customized packages.
17 # through args to be able to use the name `pkgs` for our customized packages.
18 # Otherwise we will end up with an infinite recursion.
18 # Otherwise we will end up with an infinite recursion.
19 nixpkgs = args.pkgs or (import <nixpkgs> { });
19 nixpkgs = args.pkgs or (import <nixpkgs> { });
20
20
21 # johbo: Interim bridge which allows us to build with the upcoming
21 # johbo: Interim bridge which allows us to build with the upcoming
22 # nixos.16.09 branch (unstable at the moment of writing this note) and the
22 # nixos.16.09 branch (unstable at the moment of writing this note) and the
23 # current stable nixos-16.03.
23 # current stable nixos-16.03.
24 backwardsCompatibleFetchgit = { ... }@args:
24 backwardsCompatibleFetchgit = { ... }@args:
25 let
25 let
26 origSources = nixpkgs.fetchgit args;
26 origSources = nixpkgs.fetchgit args;
27 in
27 in
28 nixpkgs.lib.overrideDerivation origSources (oldAttrs: {
28 nixpkgs.lib.overrideDerivation origSources (oldAttrs: {
29 NIX_PREFETCH_GIT_CHECKOUT_HOOK = ''
29 NIX_PREFETCH_GIT_CHECKOUT_HOOK = ''
30 find $out -name '.git*' -print0 | xargs -0 rm -rf
30 find $out -name '.git*' -print0 | xargs -0 rm -rf
31 '';
31 '';
32 });
32 });
33
33
34 # Create a customized version of nixpkgs which should be used throughout the
34 # Create a customized version of nixpkgs which should be used throughout the
35 # rest of this file.
35 # rest of this file.
36 pkgs = nixpkgs.overridePackages (self: super: {
36 pkgs = nixpkgs.overridePackages (self: super: {
37 fetchgit = backwardsCompatibleFetchgit;
37 fetchgit = backwardsCompatibleFetchgit;
38 });
38 });
39
39
40 # Evaluates to the last segment of a file system path.
40 # Evaluates to the last segment of a file system path.
41 basename = path: with pkgs.lib; last (splitString "/" path);
41 basename = path: with pkgs.lib; last (splitString "/" path);
42
42
43 # source code filter used as arugment to builtins.filterSource.
43 # source code filter used as arugment to builtins.filterSource.
44 src-filter = path: type: with pkgs.lib;
44 src-filter = path: type: with pkgs.lib;
45 let
45 let
46 ext = last (splitString "." path);
46 ext = last (splitString "." path);
47 in
47 in
48 !builtins.elem (basename path) [
48 !builtins.elem (basename path) [
49 ".git" ".hg" "__pycache__" ".eggs"
49 ".git" ".hg" "__pycache__" ".eggs"
50 "bower_components" "node_modules"
50 "bower_components" "node_modules"
51 "build" "data" "result" "tmp"] &&
51 "build" "data" "result" "tmp"] &&
52 !builtins.elem ext ["egg-info" "pyc"] &&
52 !builtins.elem ext ["egg-info" "pyc"] &&
53 # TODO: johbo: This check is wrong, since "path" contains an absolute path,
53 # TODO: johbo: This check is wrong, since "path" contains an absolute path,
54 # it would still be good to restore it since we want to ignore "result-*".
54 # it would still be good to restore it since we want to ignore "result-*".
55 !hasPrefix "result" path;
55 !hasPrefix "result" path;
56
56
57 basePythonPackages = with builtins; if isAttrs pythonPackages
57 basePythonPackages = with builtins; if isAttrs pythonPackages
58 then pythonPackages
58 then pythonPackages
59 else getAttr pythonPackages pkgs;
59 else getAttr pythonPackages pkgs;
60
60
61 buildBowerComponents =
61 buildBowerComponents =
62 pkgs.buildBowerComponents or
62 pkgs.buildBowerComponents or
63 (import ./pkgs/backport-16.03-build-bower-components.nix { inherit pkgs; });
63 (import ./pkgs/backport-16.03-build-bower-components.nix { inherit pkgs; });
64
64
65 sources = pkgs.config.rc.sources or {};
65 sources = pkgs.config.rc.sources or {};
66 version = builtins.readFile ./rhodecode/VERSION;
66 version = builtins.readFile ./rhodecode/VERSION;
67 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
67 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
68
68
69 nodeEnv = import ./pkgs/node-default.nix {
69 nodeEnv = import ./pkgs/node-default.nix {
70 inherit pkgs;
70 inherit pkgs;
71 };
71 };
72 nodeDependencies = nodeEnv.shell.nodeDependencies;
72 nodeDependencies = nodeEnv.shell.nodeDependencies;
73
73
74 bowerComponents = buildBowerComponents {
74 bowerComponents = buildBowerComponents {
75 name = "enterprise-ce-${version}";
75 name = "enterprise-ce-${version}";
76 generated = ./pkgs/bower-packages.nix;
76 generated = ./pkgs/bower-packages.nix;
77 src = rhodecode-enterprise-ce-src;
77 src = rhodecode-enterprise-ce-src;
78 };
78 };
79
79
80 pythonGeneratedPackages = self: basePythonPackages.override (a: {
80 pythonGeneratedPackages = self: basePythonPackages.override (a: {
81 inherit self;
81 inherit self;
82 })
82 })
83 // (scopedImport {
83 // (scopedImport {
84 self = self;
84 self = self;
85 super = basePythonPackages;
85 super = basePythonPackages;
86 inherit pkgs;
86 inherit pkgs;
87 inherit (pkgs) fetchurl fetchgit;
87 inherit (pkgs) fetchurl fetchgit;
88 } ./pkgs/python-packages.nix);
88 } ./pkgs/python-packages.nix);
89
89
90 pythonOverrides = import ./pkgs/python-packages-overrides.nix {
90 pythonOverrides = import ./pkgs/python-packages-overrides.nix {
91 inherit
91 inherit
92 basePythonPackages
92 basePythonPackages
93 pkgs;
93 pkgs;
94 };
94 };
95
95
96 pythonLocalOverrides = self: super: {
96 pythonLocalOverrides = self: super: {
97 rhodecode-enterprise-ce =
97 rhodecode-enterprise-ce =
98 let
98 let
99 linkNodeAndBowerPackages = ''
99 linkNodeAndBowerPackages = ''
100 echo "Export RhodeCode CE path"
100 echo "Export RhodeCode CE path"
101 export RHODECODE_CE_PATH=${rhodecode-enterprise-ce-src}
101 export RHODECODE_CE_PATH=${rhodecode-enterprise-ce-src}
102 echo "Link node packages"
102 echo "Link node packages"
103 rm -fr node_modules
103 rm -fr node_modules
104 mkdir node_modules
104 mkdir node_modules
105 # johbo: Linking individual packages allows us to run "npm install"
105 # johbo: Linking individual packages allows us to run "npm install"
106 # inside of a shell to try things out. Re-entering the shell will
106 # inside of a shell to try things out. Re-entering the shell will
107 # restore a clean environment.
107 # restore a clean environment.
108 ln -s ${nodeDependencies}/lib/node_modules/* node_modules/
108 ln -s ${nodeDependencies}/lib/node_modules/* node_modules/
109
109
110 echo "DONE: Link node packages"
110 echo "DONE: Link node packages"
111
111
112 echo "Link bower packages"
112 echo "Link bower packages"
113 rm -fr bower_components
113 rm -fr bower_components
114 mkdir bower_components
114 mkdir bower_components
115
115
116 ln -s ${bowerComponents}/bower_components/* bower_components/
116 ln -s ${bowerComponents}/bower_components/* bower_components/
117 echo "DONE: Link bower packages"
117 echo "DONE: Link bower packages"
118 '';
118 '';
119 in super.rhodecode-enterprise-ce.override (attrs: {
119 in super.rhodecode-enterprise-ce.override (attrs: {
120
120
121 inherit
121 inherit
122 doCheck
122 doCheck
123 version;
123 version;
124 name = "rhodecode-enterprise-ce-${version}";
124 name = "rhodecode-enterprise-ce-${version}";
125 releaseName = "RhodeCodeEnterpriseCE-${version}";
125 releaseName = "RhodeCodeEnterpriseCE-${version}";
126 src = rhodecode-enterprise-ce-src;
126 src = rhodecode-enterprise-ce-src;
127 dontStrip = true; # prevent strip, we don't need it.
127 dontStrip = true; # prevent strip, we don't need it.
128
128
129 buildInputs =
129 buildInputs =
130 attrs.buildInputs ++
130 attrs.buildInputs ++
131 (with self; [
131 (with self; [
132 pkgs.nodePackages.bower
132 pkgs.nodePackages.bower
133 pkgs.nodePackages.grunt-cli
133 pkgs.nodePackages.grunt-cli
134 pkgs.subversion
134 pkgs.subversion
135 pytest-catchlog
135 pytest-catchlog
136 rhodecode-testdata
136 rhodecode-testdata
137 ]);
137 ]);
138
138
139 #TODO: either move this into overrides, OR use the new machanics from
139 #TODO: either move this into overrides, OR use the new machanics from
140 # pip2nix and requiremtn.txt file
140 # pip2nix and requiremtn.txt file
141 propagatedBuildInputs = attrs.propagatedBuildInputs ++ (with self; [
141 propagatedBuildInputs = attrs.propagatedBuildInputs ++ (with self; [
142 rhodecode-tools
142 rhodecode-tools
143 ]);
143 ]);
144
144
145 # TODO: johbo: Make a nicer way to expose the parts. Maybe
145 # TODO: johbo: Make a nicer way to expose the parts. Maybe
146 # pkgs/default.nix?
146 # pkgs/default.nix?
147 passthru = {
147 passthru = {
148 inherit
148 inherit
149 bowerComponents
149 bowerComponents
150 linkNodeAndBowerPackages
150 linkNodeAndBowerPackages
151 myPythonPackagesUnfix
151 myPythonPackagesUnfix
152 pythonLocalOverrides;
152 pythonLocalOverrides;
153 pythonPackages = self;
153 pythonPackages = self;
154 };
154 };
155
155
156 LC_ALL = "en_US.UTF-8";
156 LC_ALL = "en_US.UTF-8";
157 LOCALE_ARCHIVE =
157 LOCALE_ARCHIVE =
158 if pkgs.stdenv ? glibc
158 if pkgs.stdenv ? glibc
159 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
159 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
160 else "";
160 else "";
161
161
162 preCheck = ''
162 preCheck = ''
163 export PATH="$out/bin:$PATH"
163 export PATH="$out/bin:$PATH"
164 '';
164 '';
165
165
166 postCheck = ''
166 postCheck = ''
167 rm -rf $out/lib/${self.python.libPrefix}/site-packages/pytest_pylons
167 rm -rf $out/lib/${self.python.libPrefix}/site-packages/pytest_pylons
168 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
168 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
169 '';
169 '';
170
170
171 preBuild = linkNodeAndBowerPackages + ''
171 preBuild = linkNodeAndBowerPackages + ''
172 grunt
172 grunt
173 rm -fr node_modules
173 rm -fr node_modules
174 '';
174 '';
175
175
176 postInstall = ''
176 postInstall = ''
177 echo "Writing meta information for rccontrol to nix-support/rccontrol"
178 mkdir -p $out/nix-support/rccontrol
179 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
180 echo "DONE: Meta information for rccontrol written"
181
177 # python based programs need to be wrapped
182 # python based programs need to be wrapped
183 ln -s ${self.pyramid}/bin/* $out/bin/
184 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
178 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
185 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
179 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
180 ln -s ${self.PasteScript}/bin/paster $out/bin/
186 ln -s ${self.PasteScript}/bin/paster $out/bin/
181 ln -s ${self.channelstream}/bin/channelstream $out/bin/
187 ln -s ${self.channelstream}/bin/channelstream $out/bin/
182 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
183
188
184 # rhodecode-tools
189 # rhodecode-tools
185 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
186 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
190 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
187
191
188 # note that condition should be restricted when adding further tools
192 # note that condition should be restricted when adding further tools
189 for file in $out/bin/*; do #*/
193 for file in $out/bin/*;
194 do
190 wrapProgram $file \
195 wrapProgram $file \
196 --prefix PATH : $PATH \
191 --prefix PYTHONPATH : $PYTHONPATH \
197 --prefix PYTHONPATH : $PYTHONPATH \
192 --prefix PATH : $PATH \
193 --set PYTHONHASHSEED random
198 --set PYTHONHASHSEED random
194 done
199 done
195
200
196 mkdir $out/etc
201 mkdir $out/etc
197 cp configs/production.ini $out/etc
202 cp configs/production.ini $out/etc
198
203
199 echo "Writing meta information for rccontrol to nix-support/rccontrol"
200 mkdir -p $out/nix-support/rccontrol
201 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
202 echo "DONE: Meta information for rccontrol written"
203
204
204 # TODO: johbo: Make part of ac-tests
205 # TODO: johbo: Make part of ac-tests
205 if [ ! -f rhodecode/public/js/scripts.js ]; then
206 if [ ! -f rhodecode/public/js/scripts.js ]; then
206 echo "Missing scripts.js"
207 echo "Missing scripts.js"
207 exit 1
208 exit 1
208 fi
209 fi
209 if [ ! -f rhodecode/public/css/style.css ]; then
210 if [ ! -f rhodecode/public/css/style.css ]; then
210 echo "Missing style.css"
211 echo "Missing style.css"
211 exit 1
212 exit 1
212 fi
213 fi
213 '';
214 '';
214
215
215 });
216 });
216
217
217 rhodecode-testdata = import "${rhodecode-testdata-src}/default.nix" {
218 rhodecode-testdata = import "${rhodecode-testdata-src}/default.nix" {
218 inherit
219 inherit
219 doCheck
220 doCheck
220 pkgs
221 pkgs
221 pythonPackages;
222 pythonPackages;
222 };
223 };
223
224
224 };
225 };
225
226
226 rhodecode-testdata-src = sources.rhodecode-testdata or (
227 rhodecode-testdata-src = sources.rhodecode-testdata or (
227 pkgs.fetchhg {
228 pkgs.fetchhg {
228 url = "https://code.rhodecode.com/upstream/rc_testdata";
229 url = "https://code.rhodecode.com/upstream/rc_testdata";
229 rev = "v0.10.0";
230 rev = "v0.10.0";
230 sha256 = "0zn9swwvx4vgw4qn8q3ri26vvzgrxn15x6xnjrysi1bwmz01qjl0";
231 sha256 = "0zn9swwvx4vgw4qn8q3ri26vvzgrxn15x6xnjrysi1bwmz01qjl0";
231 });
232 });
232
233
233 # Apply all overrides and fix the final package set
234 # Apply all overrides and fix the final package set
234 myPythonPackagesUnfix = with pkgs.lib;
235 myPythonPackagesUnfix = with pkgs.lib;
235 (extends pythonExternalOverrides
236 (extends pythonExternalOverrides
236 (extends pythonLocalOverrides
237 (extends pythonLocalOverrides
237 (extends pythonOverrides
238 (extends pythonOverrides
238 pythonGeneratedPackages)));
239 pythonGeneratedPackages)));
239 myPythonPackages = (pkgs.lib.fix myPythonPackagesUnfix);
240 myPythonPackages = (pkgs.lib.fix myPythonPackagesUnfix);
240
241
241 in myPythonPackages.rhodecode-enterprise-ce
242 in myPythonPackages.rhodecode-enterprise-ce
@@ -1,87 +1,90 b''
1 .. _apache-conf-eg:
1 .. _apache-conf-eg:
2
2
3 Apache Configuration Example
3 Apache Configuration Example
4 ----------------------------
4 ----------------------------
5
5
6 Use the following example to configure Apache as a your web server.
6 Use the following example to configure Apache as a your web server.
7 Below config if for an Apache Reverse Proxy configuration.
7 Below config if for an Apache Reverse Proxy configuration.
8
8
9 .. note::
9 .. note::
10
10
11 Apache requires the following modules to be enabled. Below is an example
11 Apache requires the following modules to be enabled. Below is an example
12 how to enable them on Ubuntu Server
12 how to enable them on Ubuntu Server
13
13
14
14
15 .. code-block:: bash
15 .. code-block:: bash
16
16
17 $ sudo a2enmod proxy
17 $ sudo a2enmod proxy
18 $ sudo a2enmod proxy_http
18 $ sudo a2enmod proxy_http
19 $ sudo a2enmod proxy_balancer
19 $ sudo a2enmod proxy_balancer
20 $ sudo a2enmod headers
20 $ sudo a2enmod headers
21 $ sudo a2enmod ssl
21 $ sudo a2enmod ssl
22 $ sudo a2enmod rewrite
22 $ sudo a2enmod rewrite
23
23
24 # requires Apache 2.4+, required to handle websockets/channelstream
24 # requires Apache 2.4+, required to handle websockets/channelstream
25 $ sudo a2enmod proxy_wstunnel
25 $ sudo a2enmod proxy_wstunnel
26
26
27
27
28 .. code-block:: apache
28 .. code-block:: apache
29
29
30 ## HTTP to HTTPS rewrite
30 ## HTTP to HTTPS rewrite
31 <VirtualHost *:80>
31 <VirtualHost *:80>
32 ServerName rhodecode.myserver.com
32 ServerName rhodecode.myserver.com
33 DocumentRoot /var/www/html
33 DocumentRoot /var/www/html
34 Redirect permanent / https://rhodecode.myserver.com/
34 Redirect permanent / https://rhodecode.myserver.com/
35 </VirtualHost>
35 </VirtualHost>
36
36
37 ## MAIN SSL enabled server
37 ## MAIN SSL enabled server
38 <VirtualHost *:443>
38 <VirtualHost *:443>
39
39
40 ServerName rhodecode.myserver.com
40 ServerName rhodecode.myserver.com
41 ServerAlias rhodecode.myserver.com
41 ServerAlias rhodecode.myserver.com
42
42
43 ## serve static files by Apache, recommended for performance
43 ## serve static files by Apache, recommended for performance
44 #Alias /_static /home/ubuntu/.rccontrol/community-1/static
44 #Alias /_static /home/ubuntu/.rccontrol/community-1/static
45
45
46 RequestHeader set X-Forwarded-Proto "https"
46 RequestHeader set X-Forwarded-Proto "https"
47
47
48 ## channelstream websocket handling
48 ## channelstream websocket handling
49 ProxyPass /_channelstream ws://localhost:9800
49 ProxyPass /_channelstream ws://localhost:9800
50 ProxyPassReverse /_channelstream ws://localhost:9800
50 ProxyPassReverse /_channelstream ws://localhost:9800
51
51
52 <Proxy *>
52 <Proxy *>
53 Order allow,deny
53 Order allow,deny
54 Allow from all
54 Allow from all
55 </Proxy>
55 </Proxy>
56
56
57 # Directive to properly generate url (clone url) for RhodeCode
57 # Directive to properly generate url (clone url) for RhodeCode
58 ProxyPreserveHost On
58 ProxyPreserveHost On
59
59
60 # Url to running RhodeCode instance. This is shown as `- URL:` when
60 # Url to running RhodeCode instance. This is shown as `- URL:` when
61 # running rccontrol status.
61 # running rccontrol status.
62 ProxyPass / http://127.0.0.1:10002/ timeout=7200 Keepalive=On
62 ProxyPass / http://127.0.0.1:10002/ timeout=7200 Keepalive=On
63 ProxyPassReverse / http://127.0.0.1:10002/
63 ProxyPassReverse / http://127.0.0.1:10002/
64
64
65 # Increase headers for large Mercurial headers
66 LimitRequestLine 16380
67
65 # strict http prevents from https -> http downgrade
68 # strict http prevents from https -> http downgrade
66 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
69 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
67
70
68 # Set x-frame options
71 # Set x-frame options
69 Header always append X-Frame-Options SAMEORIGIN
72 Header always append X-Frame-Options SAMEORIGIN
70
73
71 # To enable https use line below
74 # To enable https use line below
72 # SetEnvIf X-Url-Scheme https HTTPS=1
75 # SetEnvIf X-Url-Scheme https HTTPS=1
73
76
74 # SSL setup
77 # SSL setup
75 SSLEngine On
78 SSLEngine On
76 SSLCertificateFile /etc/apache2/ssl/rhodecode.myserver.pem
79 SSLCertificateFile /etc/apache2/ssl/rhodecode.myserver.pem
77 SSLCertificateKeyFile /etc/apache2/ssl/rhodecode.myserver.key
80 SSLCertificateKeyFile /etc/apache2/ssl/rhodecode.myserver.key
78
81
79 SSLProtocol all -SSLv2 -SSLv3
82 SSLProtocol all -SSLv2 -SSLv3
80 SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
83 SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
81 SSLHonorCipherOrder on
84 SSLHonorCipherOrder on
82
85
83 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
86 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
84 #SSLOpenSSLConfCmd DHParameters "/etc/apache2/dhparam.pem"
87 #SSLOpenSSLConfCmd DHParameters "/etc/apache2/dhparam.pem"
85
88
86 </VirtualHost>
89 </VirtualHost>
87
90
@@ -1,125 +1,134 b''
1 Nginx Configuration Example
1 Nginx Configuration Example
2 ---------------------------
2 ---------------------------
3
3
4 Use the following example to configure Nginx as a your web server.
4 Use the following example to configure Nginx as a your web server.
5
5
6
6
7 .. code-block:: nginx
7 .. code-block:: nginx
8 ## rate limiter for certain pages to prevent brute force attacks
9 limit_req_zone $binary_remote_addr zone=dl_limit:10m rate=1r/s;
8
10
11 ## custom log format
9 log_format log_custom '$remote_addr - $remote_user [$time_local] '
12 log_format log_custom '$remote_addr - $remote_user [$time_local] '
10 '"$request" $status $body_bytes_sent '
13 '"$request" $status $body_bytes_sent '
11 '"$http_referer" "$http_user_agent" '
14 '"$http_referer" "$http_user_agent" '
12 '$request_time $upstream_response_time $pipe';
15 '$request_time $upstream_response_time $pipe';
13
16
14 ## define upstream (local RhodeCode instance) to connect to
17 ## define upstream (local RhodeCode instance) to connect to
15 upstream rc {
18 upstream rc {
16 # Url to running RhodeCode instance.
19 # Url to running RhodeCode instance.
17 # This is shown as `- URL:` in output from rccontrol status.
20 # This is shown as `- URL:` in output from rccontrol status.
18 server 127.0.0.1:10002;
21 server 127.0.0.1:10002;
19
22
20 # add more instances for load balancing
23 # add more instances for load balancing
21 # server 127.0.0.1:10003;
24 # server 127.0.0.1:10003;
22 # server 127.0.0.1:10004;
25 # server 127.0.0.1:10004;
23 }
26 }
24
27
25 ## HTTP to HTTPS rewrite
28 ## HTTP to HTTPS rewrite
26 server {
29 server {
27 listen 80;
30 listen 80;
28 server_name rhodecode.myserver.com;
31 server_name rhodecode.myserver.com;
29
32
30 if ($http_host = rhodecode.myserver.com) {
33 if ($http_host = rhodecode.myserver.com) {
31 rewrite (.*) https://rhodecode.myserver.com$1 permanent;
34 rewrite (.*) https://rhodecode.myserver.com$1 permanent;
32 }
35 }
33 }
36 }
34
37
35 ## Optional gist alias server, for serving nicer GIST urls.
38 ## Optional gist alias server, for serving nicer GIST urls.
36 server {
39 server {
37 listen 443;
40 listen 443;
38 server_name gist.myserver.com;
41 server_name gist.myserver.com;
39 access_log /var/log/nginx/gist.access.log log_custom;
42 access_log /var/log/nginx/gist.access.log log_custom;
40 error_log /var/log/nginx/gist.error.log;
43 error_log /var/log/nginx/gist.error.log;
41
44
42 ssl on;
45 ssl on;
43 ssl_certificate gist.rhodecode.myserver.com.crt;
46 ssl_certificate gist.rhodecode.myserver.com.crt;
44 ssl_certificate_key gist.rhodecode.myserver.com.key;
47 ssl_certificate_key gist.rhodecode.myserver.com.key;
45
48
46 ssl_session_timeout 5m;
49 ssl_session_timeout 5m;
47
50
48 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
51 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
49 ssl_prefer_server_ciphers on;
52 ssl_prefer_server_ciphers on;
50 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
53 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
51
54
52 # strict http prevents from https -> http downgrade
55 # strict http prevents from https -> http downgrade
53 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
56 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
54
57
55 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
58 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
56 #ssl_dhparam /etc/nginx/ssl/dhparam.pem;
59 #ssl_dhparam /etc/nginx/ssl/dhparam.pem;
57
60
58 rewrite ^/(.+)$ https://rhodecode.myserver.com/_admin/gists/$1;
61 rewrite ^/(.+)$ https://rhodecode.myserver.com/_admin/gists/$1;
59 rewrite (.*) https://rhodecode.myserver.com/_admin/gists;
62 rewrite (.*) https://rhodecode.myserver.com/_admin/gists;
60 }
63 }
61
64
62
65
63 ## MAIN SSL enabled server
66 ## MAIN SSL enabled server
64 server {
67 server {
65 listen 443 ssl;
68 listen 443 ssl;
66 server_name rhodecode.myserver.com;
69 server_name rhodecode.myserver.com;
67
70
68 access_log /var/log/nginx/rhodecode.access.log log_custom;
71 access_log /var/log/nginx/rhodecode.access.log log_custom;
69 error_log /var/log/nginx/rhodecode.error.log;
72 error_log /var/log/nginx/rhodecode.error.log;
70
73
71 ssl on;
74 ssl on;
72 ssl_certificate rhodecode.myserver.com.crt;
75 ssl_certificate rhodecode.myserver.com.crt;
73 ssl_certificate_key rhodecode.myserver.com.key;
76 ssl_certificate_key rhodecode.myserver.com.key;
74
77
75 ssl_session_timeout 5m;
78 ssl_session_timeout 5m;
76
79
77 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
80 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
78 ssl_prefer_server_ciphers on;
81 ssl_prefer_server_ciphers on;
79 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
82 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
80
83
81 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
84 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
82 #ssl_dhparam /etc/nginx/ssl/dhparam.pem;
85 #ssl_dhparam /etc/nginx/ssl/dhparam.pem;
83
86
84 include /etc/nginx/proxy.conf;
87 include /etc/nginx/proxy.conf;
85
88
86 ## serve static files by Nginx, recommended for performance
89 ## serve static files by Nginx, recommended for performance
87 # location /_static/rhodecode {
90 # location /_static/rhodecode {
88 # alias /path/to/.rccontrol/enterprise-1/static;
91 # alias /path/to/.rccontrol/enterprise-1/static;
89 # }
92 # }
90
93
91 ## channelstream websocket handling
94 ## channelstream websocket handling
92 location /_channelstream {
95 location /_channelstream {
93 rewrite /_channelstream/(.*) /$1 break;
96 rewrite /_channelstream/(.*) /$1 break;
94
97
95 proxy_pass http://127.0.0.1:9800;
98 proxy_pass http://127.0.0.1:9800;
96
99
97 proxy_connect_timeout 10;
100 proxy_connect_timeout 10;
98 proxy_send_timeout 10m;
101 proxy_send_timeout 10m;
99 proxy_read_timeout 10m;
102 proxy_read_timeout 10m;
100 tcp_nodelay off;
103 tcp_nodelay off;
101 proxy_set_header Host $host;
104 proxy_set_header Host $host;
102 proxy_set_header X-Real-IP $remote_addr;
105 proxy_set_header X-Real-IP $remote_addr;
103 proxy_set_header X-Url-Scheme $scheme;
106 proxy_set_header X-Url-Scheme $scheme;
104 proxy_set_header X-Forwarded-Proto $scheme;
107 proxy_set_header X-Forwarded-Proto $scheme;
105 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
108 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
106 gzip off;
109 gzip off;
107 proxy_http_version 1.1;
110 proxy_http_version 1.1;
108 proxy_set_header Upgrade $http_upgrade;
111 proxy_set_header Upgrade $http_upgrade;
109 proxy_set_header Connection "upgrade";
112 proxy_set_header Connection "upgrade";
110 }
113 }
111
114
115 location /_admin/login {
116 ## rate limit this endpoint
117 limit_req zone=dl_limit burst=10 nodelay;
118 try_files $uri @rhode;
119 }
120
112 location / {
121 location / {
113 try_files $uri @rhode;
122 try_files $uri @rhode;
114 }
123 }
115
124
116 location @rhode {
125 location @rhode {
117 proxy_pass http://rc;
126 proxy_pass http://rc;
118 }
127 }
119
128
120 ## custom 502 error page
129 ## custom 502 error page
121 error_page 502 /502.html;
130 error_page 502 /502.html;
122 location = /502.html {
131 location = /502.html {
123 root /path/to/.rccontrol/enterprise-1/static;
132 root /path/to/.rccontrol/enterprise-1/static;
124 }
133 }
125 } No newline at end of file
134 }
@@ -1,68 +1,77 b''
1 .. _repo-xtra:
1 .. _repo-xtra:
2
2
3 Repository Extra Fields
3 Repository Extra Fields
4 =======================
4 =======================
5
5
6 Extra fields attached to a |repo| allow you to configure additional actions for
6 Extra fields attached to a |repo| allow you to configure additional fields for
7 |RCX|. To install and read more about |RCX|, see the :ref:`install-rcx` section.
7 each repository. This allows storing custom data per-repository.
8
9 It can be used in :ref:`integrations-webhook` or in |RCX|.
10 To install and read more about |RCX|, see the :ref:`install-rcx` section.
11
8
12
9 Enabling Extra Fields
13 Enabling Extra Fields
10 ---------------------
14 ---------------------
11
15
12 To enable extra fields on |repos|, use the following steps:
16 To enable extra fields on |repos|, use the following steps:
13
17
14 1. Go to the :menuselection:`Admin --> Settings --> Visual` page.
18 1. Go to the :menuselection:`Admin --> Settings --> Visual` page.
15 2. Check the :guilabel:`Use repository extra fields` box.
19 2. Check the :guilabel:`Use repository extra fields` box.
16 3. Save your changes.
20 3. Save your changes.
17
21
18
22
19 Configuring Extra Fields
23 Configuring Extra Fields
20 ------------------------
24 ------------------------
21
25
22 To configure extra fields per repository, use the following steps:
26 To configure extra fields per repository, use the following steps:
23
27
24 1. Go to :menuselection:`Admin --> Repositories` and select :guilabel:`Edit`
28 1. Go to :menuselection:`Admin --> Repositories` and select :guilabel:`Edit`
25 beside the |repo| to which you wish to add extra fields.
29 beside the |repo| to which you wish to add extra fields.
26 2. On the |repo| settings page, select the :guilabel:`Extra fields` tab.
30 2. On the |repo| settings page, select the :guilabel:`Extra fields` tab.
27
31
28 .. image:: ../images/extra-repo-fields.png
32 .. image:: ../images/extra-repo-fields.png
29
33
34 The most important is the `New field key` variable which under the value will
35 be stored. It needs to be unique for each repository. The label and description
36 will be generated in repository settings where users can actually save some
37 values inside generated extra fields.
30
38
31 Example Usage
39
32 -------------
40 Example Usage in extensions
41 ---------------------------
33
42
34 To use the extra fields in an extension, see the example below. For more
43 To use the extra fields in an extension, see the example below. For more
35 information and examples, see the :ref:`extensions-hooks-ref` section.
44 information and examples, see the :ref:`extensions-hooks-ref` section.
36
45
37 .. code-block:: python
46 .. code-block:: python
38
47
39 call = load_extension('http_notify.py')
48 call = load_extension('http_notify.py')
40 if call:
49 if call:
41 url = 'http://default.url' # <url for post data>
50 url = 'http://default.url' # <url for post data>
42
51
43 # possibly extract the URL from extra fields
52 # possibly extract the URL from extra fields
44 call = load_extension('extra_fields.py')
53 call = load_extension('extra_fields.py')
45 if call:
54 if call:
46 repo_extra_fields = call(**kwargs)
55 repo_extra_fields = call(**kwargs)
47 # now update if we have extra fields, they have precedence
56 # now update if we have extra fields, they have precedence
48 # this way users can store any configuration inside the database per
57 # this way users can store any configuration inside the database per
49 # repo
58 # repo
50 for key, data in repo_extra_fields.items():
59 for key, data in repo_extra_fields.items():
51 kwargs[key] = data['field_value']
60 kwargs[key] = data['field_value']
52
61
53 # an endpoint url data will be sent to, fetched from extra fields
62 # an endpoint url data will be sent to, fetched from extra fields
54 # if exists, or fallback to default
63 # if exists, or fallback to default
55 kwargs['URL'] = kwargs.pop('webhook_url', None) or url
64 kwargs['URL'] = kwargs.pop('webhook_url', None) or url
56
65
57 # fetch pushed commits, from commit_ids list
66 # fetch pushed commits, from commit_ids list
58 call = load_extension('extract_commits.py')
67 call = load_extension('extract_commits.py')
59 extracted_commits = {}
68 extracted_commits = {}
60 if call:
69 if call:
61 extracted_commits = call(**kwargs)
70 extracted_commits = call(**kwargs)
62 # store the commits for the next call chain
71 # store the commits for the next call chain
63 kwargs['COMMITS'] = extracted_commits
72 kwargs['COMMITS'] = extracted_commits
64
73
65 # set additional keys and values to be sent via POST to given URL
74 # set additional keys and values to be sent via POST to given URL
66 kwargs['caller_type'] = 'rhodecode'
75 kwargs['caller_type'] = 'rhodecode'
67 kwargs['date'] = time.time() # import time before
76 kwargs['date'] = time.time() # import time before
68 call(**kwargs)
77 call(**kwargs)
@@ -1,298 +1,324 b''
1 .. _vcs-server:
1 .. _vcs-server:
2
2
3 VCS Server Management
3 VCS Server Management
4 ---------------------
4 ---------------------
5
5
6 The VCS Server handles |RCM| backend functionality. You need to configure
6 The VCS Server handles |RCM| backend functionality. You need to configure
7 a VCS Server to run with a |RCM| instance. If you do not, you will be missing
7 a VCS Server to run with a |RCM| instance. If you do not, you will be missing
8 the connection between |RCM| and its |repos|. This will cause error messages
8 the connection between |RCM| and its |repos|. This will cause error messages
9 on the web interface. You can run your setup in the following configurations,
9 on the web interface. You can run your setup in the following configurations,
10 currently the best performance is one VCS Server per |RCM| instance:
10 currently the best performance is one of following:
11
11
12 * One VCS Server per |RCM| instance.
12 * One VCS Server per |RCM| instance.
13 * One VCS Server handling multiple instances.
13 * One VCS Server handling multiple instances.
14
14
15 .. important::
15 .. important::
16
16
17 If your server locale settings are not correctly configured,
17 If your server locale settings are not correctly configured,
18 |RCE| and the VCS Server can run into issues. See this `Ask Ubuntu`_ post
18 |RCE| and the VCS Server can run into issues. See this `Ask Ubuntu`_ post
19 which explains the problem and gives a solution.
19 which explains the problem and gives a solution.
20
20
21 For more information, see the following sections:
21 For more information, see the following sections:
22
22
23 * :ref:`install-vcs`
23 * :ref:`install-vcs`
24 * :ref:`config-vcs`
24 * :ref:`config-vcs`
25 * :ref:`vcs-server-options`
25 * :ref:`vcs-server-options`
26 * :ref:`vcs-server-versions`
26 * :ref:`vcs-server-versions`
27 * :ref:`vcs-server-maintain`
27 * :ref:`vcs-server-maintain`
28 * :ref:`vcs-server-config-file`
28 * :ref:`vcs-server-config-file`
29 * :ref:`svn-http`
29 * :ref:`svn-http`
30
30
31 .. _install-vcs:
31 .. _install-vcs:
32
32
33 VCS Server Installation
33 VCS Server Installation
34 ^^^^^^^^^^^^^^^^^^^^^^^
34 ^^^^^^^^^^^^^^^^^^^^^^^
35
35
36 To install a VCS Server, see
36 To install a VCS Server, see
37 :ref:`Installing a VCS server <control:install-vcsserver>`.
37 :ref:`Installing a VCS server <control:install-vcsserver>`.
38
38
39 .. _config-vcs:
39 .. _config-vcs:
40
40
41 Hooking |RCE| to its VCS Server
41 Hooking |RCE| to its VCS Server
42 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43
43
44 To configure a |RCE| instance to use a VCS server, see
44 To configure a |RCE| instance to use a VCS server, see
45 :ref:`Configuring the VCS Server connection <control:manually-vcsserver-ini>`.
45 :ref:`Configuring the VCS Server connection <control:manually-vcsserver-ini>`.
46
46
47 .. _vcs-server-options:
47 .. _vcs-server-options:
48
48
49 |RCE| VCS Server Options
49 |RCE| VCS Server Options
50 ^^^^^^^^^^^^^^^^^^^^^^^^
50 ^^^^^^^^^^^^^^^^^^^^^^^^
51
51
52 The following list shows the available options on the |RCM| side of the
52 The following list shows the available options on the |RCM| side of the
53 connection to the VCS Server. The settings are configured per
53 connection to the VCS Server. The settings are configured per
54 instance in the
54 instance in the
55 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
55 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
56
56
57 .. rst-class:: dl-horizontal
57 .. rst-class:: dl-horizontal
58
58
59 \vcs.backends <available-vcs-systems>
59 \vcs.backends <available-vcs-systems>
60 Set a comma-separated list of the |repo| options available from the
60 Set a comma-separated list of the |repo| options available from the
61 web interface. The default is ``hg, git, svn``,
61 web interface. The default is ``hg, git, svn``,
62 which is all |repo| types available.
62 which is all |repo| types available. The order of backends is also the
63 order backend will try to detect requests type.
63
64
64 \vcs.connection_timeout <seconds>
65 \vcs.connection_timeout <seconds>
65 Set the length of time in seconds that the VCS Server waits for
66 Set the length of time in seconds that the VCS Server waits for
66 requests to process. After the timeout expires,
67 requests to process. After the timeout expires,
67 the request is closed. The default is ``3600``. Set to a higher
68 the request is closed. The default is ``3600``. Set to a higher
68 number if you experience network latency, or timeout issues with very
69 number if you experience network latency, or timeout issues with very
69 large push/pull requests.
70 large push/pull requests.
70
71
71 \vcs.server.enable <boolean>
72 \vcs.server.enable <boolean>
72 Enable or disable the VCS Server. The available options are ``true`` or
73 Enable or disable the VCS Server. The available options are ``true`` or
73 ``false``. The default is ``true``.
74 ``false``. The default is ``true``.
74
75
75 \vcs.server <host:port>
76 \vcs.server <host:port>
76 Set the host, either hostname or IP Address, and port of the VCS server
77 Set the host, either hostname or IP Address, and port of the VCS server
77 you wish to run with your |RCM| instance.
78 you wish to run with your |RCM| instance.
78
79
79 .. code-block:: ini
80 .. code-block:: ini
80
81
81 ##################
82 ##################
82 ### VCS CONFIG ###
83 ### VCS CONFIG ###
83 ##################
84 ##################
84 # set this line to match your VCS Server
85 # set this line to match your VCS Server
85 vcs.server = 127.0.0.1:10004
86 vcs.server = 127.0.0.1:10004
86 # Set to False to disable the VCS Server
87 # Set to False to disable the VCS Server
87 vcs.server.enable = True
88 vcs.server.enable = True
88 vcs.backends = hg, git, svn
89 vcs.backends = hg, git, svn
89 vcs.connection_timeout = 3600
90 vcs.connection_timeout = 3600
90
91
91
92
92 .. _vcs-server-versions:
93 .. _vcs-server-versions:
93
94
94 VCS Server Versions
95 VCS Server Versions
95 ^^^^^^^^^^^^^^^^^^^
96 ^^^^^^^^^^^^^^^^^^^
96
97
97 An updated version of the VCS Server is released with each |RCE| version. Use
98 An updated version of the VCS Server is released with each |RCE| version. Use
98 the VCS Server number that matches with the |RCE| version to pair the
99 the VCS Server number that matches with the |RCE| version to pair the
99 appropriate ones together. For |RCE| versions pre 3.3.0,
100 appropriate ones together. For |RCE| versions pre 3.3.0,
100 VCS Server 1.X.Y works with |RCE| 3.X.Y, for example:
101 VCS Server 1.X.Y works with |RCE| 3.X.Y, for example:
101
102
102 * VCS Server 1.0.0 works with |RCE| 3.0.0
103 * VCS Server 1.0.0 works with |RCE| 3.0.0
103 * VCS Server 1.2.2 works with |RCE| 3.2.2
104 * VCS Server 1.2.2 works with |RCE| 3.2.2
104
105
105 For |RCE| versions post 3.3.0, the VCS Server and |RCE| version numbers
106 For |RCE| versions post 3.3.0, the VCS Server and |RCE| version numbers
106 match, for example:
107 match, for example:
107
108
108 * VCS Server |release| works with |RCE| |release|
109 * VCS Server |release| works with |RCE| |release|
109
110
110 .. _vcs-server-maintain:
111 .. _vcs-server-maintain:
111
112
112 VCS Server Memory Optimization
113 VCS Server Memory Optimization
113 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114
115
115 To configure the VCS server to manage the cache efficiently, you need to
116 To configure the VCS server to manage the cache efficiently, you need to
116 configure the following options in the
117 configure the following options in the
117 :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini` file. Once
118 :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini` file. Once
118 configured, restart the VCS Server.
119 configured, restart the VCS Server.
119
120
120 .. rst-class:: dl-horizontal
121 .. rst-class:: dl-horizontal
121
122
122 \beaker.cache.repo_object.type = memorylru
123 \beaker.cache.repo_object.type = memorylru
123 Configures the cache to discard the least recently used items.
124 Configures the cache to discard the least recently used items.
124 This setting takes the following valid options:
125 This setting takes the following valid options:
125
126
126 * ``memorylru``: The default setting, which removes the least recently
127 * ``memorylru``: The default setting, which removes the least recently
127 used items from the cache.
128 used items from the cache.
128 * ``memory``: Runs the VCS Server without clearing the cache.
129 * ``memory``: Runs the VCS Server without clearing the cache.
129 * ``nocache``: Runs the VCS Server without a cache. This will
130 * ``nocache``: Runs the VCS Server without a cache. This will
130 dramatically reduce the VCS Server performance.
131 dramatically reduce the VCS Server performance.
131
132
132 \beaker.cache.repo_object.max_items = 100
133 \beaker.cache.repo_object.max_items = 100
133 Sets the maximum number of items stored in the cache, before the cache
134 Sets the maximum number of items stored in the cache, before the cache
134 starts to be cleared.
135 starts to be cleared.
135
136
136 As a general rule of thumb, running this value at 120 resulted in a
137 As a general rule of thumb, running this value at 120 resulted in a
137 5GB cache. Running it at 240 resulted in a 9GB cache. Your results
138 5GB cache. Running it at 240 resulted in a 9GB cache. Your results
138 will differ based on usage patterns and |repo| sizes.
139 will differ based on usage patterns and |repo| sizes.
139
140
140 Tweaking this value to run at a fairly constant memory load on your
141 Tweaking this value to run at a fairly constant memory load on your
141 server will help performance.
142 server will help performance.
142
143
143 To clear the cache completely, you can restart the VCS Server.
144 To clear the cache completely, you can restart the VCS Server.
144
145
145 .. important::
146 .. important::
146
147
147 While the VCS Server handles a restart gracefully on the web interface,
148 While the VCS Server handles a restart gracefully on the web interface,
148 it will drop connections during push/pull requests. So it is recommended
149 it will drop connections during push/pull requests. So it is recommended
149 you only perform this when there is very little traffic on the instance.
150 you only perform this when there is very little traffic on the instance.
150
151
151 Use the following example to restart your VCS Server,
152 Use the following example to restart your VCS Server,
152 for full details see the :ref:`RhodeCode Control CLI <control:rcc-cli>`.
153 for full details see the :ref:`RhodeCode Control CLI <control:rcc-cli>`.
153
154
154 .. code-block:: bash
155 .. code-block:: bash
155
156
156 $ rccontrol status
157 $ rccontrol status
157
158
158 .. code-block:: vim
159 .. code-block:: vim
159
160
160 - NAME: vcsserver-1
161 - NAME: vcsserver-1
161 - STATUS: RUNNING
162 - STATUS: RUNNING
162 - TYPE: VCSServer
163 logs:/home/ubuntu/.rccontrol/vcsserver-1/vcsserver.log
163 - VERSION: 1.0.0
164 - VERSION: 4.7.2 VCSServer
164 - URL: http://127.0.0.1:10001
165 - URL: http://127.0.0.1:10008
166 - CONFIG: /home/ubuntu/.rccontrol/vcsserver-1/vcsserver.ini
165
167
166 $ rccontrol restart vcsserver-1
168 $ rccontrol restart vcsserver-1
167 Instance "vcsserver-1" successfully stopped.
169 Instance "vcsserver-1" successfully stopped.
168 Instance "vcsserver-1" successfully started.
170 Instance "vcsserver-1" successfully started.
169
171
170 .. _vcs-server-config-file:
172 .. _vcs-server-config-file:
171
173
172 VCS Server Configuration
174 VCS Server Configuration
173 ^^^^^^^^^^^^^^^^^^^^^^^^
175 ^^^^^^^^^^^^^^^^^^^^^^^^
174
176
175 You can configure settings for multiple VCS Servers on your
177 You can configure settings for multiple VCS Servers on your
176 system using their individual configuration files. Use the following
178 system using their individual configuration files. Use the following
177 properties inside the configuration file to set up your system. The default
179 properties inside the configuration file to set up your system. The default
178 location is :file:`home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`.
180 location is :file:`home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`.
179 For a more detailed explanation of the logger levers, see :ref:`debug-mode`.
181 For a more detailed explanation of the logger levers, see :ref:`debug-mode`.
180
182
181 .. rst-class:: dl-horizontal
183 .. rst-class:: dl-horizontal
182
184
183 \host <ip-address>
185 \host <ip-address>
184 Set the host on which the VCS Server will run.
186 Set the host on which the VCS Server will run. VCSServer is not
187 protected by any authentication, so we *highly* recommend running it
188 under localhost ip that is `127.0.0.1`
185
189
186 \port <int>
190 \port <int>
187 Set the port number on which the VCS Server will be available.
191 Set the port number on which the VCS Server will be available.
188
192
189 \locale <locale_utf>
193 \locale <locale_utf>
190 Set the locale the VCS Server expects.
194 Set the locale the VCS Server expects.
191
195
192 \threadpool_size <int>
196 \workers <int>
193 Set the size of the threadpool used to communicate
197 Set the number of process workers.Recommended
194 with the WSGI workers. This should be at least 6 times the number of
198 value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
195 WSGI worker processes.
196
199
197 \timeout <seconds>
200 \max_requests <int>
198 Set the timeout for RPC communication in seconds.
201 The maximum number of requests a worker will process before restarting.
202 Any value greater than zero will limit the number of requests a work
203 will process before automatically restarting. This is a simple method
204 to help limit the damage of memory leaks.
205
206 \max_requests_jitter <int>
207 The maximum jitter to add to the max_requests setting.
208 The jitter causes the restart per worker to be randomized by
209 randint(0, max_requests_jitter). This is intended to stagger worker
210 restarts to avoid all workers restarting at the same time.
211
199
212
200 .. note::
213 .. note::
201
214
202 After making changes, you need to restart your VCS Server to pick them up.
215 After making changes, you need to restart your VCS Server to pick them up.
203
216
204 .. code-block:: ini
217 .. code-block:: ini
205
218
206 ################################################################################
219 ################################################################################
207 # RhodeCode VCSServer - configuration #
220 # RhodeCode VCSServer with HTTP Backend - configuration #
208 # #
221 # #
209 ################################################################################
222 ################################################################################
210
223
211 [DEFAULT]
224
225 [server:main]
226 ## COMMON ##
212 host = 127.0.0.1
227 host = 127.0.0.1
213 port = 9900
228 port = 10002
229
230 ##########################
231 ## GUNICORN WSGI SERVER ##
232 ##########################
233 ## run with gunicorn --log-config vcsserver.ini --paste vcsserver.ini
234 use = egg:gunicorn#main
235 ## Sets the number of process workers. Recommended
236 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
237 workers = 3
238 ## process name
239 proc_name = rhodecode_vcsserver
240 ## type of worker class, one of sync, gevent
241 ## recommended for bigger setup is using of of other than sync one
242 worker_class = sync
243 ## The maximum number of simultaneous clients. Valid only for Gevent
244 #worker_connections = 10
245 ## max number of requests that worker will handle before being gracefully
246 ## restarted, could prevent memory leaks
247 max_requests = 1000
248 max_requests_jitter = 30
249 ## amount of time a worker can spend with handling a request before it
250 ## gets killed and restarted. Set to 6hrs
251 timeout = 21600
252
253 [app:main]
254 use = egg:rhodecode-vcsserver
255
256 pyramid.default_locale_name = en
257 pyramid.includes =
258
259 ## default locale used by VCS systems
214 locale = en_US.UTF-8
260 locale = en_US.UTF-8
215 # number of worker threads, this should be set based on a formula threadpool=N*6
216 # where N is number of RhodeCode Enterprise workers, eg. running 2 instances
217 # 8 gunicorn workers each would be 2 * 8 * 6 = 96, threadpool_size = 96
218 threadpool_size = 16
219 timeout = 0
220
261
221 # cache regions, please don't change
262 # cache regions, please don't change
222 beaker.cache.regions = repo_object
263 beaker.cache.regions = repo_object
223 beaker.cache.repo_object.type = memorylru
264 beaker.cache.repo_object.type = memorylru
224 beaker.cache.repo_object.max_items = 1000
265 beaker.cache.repo_object.max_items = 100
225
226 # cache auto-expires after N seconds
266 # cache auto-expires after N seconds
227 beaker.cache.repo_object.expire = 10
267 beaker.cache.repo_object.expire = 300
228 beaker.cache.repo_object.enabled = true
268 beaker.cache.repo_object.enabled = true
229
269
230
270
231 ################################
271 ################################
232 ### LOGGING CONFIGURATION ####
272 ### LOGGING CONFIGURATION ####
233 ################################
273 ################################
234 [loggers]
274 [loggers]
235 keys = root, vcsserver, beaker
275 keys = root, vcsserver, beaker
236
276
237 [handlers]
277 [handlers]
238 keys = console
278 keys = console
239
279
240 [formatters]
280 [formatters]
241 keys = generic
281 keys = generic
242
282
243 #############
283 #############
244 ## LOGGERS ##
284 ## LOGGERS ##
245 #############
285 #############
246 [logger_root]
286 [logger_root]
247 level = NOTSET
287 level = NOTSET
248 handlers = console
288 handlers = console
249
289
250 [logger_vcsserver]
290 [logger_vcsserver]
251 level = DEBUG
291 level = DEBUG
252 handlers =
292 handlers =
253 qualname = vcsserver
293 qualname = vcsserver
254 propagate = 1
294 propagate = 1
255
295
256 [logger_beaker]
296 [logger_beaker]
257 level = DEBUG
297 level = DEBUG
258 handlers =
298 handlers =
259 qualname = beaker
299 qualname = beaker
260 propagate = 1
300 propagate = 1
261
301
262
302
263 ##############
303 ##############
264 ## HANDLERS ##
304 ## HANDLERS ##
265 ##############
305 ##############
266
306
267 [handler_console]
307 [handler_console]
268 class = StreamHandler
308 class = StreamHandler
269 args = (sys.stderr,)
309 args = (sys.stderr,)
270 level = DEBUG
310 level = DEBUG
271 formatter = generic
311 formatter = generic
272
312
273 [handler_file]
274 class = FileHandler
275 args = ('vcsserver.log', 'a',)
276 level = DEBUG
277 formatter = generic
278
279 [handler_file_rotating]
280 class = logging.handlers.TimedRotatingFileHandler
281 # 'D', 5 - rotate every 5days
282 # you can set 'h', 'midnight'
283 args = ('vcsserver.log', 'D', 5, 10,)
284 level = DEBUG
285 formatter = generic
286
287 ################
313 ################
288 ## FORMATTERS ##
314 ## FORMATTERS ##
289 ################
315 ################
290
316
291 [formatter_generic]
317 [formatter_generic]
292 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
318 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
293 datefmt = %Y-%m-%d %H:%M:%S
319 datefmt = %Y-%m-%d %H:%M:%S
294
320
295
321
296 .. _Subversion Red Book: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.ref.svn
322 .. _Subversion Red Book: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.ref.svn
297
323
298 .. _Ask Ubuntu: http://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue
324 .. _Ask Ubuntu: http://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue
@@ -1,371 +1,379 b''
1 .. _pull-request-methods-ref:
1 .. _pull-request-methods-ref:
2
2
3 pull_request methods
3 pull_request methods
4 ====================
4 ====================
5
5
6 close_pull_request
6 close_pull_request
7 ------------------
7 ------------------
8
8
9 .. py:function:: close_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
9 .. py:function:: close_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>, message=<Optional:''>)
10
10
11 Close the pull request specified by `pullrequestid`.
11 Close the pull request specified by `pullrequestid`.
12
12
13 :param apiuser: This is filled automatically from the |authtoken|.
13 :param apiuser: This is filled automatically from the |authtoken|.
14 :type apiuser: AuthUser
14 :type apiuser: AuthUser
15 :param repoid: Repository name or repository ID to which the pull
15 :param repoid: Repository name or repository ID to which the pull
16 request belongs.
16 request belongs.
17 :type repoid: str or int
17 :type repoid: str or int
18 :param pullrequestid: ID of the pull request to be closed.
18 :param pullrequestid: ID of the pull request to be closed.
19 :type pullrequestid: int
19 :type pullrequestid: int
20 :param userid: Close the pull request as this user.
20 :param userid: Close the pull request as this user.
21 :type userid: Optional(str or int)
21 :type userid: Optional(str or int)
22 :param message: Optional message to close the Pull Request with. If not
23 specified it will be generated automatically.
24 :type message: Optional(str)
22
25
23 Example output:
26 Example output:
24
27
25 .. code-block:: bash
28 .. code-block:: bash
26
29
27 "id": <id_given_in_input>,
30 "id": <id_given_in_input>,
28 "result": {
31 "result": {
29 "pull_request_id": "<int>",
32 "pull_request_id": "<int>",
33 "close_status": "<str:status_lbl>,
30 "closed": "<bool>"
34 "closed": "<bool>"
31 },
35 },
32 "error": null
36 "error": null
33
37
34
38
35 comment_pull_request
39 comment_pull_request
36 --------------------
40 --------------------
37
41
38 .. py:function:: comment_pull_request(apiuser, repoid, pullrequestid, message=<Optional:None>, commit_id=<Optional:None>, status=<Optional:None>, comment_type=<Optional:u'note'>, resolves_comment_id=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
42 .. py:function:: comment_pull_request(apiuser, repoid, pullrequestid, message=<Optional:None>, commit_id=<Optional:None>, status=<Optional:None>, comment_type=<Optional:u'note'>, resolves_comment_id=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
39
43
40 Comment on the pull request specified with the `pullrequestid`,
44 Comment on the pull request specified with the `pullrequestid`,
41 in the |repo| specified by the `repoid`, and optionally change the
45 in the |repo| specified by the `repoid`, and optionally change the
42 review status.
46 review status.
43
47
44 :param apiuser: This is filled automatically from the |authtoken|.
48 :param apiuser: This is filled automatically from the |authtoken|.
45 :type apiuser: AuthUser
49 :type apiuser: AuthUser
46 :param repoid: The repository name or repository ID.
50 :param repoid: The repository name or repository ID.
47 :type repoid: str or int
51 :type repoid: str or int
48 :param pullrequestid: The pull request ID.
52 :param pullrequestid: The pull request ID.
49 :type pullrequestid: int
53 :type pullrequestid: int
50 :param commit_id: Specify the commit_id for which to set a comment. If
54 :param commit_id: Specify the commit_id for which to set a comment. If
51 given commit_id is different than latest in the PR status
55 given commit_id is different than latest in the PR status
52 change won't be performed.
56 change won't be performed.
53 :type commit_id: str
57 :type commit_id: str
54 :param message: The text content of the comment.
58 :param message: The text content of the comment.
55 :type message: str
59 :type message: str
56 :param status: (**Optional**) Set the approval status of the pull
60 :param status: (**Optional**) Set the approval status of the pull
57 request. One of: 'not_reviewed', 'approved', 'rejected',
61 request. One of: 'not_reviewed', 'approved', 'rejected',
58 'under_review'
62 'under_review'
59 :type status: str
63 :type status: str
60 :param comment_type: Comment type, one of: 'note', 'todo'
64 :param comment_type: Comment type, one of: 'note', 'todo'
61 :type comment_type: Optional(str), default: 'note'
65 :type comment_type: Optional(str), default: 'note'
62 :param userid: Comment on the pull request as this user
66 :param userid: Comment on the pull request as this user
63 :type userid: Optional(str or int)
67 :type userid: Optional(str or int)
64
68
65 Example output:
69 Example output:
66
70
67 .. code-block:: bash
71 .. code-block:: bash
68
72
69 id : <id_given_in_input>
73 id : <id_given_in_input>
70 result : {
74 result : {
71 "pull_request_id": "<Integer>",
75 "pull_request_id": "<Integer>",
72 "comment_id": "<Integer>",
76 "comment_id": "<Integer>",
73 "status": {"given": <given_status>,
77 "status": {"given": <given_status>,
74 "was_changed": <bool status_was_actually_changed> },
78 "was_changed": <bool status_was_actually_changed> },
75 },
79 },
76 error : null
80 error : null
77
81
78
82
79 create_pull_request
83 create_pull_request
80 -------------------
84 -------------------
81
85
82 .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, title, description=<Optional:''>, reviewers=<Optional:None>)
86 .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, title, description=<Optional:''>, reviewers=<Optional:None>)
83
87
84 Creates a new pull request.
88 Creates a new pull request.
85
89
86 Accepts refs in the following formats:
90 Accepts refs in the following formats:
87
91
88 * branch:<branch_name>:<sha>
92 * branch:<branch_name>:<sha>
89 * branch:<branch_name>
93 * branch:<branch_name>
90 * bookmark:<bookmark_name>:<sha> (Mercurial only)
94 * bookmark:<bookmark_name>:<sha> (Mercurial only)
91 * bookmark:<bookmark_name> (Mercurial only)
95 * bookmark:<bookmark_name> (Mercurial only)
92
96
93 :param apiuser: This is filled automatically from the |authtoken|.
97 :param apiuser: This is filled automatically from the |authtoken|.
94 :type apiuser: AuthUser
98 :type apiuser: AuthUser
95 :param source_repo: Set the source repository name.
99 :param source_repo: Set the source repository name.
96 :type source_repo: str
100 :type source_repo: str
97 :param target_repo: Set the target repository name.
101 :param target_repo: Set the target repository name.
98 :type target_repo: str
102 :type target_repo: str
99 :param source_ref: Set the source ref name.
103 :param source_ref: Set the source ref name.
100 :type source_ref: str
104 :type source_ref: str
101 :param target_ref: Set the target ref name.
105 :param target_ref: Set the target ref name.
102 :type target_ref: str
106 :type target_ref: str
103 :param title: Set the pull request title.
107 :param title: Set the pull request title.
104 :type title: str
108 :type title: str
105 :param description: Set the pull request description.
109 :param description: Set the pull request description.
106 :type description: Optional(str)
110 :type description: Optional(str)
107 :param reviewers: Set the new pull request reviewers list.
111 :param reviewers: Set the new pull request reviewers list.
112 Reviewer defined by review rules will be added automatically to the
113 defined list.
108 :type reviewers: Optional(list)
114 :type reviewers: Optional(list)
109 Accepts username strings or objects of the format:
115 Accepts username strings or objects of the format:
110
116
111 {'username': 'nick', 'reasons': ['original author']}
117 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
112
118
113
119
114 get_pull_request
120 get_pull_request
115 ----------------
121 ----------------
116
122
117 .. py:function:: get_pull_request(apiuser, repoid, pullrequestid)
123 .. py:function:: get_pull_request(apiuser, repoid, pullrequestid)
118
124
119 Get a pull request based on the given ID.
125 Get a pull request based on the given ID.
120
126
121 :param apiuser: This is filled automatically from the |authtoken|.
127 :param apiuser: This is filled automatically from the |authtoken|.
122 :type apiuser: AuthUser
128 :type apiuser: AuthUser
123 :param repoid: Repository name or repository ID from where the pull
129 :param repoid: Repository name or repository ID from where the pull
124 request was opened.
130 request was opened.
125 :type repoid: str or int
131 :type repoid: str or int
126 :param pullrequestid: ID of the requested pull request.
132 :param pullrequestid: ID of the requested pull request.
127 :type pullrequestid: int
133 :type pullrequestid: int
128
134
129 Example output:
135 Example output:
130
136
131 .. code-block:: bash
137 .. code-block:: bash
132
138
133 "id": <id_given_in_input>,
139 "id": <id_given_in_input>,
134 "result":
140 "result":
135 {
141 {
136 "pull_request_id": "<pull_request_id>",
142 "pull_request_id": "<pull_request_id>",
137 "url": "<url>",
143 "url": "<url>",
138 "title": "<title>",
144 "title": "<title>",
139 "description": "<description>",
145 "description": "<description>",
140 "status" : "<status>",
146 "status" : "<status>",
141 "created_on": "<date_time_created>",
147 "created_on": "<date_time_created>",
142 "updated_on": "<date_time_updated>",
148 "updated_on": "<date_time_updated>",
143 "commit_ids": [
149 "commit_ids": [
144 ...
150 ...
145 "<commit_id>",
151 "<commit_id>",
146 "<commit_id>",
152 "<commit_id>",
147 ...
153 ...
148 ],
154 ],
149 "review_status": "<review_status>",
155 "review_status": "<review_status>",
150 "mergeable": {
156 "mergeable": {
151 "status": "<bool>",
157 "status": "<bool>",
152 "message": "<message>",
158 "message": "<message>",
153 },
159 },
154 "source": {
160 "source": {
155 "clone_url": "<clone_url>",
161 "clone_url": "<clone_url>",
156 "repository": "<repository_name>",
162 "repository": "<repository_name>",
157 "reference":
163 "reference":
158 {
164 {
159 "name": "<name>",
165 "name": "<name>",
160 "type": "<type>",
166 "type": "<type>",
161 "commit_id": "<commit_id>",
167 "commit_id": "<commit_id>",
162 }
168 }
163 },
169 },
164 "target": {
170 "target": {
165 "clone_url": "<clone_url>",
171 "clone_url": "<clone_url>",
166 "repository": "<repository_name>",
172 "repository": "<repository_name>",
167 "reference":
173 "reference":
168 {
174 {
169 "name": "<name>",
175 "name": "<name>",
170 "type": "<type>",
176 "type": "<type>",
171 "commit_id": "<commit_id>",
177 "commit_id": "<commit_id>",
172 }
178 }
173 },
179 },
174 "merge": {
180 "merge": {
175 "clone_url": "<clone_url>",
181 "clone_url": "<clone_url>",
176 "reference":
182 "reference":
177 {
183 {
178 "name": "<name>",
184 "name": "<name>",
179 "type": "<type>",
185 "type": "<type>",
180 "commit_id": "<commit_id>",
186 "commit_id": "<commit_id>",
181 }
187 }
182 },
188 },
183 "author": <user_obj>,
189 "author": <user_obj>,
184 "reviewers": [
190 "reviewers": [
185 ...
191 ...
186 {
192 {
187 "user": "<user_obj>",
193 "user": "<user_obj>",
188 "review_status": "<review_status>",
194 "review_status": "<review_status>",
189 }
195 }
190 ...
196 ...
191 ]
197 ]
192 },
198 },
193 "error": null
199 "error": null
194
200
195
201
196 get_pull_requests
202 get_pull_requests
197 -----------------
203 -----------------
198
204
199 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>)
205 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>)
200
206
201 Get all pull requests from the repository specified in `repoid`.
207 Get all pull requests from the repository specified in `repoid`.
202
208
203 :param apiuser: This is filled automatically from the |authtoken|.
209 :param apiuser: This is filled automatically from the |authtoken|.
204 :type apiuser: AuthUser
210 :type apiuser: AuthUser
205 :param repoid: Repository name or repository ID.
211 :param repoid: Repository name or repository ID.
206 :type repoid: str or int
212 :type repoid: str or int
207 :param status: Only return pull requests with the specified status.
213 :param status: Only return pull requests with the specified status.
208 Valid options are.
214 Valid options are.
209 * ``new`` (default)
215 * ``new`` (default)
210 * ``open``
216 * ``open``
211 * ``closed``
217 * ``closed``
212 :type status: str
218 :type status: str
213
219
214 Example output:
220 Example output:
215
221
216 .. code-block:: bash
222 .. code-block:: bash
217
223
218 "id": <id_given_in_input>,
224 "id": <id_given_in_input>,
219 "result":
225 "result":
220 [
226 [
221 ...
227 ...
222 {
228 {
223 "pull_request_id": "<pull_request_id>",
229 "pull_request_id": "<pull_request_id>",
224 "url": "<url>",
230 "url": "<url>",
225 "title" : "<title>",
231 "title" : "<title>",
226 "description": "<description>",
232 "description": "<description>",
227 "status": "<status>",
233 "status": "<status>",
228 "created_on": "<date_time_created>",
234 "created_on": "<date_time_created>",
229 "updated_on": "<date_time_updated>",
235 "updated_on": "<date_time_updated>",
230 "commit_ids": [
236 "commit_ids": [
231 ...
237 ...
232 "<commit_id>",
238 "<commit_id>",
233 "<commit_id>",
239 "<commit_id>",
234 ...
240 ...
235 ],
241 ],
236 "review_status": "<review_status>",
242 "review_status": "<review_status>",
237 "mergeable": {
243 "mergeable": {
238 "status": "<bool>",
244 "status": "<bool>",
239 "message: "<message>",
245 "message: "<message>",
240 },
246 },
241 "source": {
247 "source": {
242 "clone_url": "<clone_url>",
248 "clone_url": "<clone_url>",
243 "reference":
249 "reference":
244 {
250 {
245 "name": "<name>",
251 "name": "<name>",
246 "type": "<type>",
252 "type": "<type>",
247 "commit_id": "<commit_id>",
253 "commit_id": "<commit_id>",
248 }
254 }
249 },
255 },
250 "target": {
256 "target": {
251 "clone_url": "<clone_url>",
257 "clone_url": "<clone_url>",
252 "reference":
258 "reference":
253 {
259 {
254 "name": "<name>",
260 "name": "<name>",
255 "type": "<type>",
261 "type": "<type>",
256 "commit_id": "<commit_id>",
262 "commit_id": "<commit_id>",
257 }
263 }
258 },
264 },
259 "merge": {
265 "merge": {
260 "clone_url": "<clone_url>",
266 "clone_url": "<clone_url>",
261 "reference":
267 "reference":
262 {
268 {
263 "name": "<name>",
269 "name": "<name>",
264 "type": "<type>",
270 "type": "<type>",
265 "commit_id": "<commit_id>",
271 "commit_id": "<commit_id>",
266 }
272 }
267 },
273 },
268 "author": <user_obj>,
274 "author": <user_obj>,
269 "reviewers": [
275 "reviewers": [
270 ...
276 ...
271 {
277 {
272 "user": "<user_obj>",
278 "user": "<user_obj>",
273 "review_status": "<review_status>",
279 "review_status": "<review_status>",
274 }
280 }
275 ...
281 ...
276 ]
282 ]
277 }
283 }
278 ...
284 ...
279 ],
285 ],
280 "error": null
286 "error": null
281
287
282
288
283 merge_pull_request
289 merge_pull_request
284 ------------------
290 ------------------
285
291
286 .. py:function:: merge_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
292 .. py:function:: merge_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
287
293
288 Merge the pull request specified by `pullrequestid` into its target
294 Merge the pull request specified by `pullrequestid` into its target
289 repository.
295 repository.
290
296
291 :param apiuser: This is filled automatically from the |authtoken|.
297 :param apiuser: This is filled automatically from the |authtoken|.
292 :type apiuser: AuthUser
298 :type apiuser: AuthUser
293 :param repoid: The Repository name or repository ID of the
299 :param repoid: The Repository name or repository ID of the
294 target repository to which the |pr| is to be merged.
300 target repository to which the |pr| is to be merged.
295 :type repoid: str or int
301 :type repoid: str or int
296 :param pullrequestid: ID of the pull request which shall be merged.
302 :param pullrequestid: ID of the pull request which shall be merged.
297 :type pullrequestid: int
303 :type pullrequestid: int
298 :param userid: Merge the pull request as this user.
304 :param userid: Merge the pull request as this user.
299 :type userid: Optional(str or int)
305 :type userid: Optional(str or int)
300
306
301 Example output:
307 Example output:
302
308
303 .. code-block:: bash
309 .. code-block:: bash
304
310
305 "id": <id_given_in_input>,
311 "id": <id_given_in_input>,
306 "result": {
312 "result": {
307 "executed": "<bool>",
313 "executed": "<bool>",
308 "failure_reason": "<int>",
314 "failure_reason": "<int>",
309 "merge_commit_id": "<merge_commit_id>",
315 "merge_commit_id": "<merge_commit_id>",
310 "possible": "<bool>",
316 "possible": "<bool>",
311 "merge_ref": {
317 "merge_ref": {
312 "commit_id": "<commit_id>",
318 "commit_id": "<commit_id>",
313 "type": "<type>",
319 "type": "<type>",
314 "name": "<name>"
320 "name": "<name>"
315 }
321 }
316 },
322 },
317 "error": null
323 "error": null
318
324
319
325
320 update_pull_request
326 update_pull_request
321 -------------------
327 -------------------
322
328
323 .. py:function:: update_pull_request(apiuser, repoid, pullrequestid, title=<Optional:''>, description=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>, close_pull_request=<Optional:None>)
329 .. py:function:: update_pull_request(apiuser, repoid, pullrequestid, title=<Optional:''>, description=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>)
324
330
325 Updates a pull request.
331 Updates a pull request.
326
332
327 :param apiuser: This is filled automatically from the |authtoken|.
333 :param apiuser: This is filled automatically from the |authtoken|.
328 :type apiuser: AuthUser
334 :type apiuser: AuthUser
329 :param repoid: The repository name or repository ID.
335 :param repoid: The repository name or repository ID.
330 :type repoid: str or int
336 :type repoid: str or int
331 :param pullrequestid: The pull request ID.
337 :param pullrequestid: The pull request ID.
332 :type pullrequestid: int
338 :type pullrequestid: int
333 :param title: Set the pull request title.
339 :param title: Set the pull request title.
334 :type title: str
340 :type title: str
335 :param description: Update pull request description.
341 :param description: Update pull request description.
336 :type description: Optional(str)
342 :type description: Optional(str)
337 :param reviewers: Update pull request reviewers list with new value.
343 :param reviewers: Update pull request reviewers list with new value.
338 :type reviewers: Optional(list)
344 :type reviewers: Optional(list)
345 Accepts username strings or objects of the format:
346
347 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
348
339 :param update_commits: Trigger update of commits for this pull request
349 :param update_commits: Trigger update of commits for this pull request
340 :type: update_commits: Optional(bool)
350 :type: update_commits: Optional(bool)
341 :param close_pull_request: Close this pull request with rejected state
342 :type: close_pull_request: Optional(bool)
343
351
344 Example output:
352 Example output:
345
353
346 .. code-block:: bash
354 .. code-block:: bash
347
355
348 id : <id_given_in_input>
356 id : <id_given_in_input>
349 result : {
357 result : {
350 "msg": "Updated pull request `63`",
358 "msg": "Updated pull request `63`",
351 "pull_request": <pull_request_object>,
359 "pull_request": <pull_request_object>,
352 "updated_reviewers": {
360 "updated_reviewers": {
353 "added": [
361 "added": [
354 "username"
362 "username"
355 ],
363 ],
356 "removed": []
364 "removed": []
357 },
365 },
358 "updated_commits": {
366 "updated_commits": {
359 "added": [
367 "added": [
360 "<sha1_hash>"
368 "<sha1_hash>"
361 ],
369 ],
362 "common": [
370 "common": [
363 "<sha1_hash>",
371 "<sha1_hash>",
364 "<sha1_hash>",
372 "<sha1_hash>",
365 ],
373 ],
366 "removed": []
374 "removed": []
367 }
375 }
368 }
376 }
369 error : null
377 error : null
370
378
371
379
@@ -1,1000 +1,1044 b''
1 .. _repo-methods-ref:
1 .. _repo-methods-ref:
2
2
3 repo methods
3 repo methods
4 ============
4 ============
5
5
6 add_field_to_repo
6 add_field_to_repo
7 -----------------
7 -----------------
8
8
9 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
9 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
10
10
11 Adds an extra field to a repository.
11 Adds an extra field to a repository.
12
12
13 This command can only be run using an |authtoken| with at least
13 This command can only be run using an |authtoken| with at least
14 write permissions to the |repo|.
14 write permissions to the |repo|.
15
15
16 :param apiuser: This is filled automatically from the |authtoken|.
16 :param apiuser: This is filled automatically from the |authtoken|.
17 :type apiuser: AuthUser
17 :type apiuser: AuthUser
18 :param repoid: Set the repository name or repository id.
18 :param repoid: Set the repository name or repository id.
19 :type repoid: str or int
19 :type repoid: str or int
20 :param key: Create a unique field key for this repository.
20 :param key: Create a unique field key for this repository.
21 :type key: str
21 :type key: str
22 :param label:
22 :param label:
23 :type label: Optional(str)
23 :type label: Optional(str)
24 :param description:
24 :param description:
25 :type description: Optional(str)
25 :type description: Optional(str)
26
26
27
27
28 comment_commit
28 comment_commit
29 --------------
29 --------------
30
30
31 .. py:function:: comment_commit(apiuser, repoid, commit_id, message, status=<Optional:None>, comment_type=<Optional:u'note'>, resolves_comment_id=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
31 .. py:function:: comment_commit(apiuser, repoid, commit_id, message, status=<Optional:None>, comment_type=<Optional:u'note'>, resolves_comment_id=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
32
32
33 Set a commit comment, and optionally change the status of the commit.
33 Set a commit comment, and optionally change the status of the commit.
34
34
35 :param apiuser: This is filled automatically from the |authtoken|.
35 :param apiuser: This is filled automatically from the |authtoken|.
36 :type apiuser: AuthUser
36 :type apiuser: AuthUser
37 :param repoid: Set the repository name or repository ID.
37 :param repoid: Set the repository name or repository ID.
38 :type repoid: str or int
38 :type repoid: str or int
39 :param commit_id: Specify the commit_id for which to set a comment.
39 :param commit_id: Specify the commit_id for which to set a comment.
40 :type commit_id: str
40 :type commit_id: str
41 :param message: The comment text.
41 :param message: The comment text.
42 :type message: str
42 :type message: str
43 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
43 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
44 'approved', 'rejected', 'under_review'
44 'approved', 'rejected', 'under_review'
45 :type status: str
45 :type status: str
46 :param comment_type: Comment type, one of: 'note', 'todo'
46 :param comment_type: Comment type, one of: 'note', 'todo'
47 :type comment_type: Optional(str), default: 'note'
47 :type comment_type: Optional(str), default: 'note'
48 :param userid: Set the user name of the comment creator.
48 :param userid: Set the user name of the comment creator.
49 :type userid: Optional(str or int)
49 :type userid: Optional(str or int)
50
50
51 Example error output:
51 Example error output:
52
52
53 .. code-block:: bash
53 .. code-block:: bash
54
54
55 {
55 {
56 "id" : <id_given_in_input>,
56 "id" : <id_given_in_input>,
57 "result" : {
57 "result" : {
58 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
58 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
59 "status_change": null or <status>,
59 "status_change": null or <status>,
60 "success": true
60 "success": true
61 },
61 },
62 "error" : null
62 "error" : null
63 }
63 }
64
64
65
65
66 create_repo
66 create_repo
67 -----------
67 -----------
68
68
69 .. py:function:: create_repo(apiuser, repo_name, repo_type, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, copy_permissions=<Optional:False>)
69 .. py:function:: create_repo(apiuser, repo_name, repo_type, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, copy_permissions=<Optional:False>)
70
70
71 Creates a repository.
71 Creates a repository.
72
72
73 * If the repository name contains "/", repository will be created inside
73 * If the repository name contains "/", repository will be created inside
74 a repository group or nested repository groups
74 a repository group or nested repository groups
75
75
76 For example "foo/bar/repo1" will create |repo| called "repo1" inside
76 For example "foo/bar/repo1" will create |repo| called "repo1" inside
77 group "foo/bar". You have to have permissions to access and write to
77 group "foo/bar". You have to have permissions to access and write to
78 the last repository group ("bar" in this example)
78 the last repository group ("bar" in this example)
79
79
80 This command can only be run using an |authtoken| with at least
80 This command can only be run using an |authtoken| with at least
81 permissions to create repositories, or write permissions to
81 permissions to create repositories, or write permissions to
82 parent repository groups.
82 parent repository groups.
83
83
84 :param apiuser: This is filled automatically from the |authtoken|.
84 :param apiuser: This is filled automatically from the |authtoken|.
85 :type apiuser: AuthUser
85 :type apiuser: AuthUser
86 :param repo_name: Set the repository name.
86 :param repo_name: Set the repository name.
87 :type repo_name: str
87 :type repo_name: str
88 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
88 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
89 :type repo_type: str
89 :type repo_type: str
90 :param owner: user_id or username
90 :param owner: user_id or username
91 :type owner: Optional(str)
91 :type owner: Optional(str)
92 :param description: Set the repository description.
92 :param description: Set the repository description.
93 :type description: Optional(str)
93 :type description: Optional(str)
94 :param private: set repository as private
94 :param private: set repository as private
95 :type private: bool
95 :type private: bool
96 :param clone_uri: set clone_uri
96 :param clone_uri: set clone_uri
97 :type clone_uri: str
97 :type clone_uri: str
98 :param landing_rev: <rev_type>:<rev>
98 :param landing_rev: <rev_type>:<rev>
99 :type landing_rev: str
99 :type landing_rev: str
100 :param enable_locking:
100 :param enable_locking:
101 :type enable_locking: bool
101 :type enable_locking: bool
102 :param enable_downloads:
102 :param enable_downloads:
103 :type enable_downloads: bool
103 :type enable_downloads: bool
104 :param enable_statistics:
104 :param enable_statistics:
105 :type enable_statistics: bool
105 :type enable_statistics: bool
106 :param copy_permissions: Copy permission from group in which the
106 :param copy_permissions: Copy permission from group in which the
107 repository is being created.
107 repository is being created.
108 :type copy_permissions: bool
108 :type copy_permissions: bool
109
109
110
110
111 Example output:
111 Example output:
112
112
113 .. code-block:: bash
113 .. code-block:: bash
114
114
115 id : <id_given_in_input>
115 id : <id_given_in_input>
116 result: {
116 result: {
117 "msg": "Created new repository `<reponame>`",
117 "msg": "Created new repository `<reponame>`",
118 "success": true,
118 "success": true,
119 "task": "<celery task id or None if done sync>"
119 "task": "<celery task id or None if done sync>"
120 }
120 }
121 error: null
121 error: null
122
122
123
123
124 Example error output:
124 Example error output:
125
125
126 .. code-block:: bash
126 .. code-block:: bash
127
127
128 id : <id_given_in_input>
128 id : <id_given_in_input>
129 result : null
129 result : null
130 error : {
130 error : {
131 'failed to create repository `<repo_name>`'
131 'failed to create repository `<repo_name>`'
132 }
132 }
133
133
134
134
135 delete_repo
135 delete_repo
136 -----------
136 -----------
137
137
138 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
138 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
139
139
140 Deletes a repository.
140 Deletes a repository.
141
141
142 * When the `forks` parameter is set it's possible to detach or delete
142 * When the `forks` parameter is set it's possible to detach or delete
143 forks of deleted repository.
143 forks of deleted repository.
144
144
145 This command can only be run using an |authtoken| with admin
145 This command can only be run using an |authtoken| with admin
146 permissions on the |repo|.
146 permissions on the |repo|.
147
147
148 :param apiuser: This is filled automatically from the |authtoken|.
148 :param apiuser: This is filled automatically from the |authtoken|.
149 :type apiuser: AuthUser
149 :type apiuser: AuthUser
150 :param repoid: Set the repository name or repository ID.
150 :param repoid: Set the repository name or repository ID.
151 :type repoid: str or int
151 :type repoid: str or int
152 :param forks: Set to `detach` or `delete` forks from the |repo|.
152 :param forks: Set to `detach` or `delete` forks from the |repo|.
153 :type forks: Optional(str)
153 :type forks: Optional(str)
154
154
155 Example error output:
155 Example error output:
156
156
157 .. code-block:: bash
157 .. code-block:: bash
158
158
159 id : <id_given_in_input>
159 id : <id_given_in_input>
160 result: {
160 result: {
161 "msg": "Deleted repository `<reponame>`",
161 "msg": "Deleted repository `<reponame>`",
162 "success": true
162 "success": true
163 }
163 }
164 error: null
164 error: null
165
165
166
166
167 fork_repo
167 fork_repo
168 ---------
168 ---------
169
169
170 .. py:function:: fork_repo(apiuser, repoid, fork_name, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, copy_permissions=<Optional:False>)
170 .. py:function:: fork_repo(apiuser, repoid, fork_name, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, copy_permissions=<Optional:False>)
171
171
172 Creates a fork of the specified |repo|.
172 Creates a fork of the specified |repo|.
173
173
174 * If the fork_name contains "/", fork will be created inside
174 * If the fork_name contains "/", fork will be created inside
175 a repository group or nested repository groups
175 a repository group or nested repository groups
176
176
177 For example "foo/bar/fork-repo" will create fork called "fork-repo"
177 For example "foo/bar/fork-repo" will create fork called "fork-repo"
178 inside group "foo/bar". You have to have permissions to access and
178 inside group "foo/bar". You have to have permissions to access and
179 write to the last repository group ("bar" in this example)
179 write to the last repository group ("bar" in this example)
180
180
181 This command can only be run using an |authtoken| with minimum
181 This command can only be run using an |authtoken| with minimum
182 read permissions of the forked repo, create fork permissions for an user.
182 read permissions of the forked repo, create fork permissions for an user.
183
183
184 :param apiuser: This is filled automatically from the |authtoken|.
184 :param apiuser: This is filled automatically from the |authtoken|.
185 :type apiuser: AuthUser
185 :type apiuser: AuthUser
186 :param repoid: Set repository name or repository ID.
186 :param repoid: Set repository name or repository ID.
187 :type repoid: str or int
187 :type repoid: str or int
188 :param fork_name: Set the fork name, including it's repository group membership.
188 :param fork_name: Set the fork name, including it's repository group membership.
189 :type fork_name: str
189 :type fork_name: str
190 :param owner: Set the fork owner.
190 :param owner: Set the fork owner.
191 :type owner: str
191 :type owner: str
192 :param description: Set the fork description.
192 :param description: Set the fork description.
193 :type description: str
193 :type description: str
194 :param copy_permissions: Copy permissions from parent |repo|. The
194 :param copy_permissions: Copy permissions from parent |repo|. The
195 default is False.
195 default is False.
196 :type copy_permissions: bool
196 :type copy_permissions: bool
197 :param private: Make the fork private. The default is False.
197 :param private: Make the fork private. The default is False.
198 :type private: bool
198 :type private: bool
199 :param landing_rev: Set the landing revision. The default is tip.
199 :param landing_rev: Set the landing revision. The default is tip.
200
200
201 Example output:
201 Example output:
202
202
203 .. code-block:: bash
203 .. code-block:: bash
204
204
205 id : <id_for_response>
205 id : <id_for_response>
206 api_key : "<api_key>"
206 api_key : "<api_key>"
207 args: {
207 args: {
208 "repoid" : "<reponame or repo_id>",
208 "repoid" : "<reponame or repo_id>",
209 "fork_name": "<forkname>",
209 "fork_name": "<forkname>",
210 "owner": "<username or user_id = Optional(=apiuser)>",
210 "owner": "<username or user_id = Optional(=apiuser)>",
211 "description": "<description>",
211 "description": "<description>",
212 "copy_permissions": "<bool>",
212 "copy_permissions": "<bool>",
213 "private": "<bool>",
213 "private": "<bool>",
214 "landing_rev": "<landing_rev>"
214 "landing_rev": "<landing_rev>"
215 }
215 }
216
216
217 Example error output:
217 Example error output:
218
218
219 .. code-block:: bash
219 .. code-block:: bash
220
220
221 id : <id_given_in_input>
221 id : <id_given_in_input>
222 result: {
222 result: {
223 "msg": "Created fork of `<reponame>` as `<forkname>`",
223 "msg": "Created fork of `<reponame>` as `<forkname>`",
224 "success": true,
224 "success": true,
225 "task": "<celery task id or None if done sync>"
225 "task": "<celery task id or None if done sync>"
226 }
226 }
227 error: null
227 error: null
228
228
229
229
230 get_repo
230 get_repo
231 --------
231 --------
232
232
233 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
233 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
234
234
235 Gets an existing repository by its name or repository_id.
235 Gets an existing repository by its name or repository_id.
236
236
237 The members section so the output returns users groups or users
237 The members section so the output returns users groups or users
238 associated with that repository.
238 associated with that repository.
239
239
240 This command can only be run using an |authtoken| with admin rights,
240 This command can only be run using an |authtoken| with admin rights,
241 or users with at least read rights to the |repo|.
241 or users with at least read rights to the |repo|.
242
242
243 :param apiuser: This is filled automatically from the |authtoken|.
243 :param apiuser: This is filled automatically from the |authtoken|.
244 :type apiuser: AuthUser
244 :type apiuser: AuthUser
245 :param repoid: The repository name or repository id.
245 :param repoid: The repository name or repository id.
246 :type repoid: str or int
246 :type repoid: str or int
247 :param cache: use the cached value for last changeset
247 :param cache: use the cached value for last changeset
248 :type: cache: Optional(bool)
248 :type: cache: Optional(bool)
249
249
250 Example output:
250 Example output:
251
251
252 .. code-block:: bash
252 .. code-block:: bash
253
253
254 {
254 {
255 "error": null,
255 "error": null,
256 "id": <repo_id>,
256 "id": <repo_id>,
257 "result": {
257 "result": {
258 "clone_uri": null,
258 "clone_uri": null,
259 "created_on": "timestamp",
259 "created_on": "timestamp",
260 "description": "repo description",
260 "description": "repo description",
261 "enable_downloads": false,
261 "enable_downloads": false,
262 "enable_locking": false,
262 "enable_locking": false,
263 "enable_statistics": false,
263 "enable_statistics": false,
264 "followers": [
264 "followers": [
265 {
265 {
266 "active": true,
266 "active": true,
267 "admin": false,
267 "admin": false,
268 "api_key": "****************************************",
268 "api_key": "****************************************",
269 "api_keys": [
269 "api_keys": [
270 "****************************************"
270 "****************************************"
271 ],
271 ],
272 "email": "user@example.com",
272 "email": "user@example.com",
273 "emails": [
273 "emails": [
274 "user@example.com"
274 "user@example.com"
275 ],
275 ],
276 "extern_name": "rhodecode",
276 "extern_name": "rhodecode",
277 "extern_type": "rhodecode",
277 "extern_type": "rhodecode",
278 "firstname": "username",
278 "firstname": "username",
279 "ip_addresses": [],
279 "ip_addresses": [],
280 "language": null,
280 "language": null,
281 "last_login": "2015-09-16T17:16:35.854",
281 "last_login": "2015-09-16T17:16:35.854",
282 "lastname": "surname",
282 "lastname": "surname",
283 "user_id": <user_id>,
283 "user_id": <user_id>,
284 "username": "name"
284 "username": "name"
285 }
285 }
286 ],
286 ],
287 "fork_of": "parent-repo",
287 "fork_of": "parent-repo",
288 "landing_rev": [
288 "landing_rev": [
289 "rev",
289 "rev",
290 "tip"
290 "tip"
291 ],
291 ],
292 "last_changeset": {
292 "last_changeset": {
293 "author": "User <user@example.com>",
293 "author": "User <user@example.com>",
294 "branch": "default",
294 "branch": "default",
295 "date": "timestamp",
295 "date": "timestamp",
296 "message": "last commit message",
296 "message": "last commit message",
297 "parents": [
297 "parents": [
298 {
298 {
299 "raw_id": "commit-id"
299 "raw_id": "commit-id"
300 }
300 }
301 ],
301 ],
302 "raw_id": "commit-id",
302 "raw_id": "commit-id",
303 "revision": <revision number>,
303 "revision": <revision number>,
304 "short_id": "short id"
304 "short_id": "short id"
305 },
305 },
306 "lock_reason": null,
306 "lock_reason": null,
307 "locked_by": null,
307 "locked_by": null,
308 "locked_date": null,
308 "locked_date": null,
309 "members": [
309 "members": [
310 {
310 {
311 "name": "super-admin-name",
311 "name": "super-admin-name",
312 "origin": "super-admin",
312 "origin": "super-admin",
313 "permission": "repository.admin",
313 "permission": "repository.admin",
314 "type": "user"
314 "type": "user"
315 },
315 },
316 {
316 {
317 "name": "owner-name",
317 "name": "owner-name",
318 "origin": "owner",
318 "origin": "owner",
319 "permission": "repository.admin",
319 "permission": "repository.admin",
320 "type": "user"
320 "type": "user"
321 },
321 },
322 {
322 {
323 "name": "user-group-name",
323 "name": "user-group-name",
324 "origin": "permission",
324 "origin": "permission",
325 "permission": "repository.write",
325 "permission": "repository.write",
326 "type": "user_group"
326 "type": "user_group"
327 }
327 }
328 ],
328 ],
329 "owner": "owner-name",
329 "owner": "owner-name",
330 "permissions": [
330 "permissions": [
331 {
331 {
332 "name": "super-admin-name",
332 "name": "super-admin-name",
333 "origin": "super-admin",
333 "origin": "super-admin",
334 "permission": "repository.admin",
334 "permission": "repository.admin",
335 "type": "user"
335 "type": "user"
336 },
336 },
337 {
337 {
338 "name": "owner-name",
338 "name": "owner-name",
339 "origin": "owner",
339 "origin": "owner",
340 "permission": "repository.admin",
340 "permission": "repository.admin",
341 "type": "user"
341 "type": "user"
342 },
342 },
343 {
343 {
344 "name": "user-group-name",
344 "name": "user-group-name",
345 "origin": "permission",
345 "origin": "permission",
346 "permission": "repository.write",
346 "permission": "repository.write",
347 "type": "user_group"
347 "type": "user_group"
348 }
348 }
349 ],
349 ],
350 "private": true,
350 "private": true,
351 "repo_id": 676,
351 "repo_id": 676,
352 "repo_name": "user-group/repo-name",
352 "repo_name": "user-group/repo-name",
353 "repo_type": "hg"
353 "repo_type": "hg"
354 }
354 }
355 }
355 }
356
356
357
357
358 get_repo_changeset
358 get_repo_changeset
359 ------------------
359 ------------------
360
360
361 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
361 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
362
362
363 Returns information about a changeset.
363 Returns information about a changeset.
364
364
365 Additionally parameters define the amount of details returned by
365 Additionally parameters define the amount of details returned by
366 this function.
366 this function.
367
367
368 This command can only be run using an |authtoken| with admin rights,
368 This command can only be run using an |authtoken| with admin rights,
369 or users with at least read rights to the |repo|.
369 or users with at least read rights to the |repo|.
370
370
371 :param apiuser: This is filled automatically from the |authtoken|.
371 :param apiuser: This is filled automatically from the |authtoken|.
372 :type apiuser: AuthUser
372 :type apiuser: AuthUser
373 :param repoid: The repository name or repository id
373 :param repoid: The repository name or repository id
374 :type repoid: str or int
374 :type repoid: str or int
375 :param revision: revision for which listing should be done
375 :param revision: revision for which listing should be done
376 :type revision: str
376 :type revision: str
377 :param details: details can be 'basic|extended|full' full gives diff
377 :param details: details can be 'basic|extended|full' full gives diff
378 info details like the diff itself, and number of changed files etc.
378 info details like the diff itself, and number of changed files etc.
379 :type details: Optional(str)
379 :type details: Optional(str)
380
380
381
381
382 get_repo_changesets
382 get_repo_changesets
383 -------------------
383 -------------------
384
384
385 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
385 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
386
386
387 Returns a set of commits limited by the number starting
387 Returns a set of commits limited by the number starting
388 from the `start_rev` option.
388 from the `start_rev` option.
389
389
390 Additional parameters define the amount of details returned by this
390 Additional parameters define the amount of details returned by this
391 function.
391 function.
392
392
393 This command can only be run using an |authtoken| with admin rights,
393 This command can only be run using an |authtoken| with admin rights,
394 or users with at least read rights to |repos|.
394 or users with at least read rights to |repos|.
395
395
396 :param apiuser: This is filled automatically from the |authtoken|.
396 :param apiuser: This is filled automatically from the |authtoken|.
397 :type apiuser: AuthUser
397 :type apiuser: AuthUser
398 :param repoid: The repository name or repository ID.
398 :param repoid: The repository name or repository ID.
399 :type repoid: str or int
399 :type repoid: str or int
400 :param start_rev: The starting revision from where to get changesets.
400 :param start_rev: The starting revision from where to get changesets.
401 :type start_rev: str
401 :type start_rev: str
402 :param limit: Limit the number of commits to this amount
402 :param limit: Limit the number of commits to this amount
403 :type limit: str or int
403 :type limit: str or int
404 :param details: Set the level of detail returned. Valid option are:
404 :param details: Set the level of detail returned. Valid option are:
405 ``basic``, ``extended`` and ``full``.
405 ``basic``, ``extended`` and ``full``.
406 :type details: Optional(str)
406 :type details: Optional(str)
407
407
408 .. note::
408 .. note::
409
409
410 Setting the parameter `details` to the value ``full`` is extensive
410 Setting the parameter `details` to the value ``full`` is extensive
411 and returns details like the diff itself, and the number
411 and returns details like the diff itself, and the number
412 of changed files.
412 of changed files.
413
413
414
414
415 get_repo_nodes
415 get_repo_nodes
416 --------------
416 --------------
417
417
418 .. py:function:: get_repo_nodes(apiuser, repoid, revision, root_path, ret_type=<Optional:'all'>, details=<Optional:'basic'>, max_file_bytes=<Optional:None>)
418 .. py:function:: get_repo_nodes(apiuser, repoid, revision, root_path, ret_type=<Optional:'all'>, details=<Optional:'basic'>, max_file_bytes=<Optional:None>)
419
419
420 Returns a list of nodes and children in a flat list for a given
420 Returns a list of nodes and children in a flat list for a given
421 path at given revision.
421 path at given revision.
422
422
423 It's possible to specify ret_type to show only `files` or `dirs`.
423 It's possible to specify ret_type to show only `files` or `dirs`.
424
424
425 This command can only be run using an |authtoken| with admin rights,
425 This command can only be run using an |authtoken| with admin rights,
426 or users with at least read rights to |repos|.
426 or users with at least read rights to |repos|.
427
427
428 :param apiuser: This is filled automatically from the |authtoken|.
428 :param apiuser: This is filled automatically from the |authtoken|.
429 :type apiuser: AuthUser
429 :type apiuser: AuthUser
430 :param repoid: The repository name or repository ID.
430 :param repoid: The repository name or repository ID.
431 :type repoid: str or int
431 :type repoid: str or int
432 :param revision: The revision for which listing should be done.
432 :param revision: The revision for which listing should be done.
433 :type revision: str
433 :type revision: str
434 :param root_path: The path from which to start displaying.
434 :param root_path: The path from which to start displaying.
435 :type root_path: str
435 :type root_path: str
436 :param ret_type: Set the return type. Valid options are
436 :param ret_type: Set the return type. Valid options are
437 ``all`` (default), ``files`` and ``dirs``.
437 ``all`` (default), ``files`` and ``dirs``.
438 :type ret_type: Optional(str)
438 :type ret_type: Optional(str)
439 :param details: Returns extended information about nodes, such as
439 :param details: Returns extended information about nodes, such as
440 md5, binary, and or content. The valid options are ``basic`` and
440 md5, binary, and or content. The valid options are ``basic`` and
441 ``full``.
441 ``full``.
442 :type details: Optional(str)
442 :type details: Optional(str)
443 :param max_file_bytes: Only return file content under this file size bytes
443 :param max_file_bytes: Only return file content under this file size bytes
444 :type details: Optional(int)
444 :type details: Optional(int)
445
445
446 Example output:
446 Example output:
447
447
448 .. code-block:: bash
448 .. code-block:: bash
449
449
450 id : <id_given_in_input>
450 id : <id_given_in_input>
451 result: [
451 result: [
452 {
452 {
453 "name" : "<name>"
453 "name" : "<name>"
454 "type" : "<type>",
454 "type" : "<type>",
455 "binary": "<true|false>" (only in extended mode)
455 "binary": "<true|false>" (only in extended mode)
456 "md5" : "<md5 of file content>" (only in extended mode)
456 "md5" : "<md5 of file content>" (only in extended mode)
457 },
457 },
458 ...
458 ...
459 ]
459 ]
460 error: null
460 error: null
461
461
462
462
463 get_repo_refs
463 get_repo_refs
464 -------------
464 -------------
465
465
466 .. py:function:: get_repo_refs(apiuser, repoid)
466 .. py:function:: get_repo_refs(apiuser, repoid)
467
467
468 Returns a dictionary of current references. It returns
468 Returns a dictionary of current references. It returns
469 bookmarks, branches, closed_branches, and tags for given repository
469 bookmarks, branches, closed_branches, and tags for given repository
470
470
471 It's possible to specify ret_type to show only `files` or `dirs`.
471 It's possible to specify ret_type to show only `files` or `dirs`.
472
472
473 This command can only be run using an |authtoken| with admin rights,
473 This command can only be run using an |authtoken| with admin rights,
474 or users with at least read rights to |repos|.
474 or users with at least read rights to |repos|.
475
475
476 :param apiuser: This is filled automatically from the |authtoken|.
476 :param apiuser: This is filled automatically from the |authtoken|.
477 :type apiuser: AuthUser
477 :type apiuser: AuthUser
478 :param repoid: The repository name or repository ID.
478 :param repoid: The repository name or repository ID.
479 :type repoid: str or int
479 :type repoid: str or int
480
480
481 Example output:
481 Example output:
482
482
483 .. code-block:: bash
483 .. code-block:: bash
484
484
485 id : <id_given_in_input>
485 id : <id_given_in_input>
486 "result": {
486 "result": {
487 "bookmarks": {
487 "bookmarks": {
488 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
488 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
489 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
489 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
490 },
490 },
491 "branches": {
491 "branches": {
492 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
492 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
493 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
493 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
494 },
494 },
495 "branches_closed": {},
495 "branches_closed": {},
496 "tags": {
496 "tags": {
497 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
497 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
498 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
498 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
499 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
499 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
500 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
500 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
501 }
501 }
502 }
502 }
503 error: null
503 error: null
504
504
505
505
506 get_repo_settings
506 get_repo_settings
507 -----------------
507 -----------------
508
508
509 .. py:function:: get_repo_settings(apiuser, repoid, key=<Optional:None>)
509 .. py:function:: get_repo_settings(apiuser, repoid, key=<Optional:None>)
510
510
511 Returns all settings for a repository. If key is given it only returns the
511 Returns all settings for a repository. If key is given it only returns the
512 setting identified by the key or null.
512 setting identified by the key or null.
513
513
514 :param apiuser: This is filled automatically from the |authtoken|.
514 :param apiuser: This is filled automatically from the |authtoken|.
515 :type apiuser: AuthUser
515 :type apiuser: AuthUser
516 :param repoid: The repository name or repository id.
516 :param repoid: The repository name or repository id.
517 :type repoid: str or int
517 :type repoid: str or int
518 :param key: Key of the setting to return.
518 :param key: Key of the setting to return.
519 :type: key: Optional(str)
519 :type: key: Optional(str)
520
520
521 Example output:
521 Example output:
522
522
523 .. code-block:: bash
523 .. code-block:: bash
524
524
525 {
525 {
526 "error": null,
526 "error": null,
527 "id": 237,
527 "id": 237,
528 "result": {
528 "result": {
529 "extensions_largefiles": true,
529 "extensions_largefiles": true,
530 "extensions_evolve": true,
530 "hooks_changegroup_push_logger": true,
531 "hooks_changegroup_push_logger": true,
531 "hooks_changegroup_repo_size": false,
532 "hooks_changegroup_repo_size": false,
532 "hooks_outgoing_pull_logger": true,
533 "hooks_outgoing_pull_logger": true,
533 "phases_publish": "True",
534 "phases_publish": "True",
534 "rhodecode_hg_use_rebase_for_merging": true,
535 "rhodecode_hg_use_rebase_for_merging": true,
535 "rhodecode_pr_merge_enabled": true,
536 "rhodecode_pr_merge_enabled": true,
536 "rhodecode_use_outdated_comments": true
537 "rhodecode_use_outdated_comments": true
537 }
538 }
538 }
539 }
539
540
540
541
541 get_repos
542 get_repos
542 ---------
543 ---------
543
544
544 .. py:function:: get_repos(apiuser, root=<Optional:None>, traverse=<Optional:True>)
545 .. py:function:: get_repos(apiuser, root=<Optional:None>, traverse=<Optional:True>)
545
546
546 Lists all existing repositories.
547 Lists all existing repositories.
547
548
548 This command can only be run using an |authtoken| with admin rights,
549 This command can only be run using an |authtoken| with admin rights,
549 or users with at least read rights to |repos|.
550 or users with at least read rights to |repos|.
550
551
551 :param apiuser: This is filled automatically from the |authtoken|.
552 :param apiuser: This is filled automatically from the |authtoken|.
552 :type apiuser: AuthUser
553 :type apiuser: AuthUser
553 :param root: specify root repository group to fetch repositories.
554 :param root: specify root repository group to fetch repositories.
554 filters the returned repositories to be members of given root group.
555 filters the returned repositories to be members of given root group.
555 :type root: Optional(None)
556 :type root: Optional(None)
556 :param traverse: traverse given root into subrepositories. With this flag
557 :param traverse: traverse given root into subrepositories. With this flag
557 set to False, it will only return top-level repositories from `root`.
558 set to False, it will only return top-level repositories from `root`.
558 if root is empty it will return just top-level repositories.
559 if root is empty it will return just top-level repositories.
559 :type traverse: Optional(True)
560 :type traverse: Optional(True)
560
561
561
562
562 Example output:
563 Example output:
563
564
564 .. code-block:: bash
565 .. code-block:: bash
565
566
566 id : <id_given_in_input>
567 id : <id_given_in_input>
567 result: [
568 result: [
568 {
569 {
569 "repo_id" : "<repo_id>",
570 "repo_id" : "<repo_id>",
570 "repo_name" : "<reponame>"
571 "repo_name" : "<reponame>"
571 "repo_type" : "<repo_type>",
572 "repo_type" : "<repo_type>",
572 "clone_uri" : "<clone_uri>",
573 "clone_uri" : "<clone_uri>",
573 "private": : "<bool>",
574 "private": : "<bool>",
574 "created_on" : "<datetimecreated>",
575 "created_on" : "<datetimecreated>",
575 "description" : "<description>",
576 "description" : "<description>",
576 "landing_rev": "<landing_rev>",
577 "landing_rev": "<landing_rev>",
577 "owner": "<repo_owner>",
578 "owner": "<repo_owner>",
578 "fork_of": "<name_of_fork_parent>",
579 "fork_of": "<name_of_fork_parent>",
579 "enable_downloads": "<bool>",
580 "enable_downloads": "<bool>",
580 "enable_locking": "<bool>",
581 "enable_locking": "<bool>",
581 "enable_statistics": "<bool>",
582 "enable_statistics": "<bool>",
582 },
583 },
583 ...
584 ...
584 ]
585 ]
585 error: null
586 error: null
586
587
587
588
588 grant_user_group_permission
589 grant_user_group_permission
589 ---------------------------
590 ---------------------------
590
591
591 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
592 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
592
593
593 Grant permission for a user group on the specified repository,
594 Grant permission for a user group on the specified repository,
594 or update existing permissions.
595 or update existing permissions.
595
596
596 This command can only be run using an |authtoken| with admin
597 This command can only be run using an |authtoken| with admin
597 permissions on the |repo|.
598 permissions on the |repo|.
598
599
599 :param apiuser: This is filled automatically from the |authtoken|.
600 :param apiuser: This is filled automatically from the |authtoken|.
600 :type apiuser: AuthUser
601 :type apiuser: AuthUser
601 :param repoid: Set the repository name or repository ID.
602 :param repoid: Set the repository name or repository ID.
602 :type repoid: str or int
603 :type repoid: str or int
603 :param usergroupid: Specify the ID of the user group.
604 :param usergroupid: Specify the ID of the user group.
604 :type usergroupid: str or int
605 :type usergroupid: str or int
605 :param perm: Set the user group permissions using the following
606 :param perm: Set the user group permissions using the following
606 format: (repository.(none|read|write|admin))
607 format: (repository.(none|read|write|admin))
607 :type perm: str
608 :type perm: str
608
609
609 Example output:
610 Example output:
610
611
611 .. code-block:: bash
612 .. code-block:: bash
612
613
613 id : <id_given_in_input>
614 id : <id_given_in_input>
614 result : {
615 result : {
615 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
616 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
616 "success": true
617 "success": true
617
618
618 }
619 }
619 error : null
620 error : null
620
621
621 Example error output:
622 Example error output:
622
623
623 .. code-block:: bash
624 .. code-block:: bash
624
625
625 id : <id_given_in_input>
626 id : <id_given_in_input>
626 result : null
627 result : null
627 error : {
628 error : {
628 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
629 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
629 }
630 }
630
631
631
632
632 grant_user_permission
633 grant_user_permission
633 ---------------------
634 ---------------------
634
635
635 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
636 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
636
637
637 Grant permissions for the specified user on the given repository,
638 Grant permissions for the specified user on the given repository,
638 or update existing permissions if found.
639 or update existing permissions if found.
639
640
640 This command can only be run using an |authtoken| with admin
641 This command can only be run using an |authtoken| with admin
641 permissions on the |repo|.
642 permissions on the |repo|.
642
643
643 :param apiuser: This is filled automatically from the |authtoken|.
644 :param apiuser: This is filled automatically from the |authtoken|.
644 :type apiuser: AuthUser
645 :type apiuser: AuthUser
645 :param repoid: Set the repository name or repository ID.
646 :param repoid: Set the repository name or repository ID.
646 :type repoid: str or int
647 :type repoid: str or int
647 :param userid: Set the user name.
648 :param userid: Set the user name.
648 :type userid: str
649 :type userid: str
649 :param perm: Set the user permissions, using the following format
650 :param perm: Set the user permissions, using the following format
650 ``(repository.(none|read|write|admin))``
651 ``(repository.(none|read|write|admin))``
651 :type perm: str
652 :type perm: str
652
653
653 Example output:
654 Example output:
654
655
655 .. code-block:: bash
656 .. code-block:: bash
656
657
657 id : <id_given_in_input>
658 id : <id_given_in_input>
658 result: {
659 result: {
659 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
660 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
660 "success": true
661 "success": true
661 }
662 }
662 error: null
663 error: null
663
664
664
665
665 invalidate_cache
666 invalidate_cache
666 ----------------
667 ----------------
667
668
668 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
669 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
669
670
670 Invalidates the cache for the specified repository.
671 Invalidates the cache for the specified repository.
671
672
672 This command can only be run using an |authtoken| with admin rights to
673 This command can only be run using an |authtoken| with admin rights to
673 the specified repository.
674 the specified repository.
674
675
675 This command takes the following options:
676 This command takes the following options:
676
677
677 :param apiuser: This is filled automatically from |authtoken|.
678 :param apiuser: This is filled automatically from |authtoken|.
678 :type apiuser: AuthUser
679 :type apiuser: AuthUser
679 :param repoid: Sets the repository name or repository ID.
680 :param repoid: Sets the repository name or repository ID.
680 :type repoid: str or int
681 :type repoid: str or int
681 :param delete_keys: This deletes the invalidated keys instead of
682 :param delete_keys: This deletes the invalidated keys instead of
682 just flagging them.
683 just flagging them.
683 :type delete_keys: Optional(``True`` | ``False``)
684 :type delete_keys: Optional(``True`` | ``False``)
684
685
685 Example output:
686 Example output:
686
687
687 .. code-block:: bash
688 .. code-block:: bash
688
689
689 id : <id_given_in_input>
690 id : <id_given_in_input>
690 result : {
691 result : {
691 'msg': Cache for repository `<repository name>` was invalidated,
692 'msg': Cache for repository `<repository name>` was invalidated,
692 'repository': <repository name>
693 'repository': <repository name>
693 }
694 }
694 error : null
695 error : null
695
696
696 Example error output:
697 Example error output:
697
698
698 .. code-block:: bash
699 .. code-block:: bash
699
700
700 id : <id_given_in_input>
701 id : <id_given_in_input>
701 result : null
702 result : null
702 error : {
703 error : {
703 'Error occurred during cache invalidation action'
704 'Error occurred during cache invalidation action'
704 }
705 }
705
706
706
707
707 lock
708 lock
708 ----
709 ----
709
710
710 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
711 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
711
712
712 Sets the lock state of the specified |repo| by the given user.
713 Sets the lock state of the specified |repo| by the given user.
713 From more information, see :ref:`repo-locking`.
714 From more information, see :ref:`repo-locking`.
714
715
715 * If the ``userid`` option is not set, the repository is locked to the
716 * If the ``userid`` option is not set, the repository is locked to the
716 user who called the method.
717 user who called the method.
717 * If the ``locked`` parameter is not set, the current lock state of the
718 * If the ``locked`` parameter is not set, the current lock state of the
718 repository is displayed.
719 repository is displayed.
719
720
720 This command can only be run using an |authtoken| with admin rights to
721 This command can only be run using an |authtoken| with admin rights to
721 the specified repository.
722 the specified repository.
722
723
723 This command takes the following options:
724 This command takes the following options:
724
725
725 :param apiuser: This is filled automatically from the |authtoken|.
726 :param apiuser: This is filled automatically from the |authtoken|.
726 :type apiuser: AuthUser
727 :type apiuser: AuthUser
727 :param repoid: Sets the repository name or repository ID.
728 :param repoid: Sets the repository name or repository ID.
728 :type repoid: str or int
729 :type repoid: str or int
729 :param locked: Sets the lock state.
730 :param locked: Sets the lock state.
730 :type locked: Optional(``True`` | ``False``)
731 :type locked: Optional(``True`` | ``False``)
731 :param userid: Set the repository lock to this user.
732 :param userid: Set the repository lock to this user.
732 :type userid: Optional(str or int)
733 :type userid: Optional(str or int)
733
734
734 Example error output:
735 Example error output:
735
736
736 .. code-block:: bash
737 .. code-block:: bash
737
738
738 id : <id_given_in_input>
739 id : <id_given_in_input>
739 result : {
740 result : {
740 'repo': '<reponame>',
741 'repo': '<reponame>',
741 'locked': <bool: lock state>,
742 'locked': <bool: lock state>,
742 'locked_since': <int: lock timestamp>,
743 'locked_since': <int: lock timestamp>,
743 'locked_by': <username of person who made the lock>,
744 'locked_by': <username of person who made the lock>,
744 'lock_reason': <str: reason for locking>,
745 'lock_reason': <str: reason for locking>,
745 'lock_state_changed': <bool: True if lock state has been changed in this request>,
746 'lock_state_changed': <bool: True if lock state has been changed in this request>,
746 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
747 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
747 or
748 or
748 'msg': 'Repo `<repository name>` not locked.'
749 'msg': 'Repo `<repository name>` not locked.'
749 or
750 or
750 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
751 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
751 }
752 }
752 error : null
753 error : null
753
754
754 Example error output:
755 Example error output:
755
756
756 .. code-block:: bash
757 .. code-block:: bash
757
758
758 id : <id_given_in_input>
759 id : <id_given_in_input>
759 result : null
760 result : null
760 error : {
761 error : {
761 'Error occurred locking repository `<reponame>`'
762 'Error occurred locking repository `<reponame>`'
762 }
763 }
763
764
764
765
766 maintenance
767 -----------
768
769 .. py:function:: maintenance(apiuser, repoid)
770
771 Triggers a maintenance on the given repository.
772
773 This command can only be run using an |authtoken| with admin
774 rights to the specified repository. For more information,
775 see :ref:`config-token-ref`.
776
777 This command takes the following options:
778
779 :param apiuser: This is filled automatically from the |authtoken|.
780 :type apiuser: AuthUser
781 :param repoid: The repository name or repository ID.
782 :type repoid: str or int
783
784 Example output:
785
786 .. code-block:: bash
787
788 id : <id_given_in_input>
789 result : {
790 "msg": "executed maintenance command",
791 "executed_actions": [
792 <action_message>, <action_message2>...
793 ],
794 "repository": "<repository name>"
795 }
796 error : null
797
798 Example error output:
799
800 .. code-block:: bash
801
802 id : <id_given_in_input>
803 result : null
804 error : {
805 "Unable to execute maintenance on `<reponame>`"
806 }
807
808
765 pull
809 pull
766 ----
810 ----
767
811
768 .. py:function:: pull(apiuser, repoid)
812 .. py:function:: pull(apiuser, repoid)
769
813
770 Triggers a pull on the given repository from a remote location. You
814 Triggers a pull on the given repository from a remote location. You
771 can use this to keep remote repositories up-to-date.
815 can use this to keep remote repositories up-to-date.
772
816
773 This command can only be run using an |authtoken| with admin
817 This command can only be run using an |authtoken| with admin
774 rights to the specified repository. For more information,
818 rights to the specified repository. For more information,
775 see :ref:`config-token-ref`.
819 see :ref:`config-token-ref`.
776
820
777 This command takes the following options:
821 This command takes the following options:
778
822
779 :param apiuser: This is filled automatically from the |authtoken|.
823 :param apiuser: This is filled automatically from the |authtoken|.
780 :type apiuser: AuthUser
824 :type apiuser: AuthUser
781 :param repoid: The repository name or repository ID.
825 :param repoid: The repository name or repository ID.
782 :type repoid: str or int
826 :type repoid: str or int
783
827
784 Example output:
828 Example output:
785
829
786 .. code-block:: bash
830 .. code-block:: bash
787
831
788 id : <id_given_in_input>
832 id : <id_given_in_input>
789 result : {
833 result : {
790 "msg": "Pulled from `<repository name>`"
834 "msg": "Pulled from `<repository name>`"
791 "repository": "<repository name>"
835 "repository": "<repository name>"
792 }
836 }
793 error : null
837 error : null
794
838
795 Example error output:
839 Example error output:
796
840
797 .. code-block:: bash
841 .. code-block:: bash
798
842
799 id : <id_given_in_input>
843 id : <id_given_in_input>
800 result : null
844 result : null
801 error : {
845 error : {
802 "Unable to pull changes from `<reponame>`"
846 "Unable to pull changes from `<reponame>`"
803 }
847 }
804
848
805
849
806 remove_field_from_repo
850 remove_field_from_repo
807 ----------------------
851 ----------------------
808
852
809 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
853 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
810
854
811 Removes an extra field from a repository.
855 Removes an extra field from a repository.
812
856
813 This command can only be run using an |authtoken| with at least
857 This command can only be run using an |authtoken| with at least
814 write permissions to the |repo|.
858 write permissions to the |repo|.
815
859
816 :param apiuser: This is filled automatically from the |authtoken|.
860 :param apiuser: This is filled automatically from the |authtoken|.
817 :type apiuser: AuthUser
861 :type apiuser: AuthUser
818 :param repoid: Set the repository name or repository ID.
862 :param repoid: Set the repository name or repository ID.
819 :type repoid: str or int
863 :type repoid: str or int
820 :param key: Set the unique field key for this repository.
864 :param key: Set the unique field key for this repository.
821 :type key: str
865 :type key: str
822
866
823
867
824 revoke_user_group_permission
868 revoke_user_group_permission
825 ----------------------------
869 ----------------------------
826
870
827 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
871 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
828
872
829 Revoke the permissions of a user group on a given repository.
873 Revoke the permissions of a user group on a given repository.
830
874
831 This command can only be run using an |authtoken| with admin
875 This command can only be run using an |authtoken| with admin
832 permissions on the |repo|.
876 permissions on the |repo|.
833
877
834 :param apiuser: This is filled automatically from the |authtoken|.
878 :param apiuser: This is filled automatically from the |authtoken|.
835 :type apiuser: AuthUser
879 :type apiuser: AuthUser
836 :param repoid: Set the repository name or repository ID.
880 :param repoid: Set the repository name or repository ID.
837 :type repoid: str or int
881 :type repoid: str or int
838 :param usergroupid: Specify the user group ID.
882 :param usergroupid: Specify the user group ID.
839 :type usergroupid: str or int
883 :type usergroupid: str or int
840
884
841 Example output:
885 Example output:
842
886
843 .. code-block:: bash
887 .. code-block:: bash
844
888
845 id : <id_given_in_input>
889 id : <id_given_in_input>
846 result: {
890 result: {
847 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
891 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
848 "success": true
892 "success": true
849 }
893 }
850 error: null
894 error: null
851
895
852
896
853 revoke_user_permission
897 revoke_user_permission
854 ----------------------
898 ----------------------
855
899
856 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
900 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
857
901
858 Revoke permission for a user on the specified repository.
902 Revoke permission for a user on the specified repository.
859
903
860 This command can only be run using an |authtoken| with admin
904 This command can only be run using an |authtoken| with admin
861 permissions on the |repo|.
905 permissions on the |repo|.
862
906
863 :param apiuser: This is filled automatically from the |authtoken|.
907 :param apiuser: This is filled automatically from the |authtoken|.
864 :type apiuser: AuthUser
908 :type apiuser: AuthUser
865 :param repoid: Set the repository name or repository ID.
909 :param repoid: Set the repository name or repository ID.
866 :type repoid: str or int
910 :type repoid: str or int
867 :param userid: Set the user name of revoked user.
911 :param userid: Set the user name of revoked user.
868 :type userid: str or int
912 :type userid: str or int
869
913
870 Example error output:
914 Example error output:
871
915
872 .. code-block:: bash
916 .. code-block:: bash
873
917
874 id : <id_given_in_input>
918 id : <id_given_in_input>
875 result: {
919 result: {
876 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
920 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
877 "success": true
921 "success": true
878 }
922 }
879 error: null
923 error: null
880
924
881
925
882 set_repo_settings
926 set_repo_settings
883 -----------------
927 -----------------
884
928
885 .. py:function:: set_repo_settings(apiuser, repoid, settings)
929 .. py:function:: set_repo_settings(apiuser, repoid, settings)
886
930
887 Update repository settings. Returns true on success.
931 Update repository settings. Returns true on success.
888
932
889 :param apiuser: This is filled automatically from the |authtoken|.
933 :param apiuser: This is filled automatically from the |authtoken|.
890 :type apiuser: AuthUser
934 :type apiuser: AuthUser
891 :param repoid: The repository name or repository id.
935 :param repoid: The repository name or repository id.
892 :type repoid: str or int
936 :type repoid: str or int
893 :param settings: The new settings for the repository.
937 :param settings: The new settings for the repository.
894 :type: settings: dict
938 :type: settings: dict
895
939
896 Example output:
940 Example output:
897
941
898 .. code-block:: bash
942 .. code-block:: bash
899
943
900 {
944 {
901 "error": null,
945 "error": null,
902 "id": 237,
946 "id": 237,
903 "result": true
947 "result": true
904 }
948 }
905
949
906
950
907 strip
951 strip
908 -----
952 -----
909
953
910 .. py:function:: strip(apiuser, repoid, revision, branch)
954 .. py:function:: strip(apiuser, repoid, revision, branch)
911
955
912 Strips the given revision from the specified repository.
956 Strips the given revision from the specified repository.
913
957
914 * This will remove the revision and all of its decendants.
958 * This will remove the revision and all of its decendants.
915
959
916 This command can only be run using an |authtoken| with admin rights to
960 This command can only be run using an |authtoken| with admin rights to
917 the specified repository.
961 the specified repository.
918
962
919 This command takes the following options:
963 This command takes the following options:
920
964
921 :param apiuser: This is filled automatically from the |authtoken|.
965 :param apiuser: This is filled automatically from the |authtoken|.
922 :type apiuser: AuthUser
966 :type apiuser: AuthUser
923 :param repoid: The repository name or repository ID.
967 :param repoid: The repository name or repository ID.
924 :type repoid: str or int
968 :type repoid: str or int
925 :param revision: The revision you wish to strip.
969 :param revision: The revision you wish to strip.
926 :type revision: str
970 :type revision: str
927 :param branch: The branch from which to strip the revision.
971 :param branch: The branch from which to strip the revision.
928 :type branch: str
972 :type branch: str
929
973
930 Example output:
974 Example output:
931
975
932 .. code-block:: bash
976 .. code-block:: bash
933
977
934 id : <id_given_in_input>
978 id : <id_given_in_input>
935 result : {
979 result : {
936 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
980 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
937 "repository": "<repository name>"
981 "repository": "<repository name>"
938 }
982 }
939 error : null
983 error : null
940
984
941 Example error output:
985 Example error output:
942
986
943 .. code-block:: bash
987 .. code-block:: bash
944
988
945 id : <id_given_in_input>
989 id : <id_given_in_input>
946 result : null
990 result : null
947 error : {
991 error : {
948 "Unable to strip commit <commit_hash> from repo `<repository name>`"
992 "Unable to strip commit <commit_hash> from repo `<repository name>`"
949 }
993 }
950
994
951
995
952 update_repo
996 update_repo
953 -----------
997 -----------
954
998
955 .. py:function:: update_repo(apiuser, repoid, repo_name=<Optional:None>, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, fork_of=<Optional:None>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, fields=<Optional:''>)
999 .. py:function:: update_repo(apiuser, repoid, repo_name=<Optional:None>, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, fork_of=<Optional:None>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, fields=<Optional:''>)
956
1000
957 Updates a repository with the given information.
1001 Updates a repository with the given information.
958
1002
959 This command can only be run using an |authtoken| with at least
1003 This command can only be run using an |authtoken| with at least
960 admin permissions to the |repo|.
1004 admin permissions to the |repo|.
961
1005
962 * If the repository name contains "/", repository will be updated
1006 * If the repository name contains "/", repository will be updated
963 accordingly with a repository group or nested repository groups
1007 accordingly with a repository group or nested repository groups
964
1008
965 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
1009 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
966 called "repo-test" and place it inside group "foo/bar".
1010 called "repo-test" and place it inside group "foo/bar".
967 You have to have permissions to access and write to the last repository
1011 You have to have permissions to access and write to the last repository
968 group ("bar" in this example)
1012 group ("bar" in this example)
969
1013
970 :param apiuser: This is filled automatically from the |authtoken|.
1014 :param apiuser: This is filled automatically from the |authtoken|.
971 :type apiuser: AuthUser
1015 :type apiuser: AuthUser
972 :param repoid: repository name or repository ID.
1016 :param repoid: repository name or repository ID.
973 :type repoid: str or int
1017 :type repoid: str or int
974 :param repo_name: Update the |repo| name, including the
1018 :param repo_name: Update the |repo| name, including the
975 repository group it's in.
1019 repository group it's in.
976 :type repo_name: str
1020 :type repo_name: str
977 :param owner: Set the |repo| owner.
1021 :param owner: Set the |repo| owner.
978 :type owner: str
1022 :type owner: str
979 :param fork_of: Set the |repo| as fork of another |repo|.
1023 :param fork_of: Set the |repo| as fork of another |repo|.
980 :type fork_of: str
1024 :type fork_of: str
981 :param description: Update the |repo| description.
1025 :param description: Update the |repo| description.
982 :type description: str
1026 :type description: str
983 :param private: Set the |repo| as private. (True | False)
1027 :param private: Set the |repo| as private. (True | False)
984 :type private: bool
1028 :type private: bool
985 :param clone_uri: Update the |repo| clone URI.
1029 :param clone_uri: Update the |repo| clone URI.
986 :type clone_uri: str
1030 :type clone_uri: str
987 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
1031 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
988 :type landing_rev: str
1032 :type landing_rev: str
989 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1033 :param enable_statistics: Enable statistics on the |repo|, (True | False).
990 :type enable_statistics: bool
1034 :type enable_statistics: bool
991 :param enable_locking: Enable |repo| locking.
1035 :param enable_locking: Enable |repo| locking.
992 :type enable_locking: bool
1036 :type enable_locking: bool
993 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1037 :param enable_downloads: Enable downloads from the |repo|, (True | False).
994 :type enable_downloads: bool
1038 :type enable_downloads: bool
995 :param fields: Add extra fields to the |repo|. Use the following
1039 :param fields: Add extra fields to the |repo|. Use the following
996 example format: ``field_key=field_val,field_key2=fieldval2``.
1040 example format: ``field_key=field_val,field_key2=fieldval2``.
997 Escape ', ' with \,
1041 Escape ', ' with \,
998 :type fields: str
1042 :type fields: str
999
1043
1000
1044
@@ -1,219 +1,219 b''
1 .. _dev-setup:
1 .. _dev-setup:
2
2
3 ===================
3 ===================
4 Development setup
4 Development setup
5 ===================
5 ===================
6
6
7
7
8 RhodeCode Enterprise runs inside a Nix managed environment. This ensures build
8 RhodeCode Enterprise runs inside a Nix managed environment. This ensures build
9 environment dependencies are correctly declared and installed during setup.
9 environment dependencies are correctly declared and installed during setup.
10 It also enables atomic upgrades, rollbacks, and multiple instances of RhodeCode
10 It also enables atomic upgrades, rollbacks, and multiple instances of RhodeCode
11 Enterprise running with isolation.
11 Enterprise running with isolation.
12
12
13 To set up RhodeCode Enterprise inside the Nix environment, use the following steps:
13 To set up RhodeCode Enterprise inside the Nix environment, use the following steps:
14
14
15
15
16
16
17 Setup Nix Package Manager
17 Setup Nix Package Manager
18 -------------------------
18 -------------------------
19
19
20 To install the Nix Package Manager, please run::
20 To install the Nix Package Manager, please run::
21
21
22 $ curl https://nixos.org/nix/install | sh
22 $ curl https://nixos.org/nix/install | sh
23
23
24 or go to https://nixos.org/nix/ and follow the installation instructions.
24 or go to https://nixos.org/nix/ and follow the installation instructions.
25 Once this is correctly set up on your system, you should be able to use the
25 Once this is correctly set up on your system, you should be able to use the
26 following commands:
26 following commands:
27
27
28 * `nix-env`
28 * `nix-env`
29
29
30 * `nix-shell`
30 * `nix-shell`
31
31
32
32
33 .. tip::
33 .. tip::
34
34
35 Update your channels frequently by running ``nix-channel --update``.
35 Update your channels frequently by running ``nix-channel --update``.
36
36
37
37
38 Switch nix to the latest STABLE channel
38 Switch nix to the latest STABLE channel
39 ---------------------------------------
39 ---------------------------------------
40
40
41 run::
41 run::
42
42
43 nix-channel --add https://nixos.org/channels/nixos-16.03 nixpkgs
43 nix-channel --add https://nixos.org/channels/nixos-16.03 nixpkgs
44
44
45 Followed by::
45 Followed by::
46
46
47 nix-channel --update
47 nix-channel --update
48
48
49
49
50 Install required binaries
50 Install required binaries
51 -------------------------
51 -------------------------
52
52
53 We need some handy tools first.
53 We need some handy tools first.
54
54
55 run::
55 run::
56
56
57 nix-env -i nix-prefetch-hg
57 nix-env -i nix-prefetch-hg
58 nix-env -i nix-prefetch-git
58 nix-env -i nix-prefetch-git
59
59
60
60
61 Clone the required repositories
61 Clone the required repositories
62 -------------------------------
62 -------------------------------
63
63
64 After Nix is set up, clone the RhodeCode Enterprise Community Edition and
64 After Nix is set up, clone the RhodeCode Enterprise Community Edition and
65 RhodeCode VCSServer repositories into the same directory.
65 RhodeCode VCSServer repositories into the same directory.
66 RhodeCode currently is using Mercurial Version Control System, please make sure
66 RhodeCode currently is using Mercurial Version Control System, please make sure
67 you have it installed before continuing.
67 you have it installed before continuing.
68
68
69 To obtain the required sources, use the following commands:
69 To obtain the required sources, use the following commands::
70
70
71 mkdir rhodecode-develop && cd rhodecode-develop
71 mkdir rhodecode-develop && cd rhodecode-develop
72 hg clone https://code.rhodecode.com/rhodecode-enterprise-ce
72 hg clone https://code.rhodecode.com/rhodecode-enterprise-ce
73 hg clone https://code.rhodecode.com/rhodecode-vcsserver
73 hg clone https://code.rhodecode.com/rhodecode-vcsserver
74
74
75 .. note::
75 .. note::
76
76
77 If you cannot clone the repository, please contact us via support@rhodecode.com
77 If you cannot clone the repository, please contact us via support@rhodecode.com
78
78
79
79
80 Install some required libraries
80 Install some required libraries
81 -------------------------------
81 -------------------------------
82
82
83 There are some required drivers that we need to install to test RhodeCode
83 There are some required drivers and dev libraries that we need to install to
84 under different types of databases. For example in Ubuntu we need to install
84 test RhodeCode under different types of databases. For example in Ubuntu we
85 the following.
85 need to install the following.
86
86
87 required libraries::
87 required libraries::
88
88
89 sudo apt-get install libapr1-dev libaprutil1-dev
89 sudo apt-get install libapr1-dev libaprutil1-dev
90 sudo apt-get install libsvn-dev
90 sudo apt-get install libsvn-dev
91 sudo apt-get install mysql-server libmysqlclient-dev
91 sudo apt-get install mysql-server libmysqlclient-dev
92 sudo apt-get install postgresql postgresql-contrib libpq-dev
92 sudo apt-get install postgresql postgresql-contrib libpq-dev
93 sudo apt-get install libcurl4-openssl-dev
93 sudo apt-get install libcurl4-openssl-dev
94
94
95
95
96 Enter the Development Shell
96 Enter the Development Shell
97 ---------------------------
97 ---------------------------
98
98
99 The final step is to start the development shells. To do this, run the
99 The final step is to start the development shells. To do this, run the
100 following command from inside the cloned repository::
100 following command from inside the cloned repository::
101
101
102 #first, the vcsserver
102 #first, the vcsserver
103 cd ~/rhodecode-vcsserver
103 cd ~/rhodecode-vcsserver
104 nix-shell
104 nix-shell
105
105
106 # then enterprise sources
106 # then enterprise sources
107 cd ~/rhodecode-enterprise-ce
107 cd ~/rhodecode-enterprise-ce
108 nix-shell
108 nix-shell
109
109
110 .. note::
110 .. note::
111
111
112 On the first run, this will take a while to download and optionally compile
112 On the first run, this will take a while to download and optionally compile
113 a few things. The following runs will be faster. The development shell works
113 a few things. The following runs will be faster. The development shell works
114 fine on both MacOS and Linux platforms.
114 fine on both MacOS and Linux platforms.
115
115
116
116
117 Create config.nix for development
117 Create config.nix for development
118 ---------------------------------
118 ---------------------------------
119
119
120 In order to run proper tests and setup linking across projects, a config.nix
120 In order to run proper tests and setup linking across projects, a config.nix
121 file needs to be setup::
121 file needs to be setup::
122
122
123 # create config
123 # create config
124 mkdir -p ~/.nixpkgs
124 mkdir -p ~/.nixpkgs
125 touch ~/.nixpkgs/config.nix
125 touch ~/.nixpkgs/config.nix
126
126
127 # put the below content into the ~/.nixpkgs/config.nix file
127 # put the below content into the ~/.nixpkgs/config.nix file
128 # adjusts, the path to where you cloned your repositories.
128 # adjusts, the path to where you cloned your repositories.
129
129
130 {
130 {
131 rc = {
131 rc = {
132 sources = {
132 sources = {
133 rhodecode-vcsserver = "/home/dev/rhodecode-vcsserver";
133 rhodecode-vcsserver = "/home/dev/rhodecode-vcsserver";
134 rhodecode-enterprise-ce = "/home/dev/rhodecode-enterprise-ce";
134 rhodecode-enterprise-ce = "/home/dev/rhodecode-enterprise-ce";
135 rhodecode-enterprise-ee = "/home/dev/rhodecode-enterprise-ee";
135 rhodecode-enterprise-ee = "/home/dev/rhodecode-enterprise-ee";
136 };
136 };
137 };
137 };
138 }
138 }
139
139
140
140
141
141
142 Creating a Development Configuration
142 Creating a Development Configuration
143 ------------------------------------
143 ------------------------------------
144
144
145 To create a development environment for RhodeCode Enterprise,
145 To create a development environment for RhodeCode Enterprise,
146 use the following steps:
146 use the following steps:
147
147
148 1. Create a copy of vcsserver config:
148 1. Create a copy of vcsserver config:
149 `cp ~/rhodecode-vcsserver/configs/development.ini ~/rhodecode-vcsserver/configs/dev.ini`
149 `cp ~/rhodecode-vcsserver/configs/development.ini ~/rhodecode-vcsserver/configs/dev.ini`
150 2. Create a copy of rhodocode config:
150 2. Create a copy of rhodocode config:
151 `cp ~/rhodecode-enterprise-ce/configs/development.ini ~/rhodecode-enterprise-ce/configs/dev.ini`
151 `cp ~/rhodecode-enterprise-ce/configs/development.ini ~/rhodecode-enterprise-ce/configs/dev.ini`
152 3. Adjust the configuration settings to your needs if needed.
152 3. Adjust the configuration settings to your needs if needed.
153
153
154 .. note::
154 .. note::
155
155
156 It is recommended to use the name `dev.ini` since it's included in .hgignore file.
156 It is recommended to use the name `dev.ini` since it's included in .hgignore file.
157
157
158
158
159 Setup the Development Database
159 Setup the Development Database
160 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
160 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
161
161
162 To create a development database, use the following example. This is a one
162 To create a development database, use the following example. This is a one
163 time operation executed from the nix-shell of rhodecode-enterprise-ce sources ::
163 time operation executed from the nix-shell of rhodecode-enterprise-ce sources ::
164
164
165 paster setup-rhodecode dev.ini \
165 paster setup-rhodecode dev.ini \
166 --user=admin --password=secret \
166 --user=admin --password=secret \
167 --email=admin@example.com \
167 --email=admin@example.com \
168 --repos=~/my_dev_repos
168 --repos=~/my_dev_repos
169
169
170
170
171 Compile CSS and JavaScript
171 Compile CSS and JavaScript
172 ^^^^^^^^^^^^^^^^^^^^^^^^^^
172 ^^^^^^^^^^^^^^^^^^^^^^^^^^
173
173
174 To use the application's frontend and prepare it for production deployment,
174 To use the application's frontend and prepare it for production deployment,
175 you will need to compile the CSS and JavaScript with Grunt.
175 you will need to compile the CSS and JavaScript with Grunt.
176 This is easily done from within the nix-shell using the following command::
176 This is easily done from within the nix-shell using the following command::
177
177
178 grunt
178 grunt
179
179
180 When developing new features you will need to recompile following any
180 When developing new features you will need to recompile following any
181 changes made to the CSS or JavaScript files when developing the code::
181 changes made to the CSS or JavaScript files when developing the code::
182
182
183 grunt watch
183 grunt watch
184
184
185 This prepares the development (with comments/whitespace) versions of files.
185 This prepares the development (with comments/whitespace) versions of files.
186
186
187 Start the Development Servers
187 Start the Development Servers
188 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
188 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
189
189
190 From the rhodecode-vcsserver directory, start the development server in another
190 From the rhodecode-vcsserver directory, start the development server in another
191 nix-shell, using the following command::
191 nix-shell, using the following command::
192
192
193 pserve configs/dev.ini
193 pserve configs/dev.ini
194
194
195 In the adjacent nix-shell which you created for your development server, you may
195 In the adjacent nix-shell which you created for your development server, you may
196 now start CE with the following command::
196 now start CE with the following command::
197
197
198
198
199 pserve --reload configs/dev.ini
199 pserve --reload configs/dev.ini
200
200
201 .. note::
201 .. note::
202
202
203 `--reload` flag will automatically reload the server when source file changes.
203 `--reload` flag will automatically reload the server when source file changes.
204
204
205
205
206 Run the Environment Tests
206 Run the Environment Tests
207 ^^^^^^^^^^^^^^^^^^^^^^^^^
207 ^^^^^^^^^^^^^^^^^^^^^^^^^
208
208
209 Please make sure that the tests are passing to verify that your environment is
209 Please make sure that the tests are passing to verify that your environment is
210 set up correctly. RhodeCode uses py.test to run tests.
210 set up correctly. RhodeCode uses py.test to run tests.
211 While your instance is running, start a new nix-shell and simply run
211 While your instance is running, start a new nix-shell and simply run
212 ``make test`` to run the basic test suite.
212 ``make test`` to run the basic test suite.
213
213
214
214
215 Need Help?
215 Need Help?
216 ^^^^^^^^^^
216 ^^^^^^^^^^
217
217
218 Join us on Slack via https://rhodecode.com/join or post questions in our
218 Join us on Slack via https://rhodecode.com/join or post questions in our
219 Community Portal at https://community.rhodecode.com
219 Community Portal at https://community.rhodecode.com
@@ -1,87 +1,87 b''
1 |RCM|
1 |RCM|
2 =====
2 =====
3
3
4 |RCM| is a high-performance source code management and collaboration system.
4 |RCM| is a high-performance source code management and collaboration system.
5 It enables you to develop projects securely behind the firewall while
5 It enables you to develop projects securely behind the firewall while
6 providing collaboration tools that work with |git|, |hg|,
6 providing collaboration tools that work with |git|, |hg|,
7 and |svn| |repos|. The user interface allows you to create, edit,
7 and |svn| |repos|. The user interface allows you to create, edit,
8 and commit files and |repos| while managing their security permissions.
8 and commit files and |repos| while managing their security permissions.
9
9
10 |RCM| provides the following features:
10 |RCM| provides the following features:
11
11
12 * Source code management.
12 * Source code management.
13 * Extended permissions management.
13 * Extended permissions management.
14 * Integrated code collaboration tools.
14 * Integrated code collaboration tools.
15 * Integrated code review and notifications.
15 * Integrated code review and notifications.
16 * Scalability provided by multi-node setup.
16 * Scalability provided by multi-node setup.
17 * Fully programmable automation API.
17 * Fully programmable automation API.
18 * Web-based hook management.
18 * Web-based hook management.
19 * Native |svn| support.
19 * Native |svn| support.
20 * Migration from existing databases.
20 * Migration from existing databases.
21 * |RCM| SDK.
21 * |RCM| SDK.
22 * Built-in analytics
22 * Built-in analytics
23 * Built in integrations including: Slack, Jenkins, Webhooks, Jira, Redmine, Hipchat
23 * Built in integrations including: Slack, Webhooks (used for Jenkins/TeamCity and other CIs), Jira, Redmine, Hipchat
24 * Pluggable authentication system.
24 * Pluggable authentication system.
25 * Support for AD, |LDAP|, Crowd, CAS, PAM.
25 * Support for AD, |LDAP|, Crowd, CAS, PAM.
26 * Support for external authentication via Oauth Google, Github, Bitbucket, Twitter.
26 * Support for external authentication via Oauth Google, Github, Bitbucket, Twitter.
27 * Debug modes of operation.
27 * Debug modes of operation.
28 * Private and public gists.
28 * Private and public gists.
29 * Gists with limited lifetimes and within instance only sharing.
29 * Gists with limited lifetimes and within instance only sharing.
30 * Fully integrated code search function.
30 * Fully integrated code search function.
31 * Always on SSL connectivity.
31 * Always on SSL connectivity.
32
32
33 .. only:: html
33 .. only:: html
34
34
35 Table of Contents
35 Table of Contents
36 -----------------
36 -----------------
37
37
38 .. toctree::
38 .. toctree::
39 :maxdepth: 1
39 :maxdepth: 1
40 :caption: Admin Documentation
40 :caption: Admin Documentation
41
41
42 install/quick-start
42 install/quick-start
43 install/install-database
43 install/install-database
44 install/install-steps
44 install/install-steps
45 admin/system-overview
45 admin/system-overview
46 nix/default-env
46 nix/default-env
47 admin/system-admin
47 admin/system-admin
48 admin/user-admin
48 admin/user-admin
49 admin/setting-repo-perms
49 admin/setting-repo-perms
50 admin/security-tips
50 admin/security-tips
51 auth/auth
51 auth/auth
52 issue-trackers/issue-trackers
52 issue-trackers/issue-trackers
53 admin/lab-settings
53 admin/lab-settings
54
54
55 .. toctree::
55 .. toctree::
56 :maxdepth: 1
56 :maxdepth: 1
57 :caption: Feature Documentation
57 :caption: Feature Documentation
58
58
59 collaboration/collaboration
59 collaboration/collaboration
60 collaboration/review-notifications
60 collaboration/review-notifications
61 collaboration/pull-requests
61 collaboration/pull-requests
62 code-review/code-review
62 code-review/code-review
63 integrations/integrations
63 integrations/integrations
64
64
65 .. toctree::
65 .. toctree::
66 :maxdepth: 1
66 :maxdepth: 1
67 :caption: Developer Documentation
67 :caption: Developer Documentation
68
68
69 api/api
69 api/api
70 tools/rhodecode-tools
70 tools/rhodecode-tools
71 extensions/extensions-hooks
71 extensions/extensions-hooks
72 contributing/contributing
72 contributing/contributing
73
73
74 .. toctree::
74 .. toctree::
75 :maxdepth: 1
75 :maxdepth: 1
76 :caption: User Documentation
76 :caption: User Documentation
77
77
78 usage/basic-usage
78 usage/basic-usage
79 tutorials/tutorials
79 tutorials/tutorials
80
80
81 .. toctree::
81 .. toctree::
82 :maxdepth: 1
82 :maxdepth: 1
83 :caption: About
83 :caption: About
84
84
85 known-issues/known-issues
85 known-issues/known-issues
86 release-notes/release-notes
86 release-notes/release-notes
87 admin/glossary
87 admin/glossary
@@ -1,53 +1,54 b''
1 .. _integrations:
1 .. _integrations:
2
2
3 Integrations
3 Integrations
4 ------------
4 ------------
5
5
6 Rhodecode supports integrations with external services for various events,
6 Rhodecode supports integrations with external services for various events,
7 such as commit pushes and pull requests. Multiple integrations of the same type
7 such as commit pushes and pull requests. Multiple integrations of the same type
8 can be added at the same time; this is useful for posting different events to
8 can be added at the same time; this is useful for posting different events to
9 different Slack channels, for example.
9 different Slack channels, for example.
10
10
11 Supported integrations
11 Supported integrations
12 ^^^^^^^^^^^^^^^^^^^^^^
12 ^^^^^^^^^^^^^^^^^^^^^^
13
13
14 ============================ ============ =====================================
14 ============================ ============ =====================================
15 Type/Name |RC| Edition Description
15 Type/Name |RC| Edition Description
16 ============================ ============ =====================================
16 ============================ ============ =====================================
17 :ref:`integrations-slack` |RCCEshort| https://slack.com/
17 :ref:`integrations-slack` |RCCEshort| https://slack.com/
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
20 :ref:`integrations-email` |RCEEshort| Send repo push commits by email
20 :ref:`integrations-ci` |RCCEshort| Trigger Builds for Common CI Systems
21 :ref:`integrations-email` |RCCEshort| Send repo push commits by email
21 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
22 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
22 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
23 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
23 ============================ ============ =====================================
24 ============================ ============ =====================================
24
25
25 .. _creating-integrations:
26 .. _creating-integrations:
26
27
27 Creating an Integration
28 Creating an Integration
28 ^^^^^^^^^^^^^^^^^^^^^^^
29 ^^^^^^^^^^^^^^^^^^^^^^^
29
30
30 Integrations can be added globally via the admin UI:
31 Integrations can be added globally via the admin UI:
31
32
32 :menuselection:`Admin --> Integrations`
33 :menuselection:`Admin --> Integrations`
33
34
34 or per repository in each repository's settings:
35 or per repository in each repository's settings:
35
36
36 :menuselection:`Admin --> Repositories --> Edit --> Integrations`
37 :menuselection:`Admin --> Repositories --> Edit --> Integrations`
37
38
38 To create an integration, select the type from the list in the *Create New
39 To create an integration, select the type from the list in the *Create New
39 Integration* section.
40 Integration* section.
40
41
41 The *Current Integrations* section shows existing integrations that have been
42 The *Current Integrations* section shows existing integrations that have been
42 created along with their type (eg. Slack) and enabled status.
43 created along with their type (eg. Slack) and enabled status.
43
44
44 See pages specific to each type of integration for more instructions:
45 See pages specific to each type of integration for more instructions:
45
46
46 .. toctree::
47 .. toctree::
47
48
48 slack
49 slack
49 hipchat
50 hipchat
50 redmine
51 redmine
51 jira
52 jira
52 webhook
53 webhook
53 email
54 email
@@ -1,22 +1,24 b''
1 .. _integrations-webhook:
1 .. _integrations-webhook:
2
2
3 Webhook integration
3 Webhook integration
4 ===================
4 ===================
5
5
6 The Webhook integration allows you to POST events such as repository pushes
6 The :ref:`creating-integrations` integration allows you to POST events such as
7 or pull requests to a custom http endpoint as a json dict with details of the
7 repository pushes or pull requests to a custom http endpoint as a JSON dict
8 event.
8 with details of the event.
9
9
10 Starting from 4.5.0 release, webhook integration allows to use variables
10 Starting from 4.5.0 release, webhook integration allows to use variables
11 inside the URL. For example in URL `https://server-example.com/${repo_name}`
11 inside the URL. For example in URL `https://server-example.com/${repo_name}`
12 ${repo_name} will be replaced with the name of repository which events is
12 ${repo_name} will be replaced with the name of repository which events is
13 triggered from. Some of the variables like
13 triggered from. Some of the variables like
14 `${branch}` will result in webhook be called multiple times when multiple
14 `${branch}` will result in webhook be called multiple times when multiple
15 branches are pushed.
15 branches are pushed.
16
16
17 Some of the variables like `${pull_request_id}` will be replaced only in
17 Starting from 4.8.0 also repository extra fields can be used. A format to use
18 the pull request related events.
18 them is `${extra:field_key}`. It's usefull to use them to specify custom
19 repo only parameters. Some of the variables like `${pull_request_id}`
20 will be replaced only in the pull request related events.
19
21
20 To create a webhook integration, select "webhook" in the integration settings
22 To create a webhook integration, select "webhook" in the integration settings
21 and use the URL and key from your any previous custom webhook created. See
23 and use the URL and key from your any previous custom webhook created. See
22 :ref:`creating-integrations` for additional instructions.
24 :ref:`creating-integrations` for additional instructions.
@@ -1,97 +1,98 b''
1 .. _rhodecode-release-notes-ref:
1 .. _rhodecode-release-notes-ref:
2
2
3 Release Notes
3 Release Notes
4 =============
4 =============
5
5
6 |RCE| 4.x Versions
6 |RCE| 4.x Versions
7 ------------------
7 ------------------
8
8
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12 release-notes-4.8.0.rst
12 release-notes-4.7.2.rst
13 release-notes-4.7.2.rst
13 release-notes-4.7.1.rst
14 release-notes-4.7.1.rst
14 release-notes-4.7.0.rst
15 release-notes-4.7.0.rst
15 release-notes-4.6.1.rst
16 release-notes-4.6.1.rst
16 release-notes-4.6.0.rst
17 release-notes-4.6.0.rst
17 release-notes-4.5.2.rst
18 release-notes-4.5.2.rst
18 release-notes-4.5.1.rst
19 release-notes-4.5.1.rst
19 release-notes-4.5.0.rst
20 release-notes-4.5.0.rst
20 release-notes-4.4.2.rst
21 release-notes-4.4.2.rst
21 release-notes-4.4.1.rst
22 release-notes-4.4.1.rst
22 release-notes-4.4.0.rst
23 release-notes-4.4.0.rst
23 release-notes-4.3.1.rst
24 release-notes-4.3.1.rst
24 release-notes-4.3.0.rst
25 release-notes-4.3.0.rst
25 release-notes-4.2.1.rst
26 release-notes-4.2.1.rst
26 release-notes-4.2.0.rst
27 release-notes-4.2.0.rst
27 release-notes-4.1.2.rst
28 release-notes-4.1.2.rst
28 release-notes-4.1.1.rst
29 release-notes-4.1.1.rst
29 release-notes-4.1.0.rst
30 release-notes-4.1.0.rst
30 release-notes-4.0.1.rst
31 release-notes-4.0.1.rst
31 release-notes-4.0.0.rst
32 release-notes-4.0.0.rst
32
33
33 |RCE| 3.x Versions
34 |RCE| 3.x Versions
34 ------------------
35 ------------------
35
36
36 .. toctree::
37 .. toctree::
37 :maxdepth: 1
38 :maxdepth: 1
38
39
39 release-notes-3.8.4.rst
40 release-notes-3.8.4.rst
40 release-notes-3.8.3.rst
41 release-notes-3.8.3.rst
41 release-notes-3.8.2.rst
42 release-notes-3.8.2.rst
42 release-notes-3.8.1.rst
43 release-notes-3.8.1.rst
43 release-notes-3.8.0.rst
44 release-notes-3.8.0.rst
44 release-notes-3.7.1.rst
45 release-notes-3.7.1.rst
45 release-notes-3.7.0.rst
46 release-notes-3.7.0.rst
46 release-notes-3.6.1.rst
47 release-notes-3.6.1.rst
47 release-notes-3.6.0.rst
48 release-notes-3.6.0.rst
48 release-notes-3.5.2.rst
49 release-notes-3.5.2.rst
49 release-notes-3.5.1.rst
50 release-notes-3.5.1.rst
50 release-notes-3.5.0.rst
51 release-notes-3.5.0.rst
51 release-notes-3.4.1.rst
52 release-notes-3.4.1.rst
52 release-notes-3.4.0.rst
53 release-notes-3.4.0.rst
53 release-notes-3.3.4.rst
54 release-notes-3.3.4.rst
54 release-notes-3.3.3.rst
55 release-notes-3.3.3.rst
55 release-notes-3.3.2.rst
56 release-notes-3.3.2.rst
56 release-notes-3.3.1.rst
57 release-notes-3.3.1.rst
57 release-notes-3.3.0.rst
58 release-notes-3.3.0.rst
58 release-notes-3.2.3.rst
59 release-notes-3.2.3.rst
59 release-notes-3.2.2.rst
60 release-notes-3.2.2.rst
60 release-notes-3.2.1.rst
61 release-notes-3.2.1.rst
61 release-notes-3.2.0.rst
62 release-notes-3.2.0.rst
62 release-notes-3.1.1.rst
63 release-notes-3.1.1.rst
63 release-notes-3.1.0.rst
64 release-notes-3.1.0.rst
64 release-notes-3.0.2.rst
65 release-notes-3.0.2.rst
65 release-notes-3.0.1.rst
66 release-notes-3.0.1.rst
66 release-notes-3.0.0.rst
67 release-notes-3.0.0.rst
67
68
68 |RCE| 2.x Versions
69 |RCE| 2.x Versions
69 ------------------
70 ------------------
70
71
71 .. toctree::
72 .. toctree::
72 :maxdepth: 1
73 :maxdepth: 1
73
74
74 release-notes-2.2.8.rst
75 release-notes-2.2.8.rst
75 release-notes-2.2.7.rst
76 release-notes-2.2.7.rst
76 release-notes-2.2.6.rst
77 release-notes-2.2.6.rst
77 release-notes-2.2.5.rst
78 release-notes-2.2.5.rst
78 release-notes-2.2.4.rst
79 release-notes-2.2.4.rst
79 release-notes-2.2.3.rst
80 release-notes-2.2.3.rst
80 release-notes-2.2.2.rst
81 release-notes-2.2.2.rst
81 release-notes-2.2.1.rst
82 release-notes-2.2.1.rst
82 release-notes-2.2.0.rst
83 release-notes-2.2.0.rst
83 release-notes-2.1.0.rst
84 release-notes-2.1.0.rst
84 release-notes-2.0.2.rst
85 release-notes-2.0.2.rst
85 release-notes-2.0.1.rst
86 release-notes-2.0.1.rst
86 release-notes-2.0.0.rst
87 release-notes-2.0.0.rst
87
88
88 |RCE| 1.x Versions
89 |RCE| 1.x Versions
89 ------------------
90 ------------------
90
91
91 .. toctree::
92 .. toctree::
92 :maxdepth: 1
93 :maxdepth: 1
93
94
94 release-notes-1.7.2.rst
95 release-notes-1.7.2.rst
95 release-notes-1.7.1.rst
96 release-notes-1.7.1.rst
96 release-notes-1.7.0.rst
97 release-notes-1.7.0.rst
97 release-notes-1.6.0.rst
98 release-notes-1.6.0.rst
@@ -1,23 +1,28 b''
1 .. _basic-vcs-cmds:
1 .. _basic-vcs-cmds:
2
2
3 Getting Started with VCS
3 Getting Started with VCS
4 ------------------------
4 ------------------------
5
5
6 When using |RCM|, you will be working with |git| or |hg| |repos| from the
6 When using |RCM|, you will be working with |git|, |svn| or |hg| |repos| from the
7 command line.
7 command line or using a GUI client such as Tortoise, Tower or SourceTree.
8
9 |RCM| uses a standard |git|, |svn| and |hg| protocols. So all tools that
10 can interact with there protocols are supported, including Eclipse or PyCharm
11 plugins.
12
8
13
9 If you have never used either before, the following information should
14 If you have never used either before, the following information should
10 help you set up your local machine so that you can sync changes with the
15 help you set up your local machine so that you can sync changes with the
11 |RCM| server.
16 |RCM| server.
12
17
13 All of the following instructions assume you have a |RCM| account,
18 All of the following instructions assume you have a |RCM| account,
14 and you can access your |repos| from the web interface.
19 and you can access your |repos| from the web interface.
15
20
16 .. note::
21 .. note::
17
22
18 |svn| |repo| management is currently only available from the web interface.
23 |svn| |repo| management is currently only available from the web interface.
19
24
20 .. toctree::
25 .. toctree::
21
26
22 get-start-hg
27 get-start-hg
23 get-start-git
28 get-start-git
@@ -1,33 +1,33 b''
1 { fetchbower, buildEnv }:
1 { fetchbower, buildEnv }:
2 buildEnv { name = "bower-env"; ignoreCollisions = true; paths = [
2 buildEnv { name = "bower-env"; ignoreCollisions = true; paths = [
3 (fetchbower "webcomponentsjs" "0.7.22" "^0.7.22" "0ggh3k8ssafd056ib1m5bvzi7cpz3ry7gr5176d79na1w0c3i7dz")
3 (fetchbower "webcomponentsjs" "0.7.22" "^0.7.22" "0ggh3k8ssafd056ib1m5bvzi7cpz3ry7gr5176d79na1w0c3i7dz")
4 (fetchbower "polymer" "Polymer/polymer#1.6.1" "Polymer/polymer#^1.6.1" "09mm0jgk457gvwqlc155swch7gjr6fs3g7spnvhi6vh5b6518540")
4 (fetchbower "polymer" "Polymer/polymer#1.6.1" "Polymer/polymer#^1.6.1" "09mm0jgk457gvwqlc155swch7gjr6fs3g7spnvhi6vh5b6518540")
5 (fetchbower "paper-button" "PolymerElements/paper-button#1.0.13" "PolymerElements/paper-button#^1.0.13" "0i3y153nqk06pn0gk282vyybnl3g1w3w41d5i9z659cgn27g3fvm")
5 (fetchbower "paper-button" "PolymerElements/paper-button#1.0.13" "PolymerElements/paper-button#^1.0.13" "0i3y153nqk06pn0gk282vyybnl3g1w3w41d5i9z659cgn27g3fvm")
6 (fetchbower "paper-spinner" "PolymerElements/paper-spinner#1.2.0" "PolymerElements/paper-spinner#^1.2.0" "1av1m6y81jw3hjhz1yqy3rwcgxarjzl58ldfn4q6sn51pgzngfqb")
6 (fetchbower "paper-spinner" "PolymerElements/paper-spinner#1.2.0" "PolymerElements/paper-spinner#^1.2.0" "1av1m6y81jw3hjhz1yqy3rwcgxarjzl58ldfn4q6sn51pgzngfqb")
7 (fetchbower "paper-tooltip" "PolymerElements/paper-tooltip#1.1.3" "PolymerElements/paper-tooltip#^1.1.2" "0vmrm1n8k9sk9nvqy03q177axy22pia6i3j1gxbk72j3pqiqvg6k")
7 (fetchbower "paper-tooltip" "PolymerElements/paper-tooltip#1.1.3" "PolymerElements/paper-tooltip#^1.1.2" "0vmrm1n8k9sk9nvqy03q177axy22pia6i3j1gxbk72j3pqiqvg6k")
8 (fetchbower "paper-toast" "PolymerElements/paper-toast#1.3.0" "PolymerElements/paper-toast#^1.3.0" "0x9rqxsks5455s8pk4aikpp99ijdn6kxr9gvhwh99nbcqdzcxq1m")
8 (fetchbower "paper-toast" "PolymerElements/paper-toast#1.3.0" "PolymerElements/paper-toast#^1.3.0" "0x9rqxsks5455s8pk4aikpp99ijdn6kxr9gvhwh99nbcqdzcxq1m")
9 (fetchbower "paper-toggle-button" "PolymerElements/paper-toggle-button#1.2.0" "PolymerElements/paper-toggle-button#^1.2.0" "0mphcng3ngspbpg4jjn0mb91nvr4xc1phq3qswib15h6sfww1b2w")
9 (fetchbower "paper-toggle-button" "PolymerElements/paper-toggle-button#1.2.0" "PolymerElements/paper-toggle-button#^1.2.0" "0mphcng3ngspbpg4jjn0mb91nvr4xc1phq3qswib15h6sfww1b2w")
10 (fetchbower "iron-ajax" "PolymerElements/iron-ajax#1.4.3" "PolymerElements/iron-ajax#^1.4.3" "0m3dx27arwmlcp00b7n516sc5a51f40p9vapr1nvd57l3i3z0pzm")
10 (fetchbower "iron-ajax" "PolymerElements/iron-ajax#1.4.3" "PolymerElements/iron-ajax#^1.4.3" "1b1z3112ggjdflgrwbpmnbsh3kgcm4hn255wshvrlzds4w069gja")
11 (fetchbower "iron-autogrow-textarea" "PolymerElements/iron-autogrow-textarea#1.0.13" "PolymerElements/iron-autogrow-textarea#^1.0.13" "0zwhpl97vii1s8k0lgain8i9dnw29b0mxc5ixdscx9las13n2lqq")
11 (fetchbower "iron-autogrow-textarea" "PolymerElements/iron-autogrow-textarea#1.0.13" "PolymerElements/iron-autogrow-textarea#^1.0.13" "0zwhpl97vii1s8k0lgain8i9dnw29b0mxc5ixdscx9las13n2lqq")
12 (fetchbower "iron-a11y-keys" "PolymerElements/iron-a11y-keys#1.0.6" "PolymerElements/iron-a11y-keys#^1.0.6" "1xz3mgghfcxixq28sdb654iaxj4nyi1bzcwf77ydkms6fviqs9mv")
12 (fetchbower "iron-a11y-keys" "PolymerElements/iron-a11y-keys#1.0.6" "PolymerElements/iron-a11y-keys#^1.0.6" "1xz3mgghfcxixq28sdb654iaxj4nyi1bzcwf77ydkms6fviqs9mv")
13 (fetchbower "iron-flex-layout" "PolymerElements/iron-flex-layout#1.3.1" "PolymerElements/iron-flex-layout#^1.0.0" "0nswv3ih3bhflgcd2wjfmddqswzgqxb2xbq65jk9w3rkj26hplbl")
13 (fetchbower "iron-flex-layout" "PolymerElements/iron-flex-layout#1.3.1" "PolymerElements/iron-flex-layout#^1.0.0" "0nswv3ih3bhflgcd2wjfmddqswzgqxb2xbq65jk9w3rkj26hplbl")
14 (fetchbower "paper-behaviors" "PolymerElements/paper-behaviors#1.0.12" "PolymerElements/paper-behaviors#^1.0.0" "012bqk97awgz55cn7rm9g7cckrdhkqhls3zvp8l6nd4rdwcrdzq8")
14 (fetchbower "paper-behaviors" "PolymerElements/paper-behaviors#1.0.12" "PolymerElements/paper-behaviors#^1.0.0" "012bqk97awgz55cn7rm9g7cckrdhkqhls3zvp8l6nd4rdwcrdzq8")
15 (fetchbower "paper-material" "PolymerElements/paper-material#1.0.6" "PolymerElements/paper-material#^1.0.0" "0rljmknfdbm5aabvx9pk77754zckj3l127c3mvnmwkpkkr353xnh")
15 (fetchbower "paper-material" "PolymerElements/paper-material#1.0.6" "PolymerElements/paper-material#^1.0.0" "0rljmknfdbm5aabvx9pk77754zckj3l127c3mvnmwkpkkr353xnh")
16 (fetchbower "paper-styles" "PolymerElements/paper-styles#1.1.4" "PolymerElements/paper-styles#^1.0.0" "0j8vg74xrcxlni8i93dsab3y80f34kk30lv4yblqpkp9c3nrilf7")
16 (fetchbower "paper-styles" "PolymerElements/paper-styles#1.1.4" "PolymerElements/paper-styles#^1.0.0" "0j8vg74xrcxlni8i93dsab3y80f34kk30lv4yblqpkp9c3nrilf7")
17 (fetchbower "neon-animation" "PolymerElements/neon-animation#1.2.4" "PolymerElements/neon-animation#^1.0.0" "16mz9i2n5w0k5j8d6gha23cnbdgm5syz3fawyh89gdbq97bi2q5j")
17 (fetchbower "neon-animation" "PolymerElements/neon-animation#1.2.4" "PolymerElements/neon-animation#^1.0.0" "16mz9i2n5w0k5j8d6gha23cnbdgm5syz3fawyh89gdbq97bi2q5j")
18 (fetchbower "iron-a11y-announcer" "PolymerElements/iron-a11y-announcer#1.0.5" "PolymerElements/iron-a11y-announcer#^1.0.0" "0n7c7j1pwk3835s7s2jd9125wdcsqf216yi5gj07wn5s8h8p7m9d")
18 (fetchbower "iron-a11y-announcer" "PolymerElements/iron-a11y-announcer#1.0.5" "PolymerElements/iron-a11y-announcer#^1.0.0" "0n7c7j1pwk3835s7s2jd9125wdcsqf216yi5gj07wn5s8h8p7m9d")
19 (fetchbower "iron-overlay-behavior" "PolymerElements/iron-overlay-behavior#1.8.6" "PolymerElements/iron-overlay-behavior#^1.0.9" "14brn9gz6qqskarg3fxk91xs7vg02vgcsz9a9743kidxr0l0413m")
19 (fetchbower "iron-overlay-behavior" "PolymerElements/iron-overlay-behavior#1.8.6" "PolymerElements/iron-overlay-behavior#^1.0.9" "14brn9gz6qqskarg3fxk91xs7vg02vgcsz9a9743kidxr0l0413m")
20 (fetchbower "iron-fit-behavior" "PolymerElements/iron-fit-behavior#1.2.5" "PolymerElements/iron-fit-behavior#^1.1.0" "1msnlh8lp1xg6v4h6dkjwj9kzac5q5q208ayla3x9hi483ki6rlf")
20 (fetchbower "iron-fit-behavior" "PolymerElements/iron-fit-behavior#1.2.5" "PolymerElements/iron-fit-behavior#^1.1.0" "1msnlh8lp1xg6v4h6dkjwj9kzac5q5q208ayla3x9hi483ki6rlf")
21 (fetchbower "iron-checked-element-behavior" "PolymerElements/iron-checked-element-behavior#1.0.5" "PolymerElements/iron-checked-element-behavior#^1.0.0" "0l0yy4ah454s8bzfv076s8by7h67zy9ni6xb932qwyhx8br6c1m7")
21 (fetchbower "iron-checked-element-behavior" "PolymerElements/iron-checked-element-behavior#1.0.5" "PolymerElements/iron-checked-element-behavior#^1.0.0" "0l0yy4ah454s8bzfv076s8by7h67zy9ni6xb932qwyhx8br6c1m7")
22 (fetchbower "promise-polyfill" "polymerlabs/promise-polyfill#1.0.1" "polymerlabs/promise-polyfill#^1.0.0" "045bj2caav3famr5hhxgs1dx7n08r4s46mlzwb313vdy17is38xb")
22 (fetchbower "promise-polyfill" "polymerlabs/promise-polyfill#1.0.1" "polymerlabs/promise-polyfill#^1.0.0" "045bj2caav3famr5hhxgs1dx7n08r4s46mlzwb313vdy17is38xb")
23 (fetchbower "iron-behaviors" "PolymerElements/iron-behaviors#1.0.17" "PolymerElements/iron-behaviors#^1.0.0" "021qvkmbk32jrrmmphpmwgby4bzi5jyf47rh1bxmq2ip07ly4bpr")
23 (fetchbower "iron-behaviors" "PolymerElements/iron-behaviors#1.0.17" "PolymerElements/iron-behaviors#^1.0.0" "021qvkmbk32jrrmmphpmwgby4bzi5jyf47rh1bxmq2ip07ly4bpr")
24 (fetchbower "iron-validatable-behavior" "PolymerElements/iron-validatable-behavior#1.1.1" "PolymerElements/iron-validatable-behavior#^1.0.0" "1yhxlvywhw2klbbgm3f3cmanxfxggagph4ii635zv0c13707wslv")
24 (fetchbower "iron-validatable-behavior" "PolymerElements/iron-validatable-behavior#1.1.1" "PolymerElements/iron-validatable-behavior#^1.0.0" "1yhxlvywhw2klbbgm3f3cmanxfxggagph4ii635zv0c13707wslv")
25 (fetchbower "iron-form-element-behavior" "PolymerElements/iron-form-element-behavior#1.0.6" "PolymerElements/iron-form-element-behavior#^1.0.0" "0rdhxivgkdhhz2yadgdbjfc70l555p3y83vjh8rfj5hr0asyn6q1")
25 (fetchbower "iron-form-element-behavior" "PolymerElements/iron-form-element-behavior#1.0.6" "PolymerElements/iron-form-element-behavior#^1.0.0" "0rdhxivgkdhhz2yadgdbjfc70l555p3y83vjh8rfj5hr0asyn6q1")
26 (fetchbower "iron-a11y-keys-behavior" "polymerelements/iron-a11y-keys-behavior#1.1.9" "polymerelements/iron-a11y-keys-behavior#^1.0.0" "1imm4gc84qizihhbyhfa8lwjh3myhj837f79i5m04xjgwrjmkaf6")
26 (fetchbower "iron-a11y-keys-behavior" "polymerelements/iron-a11y-keys-behavior#1.1.9" "polymerelements/iron-a11y-keys-behavior#^1.0.0" "1imm4gc84qizihhbyhfa8lwjh3myhj837f79i5m04xjgwrjmkaf6")
27 (fetchbower "paper-ripple" "PolymerElements/paper-ripple#1.0.8" "PolymerElements/paper-ripple#^1.0.0" "0r9sq8ik7wwrw0qb82c3rw0c030ljwd3s466c9y4qbcrsbvfjnns")
27 (fetchbower "paper-ripple" "PolymerElements/paper-ripple#1.0.8" "PolymerElements/paper-ripple#^1.0.0" "0r9sq8ik7wwrw0qb82c3rw0c030ljwd3s466c9y4qbcrsbvfjnns")
28 (fetchbower "font-roboto" "PolymerElements/font-roboto#1.0.1" "PolymerElements/font-roboto#^1.0.1" "02jz43r0wkyr3yp7rq2rc08l5cwnsgca9fr54sr4rhsnl7cjpxrj")
28 (fetchbower "font-roboto" "PolymerElements/font-roboto#1.0.1" "PolymerElements/font-roboto#^1.0.1" "02jz43r0wkyr3yp7rq2rc08l5cwnsgca9fr54sr4rhsnl7cjpxrj")
29 (fetchbower "iron-meta" "PolymerElements/iron-meta#1.1.2" "PolymerElements/iron-meta#^1.0.0" "1wl4dx8fnsknw9z9xi8bpc4cy9x70c11x4zxwxnj73hf3smifppl")
29 (fetchbower "iron-meta" "PolymerElements/iron-meta#1.1.2" "PolymerElements/iron-meta#^1.0.0" "1wl4dx8fnsknw9z9xi8bpc4cy9x70c11x4zxwxnj73hf3smifppl")
30 (fetchbower "iron-resizable-behavior" "PolymerElements/iron-resizable-behavior#1.0.5" "PolymerElements/iron-resizable-behavior#^1.0.0" "1fd5zmbr2hax42vmcasncvk7lzi38fmb1kyii26nn8pnnjak7zkn")
30 (fetchbower "iron-resizable-behavior" "PolymerElements/iron-resizable-behavior#1.0.5" "PolymerElements/iron-resizable-behavior#^1.0.0" "1fd5zmbr2hax42vmcasncvk7lzi38fmb1kyii26nn8pnnjak7zkn")
31 (fetchbower "iron-selector" "PolymerElements/iron-selector#1.5.2" "PolymerElements/iron-selector#^1.0.0" "1ajv46llqzvahm5g6g75w7nfyjcslp53ji0wm96l2k94j87spv3r")
31 (fetchbower "iron-selector" "PolymerElements/iron-selector#1.5.2" "PolymerElements/iron-selector#^1.0.0" "1ajv46llqzvahm5g6g75w7nfyjcslp53ji0wm96l2k94j87spv3r")
32 (fetchbower "web-animations-js" "web-animations/web-animations-js#2.2.2" "web-animations/web-animations-js#^2.2.0" "1izfvm3l67vwys0bqbhidi9rqziw2f8wv289386sc6jsxzgkzhga")
32 (fetchbower "web-animations-js" "web-animations/web-animations-js#2.2.2" "web-animations/web-animations-js#^2.2.0" "1izfvm3l67vwys0bqbhidi9rqziw2f8wv289386sc6jsxzgkzhga")
33 ]; }
33 ]; }
@@ -1,1995 +1,1995 b''
1 # Generated by pip2nix 0.4.0
1 # Generated by pip2nix 0.4.0
2 # See https://github.com/johbo/pip2nix
2 # See https://github.com/johbo/pip2nix
3
3
4 {
4 {
5 Babel = super.buildPythonPackage {
5 Babel = super.buildPythonPackage {
6 name = "Babel-1.3";
6 name = "Babel-1.3";
7 buildInputs = with self; [];
7 buildInputs = with self; [];
8 doCheck = false;
8 doCheck = false;
9 propagatedBuildInputs = with self; [pytz];
9 propagatedBuildInputs = with self; [pytz];
10 src = fetchurl {
10 src = fetchurl {
11 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
11 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
12 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
12 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
13 };
13 };
14 meta = {
14 meta = {
15 license = [ pkgs.lib.licenses.bsdOriginal ];
15 license = [ pkgs.lib.licenses.bsdOriginal ];
16 };
16 };
17 };
17 };
18 Beaker = super.buildPythonPackage {
18 Beaker = super.buildPythonPackage {
19 name = "Beaker-1.7.0";
19 name = "Beaker-1.7.0";
20 buildInputs = with self; [];
20 buildInputs = with self; [];
21 doCheck = false;
21 doCheck = false;
22 propagatedBuildInputs = with self; [];
22 propagatedBuildInputs = with self; [];
23 src = fetchurl {
23 src = fetchurl {
24 url = "https://pypi.python.org/packages/97/8e/409d2e7c009b8aa803dc9e6f239f1db7c3cdf578249087a404e7c27a505d/Beaker-1.7.0.tar.gz";
24 url = "https://pypi.python.org/packages/97/8e/409d2e7c009b8aa803dc9e6f239f1db7c3cdf578249087a404e7c27a505d/Beaker-1.7.0.tar.gz";
25 md5 = "386be3f7fe427358881eee4622b428b3";
25 md5 = "386be3f7fe427358881eee4622b428b3";
26 };
26 };
27 meta = {
27 meta = {
28 license = [ pkgs.lib.licenses.bsdOriginal ];
28 license = [ pkgs.lib.licenses.bsdOriginal ];
29 };
29 };
30 };
30 };
31 CProfileV = super.buildPythonPackage {
31 CProfileV = super.buildPythonPackage {
32 name = "CProfileV-1.0.6";
32 name = "CProfileV-1.0.6";
33 buildInputs = with self; [];
33 buildInputs = with self; [];
34 doCheck = false;
34 doCheck = false;
35 propagatedBuildInputs = with self; [bottle];
35 propagatedBuildInputs = with self; [bottle];
36 src = fetchurl {
36 src = fetchurl {
37 url = "https://pypi.python.org/packages/eb/df/983a0b6cfd3ac94abf023f5011cb04f33613ace196e33f53c86cf91850d5/CProfileV-1.0.6.tar.gz";
37 url = "https://pypi.python.org/packages/eb/df/983a0b6cfd3ac94abf023f5011cb04f33613ace196e33f53c86cf91850d5/CProfileV-1.0.6.tar.gz";
38 md5 = "08c7c242b6e64237bc53c5d13537e03d";
38 md5 = "08c7c242b6e64237bc53c5d13537e03d";
39 };
39 };
40 meta = {
40 meta = {
41 license = [ pkgs.lib.licenses.mit ];
41 license = [ pkgs.lib.licenses.mit ];
42 };
42 };
43 };
43 };
44 Chameleon = super.buildPythonPackage {
44 Chameleon = super.buildPythonPackage {
45 name = "Chameleon-2.24";
45 name = "Chameleon-2.24";
46 buildInputs = with self; [];
46 buildInputs = with self; [];
47 doCheck = false;
47 doCheck = false;
48 propagatedBuildInputs = with self; [];
48 propagatedBuildInputs = with self; [];
49 src = fetchurl {
49 src = fetchurl {
50 url = "https://pypi.python.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
50 url = "https://pypi.python.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
51 md5 = "1b01f1f6533a8a11d0d2f2366dec5342";
51 md5 = "1b01f1f6533a8a11d0d2f2366dec5342";
52 };
52 };
53 meta = {
53 meta = {
54 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
54 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
55 };
55 };
56 };
56 };
57 FormEncode = super.buildPythonPackage {
57 FormEncode = super.buildPythonPackage {
58 name = "FormEncode-1.2.4";
58 name = "FormEncode-1.2.4";
59 buildInputs = with self; [];
59 buildInputs = with self; [];
60 doCheck = false;
60 doCheck = false;
61 propagatedBuildInputs = with self; [];
61 propagatedBuildInputs = with self; [];
62 src = fetchurl {
62 src = fetchurl {
63 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
63 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
64 md5 = "6bc17fb9aed8aea198975e888e2077f4";
64 md5 = "6bc17fb9aed8aea198975e888e2077f4";
65 };
65 };
66 meta = {
66 meta = {
67 license = [ pkgs.lib.licenses.psfl ];
67 license = [ pkgs.lib.licenses.psfl ];
68 };
68 };
69 };
69 };
70 Jinja2 = super.buildPythonPackage {
70 Jinja2 = super.buildPythonPackage {
71 name = "Jinja2-2.7.3";
71 name = "Jinja2-2.7.3";
72 buildInputs = with self; [];
72 buildInputs = with self; [];
73 doCheck = false;
73 doCheck = false;
74 propagatedBuildInputs = with self; [MarkupSafe];
74 propagatedBuildInputs = with self; [MarkupSafe];
75 src = fetchurl {
75 src = fetchurl {
76 url = "https://pypi.python.org/packages/b0/73/eab0bca302d6d6a0b5c402f47ad1760dc9cb2dd14bbc1873ad48db258e4d/Jinja2-2.7.3.tar.gz";
76 url = "https://pypi.python.org/packages/b0/73/eab0bca302d6d6a0b5c402f47ad1760dc9cb2dd14bbc1873ad48db258e4d/Jinja2-2.7.3.tar.gz";
77 md5 = "b9dffd2f3b43d673802fe857c8445b1a";
77 md5 = "b9dffd2f3b43d673802fe857c8445b1a";
78 };
78 };
79 meta = {
79 meta = {
80 license = [ pkgs.lib.licenses.bsdOriginal ];
80 license = [ pkgs.lib.licenses.bsdOriginal ];
81 };
81 };
82 };
82 };
83 Mako = super.buildPythonPackage {
83 Mako = super.buildPythonPackage {
84 name = "Mako-1.0.6";
84 name = "Mako-1.0.6";
85 buildInputs = with self; [];
85 buildInputs = with self; [];
86 doCheck = false;
86 doCheck = false;
87 propagatedBuildInputs = with self; [MarkupSafe];
87 propagatedBuildInputs = with self; [MarkupSafe];
88 src = fetchurl {
88 src = fetchurl {
89 url = "https://pypi.python.org/packages/56/4b/cb75836863a6382199aefb3d3809937e21fa4cb0db15a4f4ba0ecc2e7e8e/Mako-1.0.6.tar.gz";
89 url = "https://pypi.python.org/packages/56/4b/cb75836863a6382199aefb3d3809937e21fa4cb0db15a4f4ba0ecc2e7e8e/Mako-1.0.6.tar.gz";
90 md5 = "a28e22a339080316b2acc352b9ee631c";
90 md5 = "a28e22a339080316b2acc352b9ee631c";
91 };
91 };
92 meta = {
92 meta = {
93 license = [ pkgs.lib.licenses.mit ];
93 license = [ pkgs.lib.licenses.mit ];
94 };
94 };
95 };
95 };
96 Markdown = super.buildPythonPackage {
96 Markdown = super.buildPythonPackage {
97 name = "Markdown-2.6.7";
97 name = "Markdown-2.6.7";
98 buildInputs = with self; [];
98 buildInputs = with self; [];
99 doCheck = false;
99 doCheck = false;
100 propagatedBuildInputs = with self; [];
100 propagatedBuildInputs = with self; [];
101 src = fetchurl {
101 src = fetchurl {
102 url = "https://pypi.python.org/packages/48/a4/fc6b002789c2239ac620ca963694c95b8f74e4747769cdf6021276939e74/Markdown-2.6.7.zip";
102 url = "https://pypi.python.org/packages/48/a4/fc6b002789c2239ac620ca963694c95b8f74e4747769cdf6021276939e74/Markdown-2.6.7.zip";
103 md5 = "632710a7474bbb74a82084392251061f";
103 md5 = "632710a7474bbb74a82084392251061f";
104 };
104 };
105 meta = {
105 meta = {
106 license = [ pkgs.lib.licenses.bsdOriginal ];
106 license = [ pkgs.lib.licenses.bsdOriginal ];
107 };
107 };
108 };
108 };
109 MarkupSafe = super.buildPythonPackage {
109 MarkupSafe = super.buildPythonPackage {
110 name = "MarkupSafe-0.23";
110 name = "MarkupSafe-0.23";
111 buildInputs = with self; [];
111 buildInputs = with self; [];
112 doCheck = false;
112 doCheck = false;
113 propagatedBuildInputs = with self; [];
113 propagatedBuildInputs = with self; [];
114 src = fetchurl {
114 src = fetchurl {
115 url = "https://pypi.python.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-0.23.tar.gz";
115 url = "https://pypi.python.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-0.23.tar.gz";
116 md5 = "f5ab3deee4c37cd6a922fb81e730da6e";
116 md5 = "f5ab3deee4c37cd6a922fb81e730da6e";
117 };
117 };
118 meta = {
118 meta = {
119 license = [ pkgs.lib.licenses.bsdOriginal ];
119 license = [ pkgs.lib.licenses.bsdOriginal ];
120 };
120 };
121 };
121 };
122 MySQL-python = super.buildPythonPackage {
122 MySQL-python = super.buildPythonPackage {
123 name = "MySQL-python-1.2.5";
123 name = "MySQL-python-1.2.5";
124 buildInputs = with self; [];
124 buildInputs = with self; [];
125 doCheck = false;
125 doCheck = false;
126 propagatedBuildInputs = with self; [];
126 propagatedBuildInputs = with self; [];
127 src = fetchurl {
127 src = fetchurl {
128 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
128 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
129 md5 = "654f75b302db6ed8dc5a898c625e030c";
129 md5 = "654f75b302db6ed8dc5a898c625e030c";
130 };
130 };
131 meta = {
131 meta = {
132 license = [ pkgs.lib.licenses.gpl1 ];
132 license = [ pkgs.lib.licenses.gpl1 ];
133 };
133 };
134 };
134 };
135 Paste = super.buildPythonPackage {
135 Paste = super.buildPythonPackage {
136 name = "Paste-2.0.3";
136 name = "Paste-2.0.3";
137 buildInputs = with self; [];
137 buildInputs = with self; [];
138 doCheck = false;
138 doCheck = false;
139 propagatedBuildInputs = with self; [six];
139 propagatedBuildInputs = with self; [six];
140 src = fetchurl {
140 src = fetchurl {
141 url = "https://pypi.python.org/packages/30/c3/5c2f7c7a02e4f58d4454353fa1c32c94f79fa4e36d07a67c0ac295ea369e/Paste-2.0.3.tar.gz";
141 url = "https://pypi.python.org/packages/30/c3/5c2f7c7a02e4f58d4454353fa1c32c94f79fa4e36d07a67c0ac295ea369e/Paste-2.0.3.tar.gz";
142 md5 = "1231e14eae62fa7ed76e9130b04bc61e";
142 md5 = "1231e14eae62fa7ed76e9130b04bc61e";
143 };
143 };
144 meta = {
144 meta = {
145 license = [ pkgs.lib.licenses.mit ];
145 license = [ pkgs.lib.licenses.mit ];
146 };
146 };
147 };
147 };
148 PasteDeploy = super.buildPythonPackage {
148 PasteDeploy = super.buildPythonPackage {
149 name = "PasteDeploy-1.5.2";
149 name = "PasteDeploy-1.5.2";
150 buildInputs = with self; [];
150 buildInputs = with self; [];
151 doCheck = false;
151 doCheck = false;
152 propagatedBuildInputs = with self; [];
152 propagatedBuildInputs = with self; [];
153 src = fetchurl {
153 src = fetchurl {
154 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
154 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
155 md5 = "352b7205c78c8de4987578d19431af3b";
155 md5 = "352b7205c78c8de4987578d19431af3b";
156 };
156 };
157 meta = {
157 meta = {
158 license = [ pkgs.lib.licenses.mit ];
158 license = [ pkgs.lib.licenses.mit ];
159 };
159 };
160 };
160 };
161 PasteScript = super.buildPythonPackage {
161 PasteScript = super.buildPythonPackage {
162 name = "PasteScript-1.7.5";
162 name = "PasteScript-1.7.5";
163 buildInputs = with self; [];
163 buildInputs = with self; [];
164 doCheck = false;
164 doCheck = false;
165 propagatedBuildInputs = with self; [Paste PasteDeploy];
165 propagatedBuildInputs = with self; [Paste PasteDeploy];
166 src = fetchurl {
166 src = fetchurl {
167 url = "https://pypi.python.org/packages/a5/05/fc60efa7c2f17a1dbaeccb2a903a1e90902d92b9d00eebabe3095829d806/PasteScript-1.7.5.tar.gz";
167 url = "https://pypi.python.org/packages/a5/05/fc60efa7c2f17a1dbaeccb2a903a1e90902d92b9d00eebabe3095829d806/PasteScript-1.7.5.tar.gz";
168 md5 = "4c72d78dcb6bb993f30536842c16af4d";
168 md5 = "4c72d78dcb6bb993f30536842c16af4d";
169 };
169 };
170 meta = {
170 meta = {
171 license = [ pkgs.lib.licenses.mit ];
171 license = [ pkgs.lib.licenses.mit ];
172 };
172 };
173 };
173 };
174 Pygments = super.buildPythonPackage {
174 Pygments = super.buildPythonPackage {
175 name = "Pygments-2.2.0";
175 name = "Pygments-2.2.0";
176 buildInputs = with self; [];
176 buildInputs = with self; [];
177 doCheck = false;
177 doCheck = false;
178 propagatedBuildInputs = with self; [];
178 propagatedBuildInputs = with self; [];
179 src = fetchurl {
179 src = fetchurl {
180 url = "https://pypi.python.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz";
180 url = "https://pypi.python.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz";
181 md5 = "13037baca42f16917cbd5ad2fab50844";
181 md5 = "13037baca42f16917cbd5ad2fab50844";
182 };
182 };
183 meta = {
183 meta = {
184 license = [ pkgs.lib.licenses.bsdOriginal ];
184 license = [ pkgs.lib.licenses.bsdOriginal ];
185 };
185 };
186 };
186 };
187 Pylons = super.buildPythonPackage {
187 Pylons = super.buildPythonPackage {
188 name = "Pylons-1.0.2.dev20170407";
188 name = "Pylons-1.0.2.dev20170407";
189 buildInputs = with self; [];
189 buildInputs = with self; [];
190 doCheck = false;
190 doCheck = false;
191 propagatedBuildInputs = with self; [Routes WebHelpers Beaker Paste PasteDeploy PasteScript FormEncode simplejson decorator nose Mako WebError WebTest Tempita MarkupSafe WebOb];
191 propagatedBuildInputs = with self; [Routes WebHelpers Beaker Paste PasteDeploy PasteScript FormEncode simplejson decorator nose Mako WebError WebTest Tempita MarkupSafe WebOb];
192 src = fetchurl {
192 src = fetchurl {
193 url = "https://code.rhodecode.com/upstream/pylons/archive/707354ee4261b9c10450404fc9852ccea4fd667d.tar.gz?md5=f26633726fa2cd3a340316ee6a5d218f";
193 url = "https://code.rhodecode.com/upstream/pylons/archive/707354ee4261b9c10450404fc9852ccea4fd667d.tar.gz?md5=f26633726fa2cd3a340316ee6a5d218f";
194 md5 = "f26633726fa2cd3a340316ee6a5d218f";
194 md5 = "f26633726fa2cd3a340316ee6a5d218f";
195 };
195 };
196 meta = {
196 meta = {
197 license = [ pkgs.lib.licenses.bsdOriginal ];
197 license = [ pkgs.lib.licenses.bsdOriginal ];
198 };
198 };
199 };
199 };
200 Routes = super.buildPythonPackage {
200 Routes = super.buildPythonPackage {
201 name = "Routes-1.13";
201 name = "Routes-1.13";
202 buildInputs = with self; [];
202 buildInputs = with self; [];
203 doCheck = false;
203 doCheck = false;
204 propagatedBuildInputs = with self; [repoze.lru];
204 propagatedBuildInputs = with self; [repoze.lru];
205 src = fetchurl {
205 src = fetchurl {
206 url = "https://pypi.python.org/packages/88/d3/259c3b3cde8837eb9441ab5f574a660e8a4acea8f54a078441d4d2acac1c/Routes-1.13.tar.gz";
206 url = "https://pypi.python.org/packages/88/d3/259c3b3cde8837eb9441ab5f574a660e8a4acea8f54a078441d4d2acac1c/Routes-1.13.tar.gz";
207 md5 = "d527b0ab7dd9172b1275a41f97448783";
207 md5 = "d527b0ab7dd9172b1275a41f97448783";
208 };
208 };
209 meta = {
209 meta = {
210 license = [ pkgs.lib.licenses.bsdOriginal ];
210 license = [ pkgs.lib.licenses.bsdOriginal ];
211 };
211 };
212 };
212 };
213 SQLAlchemy = super.buildPythonPackage {
213 SQLAlchemy = super.buildPythonPackage {
214 name = "SQLAlchemy-0.9.9";
214 name = "SQLAlchemy-0.9.9";
215 buildInputs = with self; [];
215 buildInputs = with self; [];
216 doCheck = false;
216 doCheck = false;
217 propagatedBuildInputs = with self; [];
217 propagatedBuildInputs = with self; [];
218 src = fetchurl {
218 src = fetchurl {
219 url = "https://pypi.python.org/packages/28/f7/1bbfd0d8597e8c358d5e15a166a486ad82fc5579b4e67b6ef7c05b1d182b/SQLAlchemy-0.9.9.tar.gz";
219 url = "https://pypi.python.org/packages/28/f7/1bbfd0d8597e8c358d5e15a166a486ad82fc5579b4e67b6ef7c05b1d182b/SQLAlchemy-0.9.9.tar.gz";
220 md5 = "8a10a9bd13ed3336ef7333ac2cc679ff";
220 md5 = "8a10a9bd13ed3336ef7333ac2cc679ff";
221 };
221 };
222 meta = {
222 meta = {
223 license = [ pkgs.lib.licenses.mit ];
223 license = [ pkgs.lib.licenses.mit ];
224 };
224 };
225 };
225 };
226 Sphinx = super.buildPythonPackage {
226 Sphinx = super.buildPythonPackage {
227 name = "Sphinx-1.2.2";
227 name = "Sphinx-1.2.2";
228 buildInputs = with self; [];
228 buildInputs = with self; [];
229 doCheck = false;
229 doCheck = false;
230 propagatedBuildInputs = with self; [Pygments docutils Jinja2];
230 propagatedBuildInputs = with self; [Pygments docutils Jinja2];
231 src = fetchurl {
231 src = fetchurl {
232 url = "https://pypi.python.org/packages/0a/50/34017e6efcd372893a416aba14b84a1a149fc7074537b0e9cb6ca7b7abe9/Sphinx-1.2.2.tar.gz";
232 url = "https://pypi.python.org/packages/0a/50/34017e6efcd372893a416aba14b84a1a149fc7074537b0e9cb6ca7b7abe9/Sphinx-1.2.2.tar.gz";
233 md5 = "3dc73ccaa8d0bfb2d62fb671b1f7e8a4";
233 md5 = "3dc73ccaa8d0bfb2d62fb671b1f7e8a4";
234 };
234 };
235 meta = {
235 meta = {
236 license = [ pkgs.lib.licenses.bsdOriginal ];
236 license = [ pkgs.lib.licenses.bsdOriginal ];
237 };
237 };
238 };
238 };
239 Tempita = super.buildPythonPackage {
239 Tempita = super.buildPythonPackage {
240 name = "Tempita-0.5.2";
240 name = "Tempita-0.5.2";
241 buildInputs = with self; [];
241 buildInputs = with self; [];
242 doCheck = false;
242 doCheck = false;
243 propagatedBuildInputs = with self; [];
243 propagatedBuildInputs = with self; [];
244 src = fetchurl {
244 src = fetchurl {
245 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
245 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
246 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
246 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
247 };
247 };
248 meta = {
248 meta = {
249 license = [ pkgs.lib.licenses.mit ];
249 license = [ pkgs.lib.licenses.mit ];
250 };
250 };
251 };
251 };
252 URLObject = super.buildPythonPackage {
252 URLObject = super.buildPythonPackage {
253 name = "URLObject-2.4.0";
253 name = "URLObject-2.4.0";
254 buildInputs = with self; [];
254 buildInputs = with self; [];
255 doCheck = false;
255 doCheck = false;
256 propagatedBuildInputs = with self; [];
256 propagatedBuildInputs = with self; [];
257 src = fetchurl {
257 src = fetchurl {
258 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
258 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
259 md5 = "2ed819738a9f0a3051f31dc9924e3065";
259 md5 = "2ed819738a9f0a3051f31dc9924e3065";
260 };
260 };
261 meta = {
261 meta = {
262 license = [ ];
262 license = [ ];
263 };
263 };
264 };
264 };
265 WebError = super.buildPythonPackage {
265 WebError = super.buildPythonPackage {
266 name = "WebError-0.10.3";
266 name = "WebError-0.10.3";
267 buildInputs = with self; [];
267 buildInputs = with self; [];
268 doCheck = false;
268 doCheck = false;
269 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
269 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
270 src = fetchurl {
270 src = fetchurl {
271 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
271 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
272 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
272 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
273 };
273 };
274 meta = {
274 meta = {
275 license = [ pkgs.lib.licenses.mit ];
275 license = [ pkgs.lib.licenses.mit ];
276 };
276 };
277 };
277 };
278 WebHelpers = super.buildPythonPackage {
278 WebHelpers = super.buildPythonPackage {
279 name = "WebHelpers-1.3";
279 name = "WebHelpers-1.3";
280 buildInputs = with self; [];
280 buildInputs = with self; [];
281 doCheck = false;
281 doCheck = false;
282 propagatedBuildInputs = with self; [MarkupSafe];
282 propagatedBuildInputs = with self; [MarkupSafe];
283 src = fetchurl {
283 src = fetchurl {
284 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
284 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
285 md5 = "32749ffadfc40fea51075a7def32588b";
285 md5 = "32749ffadfc40fea51075a7def32588b";
286 };
286 };
287 meta = {
287 meta = {
288 license = [ pkgs.lib.licenses.bsdOriginal ];
288 license = [ pkgs.lib.licenses.bsdOriginal ];
289 };
289 };
290 };
290 };
291 WebHelpers2 = super.buildPythonPackage {
291 WebHelpers2 = super.buildPythonPackage {
292 name = "WebHelpers2-2.0";
292 name = "WebHelpers2-2.0";
293 buildInputs = with self; [];
293 buildInputs = with self; [];
294 doCheck = false;
294 doCheck = false;
295 propagatedBuildInputs = with self; [MarkupSafe six];
295 propagatedBuildInputs = with self; [MarkupSafe six];
296 src = fetchurl {
296 src = fetchurl {
297 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
297 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
298 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
298 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
299 };
299 };
300 meta = {
300 meta = {
301 license = [ pkgs.lib.licenses.mit ];
301 license = [ pkgs.lib.licenses.mit ];
302 };
302 };
303 };
303 };
304 WebOb = super.buildPythonPackage {
304 WebOb = super.buildPythonPackage {
305 name = "WebOb-1.3.1";
305 name = "WebOb-1.3.1";
306 buildInputs = with self; [];
306 buildInputs = with self; [];
307 doCheck = false;
307 doCheck = false;
308 propagatedBuildInputs = with self; [];
308 propagatedBuildInputs = with self; [];
309 src = fetchurl {
309 src = fetchurl {
310 url = "https://pypi.python.org/packages/16/78/adfc0380b8a0d75b2d543fa7085ba98a573b1ae486d9def88d172b81b9fa/WebOb-1.3.1.tar.gz";
310 url = "https://pypi.python.org/packages/16/78/adfc0380b8a0d75b2d543fa7085ba98a573b1ae486d9def88d172b81b9fa/WebOb-1.3.1.tar.gz";
311 md5 = "20918251c5726956ba8fef22d1556177";
311 md5 = "20918251c5726956ba8fef22d1556177";
312 };
312 };
313 meta = {
313 meta = {
314 license = [ pkgs.lib.licenses.mit ];
314 license = [ pkgs.lib.licenses.mit ];
315 };
315 };
316 };
316 };
317 WebTest = super.buildPythonPackage {
317 WebTest = super.buildPythonPackage {
318 name = "WebTest-1.4.3";
318 name = "WebTest-1.4.3";
319 buildInputs = with self; [];
319 buildInputs = with self; [];
320 doCheck = false;
320 doCheck = false;
321 propagatedBuildInputs = with self; [WebOb];
321 propagatedBuildInputs = with self; [WebOb];
322 src = fetchurl {
322 src = fetchurl {
323 url = "https://pypi.python.org/packages/51/3d/84fd0f628df10b30c7db87895f56d0158e5411206b721ca903cb51bfd948/WebTest-1.4.3.zip";
323 url = "https://pypi.python.org/packages/51/3d/84fd0f628df10b30c7db87895f56d0158e5411206b721ca903cb51bfd948/WebTest-1.4.3.zip";
324 md5 = "631ce728bed92c681a4020a36adbc353";
324 md5 = "631ce728bed92c681a4020a36adbc353";
325 };
325 };
326 meta = {
326 meta = {
327 license = [ pkgs.lib.licenses.mit ];
327 license = [ pkgs.lib.licenses.mit ];
328 };
328 };
329 };
329 };
330 Whoosh = super.buildPythonPackage {
330 Whoosh = super.buildPythonPackage {
331 name = "Whoosh-2.7.4";
331 name = "Whoosh-2.7.4";
332 buildInputs = with self; [];
332 buildInputs = with self; [];
333 doCheck = false;
333 doCheck = false;
334 propagatedBuildInputs = with self; [];
334 propagatedBuildInputs = with self; [];
335 src = fetchurl {
335 src = fetchurl {
336 url = "https://pypi.python.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
336 url = "https://pypi.python.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
337 md5 = "c2710105f20b3e29936bd2357383c325";
337 md5 = "c2710105f20b3e29936bd2357383c325";
338 };
338 };
339 meta = {
339 meta = {
340 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
340 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
341 };
341 };
342 };
342 };
343 alembic = super.buildPythonPackage {
343 alembic = super.buildPythonPackage {
344 name = "alembic-0.8.4";
344 name = "alembic-0.8.4";
345 buildInputs = with self; [];
345 buildInputs = with self; [];
346 doCheck = false;
346 doCheck = false;
347 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor];
347 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor];
348 src = fetchurl {
348 src = fetchurl {
349 url = "https://pypi.python.org/packages/ca/7e/299b4499b5c75e5a38c5845145ad24755bebfb8eec07a2e1c366b7181eeb/alembic-0.8.4.tar.gz";
349 url = "https://pypi.python.org/packages/ca/7e/299b4499b5c75e5a38c5845145ad24755bebfb8eec07a2e1c366b7181eeb/alembic-0.8.4.tar.gz";
350 md5 = "5f95d8ee62b443f9b37eb5bee76c582d";
350 md5 = "5f95d8ee62b443f9b37eb5bee76c582d";
351 };
351 };
352 meta = {
352 meta = {
353 license = [ pkgs.lib.licenses.mit ];
353 license = [ pkgs.lib.licenses.mit ];
354 };
354 };
355 };
355 };
356 amqplib = super.buildPythonPackage {
356 amqplib = super.buildPythonPackage {
357 name = "amqplib-1.0.2";
357 name = "amqplib-1.0.2";
358 buildInputs = with self; [];
358 buildInputs = with self; [];
359 doCheck = false;
359 doCheck = false;
360 propagatedBuildInputs = with self; [];
360 propagatedBuildInputs = with self; [];
361 src = fetchurl {
361 src = fetchurl {
362 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
362 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
363 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
363 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
364 };
364 };
365 meta = {
365 meta = {
366 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
366 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
367 };
367 };
368 };
368 };
369 anyjson = super.buildPythonPackage {
369 anyjson = super.buildPythonPackage {
370 name = "anyjson-0.3.3";
370 name = "anyjson-0.3.3";
371 buildInputs = with self; [];
371 buildInputs = with self; [];
372 doCheck = false;
372 doCheck = false;
373 propagatedBuildInputs = with self; [];
373 propagatedBuildInputs = with self; [];
374 src = fetchurl {
374 src = fetchurl {
375 url = "https://pypi.python.org/packages/c3/4d/d4089e1a3dd25b46bebdb55a992b0797cff657b4477bc32ce28038fdecbc/anyjson-0.3.3.tar.gz";
375 url = "https://pypi.python.org/packages/c3/4d/d4089e1a3dd25b46bebdb55a992b0797cff657b4477bc32ce28038fdecbc/anyjson-0.3.3.tar.gz";
376 md5 = "2ea28d6ec311aeeebaf993cb3008b27c";
376 md5 = "2ea28d6ec311aeeebaf993cb3008b27c";
377 };
377 };
378 meta = {
378 meta = {
379 license = [ pkgs.lib.licenses.bsdOriginal ];
379 license = [ pkgs.lib.licenses.bsdOriginal ];
380 };
380 };
381 };
381 };
382 appenlight-client = super.buildPythonPackage {
382 appenlight-client = super.buildPythonPackage {
383 name = "appenlight-client-0.6.14";
383 name = "appenlight-client-0.6.14";
384 buildInputs = with self; [];
384 buildInputs = with self; [];
385 doCheck = false;
385 doCheck = false;
386 propagatedBuildInputs = with self; [WebOb requests];
386 propagatedBuildInputs = with self; [WebOb requests];
387 src = fetchurl {
387 src = fetchurl {
388 url = "https://pypi.python.org/packages/4d/e0/23fee3ebada8143f707e65c06bcb82992040ee64ea8355e044ed55ebf0c1/appenlight_client-0.6.14.tar.gz";
388 url = "https://pypi.python.org/packages/4d/e0/23fee3ebada8143f707e65c06bcb82992040ee64ea8355e044ed55ebf0c1/appenlight_client-0.6.14.tar.gz";
389 md5 = "578c69b09f4356d898fff1199b98a95c";
389 md5 = "578c69b09f4356d898fff1199b98a95c";
390 };
390 };
391 meta = {
391 meta = {
392 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "DFSG approved"; } ];
392 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "DFSG approved"; } ];
393 };
393 };
394 };
394 };
395 authomatic = super.buildPythonPackage {
395 authomatic = super.buildPythonPackage {
396 name = "authomatic-0.1.0.post1";
396 name = "authomatic-0.1.0.post1";
397 buildInputs = with self; [];
397 buildInputs = with self; [];
398 doCheck = false;
398 doCheck = false;
399 propagatedBuildInputs = with self; [];
399 propagatedBuildInputs = with self; [];
400 src = fetchurl {
400 src = fetchurl {
401 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
401 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
402 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
402 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
403 };
403 };
404 meta = {
404 meta = {
405 license = [ pkgs.lib.licenses.mit ];
405 license = [ pkgs.lib.licenses.mit ];
406 };
406 };
407 };
407 };
408 backport-ipaddress = super.buildPythonPackage {
408 backport-ipaddress = super.buildPythonPackage {
409 name = "backport-ipaddress-0.1";
409 name = "backport-ipaddress-0.1";
410 buildInputs = with self; [];
410 buildInputs = with self; [];
411 doCheck = false;
411 doCheck = false;
412 propagatedBuildInputs = with self; [];
412 propagatedBuildInputs = with self; [];
413 src = fetchurl {
413 src = fetchurl {
414 url = "https://pypi.python.org/packages/d3/30/54c6dab05a4dec44db25ff309f1fbb6b7a8bde3f2bade38bb9da67bbab8f/backport_ipaddress-0.1.tar.gz";
414 url = "https://pypi.python.org/packages/d3/30/54c6dab05a4dec44db25ff309f1fbb6b7a8bde3f2bade38bb9da67bbab8f/backport_ipaddress-0.1.tar.gz";
415 md5 = "9c1f45f4361f71b124d7293a60006c05";
415 md5 = "9c1f45f4361f71b124d7293a60006c05";
416 };
416 };
417 meta = {
417 meta = {
418 license = [ pkgs.lib.licenses.psfl ];
418 license = [ pkgs.lib.licenses.psfl ];
419 };
419 };
420 };
420 };
421 backports.shutil-get-terminal-size = super.buildPythonPackage {
421 backports.shutil-get-terminal-size = super.buildPythonPackage {
422 name = "backports.shutil-get-terminal-size-1.0.0";
422 name = "backports.shutil-get-terminal-size-1.0.0";
423 buildInputs = with self; [];
423 buildInputs = with self; [];
424 doCheck = false;
424 doCheck = false;
425 propagatedBuildInputs = with self; [];
425 propagatedBuildInputs = with self; [];
426 src = fetchurl {
426 src = fetchurl {
427 url = "https://pypi.python.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
427 url = "https://pypi.python.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
428 md5 = "03267762480bd86b50580dc19dff3c66";
428 md5 = "03267762480bd86b50580dc19dff3c66";
429 };
429 };
430 meta = {
430 meta = {
431 license = [ pkgs.lib.licenses.mit ];
431 license = [ pkgs.lib.licenses.mit ];
432 };
432 };
433 };
433 };
434 bleach = super.buildPythonPackage {
434 bleach = super.buildPythonPackage {
435 name = "bleach-1.5.0";
435 name = "bleach-1.5.0";
436 buildInputs = with self; [];
436 buildInputs = with self; [];
437 doCheck = false;
437 doCheck = false;
438 propagatedBuildInputs = with self; [six html5lib];
438 propagatedBuildInputs = with self; [six html5lib];
439 src = fetchurl {
439 src = fetchurl {
440 url = "https://pypi.python.org/packages/99/00/25a8fce4de102bf6e3cc76bc4ea60685b2fee33bde1b34830c70cacc26a7/bleach-1.5.0.tar.gz";
440 url = "https://pypi.python.org/packages/99/00/25a8fce4de102bf6e3cc76bc4ea60685b2fee33bde1b34830c70cacc26a7/bleach-1.5.0.tar.gz";
441 md5 = "b663300efdf421b3b727b19d7be9c7e7";
441 md5 = "b663300efdf421b3b727b19d7be9c7e7";
442 };
442 };
443 meta = {
443 meta = {
444 license = [ pkgs.lib.licenses.asl20 ];
444 license = [ pkgs.lib.licenses.asl20 ];
445 };
445 };
446 };
446 };
447 bottle = super.buildPythonPackage {
447 bottle = super.buildPythonPackage {
448 name = "bottle-0.12.8";
448 name = "bottle-0.12.8";
449 buildInputs = with self; [];
449 buildInputs = with self; [];
450 doCheck = false;
450 doCheck = false;
451 propagatedBuildInputs = with self; [];
451 propagatedBuildInputs = with self; [];
452 src = fetchurl {
452 src = fetchurl {
453 url = "https://pypi.python.org/packages/52/df/e4a408f3a7af396d186d4ecd3b389dd764f0f943b4fa8d257bfe7b49d343/bottle-0.12.8.tar.gz";
453 url = "https://pypi.python.org/packages/52/df/e4a408f3a7af396d186d4ecd3b389dd764f0f943b4fa8d257bfe7b49d343/bottle-0.12.8.tar.gz";
454 md5 = "13132c0a8f607bf860810a6ee9064c5b";
454 md5 = "13132c0a8f607bf860810a6ee9064c5b";
455 };
455 };
456 meta = {
456 meta = {
457 license = [ pkgs.lib.licenses.mit ];
457 license = [ pkgs.lib.licenses.mit ];
458 };
458 };
459 };
459 };
460 bumpversion = super.buildPythonPackage {
460 bumpversion = super.buildPythonPackage {
461 name = "bumpversion-0.5.3";
461 name = "bumpversion-0.5.3";
462 buildInputs = with self; [];
462 buildInputs = with self; [];
463 doCheck = false;
463 doCheck = false;
464 propagatedBuildInputs = with self; [];
464 propagatedBuildInputs = with self; [];
465 src = fetchurl {
465 src = fetchurl {
466 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
466 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
467 md5 = "c66a3492eafcf5ad4b024be9fca29820";
467 md5 = "c66a3492eafcf5ad4b024be9fca29820";
468 };
468 };
469 meta = {
469 meta = {
470 license = [ pkgs.lib.licenses.mit ];
470 license = [ pkgs.lib.licenses.mit ];
471 };
471 };
472 };
472 };
473 celery = super.buildPythonPackage {
473 celery = super.buildPythonPackage {
474 name = "celery-2.2.10";
474 name = "celery-2.2.10";
475 buildInputs = with self; [];
475 buildInputs = with self; [];
476 doCheck = false;
476 doCheck = false;
477 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
477 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
478 src = fetchurl {
478 src = fetchurl {
479 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
479 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
480 md5 = "898bc87e54f278055b561316ba73e222";
480 md5 = "898bc87e54f278055b561316ba73e222";
481 };
481 };
482 meta = {
482 meta = {
483 license = [ pkgs.lib.licenses.bsdOriginal ];
483 license = [ pkgs.lib.licenses.bsdOriginal ];
484 };
484 };
485 };
485 };
486 channelstream = super.buildPythonPackage {
486 channelstream = super.buildPythonPackage {
487 name = "channelstream-0.5.2";
487 name = "channelstream-0.5.2";
488 buildInputs = with self; [];
488 buildInputs = with self; [];
489 doCheck = false;
489 doCheck = false;
490 propagatedBuildInputs = with self; [gevent ws4py pyramid pyramid-jinja2 itsdangerous requests six];
490 propagatedBuildInputs = with self; [gevent ws4py pyramid pyramid-jinja2 itsdangerous requests six];
491 src = fetchurl {
491 src = fetchurl {
492 url = "https://pypi.python.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
492 url = "https://pypi.python.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
493 md5 = "1c5eb2a8a405be6f1073da94da6d81d3";
493 md5 = "1c5eb2a8a405be6f1073da94da6d81d3";
494 };
494 };
495 meta = {
495 meta = {
496 license = [ pkgs.lib.licenses.bsdOriginal ];
496 license = [ pkgs.lib.licenses.bsdOriginal ];
497 };
497 };
498 };
498 };
499 click = super.buildPythonPackage {
499 click = super.buildPythonPackage {
500 name = "click-5.1";
500 name = "click-5.1";
501 buildInputs = with self; [];
501 buildInputs = with self; [];
502 doCheck = false;
502 doCheck = false;
503 propagatedBuildInputs = with self; [];
503 propagatedBuildInputs = with self; [];
504 src = fetchurl {
504 src = fetchurl {
505 url = "https://pypi.python.org/packages/b7/34/a496632c4fb6c1ee76efedf77bb8d28b29363d839953d95095b12defe791/click-5.1.tar.gz";
505 url = "https://pypi.python.org/packages/b7/34/a496632c4fb6c1ee76efedf77bb8d28b29363d839953d95095b12defe791/click-5.1.tar.gz";
506 md5 = "9c5323008cccfe232a8b161fc8196d41";
506 md5 = "9c5323008cccfe232a8b161fc8196d41";
507 };
507 };
508 meta = {
508 meta = {
509 license = [ pkgs.lib.licenses.bsdOriginal ];
509 license = [ pkgs.lib.licenses.bsdOriginal ];
510 };
510 };
511 };
511 };
512 colander = super.buildPythonPackage {
512 colander = super.buildPythonPackage {
513 name = "colander-1.2";
513 name = "colander-1.2";
514 buildInputs = with self; [];
514 buildInputs = with self; [];
515 doCheck = false;
515 doCheck = false;
516 propagatedBuildInputs = with self; [translationstring iso8601];
516 propagatedBuildInputs = with self; [translationstring iso8601];
517 src = fetchurl {
517 src = fetchurl {
518 url = "https://pypi.python.org/packages/14/23/c9ceba07a6a1dc0eefbb215fc0dc64aabc2b22ee756bc0f0c13278fa0887/colander-1.2.tar.gz";
518 url = "https://pypi.python.org/packages/14/23/c9ceba07a6a1dc0eefbb215fc0dc64aabc2b22ee756bc0f0c13278fa0887/colander-1.2.tar.gz";
519 md5 = "83db21b07936a0726e588dae1914b9ed";
519 md5 = "83db21b07936a0726e588dae1914b9ed";
520 };
520 };
521 meta = {
521 meta = {
522 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
522 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
523 };
523 };
524 };
524 };
525 configobj = super.buildPythonPackage {
525 configobj = super.buildPythonPackage {
526 name = "configobj-5.0.6";
526 name = "configobj-5.0.6";
527 buildInputs = with self; [];
527 buildInputs = with self; [];
528 doCheck = false;
528 doCheck = false;
529 propagatedBuildInputs = with self; [six];
529 propagatedBuildInputs = with self; [six];
530 src = fetchurl {
530 src = fetchurl {
531 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
531 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
532 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
532 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
533 };
533 };
534 meta = {
534 meta = {
535 license = [ pkgs.lib.licenses.bsdOriginal ];
535 license = [ pkgs.lib.licenses.bsdOriginal ];
536 };
536 };
537 };
537 };
538 configparser = super.buildPythonPackage {
538 configparser = super.buildPythonPackage {
539 name = "configparser-3.5.0";
539 name = "configparser-3.5.0";
540 buildInputs = with self; [];
540 buildInputs = with self; [];
541 doCheck = false;
541 doCheck = false;
542 propagatedBuildInputs = with self; [];
542 propagatedBuildInputs = with self; [];
543 src = fetchurl {
543 src = fetchurl {
544 url = "https://pypi.python.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz";
544 url = "https://pypi.python.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz";
545 md5 = "cfdd915a5b7a6c09917a64a573140538";
545 md5 = "cfdd915a5b7a6c09917a64a573140538";
546 };
546 };
547 meta = {
547 meta = {
548 license = [ pkgs.lib.licenses.mit ];
548 license = [ pkgs.lib.licenses.mit ];
549 };
549 };
550 };
550 };
551 cov-core = super.buildPythonPackage {
551 cov-core = super.buildPythonPackage {
552 name = "cov-core-1.15.0";
552 name = "cov-core-1.15.0";
553 buildInputs = with self; [];
553 buildInputs = with self; [];
554 doCheck = false;
554 doCheck = false;
555 propagatedBuildInputs = with self; [coverage];
555 propagatedBuildInputs = with self; [coverage];
556 src = fetchurl {
556 src = fetchurl {
557 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
557 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
558 md5 = "f519d4cb4c4e52856afb14af52919fe6";
558 md5 = "f519d4cb4c4e52856afb14af52919fe6";
559 };
559 };
560 meta = {
560 meta = {
561 license = [ pkgs.lib.licenses.mit ];
561 license = [ pkgs.lib.licenses.mit ];
562 };
562 };
563 };
563 };
564 coverage = super.buildPythonPackage {
564 coverage = super.buildPythonPackage {
565 name = "coverage-3.7.1";
565 name = "coverage-3.7.1";
566 buildInputs = with self; [];
566 buildInputs = with self; [];
567 doCheck = false;
567 doCheck = false;
568 propagatedBuildInputs = with self; [];
568 propagatedBuildInputs = with self; [];
569 src = fetchurl {
569 src = fetchurl {
570 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
570 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
571 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
571 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
572 };
572 };
573 meta = {
573 meta = {
574 license = [ pkgs.lib.licenses.bsdOriginal ];
574 license = [ pkgs.lib.licenses.bsdOriginal ];
575 };
575 };
576 };
576 };
577 cssselect = super.buildPythonPackage {
577 cssselect = super.buildPythonPackage {
578 name = "cssselect-1.0.1";
578 name = "cssselect-1.0.1";
579 buildInputs = with self; [];
579 buildInputs = with self; [];
580 doCheck = false;
580 doCheck = false;
581 propagatedBuildInputs = with self; [];
581 propagatedBuildInputs = with self; [];
582 src = fetchurl {
582 src = fetchurl {
583 url = "https://pypi.python.org/packages/77/ff/9c865275cd19290feba56344eba570e719efb7ca5b34d67ed12b22ebbb0d/cssselect-1.0.1.tar.gz";
583 url = "https://pypi.python.org/packages/77/ff/9c865275cd19290feba56344eba570e719efb7ca5b34d67ed12b22ebbb0d/cssselect-1.0.1.tar.gz";
584 md5 = "3fa03bf82a9f0b1223c0f1eb1369e139";
584 md5 = "3fa03bf82a9f0b1223c0f1eb1369e139";
585 };
585 };
586 meta = {
586 meta = {
587 license = [ pkgs.lib.licenses.bsdOriginal ];
587 license = [ pkgs.lib.licenses.bsdOriginal ];
588 };
588 };
589 };
589 };
590 decorator = super.buildPythonPackage {
590 decorator = super.buildPythonPackage {
591 name = "decorator-4.0.11";
591 name = "decorator-4.0.11";
592 buildInputs = with self; [];
592 buildInputs = with self; [];
593 doCheck = false;
593 doCheck = false;
594 propagatedBuildInputs = with self; [];
594 propagatedBuildInputs = with self; [];
595 src = fetchurl {
595 src = fetchurl {
596 url = "https://pypi.python.org/packages/cc/ac/5a16f1fc0506ff72fcc8fd4e858e3a1c231f224ab79bb7c4c9b2094cc570/decorator-4.0.11.tar.gz";
596 url = "https://pypi.python.org/packages/cc/ac/5a16f1fc0506ff72fcc8fd4e858e3a1c231f224ab79bb7c4c9b2094cc570/decorator-4.0.11.tar.gz";
597 md5 = "73644c8f0bd4983d1b6a34b49adec0ae";
597 md5 = "73644c8f0bd4983d1b6a34b49adec0ae";
598 };
598 };
599 meta = {
599 meta = {
600 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
600 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
601 };
601 };
602 };
602 };
603 deform = super.buildPythonPackage {
603 deform = super.buildPythonPackage {
604 name = "deform-2.0a2";
604 name = "deform-2.0.4";
605 buildInputs = with self; [];
605 buildInputs = with self; [];
606 doCheck = false;
606 doCheck = false;
607 propagatedBuildInputs = with self; [Chameleon colander peppercorn translationstring zope.deprecation];
607 propagatedBuildInputs = with self; [Chameleon colander iso8601 peppercorn translationstring zope.deprecation];
608 src = fetchurl {
608 src = fetchurl {
609 url = "https://pypi.python.org/packages/8d/b3/aab57e81da974a806dc9c5fa024a6404720f890a6dcf2e80885e3cb4609a/deform-2.0a2.tar.gz";
609 url = "https://pypi.python.org/packages/66/3b/eefcb07abcab7a97f6665aa2d0cf1af741d9d6e78a2e4657fd2b89f89880/deform-2.0.4.tar.gz";
610 md5 = "7a90d41f7fbc18002ce74f39bd90a5e4";
610 md5 = "34756e42cf50dd4b4430809116c4ec0a";
611 };
611 };
612 meta = {
612 meta = {
613 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
613 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
614 };
614 };
615 };
615 };
616 docutils = super.buildPythonPackage {
616 docutils = super.buildPythonPackage {
617 name = "docutils-0.12";
617 name = "docutils-0.12";
618 buildInputs = with self; [];
618 buildInputs = with self; [];
619 doCheck = false;
619 doCheck = false;
620 propagatedBuildInputs = with self; [];
620 propagatedBuildInputs = with self; [];
621 src = fetchurl {
621 src = fetchurl {
622 url = "https://pypi.python.org/packages/37/38/ceda70135b9144d84884ae2fc5886c6baac4edea39550f28bcd144c1234d/docutils-0.12.tar.gz";
622 url = "https://pypi.python.org/packages/37/38/ceda70135b9144d84884ae2fc5886c6baac4edea39550f28bcd144c1234d/docutils-0.12.tar.gz";
623 md5 = "4622263b62c5c771c03502afa3157768";
623 md5 = "4622263b62c5c771c03502afa3157768";
624 };
624 };
625 meta = {
625 meta = {
626 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.publicDomain pkgs.lib.licenses.gpl1 { fullName = "public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)"; } pkgs.lib.licenses.psfl ];
626 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.publicDomain pkgs.lib.licenses.gpl1 { fullName = "public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)"; } pkgs.lib.licenses.psfl ];
627 };
627 };
628 };
628 };
629 dogpile.cache = super.buildPythonPackage {
629 dogpile.cache = super.buildPythonPackage {
630 name = "dogpile.cache-0.6.1";
630 name = "dogpile.cache-0.6.1";
631 buildInputs = with self; [];
631 buildInputs = with self; [];
632 doCheck = false;
632 doCheck = false;
633 propagatedBuildInputs = with self; [];
633 propagatedBuildInputs = with self; [];
634 src = fetchurl {
634 src = fetchurl {
635 url = "https://pypi.python.org/packages/f6/a0/6f2142c58c6588d17c734265b103ae1cd0741e1681dd9483a63f22033375/dogpile.cache-0.6.1.tar.gz";
635 url = "https://pypi.python.org/packages/f6/a0/6f2142c58c6588d17c734265b103ae1cd0741e1681dd9483a63f22033375/dogpile.cache-0.6.1.tar.gz";
636 md5 = "35d7fb30f22bbd0685763d894dd079a9";
636 md5 = "35d7fb30f22bbd0685763d894dd079a9";
637 };
637 };
638 meta = {
638 meta = {
639 license = [ pkgs.lib.licenses.bsdOriginal ];
639 license = [ pkgs.lib.licenses.bsdOriginal ];
640 };
640 };
641 };
641 };
642 dogpile.core = super.buildPythonPackage {
642 dogpile.core = super.buildPythonPackage {
643 name = "dogpile.core-0.4.1";
643 name = "dogpile.core-0.4.1";
644 buildInputs = with self; [];
644 buildInputs = with self; [];
645 doCheck = false;
645 doCheck = false;
646 propagatedBuildInputs = with self; [];
646 propagatedBuildInputs = with self; [];
647 src = fetchurl {
647 src = fetchurl {
648 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
648 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
649 md5 = "01cb19f52bba3e95c9b560f39341f045";
649 md5 = "01cb19f52bba3e95c9b560f39341f045";
650 };
650 };
651 meta = {
651 meta = {
652 license = [ pkgs.lib.licenses.bsdOriginal ];
652 license = [ pkgs.lib.licenses.bsdOriginal ];
653 };
653 };
654 };
654 };
655 ecdsa = super.buildPythonPackage {
655 ecdsa = super.buildPythonPackage {
656 name = "ecdsa-0.11";
656 name = "ecdsa-0.11";
657 buildInputs = with self; [];
657 buildInputs = with self; [];
658 doCheck = false;
658 doCheck = false;
659 propagatedBuildInputs = with self; [];
659 propagatedBuildInputs = with self; [];
660 src = fetchurl {
660 src = fetchurl {
661 url = "https://pypi.python.org/packages/6c/3f/92fe5dcdcaa7bd117be21e5520c9a54375112b66ec000d209e9e9519fad1/ecdsa-0.11.tar.gz";
661 url = "https://pypi.python.org/packages/6c/3f/92fe5dcdcaa7bd117be21e5520c9a54375112b66ec000d209e9e9519fad1/ecdsa-0.11.tar.gz";
662 md5 = "8ef586fe4dbb156697d756900cb41d7c";
662 md5 = "8ef586fe4dbb156697d756900cb41d7c";
663 };
663 };
664 meta = {
664 meta = {
665 license = [ pkgs.lib.licenses.mit ];
665 license = [ pkgs.lib.licenses.mit ];
666 };
666 };
667 };
667 };
668 elasticsearch = super.buildPythonPackage {
668 elasticsearch = super.buildPythonPackage {
669 name = "elasticsearch-2.3.0";
669 name = "elasticsearch-2.3.0";
670 buildInputs = with self; [];
670 buildInputs = with self; [];
671 doCheck = false;
671 doCheck = false;
672 propagatedBuildInputs = with self; [urllib3];
672 propagatedBuildInputs = with self; [urllib3];
673 src = fetchurl {
673 src = fetchurl {
674 url = "https://pypi.python.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz";
674 url = "https://pypi.python.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz";
675 md5 = "2550f3b51629cf1ef9636608af92c340";
675 md5 = "2550f3b51629cf1ef9636608af92c340";
676 };
676 };
677 meta = {
677 meta = {
678 license = [ pkgs.lib.licenses.asl20 ];
678 license = [ pkgs.lib.licenses.asl20 ];
679 };
679 };
680 };
680 };
681 elasticsearch-dsl = super.buildPythonPackage {
681 elasticsearch-dsl = super.buildPythonPackage {
682 name = "elasticsearch-dsl-2.2.0";
682 name = "elasticsearch-dsl-2.2.0";
683 buildInputs = with self; [];
683 buildInputs = with self; [];
684 doCheck = false;
684 doCheck = false;
685 propagatedBuildInputs = with self; [six python-dateutil elasticsearch];
685 propagatedBuildInputs = with self; [six python-dateutil elasticsearch];
686 src = fetchurl {
686 src = fetchurl {
687 url = "https://pypi.python.org/packages/66/2f/52a086968788e58461641570f45c3207a52d46ebbe9b77dc22b6a8ffda66/elasticsearch-dsl-2.2.0.tar.gz";
687 url = "https://pypi.python.org/packages/66/2f/52a086968788e58461641570f45c3207a52d46ebbe9b77dc22b6a8ffda66/elasticsearch-dsl-2.2.0.tar.gz";
688 md5 = "fa6bd3c87ea3caa8f0f051bc37c53221";
688 md5 = "fa6bd3c87ea3caa8f0f051bc37c53221";
689 };
689 };
690 meta = {
690 meta = {
691 license = [ pkgs.lib.licenses.asl20 ];
691 license = [ pkgs.lib.licenses.asl20 ];
692 };
692 };
693 };
693 };
694 entrypoints = super.buildPythonPackage {
694 entrypoints = super.buildPythonPackage {
695 name = "entrypoints-0.2.2";
695 name = "entrypoints-0.2.2";
696 buildInputs = with self; [];
696 buildInputs = with self; [];
697 doCheck = false;
697 doCheck = false;
698 propagatedBuildInputs = with self; [configparser];
698 propagatedBuildInputs = with self; [configparser];
699 src = fetchurl {
699 src = fetchurl {
700 url = "https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313";
700 url = "https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313";
701 md5 = "7db37771aea9ac9fefe093e5d6987313";
701 md5 = "7db37771aea9ac9fefe093e5d6987313";
702 };
702 };
703 meta = {
703 meta = {
704 license = [ pkgs.lib.licenses.mit ];
704 license = [ pkgs.lib.licenses.mit ];
705 };
705 };
706 };
706 };
707 enum34 = super.buildPythonPackage {
707 enum34 = super.buildPythonPackage {
708 name = "enum34-1.1.6";
708 name = "enum34-1.1.6";
709 buildInputs = with self; [];
709 buildInputs = with self; [];
710 doCheck = false;
710 doCheck = false;
711 propagatedBuildInputs = with self; [];
711 propagatedBuildInputs = with self; [];
712 src = fetchurl {
712 src = fetchurl {
713 url = "https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
713 url = "https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
714 md5 = "5f13a0841a61f7fc295c514490d120d0";
714 md5 = "5f13a0841a61f7fc295c514490d120d0";
715 };
715 };
716 meta = {
716 meta = {
717 license = [ pkgs.lib.licenses.bsdOriginal ];
717 license = [ pkgs.lib.licenses.bsdOriginal ];
718 };
718 };
719 };
719 };
720 functools32 = super.buildPythonPackage {
720 functools32 = super.buildPythonPackage {
721 name = "functools32-3.2.3.post2";
721 name = "functools32-3.2.3.post2";
722 buildInputs = with self; [];
722 buildInputs = with self; [];
723 doCheck = false;
723 doCheck = false;
724 propagatedBuildInputs = with self; [];
724 propagatedBuildInputs = with self; [];
725 src = fetchurl {
725 src = fetchurl {
726 url = "https://pypi.python.org/packages/5e/1a/0aa2c8195a204a9f51284018562dea77e25511f02fe924fac202fc012172/functools32-3.2.3-2.zip";
726 url = "https://pypi.python.org/packages/5e/1a/0aa2c8195a204a9f51284018562dea77e25511f02fe924fac202fc012172/functools32-3.2.3-2.zip";
727 md5 = "d55232eb132ec779e6893c902a0bc5ad";
727 md5 = "d55232eb132ec779e6893c902a0bc5ad";
728 };
728 };
729 meta = {
729 meta = {
730 license = [ pkgs.lib.licenses.psfl ];
730 license = [ pkgs.lib.licenses.psfl ];
731 };
731 };
732 };
732 };
733 future = super.buildPythonPackage {
733 future = super.buildPythonPackage {
734 name = "future-0.14.3";
734 name = "future-0.14.3";
735 buildInputs = with self; [];
735 buildInputs = with self; [];
736 doCheck = false;
736 doCheck = false;
737 propagatedBuildInputs = with self; [];
737 propagatedBuildInputs = with self; [];
738 src = fetchurl {
738 src = fetchurl {
739 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
739 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
740 md5 = "e94079b0bd1fc054929e8769fc0f6083";
740 md5 = "e94079b0bd1fc054929e8769fc0f6083";
741 };
741 };
742 meta = {
742 meta = {
743 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
743 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
744 };
744 };
745 };
745 };
746 futures = super.buildPythonPackage {
746 futures = super.buildPythonPackage {
747 name = "futures-3.0.2";
747 name = "futures-3.0.2";
748 buildInputs = with self; [];
748 buildInputs = with self; [];
749 doCheck = false;
749 doCheck = false;
750 propagatedBuildInputs = with self; [];
750 propagatedBuildInputs = with self; [];
751 src = fetchurl {
751 src = fetchurl {
752 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
752 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
753 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
753 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
754 };
754 };
755 meta = {
755 meta = {
756 license = [ pkgs.lib.licenses.bsdOriginal ];
756 license = [ pkgs.lib.licenses.bsdOriginal ];
757 };
757 };
758 };
758 };
759 gevent = super.buildPythonPackage {
759 gevent = super.buildPythonPackage {
760 name = "gevent-1.1.2";
760 name = "gevent-1.1.2";
761 buildInputs = with self; [];
761 buildInputs = with self; [];
762 doCheck = false;
762 doCheck = false;
763 propagatedBuildInputs = with self; [greenlet];
763 propagatedBuildInputs = with self; [greenlet];
764 src = fetchurl {
764 src = fetchurl {
765 url = "https://pypi.python.org/packages/43/8f/cb3224a0e6ab663547f45c10d0651cfd52633fde4283bf68d627084df8cc/gevent-1.1.2.tar.gz";
765 url = "https://pypi.python.org/packages/43/8f/cb3224a0e6ab663547f45c10d0651cfd52633fde4283bf68d627084df8cc/gevent-1.1.2.tar.gz";
766 md5 = "bb32a2f852a4997138014d5007215c6e";
766 md5 = "bb32a2f852a4997138014d5007215c6e";
767 };
767 };
768 meta = {
768 meta = {
769 license = [ pkgs.lib.licenses.mit ];
769 license = [ pkgs.lib.licenses.mit ];
770 };
770 };
771 };
771 };
772 gnureadline = super.buildPythonPackage {
772 gnureadline = super.buildPythonPackage {
773 name = "gnureadline-6.3.3";
773 name = "gnureadline-6.3.3";
774 buildInputs = with self; [];
774 buildInputs = with self; [];
775 doCheck = false;
775 doCheck = false;
776 propagatedBuildInputs = with self; [];
776 propagatedBuildInputs = with self; [];
777 src = fetchurl {
777 src = fetchurl {
778 url = "https://pypi.python.org/packages/3a/ee/2c3f568b0a74974791ac590ec742ef6133e2fbd287a074ba72a53fa5e97c/gnureadline-6.3.3.tar.gz";
778 url = "https://pypi.python.org/packages/3a/ee/2c3f568b0a74974791ac590ec742ef6133e2fbd287a074ba72a53fa5e97c/gnureadline-6.3.3.tar.gz";
779 md5 = "c4af83c9a3fbeac8f2da9b5a7c60e51c";
779 md5 = "c4af83c9a3fbeac8f2da9b5a7c60e51c";
780 };
780 };
781 meta = {
781 meta = {
782 license = [ pkgs.lib.licenses.gpl1 ];
782 license = [ pkgs.lib.licenses.gpl1 ];
783 };
783 };
784 };
784 };
785 gprof2dot = super.buildPythonPackage {
785 gprof2dot = super.buildPythonPackage {
786 name = "gprof2dot-2016.10.13";
786 name = "gprof2dot-2016.10.13";
787 buildInputs = with self; [];
787 buildInputs = with self; [];
788 doCheck = false;
788 doCheck = false;
789 propagatedBuildInputs = with self; [];
789 propagatedBuildInputs = with self; [];
790 src = fetchurl {
790 src = fetchurl {
791 url = "https://pypi.python.org/packages/a0/e0/73c71baed306f0402a00a94ffc7b2be94ad1296dfcb8b46912655b93154c/gprof2dot-2016.10.13.tar.gz";
791 url = "https://pypi.python.org/packages/a0/e0/73c71baed306f0402a00a94ffc7b2be94ad1296dfcb8b46912655b93154c/gprof2dot-2016.10.13.tar.gz";
792 md5 = "0125401f15fd2afe1df686a76c64a4fd";
792 md5 = "0125401f15fd2afe1df686a76c64a4fd";
793 };
793 };
794 meta = {
794 meta = {
795 license = [ { fullName = "LGPL"; } ];
795 license = [ { fullName = "LGPL"; } ];
796 };
796 };
797 };
797 };
798 greenlet = super.buildPythonPackage {
798 greenlet = super.buildPythonPackage {
799 name = "greenlet-0.4.10";
799 name = "greenlet-0.4.10";
800 buildInputs = with self; [];
800 buildInputs = with self; [];
801 doCheck = false;
801 doCheck = false;
802 propagatedBuildInputs = with self; [];
802 propagatedBuildInputs = with self; [];
803 src = fetchurl {
803 src = fetchurl {
804 url = "https://pypi.python.org/packages/67/62/ca2a95648666eaa2ffeb6a9b3964f21d419ae27f82f2e66b53da5b943fc4/greenlet-0.4.10.zip";
804 url = "https://pypi.python.org/packages/67/62/ca2a95648666eaa2ffeb6a9b3964f21d419ae27f82f2e66b53da5b943fc4/greenlet-0.4.10.zip";
805 md5 = "bed0c4b3b896702131f4d5c72f87c41d";
805 md5 = "bed0c4b3b896702131f4d5c72f87c41d";
806 };
806 };
807 meta = {
807 meta = {
808 license = [ pkgs.lib.licenses.mit ];
808 license = [ pkgs.lib.licenses.mit ];
809 };
809 };
810 };
810 };
811 gunicorn = super.buildPythonPackage {
811 gunicorn = super.buildPythonPackage {
812 name = "gunicorn-19.6.0";
812 name = "gunicorn-19.6.0";
813 buildInputs = with self; [];
813 buildInputs = with self; [];
814 doCheck = false;
814 doCheck = false;
815 propagatedBuildInputs = with self; [];
815 propagatedBuildInputs = with self; [];
816 src = fetchurl {
816 src = fetchurl {
817 url = "https://pypi.python.org/packages/84/ce/7ea5396efad1cef682bbc4068e72a0276341d9d9d0f501da609fab9fcb80/gunicorn-19.6.0.tar.gz";
817 url = "https://pypi.python.org/packages/84/ce/7ea5396efad1cef682bbc4068e72a0276341d9d9d0f501da609fab9fcb80/gunicorn-19.6.0.tar.gz";
818 md5 = "338e5e8a83ea0f0625f768dba4597530";
818 md5 = "338e5e8a83ea0f0625f768dba4597530";
819 };
819 };
820 meta = {
820 meta = {
821 license = [ pkgs.lib.licenses.mit ];
821 license = [ pkgs.lib.licenses.mit ];
822 };
822 };
823 };
823 };
824 html5lib = super.buildPythonPackage {
824 html5lib = super.buildPythonPackage {
825 name = "html5lib-0.9999999";
825 name = "html5lib-0.9999999";
826 buildInputs = with self; [];
826 buildInputs = with self; [];
827 doCheck = false;
827 doCheck = false;
828 propagatedBuildInputs = with self; [six];
828 propagatedBuildInputs = with self; [six];
829 src = fetchurl {
829 src = fetchurl {
830 url = "https://pypi.python.org/packages/ae/ae/bcb60402c60932b32dfaf19bb53870b29eda2cd17551ba5639219fb5ebf9/html5lib-0.9999999.tar.gz";
830 url = "https://pypi.python.org/packages/ae/ae/bcb60402c60932b32dfaf19bb53870b29eda2cd17551ba5639219fb5ebf9/html5lib-0.9999999.tar.gz";
831 md5 = "ef43cb05e9e799f25d65d1135838a96f";
831 md5 = "ef43cb05e9e799f25d65d1135838a96f";
832 };
832 };
833 meta = {
833 meta = {
834 license = [ pkgs.lib.licenses.mit ];
834 license = [ pkgs.lib.licenses.mit ];
835 };
835 };
836 };
836 };
837 infrae.cache = super.buildPythonPackage {
837 infrae.cache = super.buildPythonPackage {
838 name = "infrae.cache-1.0.1";
838 name = "infrae.cache-1.0.1";
839 buildInputs = with self; [];
839 buildInputs = with self; [];
840 doCheck = false;
840 doCheck = false;
841 propagatedBuildInputs = with self; [Beaker repoze.lru];
841 propagatedBuildInputs = with self; [Beaker repoze.lru];
842 src = fetchurl {
842 src = fetchurl {
843 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
843 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
844 md5 = "b09076a766747e6ed2a755cc62088e32";
844 md5 = "b09076a766747e6ed2a755cc62088e32";
845 };
845 };
846 meta = {
846 meta = {
847 license = [ pkgs.lib.licenses.zpt21 ];
847 license = [ pkgs.lib.licenses.zpt21 ];
848 };
848 };
849 };
849 };
850 invoke = super.buildPythonPackage {
850 invoke = super.buildPythonPackage {
851 name = "invoke-0.13.0";
851 name = "invoke-0.13.0";
852 buildInputs = with self; [];
852 buildInputs = with self; [];
853 doCheck = false;
853 doCheck = false;
854 propagatedBuildInputs = with self; [];
854 propagatedBuildInputs = with self; [];
855 src = fetchurl {
855 src = fetchurl {
856 url = "https://pypi.python.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
856 url = "https://pypi.python.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
857 md5 = "c0d1ed4bfb34eaab551662d8cfee6540";
857 md5 = "c0d1ed4bfb34eaab551662d8cfee6540";
858 };
858 };
859 meta = {
859 meta = {
860 license = [ pkgs.lib.licenses.bsdOriginal ];
860 license = [ pkgs.lib.licenses.bsdOriginal ];
861 };
861 };
862 };
862 };
863 ipdb = super.buildPythonPackage {
863 ipdb = super.buildPythonPackage {
864 name = "ipdb-0.10.1";
864 name = "ipdb-0.10.1";
865 buildInputs = with self; [];
865 buildInputs = with self; [];
866 doCheck = false;
866 doCheck = false;
867 propagatedBuildInputs = with self; [ipython setuptools];
867 propagatedBuildInputs = with self; [ipython setuptools];
868 src = fetchurl {
868 src = fetchurl {
869 url = "https://pypi.python.org/packages/eb/0a/0a37dc19572580336ad3813792c0d18c8d7117c2d66fc63c501f13a7a8f8/ipdb-0.10.1.tar.gz";
869 url = "https://pypi.python.org/packages/eb/0a/0a37dc19572580336ad3813792c0d18c8d7117c2d66fc63c501f13a7a8f8/ipdb-0.10.1.tar.gz";
870 md5 = "4aeab65f633ddc98ebdb5eebf08dc713";
870 md5 = "4aeab65f633ddc98ebdb5eebf08dc713";
871 };
871 };
872 meta = {
872 meta = {
873 license = [ pkgs.lib.licenses.bsdOriginal ];
873 license = [ pkgs.lib.licenses.bsdOriginal ];
874 };
874 };
875 };
875 };
876 ipython = super.buildPythonPackage {
876 ipython = super.buildPythonPackage {
877 name = "ipython-5.1.0";
877 name = "ipython-5.1.0";
878 buildInputs = with self; [];
878 buildInputs = with self; [];
879 doCheck = false;
879 doCheck = false;
880 propagatedBuildInputs = with self; [setuptools decorator pickleshare simplegeneric traitlets prompt-toolkit Pygments pexpect backports.shutil-get-terminal-size pathlib2 pexpect];
880 propagatedBuildInputs = with self; [setuptools decorator pickleshare simplegeneric traitlets prompt-toolkit Pygments pexpect backports.shutil-get-terminal-size pathlib2 pexpect];
881 src = fetchurl {
881 src = fetchurl {
882 url = "https://pypi.python.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
882 url = "https://pypi.python.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
883 md5 = "47c8122420f65b58784cb4b9b4af35e3";
883 md5 = "47c8122420f65b58784cb4b9b4af35e3";
884 };
884 };
885 meta = {
885 meta = {
886 license = [ pkgs.lib.licenses.bsdOriginal ];
886 license = [ pkgs.lib.licenses.bsdOriginal ];
887 };
887 };
888 };
888 };
889 ipython-genutils = super.buildPythonPackage {
889 ipython-genutils = super.buildPythonPackage {
890 name = "ipython-genutils-0.1.0";
890 name = "ipython-genutils-0.2.0";
891 buildInputs = with self; [];
891 buildInputs = with self; [];
892 doCheck = false;
892 doCheck = false;
893 propagatedBuildInputs = with self; [];
893 propagatedBuildInputs = with self; [];
894 src = fetchurl {
894 src = fetchurl {
895 url = "https://pypi.python.org/packages/71/b7/a64c71578521606edbbce15151358598f3dfb72a3431763edc2baf19e71f/ipython_genutils-0.1.0.tar.gz";
895 url = "https://pypi.python.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
896 md5 = "9a8afbe0978adbcbfcb3b35b2d015a56";
896 md5 = "5a4f9781f78466da0ea1a648f3e1f79f";
897 };
897 };
898 meta = {
898 meta = {
899 license = [ pkgs.lib.licenses.bsdOriginal ];
899 license = [ pkgs.lib.licenses.bsdOriginal ];
900 };
900 };
901 };
901 };
902 iso8601 = super.buildPythonPackage {
902 iso8601 = super.buildPythonPackage {
903 name = "iso8601-0.1.11";
903 name = "iso8601-0.1.11";
904 buildInputs = with self; [];
904 buildInputs = with self; [];
905 doCheck = false;
905 doCheck = false;
906 propagatedBuildInputs = with self; [];
906 propagatedBuildInputs = with self; [];
907 src = fetchurl {
907 src = fetchurl {
908 url = "https://pypi.python.org/packages/c0/75/c9209ee4d1b5975eb8c2cba4428bde6b61bd55664a98290dd015cdb18e98/iso8601-0.1.11.tar.gz";
908 url = "https://pypi.python.org/packages/c0/75/c9209ee4d1b5975eb8c2cba4428bde6b61bd55664a98290dd015cdb18e98/iso8601-0.1.11.tar.gz";
909 md5 = "b06d11cd14a64096f907086044f0fe38";
909 md5 = "b06d11cd14a64096f907086044f0fe38";
910 };
910 };
911 meta = {
911 meta = {
912 license = [ pkgs.lib.licenses.mit ];
912 license = [ pkgs.lib.licenses.mit ];
913 };
913 };
914 };
914 };
915 itsdangerous = super.buildPythonPackage {
915 itsdangerous = super.buildPythonPackage {
916 name = "itsdangerous-0.24";
916 name = "itsdangerous-0.24";
917 buildInputs = with self; [];
917 buildInputs = with self; [];
918 doCheck = false;
918 doCheck = false;
919 propagatedBuildInputs = with self; [];
919 propagatedBuildInputs = with self; [];
920 src = fetchurl {
920 src = fetchurl {
921 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
921 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
922 md5 = "a3d55aa79369aef5345c036a8a26307f";
922 md5 = "a3d55aa79369aef5345c036a8a26307f";
923 };
923 };
924 meta = {
924 meta = {
925 license = [ pkgs.lib.licenses.bsdOriginal ];
925 license = [ pkgs.lib.licenses.bsdOriginal ];
926 };
926 };
927 };
927 };
928 jsonschema = super.buildPythonPackage {
928 jsonschema = super.buildPythonPackage {
929 name = "jsonschema-2.6.0";
929 name = "jsonschema-2.6.0";
930 buildInputs = with self; [];
930 buildInputs = with self; [];
931 doCheck = false;
931 doCheck = false;
932 propagatedBuildInputs = with self; [functools32];
932 propagatedBuildInputs = with self; [functools32];
933 src = fetchurl {
933 src = fetchurl {
934 url = "https://pypi.python.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
934 url = "https://pypi.python.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
935 md5 = "50c6b69a373a8b55ff1e0ec6e78f13f4";
935 md5 = "50c6b69a373a8b55ff1e0ec6e78f13f4";
936 };
936 };
937 meta = {
937 meta = {
938 license = [ pkgs.lib.licenses.mit ];
938 license = [ pkgs.lib.licenses.mit ];
939 };
939 };
940 };
940 };
941 jupyter-client = super.buildPythonPackage {
941 jupyter-client = super.buildPythonPackage {
942 name = "jupyter-client-5.0.0";
942 name = "jupyter-client-5.0.0";
943 buildInputs = with self; [];
943 buildInputs = with self; [];
944 doCheck = false;
944 doCheck = false;
945 propagatedBuildInputs = with self; [traitlets jupyter-core pyzmq python-dateutil];
945 propagatedBuildInputs = with self; [traitlets jupyter-core pyzmq python-dateutil];
946 src = fetchurl {
946 src = fetchurl {
947 url = "https://pypi.python.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
947 url = "https://pypi.python.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
948 md5 = "1acd331b5c9fb4d79dae9939e79f2426";
948 md5 = "1acd331b5c9fb4d79dae9939e79f2426";
949 };
949 };
950 meta = {
950 meta = {
951 license = [ pkgs.lib.licenses.bsdOriginal ];
951 license = [ pkgs.lib.licenses.bsdOriginal ];
952 };
952 };
953 };
953 };
954 jupyter-core = super.buildPythonPackage {
954 jupyter-core = super.buildPythonPackage {
955 name = "jupyter-core-4.3.0";
955 name = "jupyter-core-4.3.0";
956 buildInputs = with self; [];
956 buildInputs = with self; [];
957 doCheck = false;
957 doCheck = false;
958 propagatedBuildInputs = with self; [traitlets];
958 propagatedBuildInputs = with self; [traitlets];
959 src = fetchurl {
959 src = fetchurl {
960 url = "https://pypi.python.org/packages/2f/39/5138f975100ce14d150938df48a83cd852a3fd8e24b1244f4113848e69e2/jupyter_core-4.3.0.tar.gz";
960 url = "https://pypi.python.org/packages/2f/39/5138f975100ce14d150938df48a83cd852a3fd8e24b1244f4113848e69e2/jupyter_core-4.3.0.tar.gz";
961 md5 = "18819511a809afdeed9a995a9c27bcfb";
961 md5 = "18819511a809afdeed9a995a9c27bcfb";
962 };
962 };
963 meta = {
963 meta = {
964 license = [ pkgs.lib.licenses.bsdOriginal ];
964 license = [ pkgs.lib.licenses.bsdOriginal ];
965 };
965 };
966 };
966 };
967 kombu = super.buildPythonPackage {
967 kombu = super.buildPythonPackage {
968 name = "kombu-1.5.1";
968 name = "kombu-1.5.1";
969 buildInputs = with self; [];
969 buildInputs = with self; [];
970 doCheck = false;
970 doCheck = false;
971 propagatedBuildInputs = with self; [anyjson amqplib];
971 propagatedBuildInputs = with self; [anyjson amqplib];
972 src = fetchurl {
972 src = fetchurl {
973 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
973 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
974 md5 = "50662f3c7e9395b3d0721fb75d100b63";
974 md5 = "50662f3c7e9395b3d0721fb75d100b63";
975 };
975 };
976 meta = {
976 meta = {
977 license = [ pkgs.lib.licenses.bsdOriginal ];
977 license = [ pkgs.lib.licenses.bsdOriginal ];
978 };
978 };
979 };
979 };
980 lxml = super.buildPythonPackage {
980 lxml = super.buildPythonPackage {
981 name = "lxml-3.7.3";
981 name = "lxml-3.7.3";
982 buildInputs = with self; [];
982 buildInputs = with self; [];
983 doCheck = false;
983 doCheck = false;
984 propagatedBuildInputs = with self; [];
984 propagatedBuildInputs = with self; [];
985 src = fetchurl {
985 src = fetchurl {
986 url = "https://pypi.python.org/packages/39/e8/a8e0b1fa65dd021d48fe21464f71783655f39a41f218293c1c590d54eb82/lxml-3.7.3.tar.gz";
986 url = "https://pypi.python.org/packages/39/e8/a8e0b1fa65dd021d48fe21464f71783655f39a41f218293c1c590d54eb82/lxml-3.7.3.tar.gz";
987 md5 = "075692ce442e69bbd604d44e21c02753";
987 md5 = "075692ce442e69bbd604d44e21c02753";
988 };
988 };
989 meta = {
989 meta = {
990 license = [ pkgs.lib.licenses.bsdOriginal ];
990 license = [ pkgs.lib.licenses.bsdOriginal ];
991 };
991 };
992 };
992 };
993 meld3 = super.buildPythonPackage {
993 meld3 = super.buildPythonPackage {
994 name = "meld3-1.0.2";
994 name = "meld3-1.0.2";
995 buildInputs = with self; [];
995 buildInputs = with self; [];
996 doCheck = false;
996 doCheck = false;
997 propagatedBuildInputs = with self; [];
997 propagatedBuildInputs = with self; [];
998 src = fetchurl {
998 src = fetchurl {
999 url = "https://pypi.python.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz";
999 url = "https://pypi.python.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz";
1000 md5 = "3ccc78cd79cffd63a751ad7684c02c91";
1000 md5 = "3ccc78cd79cffd63a751ad7684c02c91";
1001 };
1001 };
1002 meta = {
1002 meta = {
1003 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1003 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1004 };
1004 };
1005 };
1005 };
1006 mistune = super.buildPythonPackage {
1006 mistune = super.buildPythonPackage {
1007 name = "mistune-0.7.3";
1007 name = "mistune-0.7.4";
1008 buildInputs = with self; [];
1008 buildInputs = with self; [];
1009 doCheck = false;
1009 doCheck = false;
1010 propagatedBuildInputs = with self; [];
1010 propagatedBuildInputs = with self; [];
1011 src = fetchurl {
1011 src = fetchurl {
1012 url = "https://pypi.python.org/packages/88/1e/be99791262b3a794332fda598a07c2749a433b9378586361ba9d8e824607/mistune-0.7.3.tar.gz";
1012 url = "https://pypi.python.org/packages/25/a4/12a584c0c59c9fed529f8b3c47ca8217c0cf8bcc5e1089d3256410cfbdbc/mistune-0.7.4.tar.gz";
1013 md5 = "4eba50bd121b83716fa4be6a4049004b";
1013 md5 = "92d01cb717e9e74429e9bde9d29ac43b";
1014 };
1014 };
1015 meta = {
1015 meta = {
1016 license = [ pkgs.lib.licenses.bsdOriginal ];
1016 license = [ pkgs.lib.licenses.bsdOriginal ];
1017 };
1017 };
1018 };
1018 };
1019 mock = super.buildPythonPackage {
1019 mock = super.buildPythonPackage {
1020 name = "mock-1.0.1";
1020 name = "mock-1.0.1";
1021 buildInputs = with self; [];
1021 buildInputs = with self; [];
1022 doCheck = false;
1022 doCheck = false;
1023 propagatedBuildInputs = with self; [];
1023 propagatedBuildInputs = with self; [];
1024 src = fetchurl {
1024 src = fetchurl {
1025 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
1025 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
1026 md5 = "869f08d003c289a97c1a6610faf5e913";
1026 md5 = "869f08d003c289a97c1a6610faf5e913";
1027 };
1027 };
1028 meta = {
1028 meta = {
1029 license = [ pkgs.lib.licenses.bsdOriginal ];
1029 license = [ pkgs.lib.licenses.bsdOriginal ];
1030 };
1030 };
1031 };
1031 };
1032 msgpack-python = super.buildPythonPackage {
1032 msgpack-python = super.buildPythonPackage {
1033 name = "msgpack-python-0.4.8";
1033 name = "msgpack-python-0.4.8";
1034 buildInputs = with self; [];
1034 buildInputs = with self; [];
1035 doCheck = false;
1035 doCheck = false;
1036 propagatedBuildInputs = with self; [];
1036 propagatedBuildInputs = with self; [];
1037 src = fetchurl {
1037 src = fetchurl {
1038 url = "https://pypi.python.org/packages/21/27/8a1d82041c7a2a51fcc73675875a5f9ea06c2663e02fcfeb708be1d081a0/msgpack-python-0.4.8.tar.gz";
1038 url = "https://pypi.python.org/packages/21/27/8a1d82041c7a2a51fcc73675875a5f9ea06c2663e02fcfeb708be1d081a0/msgpack-python-0.4.8.tar.gz";
1039 md5 = "dcd854fb41ee7584ebbf35e049e6be98";
1039 md5 = "dcd854fb41ee7584ebbf35e049e6be98";
1040 };
1040 };
1041 meta = {
1041 meta = {
1042 license = [ pkgs.lib.licenses.asl20 ];
1042 license = [ pkgs.lib.licenses.asl20 ];
1043 };
1043 };
1044 };
1044 };
1045 nbconvert = super.buildPythonPackage {
1045 nbconvert = super.buildPythonPackage {
1046 name = "nbconvert-5.1.1";
1046 name = "nbconvert-5.1.1";
1047 buildInputs = with self; [];
1047 buildInputs = with self; [];
1048 doCheck = false;
1048 doCheck = false;
1049 propagatedBuildInputs = with self; [mistune Jinja2 Pygments traitlets jupyter-core nbformat entrypoints bleach pandocfilters testpath];
1049 propagatedBuildInputs = with self; [mistune Jinja2 Pygments traitlets jupyter-core nbformat entrypoints bleach pandocfilters testpath];
1050 src = fetchurl {
1050 src = fetchurl {
1051 url = "https://pypi.python.org/packages/95/58/df1c91f1658ee5df19097f915a1e71c91fc824a708d82d2b2e35f8b80e9a/nbconvert-5.1.1.tar.gz";
1051 url = "https://pypi.python.org/packages/95/58/df1c91f1658ee5df19097f915a1e71c91fc824a708d82d2b2e35f8b80e9a/nbconvert-5.1.1.tar.gz";
1052 md5 = "d0263fb03a44db2f94eea09a608ed813";
1052 md5 = "d0263fb03a44db2f94eea09a608ed813";
1053 };
1053 };
1054 meta = {
1054 meta = {
1055 license = [ pkgs.lib.licenses.bsdOriginal ];
1055 license = [ pkgs.lib.licenses.bsdOriginal ];
1056 };
1056 };
1057 };
1057 };
1058 nbformat = super.buildPythonPackage {
1058 nbformat = super.buildPythonPackage {
1059 name = "nbformat-4.3.0";
1059 name = "nbformat-4.3.0";
1060 buildInputs = with self; [];
1060 buildInputs = with self; [];
1061 doCheck = false;
1061 doCheck = false;
1062 propagatedBuildInputs = with self; [ipython-genutils traitlets jsonschema jupyter-core];
1062 propagatedBuildInputs = with self; [ipython-genutils traitlets jsonschema jupyter-core];
1063 src = fetchurl {
1063 src = fetchurl {
1064 url = "https://pypi.python.org/packages/f9/c5/89df4abf906f766727f976e170caa85b4f1c1d1feb1f45d716016e68e19f/nbformat-4.3.0.tar.gz";
1064 url = "https://pypi.python.org/packages/f9/c5/89df4abf906f766727f976e170caa85b4f1c1d1feb1f45d716016e68e19f/nbformat-4.3.0.tar.gz";
1065 md5 = "9a00d20425914cd5ba5f97769d9963ca";
1065 md5 = "9a00d20425914cd5ba5f97769d9963ca";
1066 };
1066 };
1067 meta = {
1067 meta = {
1068 license = [ pkgs.lib.licenses.bsdOriginal ];
1068 license = [ pkgs.lib.licenses.bsdOriginal ];
1069 };
1069 };
1070 };
1070 };
1071 nose = super.buildPythonPackage {
1071 nose = super.buildPythonPackage {
1072 name = "nose-1.3.6";
1072 name = "nose-1.3.6";
1073 buildInputs = with self; [];
1073 buildInputs = with self; [];
1074 doCheck = false;
1074 doCheck = false;
1075 propagatedBuildInputs = with self; [];
1075 propagatedBuildInputs = with self; [];
1076 src = fetchurl {
1076 src = fetchurl {
1077 url = "https://pypi.python.org/packages/70/c7/469e68148d17a0d3db5ed49150242fd70a74a8147b8f3f8b87776e028d99/nose-1.3.6.tar.gz";
1077 url = "https://pypi.python.org/packages/70/c7/469e68148d17a0d3db5ed49150242fd70a74a8147b8f3f8b87776e028d99/nose-1.3.6.tar.gz";
1078 md5 = "0ca546d81ca8309080fc80cb389e7a16";
1078 md5 = "0ca546d81ca8309080fc80cb389e7a16";
1079 };
1079 };
1080 meta = {
1080 meta = {
1081 license = [ { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "GNU LGPL"; } ];
1081 license = [ { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "GNU LGPL"; } ];
1082 };
1082 };
1083 };
1083 };
1084 objgraph = super.buildPythonPackage {
1084 objgraph = super.buildPythonPackage {
1085 name = "objgraph-2.0.0";
1085 name = "objgraph-3.1.0";
1086 buildInputs = with self; [];
1086 buildInputs = with self; [];
1087 doCheck = false;
1087 doCheck = false;
1088 propagatedBuildInputs = with self; [];
1088 propagatedBuildInputs = with self; [];
1089 src = fetchurl {
1089 src = fetchurl {
1090 url = "https://pypi.python.org/packages/d7/33/ace750b59247496ed769b170586c5def7202683f3d98e737b75b767ff29e/objgraph-2.0.0.tar.gz";
1090 url = "https://pypi.python.org/packages/f4/b3/082e54e62094cb2ec84f8d5a49e0142cef99016491cecba83309cff920ae/objgraph-3.1.0.tar.gz";
1091 md5 = "25b0d5e5adc74aa63ead15699614159c";
1091 md5 = "eddbd96039796bfbd13eee403701e64a";
1092 };
1092 };
1093 meta = {
1093 meta = {
1094 license = [ pkgs.lib.licenses.mit ];
1094 license = [ pkgs.lib.licenses.mit ];
1095 };
1095 };
1096 };
1096 };
1097 packaging = super.buildPythonPackage {
1097 packaging = super.buildPythonPackage {
1098 name = "packaging-15.2";
1098 name = "packaging-15.2";
1099 buildInputs = with self; [];
1099 buildInputs = with self; [];
1100 doCheck = false;
1100 doCheck = false;
1101 propagatedBuildInputs = with self; [];
1101 propagatedBuildInputs = with self; [];
1102 src = fetchurl {
1102 src = fetchurl {
1103 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
1103 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
1104 md5 = "c16093476f6ced42128bf610e5db3784";
1104 md5 = "c16093476f6ced42128bf610e5db3784";
1105 };
1105 };
1106 meta = {
1106 meta = {
1107 license = [ pkgs.lib.licenses.asl20 ];
1107 license = [ pkgs.lib.licenses.asl20 ];
1108 };
1108 };
1109 };
1109 };
1110 pandocfilters = super.buildPythonPackage {
1110 pandocfilters = super.buildPythonPackage {
1111 name = "pandocfilters-1.4.1";
1111 name = "pandocfilters-1.4.1";
1112 buildInputs = with self; [];
1112 buildInputs = with self; [];
1113 doCheck = false;
1113 doCheck = false;
1114 propagatedBuildInputs = with self; [];
1114 propagatedBuildInputs = with self; [];
1115 src = fetchurl {
1115 src = fetchurl {
1116 url = "https://pypi.python.org/packages/e3/1f/21d1b7e8ca571e80b796c758d361fdf5554335ff138158654684bc5401d8/pandocfilters-1.4.1.tar.gz";
1116 url = "https://pypi.python.org/packages/e3/1f/21d1b7e8ca571e80b796c758d361fdf5554335ff138158654684bc5401d8/pandocfilters-1.4.1.tar.gz";
1117 md5 = "7680d9f9ec07397dd17f380ee3818b9d";
1117 md5 = "7680d9f9ec07397dd17f380ee3818b9d";
1118 };
1118 };
1119 meta = {
1119 meta = {
1120 license = [ pkgs.lib.licenses.bsdOriginal ];
1120 license = [ pkgs.lib.licenses.bsdOriginal ];
1121 };
1121 };
1122 };
1122 };
1123 paramiko = super.buildPythonPackage {
1123 paramiko = super.buildPythonPackage {
1124 name = "paramiko-1.15.1";
1124 name = "paramiko-1.15.1";
1125 buildInputs = with self; [];
1125 buildInputs = with self; [];
1126 doCheck = false;
1126 doCheck = false;
1127 propagatedBuildInputs = with self; [pycrypto ecdsa];
1127 propagatedBuildInputs = with self; [pycrypto ecdsa];
1128 src = fetchurl {
1128 src = fetchurl {
1129 url = "https://pypi.python.org/packages/04/2b/a22d2a560c1951abbbf95a0628e245945565f70dc082d9e784666887222c/paramiko-1.15.1.tar.gz";
1129 url = "https://pypi.python.org/packages/04/2b/a22d2a560c1951abbbf95a0628e245945565f70dc082d9e784666887222c/paramiko-1.15.1.tar.gz";
1130 md5 = "48c274c3f9b1282932567b21f6acf3b5";
1130 md5 = "48c274c3f9b1282932567b21f6acf3b5";
1131 };
1131 };
1132 meta = {
1132 meta = {
1133 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1133 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1134 };
1134 };
1135 };
1135 };
1136 pathlib2 = super.buildPythonPackage {
1136 pathlib2 = super.buildPythonPackage {
1137 name = "pathlib2-2.1.0";
1137 name = "pathlib2-2.1.0";
1138 buildInputs = with self; [];
1138 buildInputs = with self; [];
1139 doCheck = false;
1139 doCheck = false;
1140 propagatedBuildInputs = with self; [six];
1140 propagatedBuildInputs = with self; [six];
1141 src = fetchurl {
1141 src = fetchurl {
1142 url = "https://pypi.python.org/packages/c9/27/8448b10d8440c08efeff0794adf7d0ed27adb98372c70c7b38f3947d4749/pathlib2-2.1.0.tar.gz";
1142 url = "https://pypi.python.org/packages/c9/27/8448b10d8440c08efeff0794adf7d0ed27adb98372c70c7b38f3947d4749/pathlib2-2.1.0.tar.gz";
1143 md5 = "38e4f58b4d69dfcb9edb49a54a8b28d2";
1143 md5 = "38e4f58b4d69dfcb9edb49a54a8b28d2";
1144 };
1144 };
1145 meta = {
1145 meta = {
1146 license = [ pkgs.lib.licenses.mit ];
1146 license = [ pkgs.lib.licenses.mit ];
1147 };
1147 };
1148 };
1148 };
1149 peppercorn = super.buildPythonPackage {
1149 peppercorn = super.buildPythonPackage {
1150 name = "peppercorn-0.5";
1150 name = "peppercorn-0.5";
1151 buildInputs = with self; [];
1151 buildInputs = with self; [];
1152 doCheck = false;
1152 doCheck = false;
1153 propagatedBuildInputs = with self; [];
1153 propagatedBuildInputs = with self; [];
1154 src = fetchurl {
1154 src = fetchurl {
1155 url = "https://pypi.python.org/packages/45/ec/a62ec317d1324a01567c5221b420742f094f05ee48097e5157d32be3755c/peppercorn-0.5.tar.gz";
1155 url = "https://pypi.python.org/packages/45/ec/a62ec317d1324a01567c5221b420742f094f05ee48097e5157d32be3755c/peppercorn-0.5.tar.gz";
1156 md5 = "f08efbca5790019ab45d76b7244abd40";
1156 md5 = "f08efbca5790019ab45d76b7244abd40";
1157 };
1157 };
1158 meta = {
1158 meta = {
1159 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1159 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1160 };
1160 };
1161 };
1161 };
1162 pexpect = super.buildPythonPackage {
1162 pexpect = super.buildPythonPackage {
1163 name = "pexpect-4.2.1";
1163 name = "pexpect-4.2.1";
1164 buildInputs = with self; [];
1164 buildInputs = with self; [];
1165 doCheck = false;
1165 doCheck = false;
1166 propagatedBuildInputs = with self; [ptyprocess];
1166 propagatedBuildInputs = with self; [ptyprocess];
1167 src = fetchurl {
1167 src = fetchurl {
1168 url = "https://pypi.python.org/packages/e8/13/d0b0599099d6cd23663043a2a0bb7c61e58c6ba359b2656e6fb000ef5b98/pexpect-4.2.1.tar.gz";
1168 url = "https://pypi.python.org/packages/e8/13/d0b0599099d6cd23663043a2a0bb7c61e58c6ba359b2656e6fb000ef5b98/pexpect-4.2.1.tar.gz";
1169 md5 = "3694410001a99dff83f0b500a1ca1c95";
1169 md5 = "3694410001a99dff83f0b500a1ca1c95";
1170 };
1170 };
1171 meta = {
1171 meta = {
1172 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1172 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1173 };
1173 };
1174 };
1174 };
1175 pickleshare = super.buildPythonPackage {
1175 pickleshare = super.buildPythonPackage {
1176 name = "pickleshare-0.7.4";
1176 name = "pickleshare-0.7.4";
1177 buildInputs = with self; [];
1177 buildInputs = with self; [];
1178 doCheck = false;
1178 doCheck = false;
1179 propagatedBuildInputs = with self; [pathlib2];
1179 propagatedBuildInputs = with self; [pathlib2];
1180 src = fetchurl {
1180 src = fetchurl {
1181 url = "https://pypi.python.org/packages/69/fe/dd137d84daa0fd13a709e448138e310d9ea93070620c9db5454e234af525/pickleshare-0.7.4.tar.gz";
1181 url = "https://pypi.python.org/packages/69/fe/dd137d84daa0fd13a709e448138e310d9ea93070620c9db5454e234af525/pickleshare-0.7.4.tar.gz";
1182 md5 = "6a9e5dd8dfc023031f6b7b3f824cab12";
1182 md5 = "6a9e5dd8dfc023031f6b7b3f824cab12";
1183 };
1183 };
1184 meta = {
1184 meta = {
1185 license = [ pkgs.lib.licenses.mit ];
1185 license = [ pkgs.lib.licenses.mit ];
1186 };
1186 };
1187 };
1187 };
1188 prompt-toolkit = super.buildPythonPackage {
1188 prompt-toolkit = super.buildPythonPackage {
1189 name = "prompt-toolkit-1.0.13";
1189 name = "prompt-toolkit-1.0.14";
1190 buildInputs = with self; [];
1190 buildInputs = with self; [];
1191 doCheck = false;
1191 doCheck = false;
1192 propagatedBuildInputs = with self; [six wcwidth];
1192 propagatedBuildInputs = with self; [six wcwidth];
1193 src = fetchurl {
1193 src = fetchurl {
1194 url = "https://pypi.python.org/packages/23/be/4876b52d5cc159cbd4b0ff6e7aa419a26470849a43a8f647857a4a24467b/prompt_toolkit-1.0.13.tar.gz";
1194 url = "https://pypi.python.org/packages/55/56/8c39509b614bda53e638b7500f12577d663ac1b868aef53426fc6a26c3f5/prompt_toolkit-1.0.14.tar.gz";
1195 md5 = "427b496d2c147bd3819bc3a7f6e0d493";
1195 md5 = "f24061ae133ed32c6b764e92bd48c496";
1196 };
1196 };
1197 meta = {
1197 meta = {
1198 license = [ pkgs.lib.licenses.bsdOriginal ];
1198 license = [ pkgs.lib.licenses.bsdOriginal ];
1199 };
1199 };
1200 };
1200 };
1201 psutil = super.buildPythonPackage {
1201 psutil = super.buildPythonPackage {
1202 name = "psutil-4.3.1";
1202 name = "psutil-4.3.1";
1203 buildInputs = with self; [];
1203 buildInputs = with self; [];
1204 doCheck = false;
1204 doCheck = false;
1205 propagatedBuildInputs = with self; [];
1205 propagatedBuildInputs = with self; [];
1206 src = fetchurl {
1206 src = fetchurl {
1207 url = "https://pypi.python.org/packages/78/cc/f267a1371f229bf16db6a4e604428c3b032b823b83155bd33cef45e49a53/psutil-4.3.1.tar.gz";
1207 url = "https://pypi.python.org/packages/78/cc/f267a1371f229bf16db6a4e604428c3b032b823b83155bd33cef45e49a53/psutil-4.3.1.tar.gz";
1208 md5 = "199a366dba829c88bddaf5b41d19ddc0";
1208 md5 = "199a366dba829c88bddaf5b41d19ddc0";
1209 };
1209 };
1210 meta = {
1210 meta = {
1211 license = [ pkgs.lib.licenses.bsdOriginal ];
1211 license = [ pkgs.lib.licenses.bsdOriginal ];
1212 };
1212 };
1213 };
1213 };
1214 psycopg2 = super.buildPythonPackage {
1214 psycopg2 = super.buildPythonPackage {
1215 name = "psycopg2-2.6.1";
1215 name = "psycopg2-2.6.1";
1216 buildInputs = with self; [];
1216 buildInputs = with self; [];
1217 doCheck = false;
1217 doCheck = false;
1218 propagatedBuildInputs = with self; [];
1218 propagatedBuildInputs = with self; [];
1219 src = fetchurl {
1219 src = fetchurl {
1220 url = "https://pypi.python.org/packages/86/fd/cc8315be63a41fe000cce20482a917e874cdc1151e62cb0141f5e55f711e/psycopg2-2.6.1.tar.gz";
1220 url = "https://pypi.python.org/packages/86/fd/cc8315be63a41fe000cce20482a917e874cdc1151e62cb0141f5e55f711e/psycopg2-2.6.1.tar.gz";
1221 md5 = "842b44f8c95517ed5b792081a2370da1";
1221 md5 = "842b44f8c95517ed5b792081a2370da1";
1222 };
1222 };
1223 meta = {
1223 meta = {
1224 license = [ pkgs.lib.licenses.zpt21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1224 license = [ pkgs.lib.licenses.zpt21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1225 };
1225 };
1226 };
1226 };
1227 ptyprocess = super.buildPythonPackage {
1227 ptyprocess = super.buildPythonPackage {
1228 name = "ptyprocess-0.5.1";
1228 name = "ptyprocess-0.5.1";
1229 buildInputs = with self; [];
1229 buildInputs = with self; [];
1230 doCheck = false;
1230 doCheck = false;
1231 propagatedBuildInputs = with self; [];
1231 propagatedBuildInputs = with self; [];
1232 src = fetchurl {
1232 src = fetchurl {
1233 url = "https://pypi.python.org/packages/db/d7/b465161910f3d1cef593c5e002bff67e0384898f597f1a7fdc8db4c02bf6/ptyprocess-0.5.1.tar.gz";
1233 url = "https://pypi.python.org/packages/db/d7/b465161910f3d1cef593c5e002bff67e0384898f597f1a7fdc8db4c02bf6/ptyprocess-0.5.1.tar.gz";
1234 md5 = "94e537122914cc9ec9c1eadcd36e73a1";
1234 md5 = "94e537122914cc9ec9c1eadcd36e73a1";
1235 };
1235 };
1236 meta = {
1236 meta = {
1237 license = [ ];
1237 license = [ ];
1238 };
1238 };
1239 };
1239 };
1240 py = super.buildPythonPackage {
1240 py = super.buildPythonPackage {
1241 name = "py-1.4.31";
1241 name = "py-1.4.31";
1242 buildInputs = with self; [];
1242 buildInputs = with self; [];
1243 doCheck = false;
1243 doCheck = false;
1244 propagatedBuildInputs = with self; [];
1244 propagatedBuildInputs = with self; [];
1245 src = fetchurl {
1245 src = fetchurl {
1246 url = "https://pypi.python.org/packages/f4/9a/8dfda23f36600dd701c6722316ba8a3ab4b990261f83e7d3ffc6dfedf7ef/py-1.4.31.tar.gz";
1246 url = "https://pypi.python.org/packages/f4/9a/8dfda23f36600dd701c6722316ba8a3ab4b990261f83e7d3ffc6dfedf7ef/py-1.4.31.tar.gz";
1247 md5 = "5d2c63c56dc3f2115ec35c066ecd582b";
1247 md5 = "5d2c63c56dc3f2115ec35c066ecd582b";
1248 };
1248 };
1249 meta = {
1249 meta = {
1250 license = [ pkgs.lib.licenses.mit ];
1250 license = [ pkgs.lib.licenses.mit ];
1251 };
1251 };
1252 };
1252 };
1253 py-bcrypt = super.buildPythonPackage {
1253 py-bcrypt = super.buildPythonPackage {
1254 name = "py-bcrypt-0.4";
1254 name = "py-bcrypt-0.4";
1255 buildInputs = with self; [];
1255 buildInputs = with self; [];
1256 doCheck = false;
1256 doCheck = false;
1257 propagatedBuildInputs = with self; [];
1257 propagatedBuildInputs = with self; [];
1258 src = fetchurl {
1258 src = fetchurl {
1259 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1259 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1260 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
1260 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
1261 };
1261 };
1262 meta = {
1262 meta = {
1263 license = [ pkgs.lib.licenses.bsdOriginal ];
1263 license = [ pkgs.lib.licenses.bsdOriginal ];
1264 };
1264 };
1265 };
1265 };
1266 py-gfm = super.buildPythonPackage {
1266 py-gfm = super.buildPythonPackage {
1267 name = "py-gfm-0.1.3";
1267 name = "py-gfm-0.1.3";
1268 buildInputs = with self; [];
1268 buildInputs = with self; [];
1269 doCheck = false;
1269 doCheck = false;
1270 propagatedBuildInputs = with self; [setuptools Markdown];
1270 propagatedBuildInputs = with self; [setuptools Markdown];
1271 src = fetchurl {
1271 src = fetchurl {
1272 url = "https://code.rhodecode.com/upstream/py-gfm/archive/0d66a19bc16e3d49de273c0f797d4e4781e8c0f2.tar.gz?md5=0d0d5385bfb629eea636a80b9c2bfd16";
1272 url = "https://code.rhodecode.com/upstream/py-gfm/archive/0d66a19bc16e3d49de273c0f797d4e4781e8c0f2.tar.gz?md5=0d0d5385bfb629eea636a80b9c2bfd16";
1273 md5 = "0d0d5385bfb629eea636a80b9c2bfd16";
1273 md5 = "0d0d5385bfb629eea636a80b9c2bfd16";
1274 };
1274 };
1275 meta = {
1275 meta = {
1276 license = [ pkgs.lib.licenses.bsdOriginal ];
1276 license = [ pkgs.lib.licenses.bsdOriginal ];
1277 };
1277 };
1278 };
1278 };
1279 pycrypto = super.buildPythonPackage {
1279 pycrypto = super.buildPythonPackage {
1280 name = "pycrypto-2.6.1";
1280 name = "pycrypto-2.6.1";
1281 buildInputs = with self; [];
1281 buildInputs = with self; [];
1282 doCheck = false;
1282 doCheck = false;
1283 propagatedBuildInputs = with self; [];
1283 propagatedBuildInputs = with self; [];
1284 src = fetchurl {
1284 src = fetchurl {
1285 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1285 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1286 md5 = "55a61a054aa66812daf5161a0d5d7eda";
1286 md5 = "55a61a054aa66812daf5161a0d5d7eda";
1287 };
1287 };
1288 meta = {
1288 meta = {
1289 license = [ pkgs.lib.licenses.publicDomain ];
1289 license = [ pkgs.lib.licenses.publicDomain ];
1290 };
1290 };
1291 };
1291 };
1292 pycurl = super.buildPythonPackage {
1292 pycurl = super.buildPythonPackage {
1293 name = "pycurl-7.19.5";
1293 name = "pycurl-7.19.5";
1294 buildInputs = with self; [];
1294 buildInputs = with self; [];
1295 doCheck = false;
1295 doCheck = false;
1296 propagatedBuildInputs = with self; [];
1296 propagatedBuildInputs = with self; [];
1297 src = fetchurl {
1297 src = fetchurl {
1298 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
1298 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
1299 md5 = "47b4eac84118e2606658122104e62072";
1299 md5 = "47b4eac84118e2606658122104e62072";
1300 };
1300 };
1301 meta = {
1301 meta = {
1302 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1302 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1303 };
1303 };
1304 };
1304 };
1305 pyflakes = super.buildPythonPackage {
1305 pyflakes = super.buildPythonPackage {
1306 name = "pyflakes-0.8.1";
1306 name = "pyflakes-0.8.1";
1307 buildInputs = with self; [];
1307 buildInputs = with self; [];
1308 doCheck = false;
1308 doCheck = false;
1309 propagatedBuildInputs = with self; [];
1309 propagatedBuildInputs = with self; [];
1310 src = fetchurl {
1310 src = fetchurl {
1311 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
1311 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
1312 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
1312 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
1313 };
1313 };
1314 meta = {
1314 meta = {
1315 license = [ pkgs.lib.licenses.mit ];
1315 license = [ pkgs.lib.licenses.mit ];
1316 };
1316 };
1317 };
1317 };
1318 pygments-markdown-lexer = super.buildPythonPackage {
1318 pygments-markdown-lexer = super.buildPythonPackage {
1319 name = "pygments-markdown-lexer-0.1.0.dev39";
1319 name = "pygments-markdown-lexer-0.1.0.dev39";
1320 buildInputs = with self; [];
1320 buildInputs = with self; [];
1321 doCheck = false;
1321 doCheck = false;
1322 propagatedBuildInputs = with self; [Pygments];
1322 propagatedBuildInputs = with self; [Pygments];
1323 src = fetchurl {
1323 src = fetchurl {
1324 url = "https://pypi.python.org/packages/c3/12/674cdee66635d638cedb2c5d9c85ce507b7b2f91bdba29e482f1b1160ff6/pygments-markdown-lexer-0.1.0.dev39.zip";
1324 url = "https://pypi.python.org/packages/c3/12/674cdee66635d638cedb2c5d9c85ce507b7b2f91bdba29e482f1b1160ff6/pygments-markdown-lexer-0.1.0.dev39.zip";
1325 md5 = "6360fe0f6d1f896e35b7a0142ce6459c";
1325 md5 = "6360fe0f6d1f896e35b7a0142ce6459c";
1326 };
1326 };
1327 meta = {
1327 meta = {
1328 license = [ pkgs.lib.licenses.asl20 ];
1328 license = [ pkgs.lib.licenses.asl20 ];
1329 };
1329 };
1330 };
1330 };
1331 pyparsing = super.buildPythonPackage {
1331 pyparsing = super.buildPythonPackage {
1332 name = "pyparsing-1.5.7";
1332 name = "pyparsing-1.5.7";
1333 buildInputs = with self; [];
1333 buildInputs = with self; [];
1334 doCheck = false;
1334 doCheck = false;
1335 propagatedBuildInputs = with self; [];
1335 propagatedBuildInputs = with self; [];
1336 src = fetchurl {
1336 src = fetchurl {
1337 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
1337 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
1338 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
1338 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
1339 };
1339 };
1340 meta = {
1340 meta = {
1341 license = [ pkgs.lib.licenses.mit ];
1341 license = [ pkgs.lib.licenses.mit ];
1342 };
1342 };
1343 };
1343 };
1344 pyramid = super.buildPythonPackage {
1344 pyramid = super.buildPythonPackage {
1345 name = "pyramid-1.7.4";
1345 name = "pyramid-1.7.4";
1346 buildInputs = with self; [];
1346 buildInputs = with self; [];
1347 doCheck = false;
1347 doCheck = false;
1348 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
1348 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
1349 src = fetchurl {
1349 src = fetchurl {
1350 url = "https://pypi.python.org/packages/33/91/55f5c661f8923902cd1f68d75f2b937c45e7682857356cf18f0be5493899/pyramid-1.7.4.tar.gz";
1350 url = "https://pypi.python.org/packages/33/91/55f5c661f8923902cd1f68d75f2b937c45e7682857356cf18f0be5493899/pyramid-1.7.4.tar.gz";
1351 md5 = "6ef1dfdcff9136d04490410757c4c446";
1351 md5 = "6ef1dfdcff9136d04490410757c4c446";
1352 };
1352 };
1353 meta = {
1353 meta = {
1354 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1354 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1355 };
1355 };
1356 };
1356 };
1357 pyramid-beaker = super.buildPythonPackage {
1357 pyramid-beaker = super.buildPythonPackage {
1358 name = "pyramid-beaker-0.8";
1358 name = "pyramid-beaker-0.8";
1359 buildInputs = with self; [];
1359 buildInputs = with self; [];
1360 doCheck = false;
1360 doCheck = false;
1361 propagatedBuildInputs = with self; [pyramid Beaker];
1361 propagatedBuildInputs = with self; [pyramid Beaker];
1362 src = fetchurl {
1362 src = fetchurl {
1363 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
1363 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
1364 md5 = "22f14be31b06549f80890e2c63a93834";
1364 md5 = "22f14be31b06549f80890e2c63a93834";
1365 };
1365 };
1366 meta = {
1366 meta = {
1367 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1367 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1368 };
1368 };
1369 };
1369 };
1370 pyramid-debugtoolbar = super.buildPythonPackage {
1370 pyramid-debugtoolbar = super.buildPythonPackage {
1371 name = "pyramid-debugtoolbar-3.0.5";
1371 name = "pyramid-debugtoolbar-3.0.5";
1372 buildInputs = with self; [];
1372 buildInputs = with self; [];
1373 doCheck = false;
1373 doCheck = false;
1374 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
1374 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
1375 src = fetchurl {
1375 src = fetchurl {
1376 url = "https://pypi.python.org/packages/64/0e/df00bfb55605900e7a2f7e4a18dd83575a6651688e297d5a0aa4c208fd7d/pyramid_debugtoolbar-3.0.5.tar.gz";
1376 url = "https://pypi.python.org/packages/64/0e/df00bfb55605900e7a2f7e4a18dd83575a6651688e297d5a0aa4c208fd7d/pyramid_debugtoolbar-3.0.5.tar.gz";
1377 md5 = "aebab8c3bfdc6f89e4d3adc1d126538e";
1377 md5 = "aebab8c3bfdc6f89e4d3adc1d126538e";
1378 };
1378 };
1379 meta = {
1379 meta = {
1380 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1380 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1381 };
1381 };
1382 };
1382 };
1383 pyramid-jinja2 = super.buildPythonPackage {
1383 pyramid-jinja2 = super.buildPythonPackage {
1384 name = "pyramid-jinja2-2.5";
1384 name = "pyramid-jinja2-2.5";
1385 buildInputs = with self; [];
1385 buildInputs = with self; [];
1386 doCheck = false;
1386 doCheck = false;
1387 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
1387 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
1388 src = fetchurl {
1388 src = fetchurl {
1389 url = "https://pypi.python.org/packages/a1/80/595e26ffab7deba7208676b6936b7e5a721875710f982e59899013cae1ed/pyramid_jinja2-2.5.tar.gz";
1389 url = "https://pypi.python.org/packages/a1/80/595e26ffab7deba7208676b6936b7e5a721875710f982e59899013cae1ed/pyramid_jinja2-2.5.tar.gz";
1390 md5 = "07cb6547204ac5e6f0b22a954ccee928";
1390 md5 = "07cb6547204ac5e6f0b22a954ccee928";
1391 };
1391 };
1392 meta = {
1392 meta = {
1393 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1393 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1394 };
1394 };
1395 };
1395 };
1396 pyramid-mako = super.buildPythonPackage {
1396 pyramid-mako = super.buildPythonPackage {
1397 name = "pyramid-mako-1.0.2";
1397 name = "pyramid-mako-1.0.2";
1398 buildInputs = with self; [];
1398 buildInputs = with self; [];
1399 doCheck = false;
1399 doCheck = false;
1400 propagatedBuildInputs = with self; [pyramid Mako];
1400 propagatedBuildInputs = with self; [pyramid Mako];
1401 src = fetchurl {
1401 src = fetchurl {
1402 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
1402 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
1403 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
1403 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
1404 };
1404 };
1405 meta = {
1405 meta = {
1406 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1406 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1407 };
1407 };
1408 };
1408 };
1409 pysqlite = super.buildPythonPackage {
1409 pysqlite = super.buildPythonPackage {
1410 name = "pysqlite-2.6.3";
1410 name = "pysqlite-2.6.3";
1411 buildInputs = with self; [];
1411 buildInputs = with self; [];
1412 doCheck = false;
1412 doCheck = false;
1413 propagatedBuildInputs = with self; [];
1413 propagatedBuildInputs = with self; [];
1414 src = fetchurl {
1414 src = fetchurl {
1415 url = "https://pypi.python.org/packages/5c/a6/1c429cd4c8069cf4bfbd0eb4d592b3f4042155a8202df83d7e9b93aa3dc2/pysqlite-2.6.3.tar.gz";
1415 url = "https://pypi.python.org/packages/5c/a6/1c429cd4c8069cf4bfbd0eb4d592b3f4042155a8202df83d7e9b93aa3dc2/pysqlite-2.6.3.tar.gz";
1416 md5 = "7ff1cedee74646b50117acff87aa1cfa";
1416 md5 = "7ff1cedee74646b50117acff87aa1cfa";
1417 };
1417 };
1418 meta = {
1418 meta = {
1419 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1419 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1420 };
1420 };
1421 };
1421 };
1422 pytest = super.buildPythonPackage {
1422 pytest = super.buildPythonPackage {
1423 name = "pytest-3.0.5";
1423 name = "pytest-3.0.5";
1424 buildInputs = with self; [];
1424 buildInputs = with self; [];
1425 doCheck = false;
1425 doCheck = false;
1426 propagatedBuildInputs = with self; [py];
1426 propagatedBuildInputs = with self; [py];
1427 src = fetchurl {
1427 src = fetchurl {
1428 url = "https://pypi.python.org/packages/a8/87/b7ca49efe52d2b4169f2bfc49aa5e384173c4619ea8e635f123a0dac5b75/pytest-3.0.5.tar.gz";
1428 url = "https://pypi.python.org/packages/a8/87/b7ca49efe52d2b4169f2bfc49aa5e384173c4619ea8e635f123a0dac5b75/pytest-3.0.5.tar.gz";
1429 md5 = "cefd527b59332688bf5db4a10aa8a7cb";
1429 md5 = "cefd527b59332688bf5db4a10aa8a7cb";
1430 };
1430 };
1431 meta = {
1431 meta = {
1432 license = [ pkgs.lib.licenses.mit ];
1432 license = [ pkgs.lib.licenses.mit ];
1433 };
1433 };
1434 };
1434 };
1435 pytest-catchlog = super.buildPythonPackage {
1435 pytest-catchlog = super.buildPythonPackage {
1436 name = "pytest-catchlog-1.2.2";
1436 name = "pytest-catchlog-1.2.2";
1437 buildInputs = with self; [];
1437 buildInputs = with self; [];
1438 doCheck = false;
1438 doCheck = false;
1439 propagatedBuildInputs = with self; [py pytest];
1439 propagatedBuildInputs = with self; [py pytest];
1440 src = fetchurl {
1440 src = fetchurl {
1441 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
1441 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
1442 md5 = "09d890c54c7456c818102b7ff8c182c8";
1442 md5 = "09d890c54c7456c818102b7ff8c182c8";
1443 };
1443 };
1444 meta = {
1444 meta = {
1445 license = [ pkgs.lib.licenses.mit ];
1445 license = [ pkgs.lib.licenses.mit ];
1446 };
1446 };
1447 };
1447 };
1448 pytest-cov = super.buildPythonPackage {
1448 pytest-cov = super.buildPythonPackage {
1449 name = "pytest-cov-2.4.0";
1449 name = "pytest-cov-2.4.0";
1450 buildInputs = with self; [];
1450 buildInputs = with self; [];
1451 doCheck = false;
1451 doCheck = false;
1452 propagatedBuildInputs = with self; [pytest coverage];
1452 propagatedBuildInputs = with self; [pytest coverage];
1453 src = fetchurl {
1453 src = fetchurl {
1454 url = "https://pypi.python.org/packages/00/c0/2bfd1fcdb9d407b8ac8185b1cb5ff458105c6b207a9a7f0e13032de9828f/pytest-cov-2.4.0.tar.gz";
1454 url = "https://pypi.python.org/packages/00/c0/2bfd1fcdb9d407b8ac8185b1cb5ff458105c6b207a9a7f0e13032de9828f/pytest-cov-2.4.0.tar.gz";
1455 md5 = "2fda09677d232acc99ec1b3c5831e33f";
1455 md5 = "2fda09677d232acc99ec1b3c5831e33f";
1456 };
1456 };
1457 meta = {
1457 meta = {
1458 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1458 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1459 };
1459 };
1460 };
1460 };
1461 pytest-profiling = super.buildPythonPackage {
1461 pytest-profiling = super.buildPythonPackage {
1462 name = "pytest-profiling-1.2.2";
1462 name = "pytest-profiling-1.2.2";
1463 buildInputs = with self; [];
1463 buildInputs = with self; [];
1464 doCheck = false;
1464 doCheck = false;
1465 propagatedBuildInputs = with self; [six pytest gprof2dot];
1465 propagatedBuildInputs = with self; [six pytest gprof2dot];
1466 src = fetchurl {
1466 src = fetchurl {
1467 url = "https://pypi.python.org/packages/73/e8/804681323bac0bc45c520ec34185ba8469008942266d0074699b204835c1/pytest-profiling-1.2.2.tar.gz";
1467 url = "https://pypi.python.org/packages/73/e8/804681323bac0bc45c520ec34185ba8469008942266d0074699b204835c1/pytest-profiling-1.2.2.tar.gz";
1468 md5 = "0a16d7dda2d23b91e9730fa4558cf728";
1468 md5 = "0a16d7dda2d23b91e9730fa4558cf728";
1469 };
1469 };
1470 meta = {
1470 meta = {
1471 license = [ pkgs.lib.licenses.mit ];
1471 license = [ pkgs.lib.licenses.mit ];
1472 };
1472 };
1473 };
1473 };
1474 pytest-runner = super.buildPythonPackage {
1474 pytest-runner = super.buildPythonPackage {
1475 name = "pytest-runner-2.9";
1475 name = "pytest-runner-2.9";
1476 buildInputs = with self; [];
1476 buildInputs = with self; [];
1477 doCheck = false;
1477 doCheck = false;
1478 propagatedBuildInputs = with self; [];
1478 propagatedBuildInputs = with self; [];
1479 src = fetchurl {
1479 src = fetchurl {
1480 url = "https://pypi.python.org/packages/11/d4/c335ddf94463e451109e3494e909765c3e5205787b772e3b25ee8601b86a/pytest-runner-2.9.tar.gz";
1480 url = "https://pypi.python.org/packages/11/d4/c335ddf94463e451109e3494e909765c3e5205787b772e3b25ee8601b86a/pytest-runner-2.9.tar.gz";
1481 md5 = "2212a2e34404b0960b2fdc2c469247b2";
1481 md5 = "2212a2e34404b0960b2fdc2c469247b2";
1482 };
1482 };
1483 meta = {
1483 meta = {
1484 license = [ pkgs.lib.licenses.mit ];
1484 license = [ pkgs.lib.licenses.mit ];
1485 };
1485 };
1486 };
1486 };
1487 pytest-sugar = super.buildPythonPackage {
1487 pytest-sugar = super.buildPythonPackage {
1488 name = "pytest-sugar-0.7.1";
1488 name = "pytest-sugar-0.7.1";
1489 buildInputs = with self; [];
1489 buildInputs = with self; [];
1490 doCheck = false;
1490 doCheck = false;
1491 propagatedBuildInputs = with self; [pytest termcolor];
1491 propagatedBuildInputs = with self; [pytest termcolor];
1492 src = fetchurl {
1492 src = fetchurl {
1493 url = "https://pypi.python.org/packages/03/97/05d988b4fa870e7373e8ee4582408543b9ca2bd35c3c67b569369c6f9c49/pytest-sugar-0.7.1.tar.gz";
1493 url = "https://pypi.python.org/packages/03/97/05d988b4fa870e7373e8ee4582408543b9ca2bd35c3c67b569369c6f9c49/pytest-sugar-0.7.1.tar.gz";
1494 md5 = "7400f7c11f3d572b2c2a3b60352d35fe";
1494 md5 = "7400f7c11f3d572b2c2a3b60352d35fe";
1495 };
1495 };
1496 meta = {
1496 meta = {
1497 license = [ pkgs.lib.licenses.bsdOriginal ];
1497 license = [ pkgs.lib.licenses.bsdOriginal ];
1498 };
1498 };
1499 };
1499 };
1500 pytest-timeout = super.buildPythonPackage {
1500 pytest-timeout = super.buildPythonPackage {
1501 name = "pytest-timeout-1.2.0";
1501 name = "pytest-timeout-1.2.0";
1502 buildInputs = with self; [];
1502 buildInputs = with self; [];
1503 doCheck = false;
1503 doCheck = false;
1504 propagatedBuildInputs = with self; [pytest];
1504 propagatedBuildInputs = with self; [pytest];
1505 src = fetchurl {
1505 src = fetchurl {
1506 url = "https://pypi.python.org/packages/cc/b7/b2a61365ea6b6d2e8881360ae7ed8dad0327ad2df89f2f0be4a02304deb2/pytest-timeout-1.2.0.tar.gz";
1506 url = "https://pypi.python.org/packages/cc/b7/b2a61365ea6b6d2e8881360ae7ed8dad0327ad2df89f2f0be4a02304deb2/pytest-timeout-1.2.0.tar.gz";
1507 md5 = "83607d91aa163562c7ee835da57d061d";
1507 md5 = "83607d91aa163562c7ee835da57d061d";
1508 };
1508 };
1509 meta = {
1509 meta = {
1510 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1510 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1511 };
1511 };
1512 };
1512 };
1513 python-dateutil = super.buildPythonPackage {
1513 python-dateutil = super.buildPythonPackage {
1514 name = "python-dateutil-2.1";
1514 name = "python-dateutil-2.1";
1515 buildInputs = with self; [];
1515 buildInputs = with self; [];
1516 doCheck = false;
1516 doCheck = false;
1517 propagatedBuildInputs = with self; [six];
1517 propagatedBuildInputs = with self; [six];
1518 src = fetchurl {
1518 src = fetchurl {
1519 url = "https://pypi.python.org/packages/65/52/9c18dac21f174ad31b65e22d24297864a954e6fe65876eba3f5773d2da43/python-dateutil-2.1.tar.gz";
1519 url = "https://pypi.python.org/packages/65/52/9c18dac21f174ad31b65e22d24297864a954e6fe65876eba3f5773d2da43/python-dateutil-2.1.tar.gz";
1520 md5 = "1534bb15cf311f07afaa3aacba1c028b";
1520 md5 = "1534bb15cf311f07afaa3aacba1c028b";
1521 };
1521 };
1522 meta = {
1522 meta = {
1523 license = [ { fullName = "Simplified BSD"; } ];
1523 license = [ { fullName = "Simplified BSD"; } ];
1524 };
1524 };
1525 };
1525 };
1526 python-editor = super.buildPythonPackage {
1526 python-editor = super.buildPythonPackage {
1527 name = "python-editor-1.0.3";
1527 name = "python-editor-1.0.3";
1528 buildInputs = with self; [];
1528 buildInputs = with self; [];
1529 doCheck = false;
1529 doCheck = false;
1530 propagatedBuildInputs = with self; [];
1530 propagatedBuildInputs = with self; [];
1531 src = fetchurl {
1531 src = fetchurl {
1532 url = "https://pypi.python.org/packages/65/1e/adf6e000ea5dc909aa420352d6ba37f16434c8a3c2fa030445411a1ed545/python-editor-1.0.3.tar.gz";
1532 url = "https://pypi.python.org/packages/65/1e/adf6e000ea5dc909aa420352d6ba37f16434c8a3c2fa030445411a1ed545/python-editor-1.0.3.tar.gz";
1533 md5 = "0aca5f2ef176ce68e98a5b7e31372835";
1533 md5 = "0aca5f2ef176ce68e98a5b7e31372835";
1534 };
1534 };
1535 meta = {
1535 meta = {
1536 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1536 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1537 };
1537 };
1538 };
1538 };
1539 python-ldap = super.buildPythonPackage {
1539 python-ldap = super.buildPythonPackage {
1540 name = "python-ldap-2.4.19";
1540 name = "python-ldap-2.4.19";
1541 buildInputs = with self; [];
1541 buildInputs = with self; [];
1542 doCheck = false;
1542 doCheck = false;
1543 propagatedBuildInputs = with self; [setuptools];
1543 propagatedBuildInputs = with self; [setuptools];
1544 src = fetchurl {
1544 src = fetchurl {
1545 url = "https://pypi.python.org/packages/42/81/1b64838c82e64f14d4e246ff00b52e650a35c012551b891ada2b85d40737/python-ldap-2.4.19.tar.gz";
1545 url = "https://pypi.python.org/packages/42/81/1b64838c82e64f14d4e246ff00b52e650a35c012551b891ada2b85d40737/python-ldap-2.4.19.tar.gz";
1546 md5 = "b941bf31d09739492aa19ef679e94ae3";
1546 md5 = "b941bf31d09739492aa19ef679e94ae3";
1547 };
1547 };
1548 meta = {
1548 meta = {
1549 license = [ pkgs.lib.licenses.psfl ];
1549 license = [ pkgs.lib.licenses.psfl ];
1550 };
1550 };
1551 };
1551 };
1552 python-memcached = super.buildPythonPackage {
1552 python-memcached = super.buildPythonPackage {
1553 name = "python-memcached-1.57";
1553 name = "python-memcached-1.57";
1554 buildInputs = with self; [];
1554 buildInputs = with self; [];
1555 doCheck = false;
1555 doCheck = false;
1556 propagatedBuildInputs = with self; [six];
1556 propagatedBuildInputs = with self; [six];
1557 src = fetchurl {
1557 src = fetchurl {
1558 url = "https://pypi.python.org/packages/52/9d/eebc0dcbc5c7c66840ad207dfc1baa376dadb74912484bff73819cce01e6/python-memcached-1.57.tar.gz";
1558 url = "https://pypi.python.org/packages/52/9d/eebc0dcbc5c7c66840ad207dfc1baa376dadb74912484bff73819cce01e6/python-memcached-1.57.tar.gz";
1559 md5 = "de21f64b42b2d961f3d4ad7beb5468a1";
1559 md5 = "de21f64b42b2d961f3d4ad7beb5468a1";
1560 };
1560 };
1561 meta = {
1561 meta = {
1562 license = [ pkgs.lib.licenses.psfl ];
1562 license = [ pkgs.lib.licenses.psfl ];
1563 };
1563 };
1564 };
1564 };
1565 python-pam = super.buildPythonPackage {
1565 python-pam = super.buildPythonPackage {
1566 name = "python-pam-1.8.2";
1566 name = "python-pam-1.8.2";
1567 buildInputs = with self; [];
1567 buildInputs = with self; [];
1568 doCheck = false;
1568 doCheck = false;
1569 propagatedBuildInputs = with self; [];
1569 propagatedBuildInputs = with self; [];
1570 src = fetchurl {
1570 src = fetchurl {
1571 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
1571 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
1572 md5 = "db71b6b999246fb05d78ecfbe166629d";
1572 md5 = "db71b6b999246fb05d78ecfbe166629d";
1573 };
1573 };
1574 meta = {
1574 meta = {
1575 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1575 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1576 };
1576 };
1577 };
1577 };
1578 pytz = super.buildPythonPackage {
1578 pytz = super.buildPythonPackage {
1579 name = "pytz-2015.4";
1579 name = "pytz-2015.4";
1580 buildInputs = with self; [];
1580 buildInputs = with self; [];
1581 doCheck = false;
1581 doCheck = false;
1582 propagatedBuildInputs = with self; [];
1582 propagatedBuildInputs = with self; [];
1583 src = fetchurl {
1583 src = fetchurl {
1584 url = "https://pypi.python.org/packages/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
1584 url = "https://pypi.python.org/packages/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
1585 md5 = "233f2a2b370d03f9b5911700cc9ebf3c";
1585 md5 = "233f2a2b370d03f9b5911700cc9ebf3c";
1586 };
1586 };
1587 meta = {
1587 meta = {
1588 license = [ pkgs.lib.licenses.mit ];
1588 license = [ pkgs.lib.licenses.mit ];
1589 };
1589 };
1590 };
1590 };
1591 pyzmq = super.buildPythonPackage {
1591 pyzmq = super.buildPythonPackage {
1592 name = "pyzmq-14.6.0";
1592 name = "pyzmq-14.6.0";
1593 buildInputs = with self; [];
1593 buildInputs = with self; [];
1594 doCheck = false;
1594 doCheck = false;
1595 propagatedBuildInputs = with self; [];
1595 propagatedBuildInputs = with self; [];
1596 src = fetchurl {
1596 src = fetchurl {
1597 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1597 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1598 md5 = "395b5de95a931afa5b14c9349a5b8024";
1598 md5 = "395b5de95a931afa5b14c9349a5b8024";
1599 };
1599 };
1600 meta = {
1600 meta = {
1601 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1601 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1602 };
1602 };
1603 };
1603 };
1604 recaptcha-client = super.buildPythonPackage {
1604 recaptcha-client = super.buildPythonPackage {
1605 name = "recaptcha-client-1.0.6";
1605 name = "recaptcha-client-1.0.6";
1606 buildInputs = with self; [];
1606 buildInputs = with self; [];
1607 doCheck = false;
1607 doCheck = false;
1608 propagatedBuildInputs = with self; [];
1608 propagatedBuildInputs = with self; [];
1609 src = fetchurl {
1609 src = fetchurl {
1610 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1610 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1611 md5 = "74228180f7e1fb76c4d7089160b0d919";
1611 md5 = "74228180f7e1fb76c4d7089160b0d919";
1612 };
1612 };
1613 meta = {
1613 meta = {
1614 license = [ { fullName = "MIT/X11"; } ];
1614 license = [ { fullName = "MIT/X11"; } ];
1615 };
1615 };
1616 };
1616 };
1617 repoze.lru = super.buildPythonPackage {
1617 repoze.lru = super.buildPythonPackage {
1618 name = "repoze.lru-0.6";
1618 name = "repoze.lru-0.6";
1619 buildInputs = with self; [];
1619 buildInputs = with self; [];
1620 doCheck = false;
1620 doCheck = false;
1621 propagatedBuildInputs = with self; [];
1621 propagatedBuildInputs = with self; [];
1622 src = fetchurl {
1622 src = fetchurl {
1623 url = "https://pypi.python.org/packages/6e/1e/aa15cc90217e086dc8769872c8778b409812ff036bf021b15795638939e4/repoze.lru-0.6.tar.gz";
1623 url = "https://pypi.python.org/packages/6e/1e/aa15cc90217e086dc8769872c8778b409812ff036bf021b15795638939e4/repoze.lru-0.6.tar.gz";
1624 md5 = "2c3b64b17a8e18b405f55d46173e14dd";
1624 md5 = "2c3b64b17a8e18b405f55d46173e14dd";
1625 };
1625 };
1626 meta = {
1626 meta = {
1627 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1627 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1628 };
1628 };
1629 };
1629 };
1630 requests = super.buildPythonPackage {
1630 requests = super.buildPythonPackage {
1631 name = "requests-2.9.1";
1631 name = "requests-2.9.1";
1632 buildInputs = with self; [];
1632 buildInputs = with self; [];
1633 doCheck = false;
1633 doCheck = false;
1634 propagatedBuildInputs = with self; [];
1634 propagatedBuildInputs = with self; [];
1635 src = fetchurl {
1635 src = fetchurl {
1636 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1636 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1637 md5 = "0b7f480d19012ec52bab78292efd976d";
1637 md5 = "0b7f480d19012ec52bab78292efd976d";
1638 };
1638 };
1639 meta = {
1639 meta = {
1640 license = [ pkgs.lib.licenses.asl20 ];
1640 license = [ pkgs.lib.licenses.asl20 ];
1641 };
1641 };
1642 };
1642 };
1643 rhodecode-enterprise-ce = super.buildPythonPackage {
1643 rhodecode-enterprise-ce = super.buildPythonPackage {
1644 name = "rhodecode-enterprise-ce-4.7.2";
1644 name = "rhodecode-enterprise-ce-4.8.0";
1645 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
1645 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
1646 doCheck = true;
1646 doCheck = true;
1647 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1647 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1648 src = ./.;
1648 src = ./.;
1649 meta = {
1649 meta = {
1650 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1650 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1651 };
1651 };
1652 };
1652 };
1653 rhodecode-tools = super.buildPythonPackage {
1653 rhodecode-tools = super.buildPythonPackage {
1654 name = "rhodecode-tools-0.12.0";
1654 name = "rhodecode-tools-0.12.0";
1655 buildInputs = with self; [];
1655 buildInputs = with self; [];
1656 doCheck = false;
1656 doCheck = false;
1657 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests elasticsearch elasticsearch-dsl urllib3 Whoosh];
1657 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests elasticsearch elasticsearch-dsl urllib3 Whoosh];
1658 src = fetchurl {
1658 src = fetchurl {
1659 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.12.0.tar.gz?md5=9ca040356fa7e38d3f64529a4cffdca4";
1659 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.12.0.tar.gz?md5=9ca040356fa7e38d3f64529a4cffdca4";
1660 md5 = "9ca040356fa7e38d3f64529a4cffdca4";
1660 md5 = "9ca040356fa7e38d3f64529a4cffdca4";
1661 };
1661 };
1662 meta = {
1662 meta = {
1663 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
1663 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
1664 };
1664 };
1665 };
1665 };
1666 setproctitle = super.buildPythonPackage {
1666 setproctitle = super.buildPythonPackage {
1667 name = "setproctitle-1.1.8";
1667 name = "setproctitle-1.1.8";
1668 buildInputs = with self; [];
1668 buildInputs = with self; [];
1669 doCheck = false;
1669 doCheck = false;
1670 propagatedBuildInputs = with self; [];
1670 propagatedBuildInputs = with self; [];
1671 src = fetchurl {
1671 src = fetchurl {
1672 url = "https://pypi.python.org/packages/33/c3/ad367a4f4f1ca90468863ae727ac62f6edb558fc09a003d344a02cfc6ea6/setproctitle-1.1.8.tar.gz";
1672 url = "https://pypi.python.org/packages/33/c3/ad367a4f4f1ca90468863ae727ac62f6edb558fc09a003d344a02cfc6ea6/setproctitle-1.1.8.tar.gz";
1673 md5 = "728f4c8c6031bbe56083a48594027edd";
1673 md5 = "728f4c8c6031bbe56083a48594027edd";
1674 };
1674 };
1675 meta = {
1675 meta = {
1676 license = [ pkgs.lib.licenses.bsdOriginal ];
1676 license = [ pkgs.lib.licenses.bsdOriginal ];
1677 };
1677 };
1678 };
1678 };
1679 setuptools = super.buildPythonPackage {
1679 setuptools = super.buildPythonPackage {
1680 name = "setuptools-30.1.0";
1680 name = "setuptools-30.1.0";
1681 buildInputs = with self; [];
1681 buildInputs = with self; [];
1682 doCheck = false;
1682 doCheck = false;
1683 propagatedBuildInputs = with self; [];
1683 propagatedBuildInputs = with self; [];
1684 src = fetchurl {
1684 src = fetchurl {
1685 url = "https://pypi.python.org/packages/1e/43/002c8616db9a3e7be23c2556e39b90a32bb40ba0dc652de1999d5334d372/setuptools-30.1.0.tar.gz";
1685 url = "https://pypi.python.org/packages/1e/43/002c8616db9a3e7be23c2556e39b90a32bb40ba0dc652de1999d5334d372/setuptools-30.1.0.tar.gz";
1686 md5 = "cac497f42e5096ac8df29e38d3f81c3e";
1686 md5 = "cac497f42e5096ac8df29e38d3f81c3e";
1687 };
1687 };
1688 meta = {
1688 meta = {
1689 license = [ pkgs.lib.licenses.mit ];
1689 license = [ pkgs.lib.licenses.mit ];
1690 };
1690 };
1691 };
1691 };
1692 setuptools-scm = super.buildPythonPackage {
1692 setuptools-scm = super.buildPythonPackage {
1693 name = "setuptools-scm-1.15.0";
1693 name = "setuptools-scm-1.15.0";
1694 buildInputs = with self; [];
1694 buildInputs = with self; [];
1695 doCheck = false;
1695 doCheck = false;
1696 propagatedBuildInputs = with self; [];
1696 propagatedBuildInputs = with self; [];
1697 src = fetchurl {
1697 src = fetchurl {
1698 url = "https://pypi.python.org/packages/80/b7/31b6ae5fcb188e37f7e31abe75f9be90490a5456a72860fa6e643f8a3cbc/setuptools_scm-1.15.0.tar.gz";
1698 url = "https://pypi.python.org/packages/80/b7/31b6ae5fcb188e37f7e31abe75f9be90490a5456a72860fa6e643f8a3cbc/setuptools_scm-1.15.0.tar.gz";
1699 md5 = "b6916c78ed6253d6602444fad4279c5b";
1699 md5 = "b6916c78ed6253d6602444fad4279c5b";
1700 };
1700 };
1701 meta = {
1701 meta = {
1702 license = [ pkgs.lib.licenses.mit ];
1702 license = [ pkgs.lib.licenses.mit ];
1703 };
1703 };
1704 };
1704 };
1705 simplegeneric = super.buildPythonPackage {
1705 simplegeneric = super.buildPythonPackage {
1706 name = "simplegeneric-0.8.1";
1706 name = "simplegeneric-0.8.1";
1707 buildInputs = with self; [];
1707 buildInputs = with self; [];
1708 doCheck = false;
1708 doCheck = false;
1709 propagatedBuildInputs = with self; [];
1709 propagatedBuildInputs = with self; [];
1710 src = fetchurl {
1710 src = fetchurl {
1711 url = "https://pypi.python.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1711 url = "https://pypi.python.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1712 md5 = "f9c1fab00fd981be588fc32759f474e3";
1712 md5 = "f9c1fab00fd981be588fc32759f474e3";
1713 };
1713 };
1714 meta = {
1714 meta = {
1715 license = [ pkgs.lib.licenses.zpt21 ];
1715 license = [ pkgs.lib.licenses.zpt21 ];
1716 };
1716 };
1717 };
1717 };
1718 simplejson = super.buildPythonPackage {
1718 simplejson = super.buildPythonPackage {
1719 name = "simplejson-3.7.2";
1719 name = "simplejson-3.7.2";
1720 buildInputs = with self; [];
1720 buildInputs = with self; [];
1721 doCheck = false;
1721 doCheck = false;
1722 propagatedBuildInputs = with self; [];
1722 propagatedBuildInputs = with self; [];
1723 src = fetchurl {
1723 src = fetchurl {
1724 url = "https://pypi.python.org/packages/6d/89/7f13f099344eea9d6722779a1f165087cb559598107844b1ac5dbd831fb1/simplejson-3.7.2.tar.gz";
1724 url = "https://pypi.python.org/packages/6d/89/7f13f099344eea9d6722779a1f165087cb559598107844b1ac5dbd831fb1/simplejson-3.7.2.tar.gz";
1725 md5 = "a5fc7d05d4cb38492285553def5d4b46";
1725 md5 = "a5fc7d05d4cb38492285553def5d4b46";
1726 };
1726 };
1727 meta = {
1727 meta = {
1728 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1728 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1729 };
1729 };
1730 };
1730 };
1731 six = super.buildPythonPackage {
1731 six = super.buildPythonPackage {
1732 name = "six-1.9.0";
1732 name = "six-1.9.0";
1733 buildInputs = with self; [];
1733 buildInputs = with self; [];
1734 doCheck = false;
1734 doCheck = false;
1735 propagatedBuildInputs = with self; [];
1735 propagatedBuildInputs = with self; [];
1736 src = fetchurl {
1736 src = fetchurl {
1737 url = "https://pypi.python.org/packages/16/64/1dc5e5976b17466fd7d712e59cbe9fb1e18bec153109e5ba3ed6c9102f1a/six-1.9.0.tar.gz";
1737 url = "https://pypi.python.org/packages/16/64/1dc5e5976b17466fd7d712e59cbe9fb1e18bec153109e5ba3ed6c9102f1a/six-1.9.0.tar.gz";
1738 md5 = "476881ef4012262dfc8adc645ee786c4";
1738 md5 = "476881ef4012262dfc8adc645ee786c4";
1739 };
1739 };
1740 meta = {
1740 meta = {
1741 license = [ pkgs.lib.licenses.mit ];
1741 license = [ pkgs.lib.licenses.mit ];
1742 };
1742 };
1743 };
1743 };
1744 subprocess32 = super.buildPythonPackage {
1744 subprocess32 = super.buildPythonPackage {
1745 name = "subprocess32-3.2.6";
1745 name = "subprocess32-3.2.6";
1746 buildInputs = with self; [];
1746 buildInputs = with self; [];
1747 doCheck = false;
1747 doCheck = false;
1748 propagatedBuildInputs = with self; [];
1748 propagatedBuildInputs = with self; [];
1749 src = fetchurl {
1749 src = fetchurl {
1750 url = "https://pypi.python.org/packages/28/8d/33ccbff51053f59ae6c357310cac0e79246bbed1d345ecc6188b176d72c3/subprocess32-3.2.6.tar.gz";
1750 url = "https://pypi.python.org/packages/28/8d/33ccbff51053f59ae6c357310cac0e79246bbed1d345ecc6188b176d72c3/subprocess32-3.2.6.tar.gz";
1751 md5 = "754c5ab9f533e764f931136974b618f1";
1751 md5 = "754c5ab9f533e764f931136974b618f1";
1752 };
1752 };
1753 meta = {
1753 meta = {
1754 license = [ pkgs.lib.licenses.psfl ];
1754 license = [ pkgs.lib.licenses.psfl ];
1755 };
1755 };
1756 };
1756 };
1757 supervisor = super.buildPythonPackage {
1757 supervisor = super.buildPythonPackage {
1758 name = "supervisor-3.3.1";
1758 name = "supervisor-3.3.1";
1759 buildInputs = with self; [];
1759 buildInputs = with self; [];
1760 doCheck = false;
1760 doCheck = false;
1761 propagatedBuildInputs = with self; [meld3];
1761 propagatedBuildInputs = with self; [meld3];
1762 src = fetchurl {
1762 src = fetchurl {
1763 url = "https://pypi.python.org/packages/80/37/964c0d53cbd328796b1aeb7abea4c0f7b0e8c7197ea9b0b9967b7d004def/supervisor-3.3.1.tar.gz";
1763 url = "https://pypi.python.org/packages/80/37/964c0d53cbd328796b1aeb7abea4c0f7b0e8c7197ea9b0b9967b7d004def/supervisor-3.3.1.tar.gz";
1764 md5 = "202f760f9bf4930ec06557bac73e5cf2";
1764 md5 = "202f760f9bf4930ec06557bac73e5cf2";
1765 };
1765 };
1766 meta = {
1766 meta = {
1767 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1767 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1768 };
1768 };
1769 };
1769 };
1770 termcolor = super.buildPythonPackage {
1770 termcolor = super.buildPythonPackage {
1771 name = "termcolor-1.1.0";
1771 name = "termcolor-1.1.0";
1772 buildInputs = with self; [];
1772 buildInputs = with self; [];
1773 doCheck = false;
1773 doCheck = false;
1774 propagatedBuildInputs = with self; [];
1774 propagatedBuildInputs = with self; [];
1775 src = fetchurl {
1775 src = fetchurl {
1776 url = "https://pypi.python.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
1776 url = "https://pypi.python.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
1777 md5 = "043e89644f8909d462fbbfa511c768df";
1777 md5 = "043e89644f8909d462fbbfa511c768df";
1778 };
1778 };
1779 meta = {
1779 meta = {
1780 license = [ pkgs.lib.licenses.mit ];
1780 license = [ pkgs.lib.licenses.mit ];
1781 };
1781 };
1782 };
1782 };
1783 testpath = super.buildPythonPackage {
1783 testpath = super.buildPythonPackage {
1784 name = "testpath-0.1";
1784 name = "testpath-0.1";
1785 buildInputs = with self; [];
1785 buildInputs = with self; [];
1786 doCheck = false;
1786 doCheck = false;
1787 propagatedBuildInputs = with self; [];
1787 propagatedBuildInputs = with self; [];
1788 src = fetchurl {
1788 src = fetchurl {
1789 url = "https://pypi.python.org/packages/f9/c4/c0b22f35138bc26a6058c39cb61db1e8977e5e9550b12cd2cb02ef56fc51/testpath-0.1.tar.gz";
1789 url = "https://pypi.python.org/packages/f9/c4/c0b22f35138bc26a6058c39cb61db1e8977e5e9550b12cd2cb02ef56fc51/testpath-0.1.tar.gz";
1790 md5 = "401918bcd0b0e5b71a9b909835117bc6";
1790 md5 = "401918bcd0b0e5b71a9b909835117bc6";
1791 };
1791 };
1792 meta = {
1792 meta = {
1793 license = [ pkgs.lib.licenses.mit ];
1793 license = [ pkgs.lib.licenses.mit ];
1794 };
1794 };
1795 };
1795 };
1796 traitlets = super.buildPythonPackage {
1796 traitlets = super.buildPythonPackage {
1797 name = "traitlets-4.3.2";
1797 name = "traitlets-4.3.2";
1798 buildInputs = with self; [];
1798 buildInputs = with self; [];
1799 doCheck = false;
1799 doCheck = false;
1800 propagatedBuildInputs = with self; [ipython-genutils six decorator enum34];
1800 propagatedBuildInputs = with self; [ipython-genutils six decorator enum34];
1801 src = fetchurl {
1801 src = fetchurl {
1802 url = "https://pypi.python.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
1802 url = "https://pypi.python.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
1803 md5 = "3068663f2f38fd939a9eb3a500ccc154";
1803 md5 = "3068663f2f38fd939a9eb3a500ccc154";
1804 };
1804 };
1805 meta = {
1805 meta = {
1806 license = [ pkgs.lib.licenses.bsdOriginal ];
1806 license = [ pkgs.lib.licenses.bsdOriginal ];
1807 };
1807 };
1808 };
1808 };
1809 transifex-client = super.buildPythonPackage {
1809 transifex-client = super.buildPythonPackage {
1810 name = "transifex-client-0.10";
1810 name = "transifex-client-0.10";
1811 buildInputs = with self; [];
1811 buildInputs = with self; [];
1812 doCheck = false;
1812 doCheck = false;
1813 propagatedBuildInputs = with self; [];
1813 propagatedBuildInputs = with self; [];
1814 src = fetchurl {
1814 src = fetchurl {
1815 url = "https://pypi.python.org/packages/f3/4e/7b925192aee656fb3e04fa6381c8b3dc40198047c3b4a356f6cfd642c809/transifex-client-0.10.tar.gz";
1815 url = "https://pypi.python.org/packages/f3/4e/7b925192aee656fb3e04fa6381c8b3dc40198047c3b4a356f6cfd642c809/transifex-client-0.10.tar.gz";
1816 md5 = "5549538d84b8eede6b254cd81ae024fa";
1816 md5 = "5549538d84b8eede6b254cd81ae024fa";
1817 };
1817 };
1818 meta = {
1818 meta = {
1819 license = [ pkgs.lib.licenses.gpl2 ];
1819 license = [ pkgs.lib.licenses.gpl2 ];
1820 };
1820 };
1821 };
1821 };
1822 translationstring = super.buildPythonPackage {
1822 translationstring = super.buildPythonPackage {
1823 name = "translationstring-1.3";
1823 name = "translationstring-1.3";
1824 buildInputs = with self; [];
1824 buildInputs = with self; [];
1825 doCheck = false;
1825 doCheck = false;
1826 propagatedBuildInputs = with self; [];
1826 propagatedBuildInputs = with self; [];
1827 src = fetchurl {
1827 src = fetchurl {
1828 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1828 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1829 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1829 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1830 };
1830 };
1831 meta = {
1831 meta = {
1832 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
1832 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
1833 };
1833 };
1834 };
1834 };
1835 trollius = super.buildPythonPackage {
1835 trollius = super.buildPythonPackage {
1836 name = "trollius-1.0.4";
1836 name = "trollius-1.0.4";
1837 buildInputs = with self; [];
1837 buildInputs = with self; [];
1838 doCheck = false;
1838 doCheck = false;
1839 propagatedBuildInputs = with self; [futures];
1839 propagatedBuildInputs = with self; [futures];
1840 src = fetchurl {
1840 src = fetchurl {
1841 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1841 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1842 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1842 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1843 };
1843 };
1844 meta = {
1844 meta = {
1845 license = [ pkgs.lib.licenses.asl20 ];
1845 license = [ pkgs.lib.licenses.asl20 ];
1846 };
1846 };
1847 };
1847 };
1848 uWSGI = super.buildPythonPackage {
1848 uWSGI = super.buildPythonPackage {
1849 name = "uWSGI-2.0.11.2";
1849 name = "uWSGI-2.0.11.2";
1850 buildInputs = with self; [];
1850 buildInputs = with self; [];
1851 doCheck = false;
1851 doCheck = false;
1852 propagatedBuildInputs = with self; [];
1852 propagatedBuildInputs = with self; [];
1853 src = fetchurl {
1853 src = fetchurl {
1854 url = "https://pypi.python.org/packages/9b/78/918db0cfab0546afa580c1e565209c49aaf1476bbfe491314eadbe47c556/uwsgi-2.0.11.2.tar.gz";
1854 url = "https://pypi.python.org/packages/9b/78/918db0cfab0546afa580c1e565209c49aaf1476bbfe491314eadbe47c556/uwsgi-2.0.11.2.tar.gz";
1855 md5 = "1f02dcbee7f6f61de4b1fd68350cf16f";
1855 md5 = "1f02dcbee7f6f61de4b1fd68350cf16f";
1856 };
1856 };
1857 meta = {
1857 meta = {
1858 license = [ pkgs.lib.licenses.gpl2 ];
1858 license = [ pkgs.lib.licenses.gpl2 ];
1859 };
1859 };
1860 };
1860 };
1861 urllib3 = super.buildPythonPackage {
1861 urllib3 = super.buildPythonPackage {
1862 name = "urllib3-1.16";
1862 name = "urllib3-1.16";
1863 buildInputs = with self; [];
1863 buildInputs = with self; [];
1864 doCheck = false;
1864 doCheck = false;
1865 propagatedBuildInputs = with self; [];
1865 propagatedBuildInputs = with self; [];
1866 src = fetchurl {
1866 src = fetchurl {
1867 url = "https://pypi.python.org/packages/3b/f0/e763169124e3f5db0926bc3dbfcd580a105f9ca44cf5d8e6c7a803c9f6b5/urllib3-1.16.tar.gz";
1867 url = "https://pypi.python.org/packages/3b/f0/e763169124e3f5db0926bc3dbfcd580a105f9ca44cf5d8e6c7a803c9f6b5/urllib3-1.16.tar.gz";
1868 md5 = "fcaab1c5385c57deeb7053d3d7d81d59";
1868 md5 = "fcaab1c5385c57deeb7053d3d7d81d59";
1869 };
1869 };
1870 meta = {
1870 meta = {
1871 license = [ pkgs.lib.licenses.mit ];
1871 license = [ pkgs.lib.licenses.mit ];
1872 };
1872 };
1873 };
1873 };
1874 venusian = super.buildPythonPackage {
1874 venusian = super.buildPythonPackage {
1875 name = "venusian-1.0";
1875 name = "venusian-1.0";
1876 buildInputs = with self; [];
1876 buildInputs = with self; [];
1877 doCheck = false;
1877 doCheck = false;
1878 propagatedBuildInputs = with self; [];
1878 propagatedBuildInputs = with self; [];
1879 src = fetchurl {
1879 src = fetchurl {
1880 url = "https://pypi.python.org/packages/86/20/1948e0dfc4930ddde3da8c33612f6a5717c0b4bc28f591a5c5cf014dd390/venusian-1.0.tar.gz";
1880 url = "https://pypi.python.org/packages/86/20/1948e0dfc4930ddde3da8c33612f6a5717c0b4bc28f591a5c5cf014dd390/venusian-1.0.tar.gz";
1881 md5 = "dccf2eafb7113759d60c86faf5538756";
1881 md5 = "dccf2eafb7113759d60c86faf5538756";
1882 };
1882 };
1883 meta = {
1883 meta = {
1884 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1884 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1885 };
1885 };
1886 };
1886 };
1887 waitress = super.buildPythonPackage {
1887 waitress = super.buildPythonPackage {
1888 name = "waitress-1.0.1";
1888 name = "waitress-1.0.1";
1889 buildInputs = with self; [];
1889 buildInputs = with self; [];
1890 doCheck = false;
1890 doCheck = false;
1891 propagatedBuildInputs = with self; [];
1891 propagatedBuildInputs = with self; [];
1892 src = fetchurl {
1892 src = fetchurl {
1893 url = "https://pypi.python.org/packages/78/7d/84d11b96c3f60164dec3bef4a859a03aeae0231aa93f57fbe0d05fa4ff36/waitress-1.0.1.tar.gz";
1893 url = "https://pypi.python.org/packages/78/7d/84d11b96c3f60164dec3bef4a859a03aeae0231aa93f57fbe0d05fa4ff36/waitress-1.0.1.tar.gz";
1894 md5 = "dda92358a7569669086155923a46e57c";
1894 md5 = "dda92358a7569669086155923a46e57c";
1895 };
1895 };
1896 meta = {
1896 meta = {
1897 license = [ pkgs.lib.licenses.zpt21 ];
1897 license = [ pkgs.lib.licenses.zpt21 ];
1898 };
1898 };
1899 };
1899 };
1900 wcwidth = super.buildPythonPackage {
1900 wcwidth = super.buildPythonPackage {
1901 name = "wcwidth-0.1.7";
1901 name = "wcwidth-0.1.7";
1902 buildInputs = with self; [];
1902 buildInputs = with self; [];
1903 doCheck = false;
1903 doCheck = false;
1904 propagatedBuildInputs = with self; [];
1904 propagatedBuildInputs = with self; [];
1905 src = fetchurl {
1905 src = fetchurl {
1906 url = "https://pypi.python.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
1906 url = "https://pypi.python.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
1907 md5 = "b3b6a0a08f0c8a34d1de8cf44150a4ad";
1907 md5 = "b3b6a0a08f0c8a34d1de8cf44150a4ad";
1908 };
1908 };
1909 meta = {
1909 meta = {
1910 license = [ pkgs.lib.licenses.mit ];
1910 license = [ pkgs.lib.licenses.mit ];
1911 };
1911 };
1912 };
1912 };
1913 ws4py = super.buildPythonPackage {
1913 ws4py = super.buildPythonPackage {
1914 name = "ws4py-0.3.5";
1914 name = "ws4py-0.3.5";
1915 buildInputs = with self; [];
1915 buildInputs = with self; [];
1916 doCheck = false;
1916 doCheck = false;
1917 propagatedBuildInputs = with self; [];
1917 propagatedBuildInputs = with self; [];
1918 src = fetchurl {
1918 src = fetchurl {
1919 url = "https://pypi.python.org/packages/b6/4f/34af703be86939629479e74d6e650e39f3bd73b3b09212c34e5125764cbc/ws4py-0.3.5.zip";
1919 url = "https://pypi.python.org/packages/b6/4f/34af703be86939629479e74d6e650e39f3bd73b3b09212c34e5125764cbc/ws4py-0.3.5.zip";
1920 md5 = "a261b75c20b980e55ce7451a3576a867";
1920 md5 = "a261b75c20b980e55ce7451a3576a867";
1921 };
1921 };
1922 meta = {
1922 meta = {
1923 license = [ pkgs.lib.licenses.bsdOriginal ];
1923 license = [ pkgs.lib.licenses.bsdOriginal ];
1924 };
1924 };
1925 };
1925 };
1926 wsgiref = super.buildPythonPackage {
1926 wsgiref = super.buildPythonPackage {
1927 name = "wsgiref-0.1.2";
1927 name = "wsgiref-0.1.2";
1928 buildInputs = with self; [];
1928 buildInputs = with self; [];
1929 doCheck = false;
1929 doCheck = false;
1930 propagatedBuildInputs = with self; [];
1930 propagatedBuildInputs = with self; [];
1931 src = fetchurl {
1931 src = fetchurl {
1932 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1932 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1933 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1933 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1934 };
1934 };
1935 meta = {
1935 meta = {
1936 license = [ { fullName = "PSF or ZPL"; } ];
1936 license = [ { fullName = "PSF or ZPL"; } ];
1937 };
1937 };
1938 };
1938 };
1939 zope.cachedescriptors = super.buildPythonPackage {
1939 zope.cachedescriptors = super.buildPythonPackage {
1940 name = "zope.cachedescriptors-4.0.0";
1940 name = "zope.cachedescriptors-4.0.0";
1941 buildInputs = with self; [];
1941 buildInputs = with self; [];
1942 doCheck = false;
1942 doCheck = false;
1943 propagatedBuildInputs = with self; [setuptools];
1943 propagatedBuildInputs = with self; [setuptools];
1944 src = fetchurl {
1944 src = fetchurl {
1945 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
1945 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
1946 md5 = "8d308de8c936792c8e758058fcb7d0f0";
1946 md5 = "8d308de8c936792c8e758058fcb7d0f0";
1947 };
1947 };
1948 meta = {
1948 meta = {
1949 license = [ pkgs.lib.licenses.zpt21 ];
1949 license = [ pkgs.lib.licenses.zpt21 ];
1950 };
1950 };
1951 };
1951 };
1952 zope.deprecation = super.buildPythonPackage {
1952 zope.deprecation = super.buildPythonPackage {
1953 name = "zope.deprecation-4.1.2";
1953 name = "zope.deprecation-4.1.2";
1954 buildInputs = with self; [];
1954 buildInputs = with self; [];
1955 doCheck = false;
1955 doCheck = false;
1956 propagatedBuildInputs = with self; [setuptools];
1956 propagatedBuildInputs = with self; [setuptools];
1957 src = fetchurl {
1957 src = fetchurl {
1958 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
1958 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
1959 md5 = "e9a663ded58f4f9f7881beb56cae2782";
1959 md5 = "e9a663ded58f4f9f7881beb56cae2782";
1960 };
1960 };
1961 meta = {
1961 meta = {
1962 license = [ pkgs.lib.licenses.zpt21 ];
1962 license = [ pkgs.lib.licenses.zpt21 ];
1963 };
1963 };
1964 };
1964 };
1965 zope.event = super.buildPythonPackage {
1965 zope.event = super.buildPythonPackage {
1966 name = "zope.event-4.0.3";
1966 name = "zope.event-4.0.3";
1967 buildInputs = with self; [];
1967 buildInputs = with self; [];
1968 doCheck = false;
1968 doCheck = false;
1969 propagatedBuildInputs = with self; [setuptools];
1969 propagatedBuildInputs = with self; [setuptools];
1970 src = fetchurl {
1970 src = fetchurl {
1971 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
1971 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
1972 md5 = "9a3780916332b18b8b85f522bcc3e249";
1972 md5 = "9a3780916332b18b8b85f522bcc3e249";
1973 };
1973 };
1974 meta = {
1974 meta = {
1975 license = [ pkgs.lib.licenses.zpt21 ];
1975 license = [ pkgs.lib.licenses.zpt21 ];
1976 };
1976 };
1977 };
1977 };
1978 zope.interface = super.buildPythonPackage {
1978 zope.interface = super.buildPythonPackage {
1979 name = "zope.interface-4.1.3";
1979 name = "zope.interface-4.1.3";
1980 buildInputs = with self; [];
1980 buildInputs = with self; [];
1981 doCheck = false;
1981 doCheck = false;
1982 propagatedBuildInputs = with self; [setuptools];
1982 propagatedBuildInputs = with self; [setuptools];
1983 src = fetchurl {
1983 src = fetchurl {
1984 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
1984 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
1985 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
1985 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
1986 };
1986 };
1987 meta = {
1987 meta = {
1988 license = [ pkgs.lib.licenses.zpt21 ];
1988 license = [ pkgs.lib.licenses.zpt21 ];
1989 };
1989 };
1990 };
1990 };
1991
1991
1992 ### Test requirements
1992 ### Test requirements
1993
1993
1994
1994
1995 }
1995 }
@@ -1,136 +1,136 b''
1 ## core
1 ## core
2 setuptools==30.1.0
2 setuptools==30.1.0
3 setuptools-scm==1.15.0
3 setuptools-scm==1.15.0
4
4
5 amqplib==1.0.2
5 amqplib==1.0.2
6 anyjson==0.3.3
6 anyjson==0.3.3
7 authomatic==0.1.0.post1
7 authomatic==0.1.0.post1
8 Babel==1.3
8 Babel==1.3
9 backport-ipaddress==0.1
9 backport-ipaddress==0.1
10 Beaker==1.7.0
10 Beaker==1.7.0
11 celery==2.2.10
11 celery==2.2.10
12 Chameleon==2.24
12 Chameleon==2.24
13 channelstream==0.5.2
13 channelstream==0.5.2
14 click==5.1
14 click==5.1
15 colander==1.2
15 colander==1.2
16 configobj==5.0.6
16 configobj==5.0.6
17 cssselect==1.0.1
17 cssselect==1.0.1
18 decorator==4.0.11
18 decorator==4.0.11
19 deform==2.0a2
19 deform==2.0.4
20 docutils==0.12
20 docutils==0.12
21 dogpile.cache==0.6.1
21 dogpile.cache==0.6.1
22 dogpile.core==0.4.1
22 dogpile.core==0.4.1
23 ecdsa==0.11
23 ecdsa==0.11
24 FormEncode==1.2.4
24 FormEncode==1.2.4
25 future==0.14.3
25 future==0.14.3
26 futures==3.0.2
26 futures==3.0.2
27 gnureadline==6.3.3
27 gnureadline==6.3.3
28 infrae.cache==1.0.1
28 infrae.cache==1.0.1
29 iso8601==0.1.11
29 iso8601==0.1.11
30 itsdangerous==0.24
30 itsdangerous==0.24
31 Jinja2==2.7.3
31 Jinja2==2.7.3
32 kombu==1.5.1
32 kombu==1.5.1
33 lxml==3.7.3
33 lxml==3.7.3
34 Mako==1.0.6
34 Mako==1.0.6
35 Markdown==2.6.7
35 Markdown==2.6.7
36 MarkupSafe==0.23
36 MarkupSafe==0.23
37 meld3==1.0.2
37 meld3==1.0.2
38 msgpack-python==0.4.8
38 msgpack-python==0.4.8
39 MySQL-python==1.2.5
39 MySQL-python==1.2.5
40 nose==1.3.6
40 nose==1.3.6
41 objgraph==2.0.0
41 objgraph==3.1.0
42 packaging==15.2
42 packaging==15.2
43 paramiko==1.15.1
43 paramiko==1.15.1
44 Paste==2.0.3
44 Paste==2.0.3
45 PasteDeploy==1.5.2
45 PasteDeploy==1.5.2
46 PasteScript==1.7.5
46 PasteScript==1.7.5
47 pathlib2==2.1.0
47 pathlib2==2.1.0
48 psutil==4.3.1
48 psutil==4.3.1
49 psycopg2==2.6.1
49 psycopg2==2.6.1
50 py-bcrypt==0.4
50 py-bcrypt==0.4
51 pycrypto==2.6.1
51 pycrypto==2.6.1
52 pycurl==7.19.5
52 pycurl==7.19.5
53 pyflakes==0.8.1
53 pyflakes==0.8.1
54 pygments-markdown-lexer==0.1.0.dev39
54 pygments-markdown-lexer==0.1.0.dev39
55 Pygments==2.2.0
55 Pygments==2.2.0
56 pyparsing==1.5.7
56 pyparsing==1.5.7
57 pyramid-beaker==0.8
57 pyramid-beaker==0.8
58 pyramid-debugtoolbar==3.0.5
58 pyramid-debugtoolbar==3.0.5
59 pyramid-jinja2==2.5
59 pyramid-jinja2==2.5
60 pyramid-mako==1.0.2
60 pyramid-mako==1.0.2
61 pyramid==1.7.4
61 pyramid==1.7.4
62 pysqlite==2.6.3
62 pysqlite==2.6.3
63 python-dateutil==2.1
63 python-dateutil==2.1
64 python-ldap==2.4.19
64 python-ldap==2.4.19
65 python-memcached==1.57
65 python-memcached==1.57
66 python-pam==1.8.2
66 python-pam==1.8.2
67 pytz==2015.4
67 pytz==2015.4
68 pyzmq==14.6.0
68 pyzmq==14.6.0
69 recaptcha-client==1.0.6
69 recaptcha-client==1.0.6
70 repoze.lru==0.6
70 repoze.lru==0.6
71 requests==2.9.1
71 requests==2.9.1
72 Routes==1.13
72 Routes==1.13
73 setproctitle==1.1.8
73 setproctitle==1.1.8
74 simplejson==3.7.2
74 simplejson==3.7.2
75 six==1.9.0
75 six==1.9.0
76 Sphinx==1.2.2
76 Sphinx==1.2.2
77 SQLAlchemy==0.9.9
77 SQLAlchemy==0.9.9
78 subprocess32==3.2.6
78 subprocess32==3.2.6
79 supervisor==3.3.1
79 supervisor==3.3.1
80 Tempita==0.5.2
80 Tempita==0.5.2
81 translationstring==1.3
81 translationstring==1.3
82 trollius==1.0.4
82 trollius==1.0.4
83 urllib3==1.16
83 urllib3==1.16
84 URLObject==2.4.0
84 URLObject==2.4.0
85 venusian==1.0
85 venusian==1.0
86 WebError==0.10.3
86 WebError==0.10.3
87 WebHelpers2==2.0
87 WebHelpers2==2.0
88 WebHelpers==1.3
88 WebHelpers==1.3
89 WebOb==1.3.1
89 WebOb==1.3.1
90 Whoosh==2.7.4
90 Whoosh==2.7.4
91 wsgiref==0.1.2
91 wsgiref==0.1.2
92 zope.cachedescriptors==4.0.0
92 zope.cachedescriptors==4.0.0
93 zope.deprecation==4.1.2
93 zope.deprecation==4.1.2
94 zope.event==4.0.3
94 zope.event==4.0.3
95 zope.interface==4.1.3
95 zope.interface==4.1.3
96
96
97 ## customized/patched libs
97 ## customized/patched libs
98 # our patched version of Pylons==1.0.2
98 # our patched version of Pylons==1.0.2
99 https://code.rhodecode.com/upstream/pylons/archive/707354ee4261b9c10450404fc9852ccea4fd667d.tar.gz?md5=f26633726fa2cd3a340316ee6a5d218f#egg=Pylons==1.0.2.rhodecode-patch-1
99 https://code.rhodecode.com/upstream/pylons/archive/707354ee4261b9c10450404fc9852ccea4fd667d.tar.gz?md5=f26633726fa2cd3a340316ee6a5d218f#egg=Pylons==1.0.2.rhodecode-patch-1
100 # not released py-gfm==0.1.3
100 # not released py-gfm==0.1.3
101 https://code.rhodecode.com/upstream/py-gfm/archive/0d66a19bc16e3d49de273c0f797d4e4781e8c0f2.tar.gz?md5=0d0d5385bfb629eea636a80b9c2bfd16#egg=py-gfm==0.1.3.rhodecode-upstream1
101 https://code.rhodecode.com/upstream/py-gfm/archive/0d66a19bc16e3d49de273c0f797d4e4781e8c0f2.tar.gz?md5=0d0d5385bfb629eea636a80b9c2bfd16#egg=py-gfm==0.1.3.rhodecode-upstream1
102
102
103 # IPYTHON RENDERING
103 # IPYTHON RENDERING
104 # entrypoints backport, pypi version doesn't support egg installs
104 # entrypoints backport, pypi version doesn't support egg installs
105 https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1
105 https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1
106 nbconvert==5.1.1
106 nbconvert==5.1.1
107 nbformat==4.3.0
107 nbformat==4.3.0
108 jupyter_client==5.0.0
108 jupyter_client==5.0.0
109
109
110 ## cli tools
110 ## cli tools
111 alembic==0.8.4
111 alembic==0.8.4
112 invoke==0.13.0
112 invoke==0.13.0
113 bumpversion==0.5.3
113 bumpversion==0.5.3
114 transifex-client==0.10
114 transifex-client==0.10
115
115
116 ## http servers
116 ## http servers
117 gevent==1.1.2
117 gevent==1.1.2
118 greenlet==0.4.10
118 greenlet==0.4.10
119 gunicorn==19.6.0
119 gunicorn==19.6.0
120 waitress==1.0.1
120 waitress==1.0.1
121 uWSGI==2.0.11.2
121 uWSGI==2.0.11.2
122
122
123 ## debug
123 ## debug
124 ipdb==0.10.1
124 ipdb==0.10.1
125 ipython==5.1.0
125 ipython==5.1.0
126 CProfileV==1.0.6
126 CProfileV==1.0.6
127 bottle==0.12.8
127 bottle==0.12.8
128
128
129 ## rhodecode-tools, special case
129 ## rhodecode-tools, special case
130 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.12.0.tar.gz?md5=9ca040356fa7e38d3f64529a4cffdca4#egg=rhodecode-tools==0.12.0
130 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.12.0.tar.gz?md5=9ca040356fa7e38d3f64529a4cffdca4#egg=rhodecode-tools==0.12.0
131
131
132 ## appenlight
132 ## appenlight
133 appenlight-client==0.6.14
133 appenlight-client==0.6.14
134
134
135 ## test related requirements
135 ## test related requirements
136 -r requirements_test.txt
136 -r requirements_test.txt
@@ -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
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 71 # defines current db version for migrations
54 __dbversion__ = 78 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,536 +1,542 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import inspect
21 import inspect
22 import itertools
22 import itertools
23 import logging
23 import logging
24 import types
24 import types
25 import fnmatch
25 import fnmatch
26
26
27 import decorator
27 import decorator
28 import venusian
28 import venusian
29 from collections import OrderedDict
29 from collections import OrderedDict
30
30
31 from pyramid.exceptions import ConfigurationError
31 from pyramid.exceptions import ConfigurationError
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34 from pyramid.httpexceptions import HTTPNotFound
34 from pyramid.httpexceptions import HTTPNotFound
35
35
36 from rhodecode.api.exc import (
36 from rhodecode.api.exc import (
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.db import User, UserApiKeys
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_URL = '/_admin/apiv2'
49 DEFAULT_URL = '/_admin/apiv2'
49
50
50
51
51 def find_methods(jsonrpc_methods, pattern):
52 def find_methods(jsonrpc_methods, pattern):
52 matches = OrderedDict()
53 matches = OrderedDict()
53 if not isinstance(pattern, (list, tuple)):
54 if not isinstance(pattern, (list, tuple)):
54 pattern = [pattern]
55 pattern = [pattern]
55
56
56 for single_pattern in pattern:
57 for single_pattern in pattern:
57 for method_name, method in jsonrpc_methods.items():
58 for method_name, method in jsonrpc_methods.items():
58 if fnmatch.fnmatch(method_name, single_pattern):
59 if fnmatch.fnmatch(method_name, single_pattern):
59 matches[method_name] = method
60 matches[method_name] = method
60 return matches
61 return matches
61
62
62
63
63 class ExtJsonRenderer(object):
64 class ExtJsonRenderer(object):
64 """
65 """
65 Custom renderer that mkaes use of our ext_json lib
66 Custom renderer that mkaes use of our ext_json lib
66
67
67 """
68 """
68
69
69 def __init__(self, serializer=json.dumps, **kw):
70 def __init__(self, serializer=json.dumps, **kw):
70 """ Any keyword arguments will be passed to the ``serializer``
71 """ Any keyword arguments will be passed to the ``serializer``
71 function."""
72 function."""
72 self.serializer = serializer
73 self.serializer = serializer
73 self.kw = kw
74 self.kw = kw
74
75
75 def __call__(self, info):
76 def __call__(self, info):
76 """ Returns a plain JSON-encoded string with content-type
77 """ Returns a plain JSON-encoded string with content-type
77 ``application/json``. The content-type may be overridden by
78 ``application/json``. The content-type may be overridden by
78 setting ``request.response.content_type``."""
79 setting ``request.response.content_type``."""
79
80
80 def _render(value, system):
81 def _render(value, system):
81 request = system.get('request')
82 request = system.get('request')
82 if request is not None:
83 if request is not None:
83 response = request.response
84 response = request.response
84 ct = response.content_type
85 ct = response.content_type
85 if ct == response.default_content_type:
86 if ct == response.default_content_type:
86 response.content_type = 'application/json'
87 response.content_type = 'application/json'
87
88
88 return self.serializer(value, **self.kw)
89 return self.serializer(value, **self.kw)
89
90
90 return _render
91 return _render
91
92
92
93
93 def jsonrpc_response(request, result):
94 def jsonrpc_response(request, result):
94 rpc_id = getattr(request, 'rpc_id', None)
95 rpc_id = getattr(request, 'rpc_id', None)
95 response = request.response
96 response = request.response
96
97
97 # store content_type before render is called
98 # store content_type before render is called
98 ct = response.content_type
99 ct = response.content_type
99
100
100 ret_value = ''
101 ret_value = ''
101 if rpc_id:
102 if rpc_id:
102 ret_value = {
103 ret_value = {
103 'id': rpc_id,
104 'id': rpc_id,
104 'result': result,
105 'result': result,
105 'error': None,
106 'error': None,
106 }
107 }
107
108
108 # fetch deprecation warnings, and store it inside results
109 # fetch deprecation warnings, and store it inside results
109 deprecation = getattr(request, 'rpc_deprecation', None)
110 deprecation = getattr(request, 'rpc_deprecation', None)
110 if deprecation:
111 if deprecation:
111 ret_value['DEPRECATION_WARNING'] = deprecation
112 ret_value['DEPRECATION_WARNING'] = deprecation
112
113
113 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
114 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
114 response.body = safe_str(raw_body, response.charset)
115 response.body = safe_str(raw_body, response.charset)
115
116
116 if ct == response.default_content_type:
117 if ct == response.default_content_type:
117 response.content_type = 'application/json'
118 response.content_type = 'application/json'
118
119
119 return response
120 return response
120
121
121
122
122 def jsonrpc_error(request, message, retid=None, code=None):
123 def jsonrpc_error(request, message, retid=None, code=None):
123 """
124 """
124 Generate a Response object with a JSON-RPC error body
125 Generate a Response object with a JSON-RPC error body
125
126
126 :param code:
127 :param code:
127 :param retid:
128 :param retid:
128 :param message:
129 :param message:
129 """
130 """
130 err_dict = {'id': retid, 'result': None, 'error': message}
131 err_dict = {'id': retid, 'result': None, 'error': message}
131 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
132 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
132 return Response(
133 return Response(
133 body=body,
134 body=body,
134 status=code,
135 status=code,
135 content_type='application/json'
136 content_type='application/json'
136 )
137 )
137
138
138
139
139 def exception_view(exc, request):
140 def exception_view(exc, request):
140 rpc_id = getattr(request, 'rpc_id', None)
141 rpc_id = getattr(request, 'rpc_id', None)
141
142
142 fault_message = 'undefined error'
143 fault_message = 'undefined error'
143 if isinstance(exc, JSONRPCError):
144 if isinstance(exc, JSONRPCError):
144 fault_message = exc.message
145 fault_message = exc.message
145 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
146 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
146 elif isinstance(exc, JSONRPCValidationError):
147 elif isinstance(exc, JSONRPCValidationError):
147 colander_exc = exc.colander_exception
148 colander_exc = exc.colander_exception
148 # TODO(marcink): think maybe of nicer way to serialize errors ?
149 # TODO(marcink): think maybe of nicer way to serialize errors ?
149 fault_message = colander_exc.asdict()
150 fault_message = colander_exc.asdict()
150 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
151 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
151 elif isinstance(exc, JSONRPCForbidden):
152 elif isinstance(exc, JSONRPCForbidden):
152 fault_message = 'Access was denied to this resource.'
153 fault_message = 'Access was denied to this resource.'
153 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
154 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
154 elif isinstance(exc, HTTPNotFound):
155 elif isinstance(exc, HTTPNotFound):
155 method = request.rpc_method
156 method = request.rpc_method
156 log.debug('json-rpc method `%s` not found in list of '
157 log.debug('json-rpc method `%s` not found in list of '
157 'api calls: %s, rpc_id:%s',
158 'api calls: %s, rpc_id:%s',
158 method, request.registry.jsonrpc_methods.keys(), rpc_id)
159 method, request.registry.jsonrpc_methods.keys(), rpc_id)
159
160
160 similar = 'none'
161 similar = 'none'
161 try:
162 try:
162 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
163 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
163 similar_found = find_methods(
164 similar_found = find_methods(
164 request.registry.jsonrpc_methods, similar_paterns)
165 request.registry.jsonrpc_methods, similar_paterns)
165 similar = ', '.join(similar_found.keys()) or similar
166 similar = ', '.join(similar_found.keys()) or similar
166 except Exception:
167 except Exception:
167 # make the whole above block safe
168 # make the whole above block safe
168 pass
169 pass
169
170
170 fault_message = "No such method: {}. Similar methods: {}".format(
171 fault_message = "No such method: {}. Similar methods: {}".format(
171 method, similar)
172 method, similar)
172
173
173 return jsonrpc_error(request, fault_message, rpc_id)
174 return jsonrpc_error(request, fault_message, rpc_id)
174
175
175
176
176 def request_view(request):
177 def request_view(request):
177 """
178 """
178 Main request handling method. It handles all logic to call a specific
179 Main request handling method. It handles all logic to call a specific
179 exposed method
180 exposed method
180 """
181 """
181
182
182 # check if we can find this session using api_key, get_by_auth_token
183 # check if we can find this session using api_key, get_by_auth_token
183 # search not expired tokens only
184 # search not expired tokens only
184
185
185 try:
186 try:
186 api_user = User.get_by_auth_token(request.rpc_api_key)
187 api_user = User.get_by_auth_token(request.rpc_api_key)
187
188
188 if api_user is None:
189 if api_user is None:
189 return jsonrpc_error(
190 return jsonrpc_error(
190 request, retid=request.rpc_id, message='Invalid API KEY')
191 request, retid=request.rpc_id, message='Invalid API KEY')
191
192
192 if not api_user.active:
193 if not api_user.active:
193 return jsonrpc_error(
194 return jsonrpc_error(
194 request, retid=request.rpc_id,
195 request, retid=request.rpc_id,
195 message='Request from this user not allowed')
196 message='Request from this user not allowed')
196
197
197 # check if we are allowed to use this IP
198 # check if we are allowed to use this IP
198 auth_u = AuthUser(
199 auth_u = AuthUser(
199 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
200 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
200 if not auth_u.ip_allowed:
201 if not auth_u.ip_allowed:
201 return jsonrpc_error(
202 return jsonrpc_error(
202 request, retid=request.rpc_id,
203 request, retid=request.rpc_id,
203 message='Request from IP:%s not allowed' % (
204 message='Request from IP:%s not allowed' % (
204 request.rpc_ip_addr,))
205 request.rpc_ip_addr,))
205 else:
206 else:
206 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
207 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
207
208
208 # register our auth-user
209 # register our auth-user
209 request.rpc_user = auth_u
210 request.rpc_user = auth_u
210
211
211 # now check if token is valid for API
212 # now check if token is valid for API
212 auth_token = request.rpc_api_key
213 auth_token = request.rpc_api_key
213 token_match = api_user.authenticate_by_token(
214 token_match = api_user.authenticate_by_token(
214 auth_token, roles=[UserApiKeys.ROLE_API])
215 auth_token, roles=[UserApiKeys.ROLE_API])
215 invalid_token = not token_match
216 invalid_token = not token_match
216
217
217 log.debug('Checking if API KEY is valid with proper role')
218 log.debug('Checking if API KEY is valid with proper role')
218 if invalid_token:
219 if invalid_token:
219 return jsonrpc_error(
220 return jsonrpc_error(
220 request, retid=request.rpc_id,
221 request, retid=request.rpc_id,
221 message='API KEY invalid or, has bad role for an API call')
222 message='API KEY invalid or, has bad role for an API call')
222
223
223 except Exception:
224 except Exception:
224 log.exception('Error on API AUTH')
225 log.exception('Error on API AUTH')
225 return jsonrpc_error(
226 return jsonrpc_error(
226 request, retid=request.rpc_id, message='Invalid API KEY')
227 request, retid=request.rpc_id, message='Invalid API KEY')
227
228
228 method = request.rpc_method
229 method = request.rpc_method
229 func = request.registry.jsonrpc_methods[method]
230 func = request.registry.jsonrpc_methods[method]
230
231
231 # now that we have a method, add request._req_params to
232 # now that we have a method, add request._req_params to
232 # self.kargs and dispatch control to WGIController
233 # self.kargs and dispatch control to WGIController
233 argspec = inspect.getargspec(func)
234 argspec = inspect.getargspec(func)
234 arglist = argspec[0]
235 arglist = argspec[0]
235 defaults = map(type, argspec[3] or [])
236 defaults = map(type, argspec[3] or [])
236 default_empty = types.NotImplementedType
237 default_empty = types.NotImplementedType
237
238
238 # kw arguments required by this method
239 # kw arguments required by this method
239 func_kwargs = dict(itertools.izip_longest(
240 func_kwargs = dict(itertools.izip_longest(
240 reversed(arglist), reversed(defaults), fillvalue=default_empty))
241 reversed(arglist), reversed(defaults), fillvalue=default_empty))
241
242
242 # This attribute will need to be first param of a method that uses
243 # This attribute will need to be first param of a method that uses
243 # api_key, which is translated to instance of user at that name
244 # api_key, which is translated to instance of user at that name
244 user_var = 'apiuser'
245 user_var = 'apiuser'
245 request_var = 'request'
246 request_var = 'request'
246
247
247 for arg in [user_var, request_var]:
248 for arg in [user_var, request_var]:
248 if arg not in arglist:
249 if arg not in arglist:
249 return jsonrpc_error(
250 return jsonrpc_error(
250 request,
251 request,
251 retid=request.rpc_id,
252 retid=request.rpc_id,
252 message='This method [%s] does not support '
253 message='This method [%s] does not support '
253 'required parameter `%s`' % (func.__name__, arg))
254 'required parameter `%s`' % (func.__name__, arg))
254
255
255 # get our arglist and check if we provided them as args
256 # get our arglist and check if we provided them as args
256 for arg, default in func_kwargs.items():
257 for arg, default in func_kwargs.items():
257 if arg in [user_var, request_var]:
258 if arg in [user_var, request_var]:
258 # user_var and request_var are pre-hardcoded parameters and we
259 # user_var and request_var are pre-hardcoded parameters and we
259 # don't need to do any translation
260 # don't need to do any translation
260 continue
261 continue
261
262
262 # skip the required param check if it's default value is
263 # skip the required param check if it's default value is
263 # NotImplementedType (default_empty)
264 # NotImplementedType (default_empty)
264 if default == default_empty and arg not in request.rpc_params:
265 if default == default_empty and arg not in request.rpc_params:
265 return jsonrpc_error(
266 return jsonrpc_error(
266 request,
267 request,
267 retid=request.rpc_id,
268 retid=request.rpc_id,
268 message=('Missing non optional `%s` arg in JSON DATA' % arg)
269 message=('Missing non optional `%s` arg in JSON DATA' % arg)
269 )
270 )
270
271
271 # sanitize extra passed arguments
272 # sanitize extra passed arguments
272 for k in request.rpc_params.keys()[:]:
273 for k in request.rpc_params.keys()[:]:
273 if k not in func_kwargs:
274 if k not in func_kwargs:
274 del request.rpc_params[k]
275 del request.rpc_params[k]
275
276
276 call_params = request.rpc_params
277 call_params = request.rpc_params
277 call_params.update({
278 call_params.update({
278 'request': request,
279 'request': request,
279 'apiuser': auth_u
280 'apiuser': auth_u
280 })
281 })
282
283 # register some common functions for usage
284 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id,
285 attach_to_request=True)
286
281 try:
287 try:
282 ret_value = func(**call_params)
288 ret_value = func(**call_params)
283 return jsonrpc_response(request, ret_value)
289 return jsonrpc_response(request, ret_value)
284 except JSONRPCBaseError:
290 except JSONRPCBaseError:
285 raise
291 raise
286 except Exception:
292 except Exception:
287 log.exception('Unhandled exception occurred on api call: %s', func)
293 log.exception('Unhandled exception occurred on api call: %s', func)
288 return jsonrpc_error(request, retid=request.rpc_id,
294 return jsonrpc_error(request, retid=request.rpc_id,
289 message='Internal server error')
295 message='Internal server error')
290
296
291
297
292 def setup_request(request):
298 def setup_request(request):
293 """
299 """
294 Parse a JSON-RPC request body. It's used inside the predicates method
300 Parse a JSON-RPC request body. It's used inside the predicates method
295 to validate and bootstrap requests for usage in rpc calls.
301 to validate and bootstrap requests for usage in rpc calls.
296
302
297 We need to raise JSONRPCError here if we want to return some errors back to
303 We need to raise JSONRPCError here if we want to return some errors back to
298 user.
304 user.
299 """
305 """
300
306
301 log.debug('Executing setup request: %r', request)
307 log.debug('Executing setup request: %r', request)
302 request.rpc_ip_addr = get_ip_addr(request.environ)
308 request.rpc_ip_addr = get_ip_addr(request.environ)
303 # TODO(marcink): deprecate GET at some point
309 # TODO(marcink): deprecate GET at some point
304 if request.method not in ['POST', 'GET']:
310 if request.method not in ['POST', 'GET']:
305 log.debug('unsupported request method "%s"', request.method)
311 log.debug('unsupported request method "%s"', request.method)
306 raise JSONRPCError(
312 raise JSONRPCError(
307 'unsupported request method "%s". Please use POST' % request.method)
313 'unsupported request method "%s". Please use POST' % request.method)
308
314
309 if 'CONTENT_LENGTH' not in request.environ:
315 if 'CONTENT_LENGTH' not in request.environ:
310 log.debug("No Content-Length")
316 log.debug("No Content-Length")
311 raise JSONRPCError("Empty body, No Content-Length in request")
317 raise JSONRPCError("Empty body, No Content-Length in request")
312
318
313 else:
319 else:
314 length = request.environ['CONTENT_LENGTH']
320 length = request.environ['CONTENT_LENGTH']
315 log.debug('Content-Length: %s', length)
321 log.debug('Content-Length: %s', length)
316
322
317 if length == 0:
323 if length == 0:
318 log.debug("Content-Length is 0")
324 log.debug("Content-Length is 0")
319 raise JSONRPCError("Content-Length is 0")
325 raise JSONRPCError("Content-Length is 0")
320
326
321 raw_body = request.body
327 raw_body = request.body
322 try:
328 try:
323 json_body = json.loads(raw_body)
329 json_body = json.loads(raw_body)
324 except ValueError as e:
330 except ValueError as e:
325 # catch JSON errors Here
331 # catch JSON errors Here
326 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
332 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
327
333
328 request.rpc_id = json_body.get('id')
334 request.rpc_id = json_body.get('id')
329 request.rpc_method = json_body.get('method')
335 request.rpc_method = json_body.get('method')
330
336
331 # check required base parameters
337 # check required base parameters
332 try:
338 try:
333 api_key = json_body.get('api_key')
339 api_key = json_body.get('api_key')
334 if not api_key:
340 if not api_key:
335 api_key = json_body.get('auth_token')
341 api_key = json_body.get('auth_token')
336
342
337 if not api_key:
343 if not api_key:
338 raise KeyError('api_key or auth_token')
344 raise KeyError('api_key or auth_token')
339
345
340 # TODO(marcink): support passing in token in request header
346 # TODO(marcink): support passing in token in request header
341
347
342 request.rpc_api_key = api_key
348 request.rpc_api_key = api_key
343 request.rpc_id = json_body['id']
349 request.rpc_id = json_body['id']
344 request.rpc_method = json_body['method']
350 request.rpc_method = json_body['method']
345 request.rpc_params = json_body['args'] \
351 request.rpc_params = json_body['args'] \
346 if isinstance(json_body['args'], dict) else {}
352 if isinstance(json_body['args'], dict) else {}
347
353
348 log.debug(
354 log.debug(
349 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
355 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
350 except KeyError as e:
356 except KeyError as e:
351 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
357 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
352
358
353 log.debug('setup complete, now handling method:%s rpcid:%s',
359 log.debug('setup complete, now handling method:%s rpcid:%s',
354 request.rpc_method, request.rpc_id, )
360 request.rpc_method, request.rpc_id, )
355
361
356
362
357 class RoutePredicate(object):
363 class RoutePredicate(object):
358 def __init__(self, val, config):
364 def __init__(self, val, config):
359 self.val = val
365 self.val = val
360
366
361 def text(self):
367 def text(self):
362 return 'jsonrpc route = %s' % self.val
368 return 'jsonrpc route = %s' % self.val
363
369
364 phash = text
370 phash = text
365
371
366 def __call__(self, info, request):
372 def __call__(self, info, request):
367 if self.val:
373 if self.val:
368 # potentially setup and bootstrap our call
374 # potentially setup and bootstrap our call
369 setup_request(request)
375 setup_request(request)
370
376
371 # Always return True so that even if it isn't a valid RPC it
377 # Always return True so that even if it isn't a valid RPC it
372 # will fall through to the underlaying handlers like notfound_view
378 # will fall through to the underlaying handlers like notfound_view
373 return True
379 return True
374
380
375
381
376 class NotFoundPredicate(object):
382 class NotFoundPredicate(object):
377 def __init__(self, val, config):
383 def __init__(self, val, config):
378 self.val = val
384 self.val = val
379 self.methods = config.registry.jsonrpc_methods
385 self.methods = config.registry.jsonrpc_methods
380
386
381 def text(self):
387 def text(self):
382 return 'jsonrpc method not found = {}.'.format(self.val)
388 return 'jsonrpc method not found = {}.'.format(self.val)
383
389
384 phash = text
390 phash = text
385
391
386 def __call__(self, info, request):
392 def __call__(self, info, request):
387 return hasattr(request, 'rpc_method')
393 return hasattr(request, 'rpc_method')
388
394
389
395
390 class MethodPredicate(object):
396 class MethodPredicate(object):
391 def __init__(self, val, config):
397 def __init__(self, val, config):
392 self.method = val
398 self.method = val
393
399
394 def text(self):
400 def text(self):
395 return 'jsonrpc method = %s' % self.method
401 return 'jsonrpc method = %s' % self.method
396
402
397 phash = text
403 phash = text
398
404
399 def __call__(self, context, request):
405 def __call__(self, context, request):
400 # we need to explicitly return False here, so pyramid doesn't try to
406 # we need to explicitly return False here, so pyramid doesn't try to
401 # execute our view directly. We need our main handler to execute things
407 # execute our view directly. We need our main handler to execute things
402 return getattr(request, 'rpc_method') == self.method
408 return getattr(request, 'rpc_method') == self.method
403
409
404
410
405 def add_jsonrpc_method(config, view, **kwargs):
411 def add_jsonrpc_method(config, view, **kwargs):
406 # pop the method name
412 # pop the method name
407 method = kwargs.pop('method', None)
413 method = kwargs.pop('method', None)
408
414
409 if method is None:
415 if method is None:
410 raise ConfigurationError(
416 raise ConfigurationError(
411 'Cannot register a JSON-RPC method without specifying the '
417 'Cannot register a JSON-RPC method without specifying the '
412 '"method"')
418 '"method"')
413
419
414 # we define custom predicate, to enable to detect conflicting methods,
420 # we define custom predicate, to enable to detect conflicting methods,
415 # those predicates are kind of "translation" from the decorator variables
421 # those predicates are kind of "translation" from the decorator variables
416 # to internal predicates names
422 # to internal predicates names
417
423
418 kwargs['jsonrpc_method'] = method
424 kwargs['jsonrpc_method'] = method
419
425
420 # register our view into global view store for validation
426 # register our view into global view store for validation
421 config.registry.jsonrpc_methods[method] = view
427 config.registry.jsonrpc_methods[method] = view
422
428
423 # we're using our main request_view handler, here, so each method
429 # we're using our main request_view handler, here, so each method
424 # has a unified handler for itself
430 # has a unified handler for itself
425 config.add_view(request_view, route_name='apiv2', **kwargs)
431 config.add_view(request_view, route_name='apiv2', **kwargs)
426
432
427
433
428 class jsonrpc_method(object):
434 class jsonrpc_method(object):
429 """
435 """
430 decorator that works similar to @add_view_config decorator,
436 decorator that works similar to @add_view_config decorator,
431 but tailored for our JSON RPC
437 but tailored for our JSON RPC
432 """
438 """
433
439
434 venusian = venusian # for testing injection
440 venusian = venusian # for testing injection
435
441
436 def __init__(self, method=None, **kwargs):
442 def __init__(self, method=None, **kwargs):
437 self.method = method
443 self.method = method
438 self.kwargs = kwargs
444 self.kwargs = kwargs
439
445
440 def __call__(self, wrapped):
446 def __call__(self, wrapped):
441 kwargs = self.kwargs.copy()
447 kwargs = self.kwargs.copy()
442 kwargs['method'] = self.method or wrapped.__name__
448 kwargs['method'] = self.method or wrapped.__name__
443 depth = kwargs.pop('_depth', 0)
449 depth = kwargs.pop('_depth', 0)
444
450
445 def callback(context, name, ob):
451 def callback(context, name, ob):
446 config = context.config.with_package(info.module)
452 config = context.config.with_package(info.module)
447 config.add_jsonrpc_method(view=ob, **kwargs)
453 config.add_jsonrpc_method(view=ob, **kwargs)
448
454
449 info = venusian.attach(wrapped, callback, category='pyramid',
455 info = venusian.attach(wrapped, callback, category='pyramid',
450 depth=depth + 1)
456 depth=depth + 1)
451 if info.scope == 'class':
457 if info.scope == 'class':
452 # ensure that attr is set if decorating a class method
458 # ensure that attr is set if decorating a class method
453 kwargs.setdefault('attr', wrapped.__name__)
459 kwargs.setdefault('attr', wrapped.__name__)
454
460
455 kwargs['_info'] = info.codeinfo # fbo action_method
461 kwargs['_info'] = info.codeinfo # fbo action_method
456 return wrapped
462 return wrapped
457
463
458
464
459 class jsonrpc_deprecated_method(object):
465 class jsonrpc_deprecated_method(object):
460 """
466 """
461 Marks method as deprecated, adds log.warning, and inject special key to
467 Marks method as deprecated, adds log.warning, and inject special key to
462 the request variable to mark method as deprecated.
468 the request variable to mark method as deprecated.
463 Also injects special docstring that extract_docs will catch to mark
469 Also injects special docstring that extract_docs will catch to mark
464 method as deprecated.
470 method as deprecated.
465
471
466 :param use_method: specify which method should be used instead of
472 :param use_method: specify which method should be used instead of
467 the decorated one
473 the decorated one
468
474
469 Use like::
475 Use like::
470
476
471 @jsonrpc_method()
477 @jsonrpc_method()
472 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
478 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
473 def old_func(request, apiuser, arg1, arg2):
479 def old_func(request, apiuser, arg1, arg2):
474 ...
480 ...
475 """
481 """
476
482
477 def __init__(self, use_method, deprecated_at_version):
483 def __init__(self, use_method, deprecated_at_version):
478 self.use_method = use_method
484 self.use_method = use_method
479 self.deprecated_at_version = deprecated_at_version
485 self.deprecated_at_version = deprecated_at_version
480 self.deprecated_msg = ''
486 self.deprecated_msg = ''
481
487
482 def __call__(self, func):
488 def __call__(self, func):
483 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
489 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
484 method=self.use_method)
490 method=self.use_method)
485
491
486 docstring = """\n
492 docstring = """\n
487 .. deprecated:: {version}
493 .. deprecated:: {version}
488
494
489 {deprecation_message}
495 {deprecation_message}
490
496
491 {original_docstring}
497 {original_docstring}
492 """
498 """
493 func.__doc__ = docstring.format(
499 func.__doc__ = docstring.format(
494 version=self.deprecated_at_version,
500 version=self.deprecated_at_version,
495 deprecation_message=self.deprecated_msg,
501 deprecation_message=self.deprecated_msg,
496 original_docstring=func.__doc__)
502 original_docstring=func.__doc__)
497 return decorator.decorator(self.__wrapper, func)
503 return decorator.decorator(self.__wrapper, func)
498
504
499 def __wrapper(self, func, *fargs, **fkwargs):
505 def __wrapper(self, func, *fargs, **fkwargs):
500 log.warning('DEPRECATED API CALL on function %s, please '
506 log.warning('DEPRECATED API CALL on function %s, please '
501 'use `%s` instead', func, self.use_method)
507 'use `%s` instead', func, self.use_method)
502 # alter function docstring to mark as deprecated, this is picked up
508 # alter function docstring to mark as deprecated, this is picked up
503 # via fabric file that generates API DOC.
509 # via fabric file that generates API DOC.
504 result = func(*fargs, **fkwargs)
510 result = func(*fargs, **fkwargs)
505
511
506 request = fargs[0]
512 request = fargs[0]
507 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
513 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
508 return result
514 return result
509
515
510
516
511 def includeme(config):
517 def includeme(config):
512 plugin_module = 'rhodecode.api'
518 plugin_module = 'rhodecode.api'
513 plugin_settings = get_plugin_settings(
519 plugin_settings = get_plugin_settings(
514 plugin_module, config.registry.settings)
520 plugin_module, config.registry.settings)
515
521
516 if not hasattr(config.registry, 'jsonrpc_methods'):
522 if not hasattr(config.registry, 'jsonrpc_methods'):
517 config.registry.jsonrpc_methods = OrderedDict()
523 config.registry.jsonrpc_methods = OrderedDict()
518
524
519 # match filter by given method only
525 # match filter by given method only
520 config.add_view_predicate('jsonrpc_method', MethodPredicate)
526 config.add_view_predicate('jsonrpc_method', MethodPredicate)
521
527
522 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
528 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
523 serializer=json.dumps, indent=4))
529 serializer=json.dumps, indent=4))
524 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
530 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
525
531
526 config.add_route_predicate(
532 config.add_route_predicate(
527 'jsonrpc_call', RoutePredicate)
533 'jsonrpc_call', RoutePredicate)
528
534
529 config.add_route(
535 config.add_route(
530 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
536 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
531
537
532 config.scan(plugin_module, ignore='rhodecode.api.tests')
538 config.scan(plugin_module, ignore='rhodecode.api.tests')
533 # register some exception handling view
539 # register some exception handling view
534 config.add_view(exception_view, context=JSONRPCBaseError)
540 config.add_view(exception_view, context=JSONRPCBaseError)
535 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
541 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
536 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
542 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,112 +1,112 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog
23 from rhodecode.model.db import UserLog
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestClosePullRequest(object):
31 class TestClosePullRequest(object):
32
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_close_pull_request(self, pr_util):
34 def test_api_close_pull_request(self, pr_util):
35 pull_request = pr_util.create_pull_request()
35 pull_request = pr_util.create_pull_request()
36 pull_request_id = pull_request.pull_request_id
36 pull_request_id = pull_request.pull_request_id
37 author = pull_request.user_id
37 author = pull_request.user_id
38 repo = pull_request.target_repo.repo_id
38 repo = pull_request.target_repo.repo_id
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'close_pull_request',
40 self.apikey, 'close_pull_request',
41 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44 expected = {
44 expected = {
45 'pull_request_id': pull_request_id,
45 'pull_request_id': pull_request_id,
46 'close_status': 'Rejected',
46 'closed': True,
47 'closed': True,
47 }
48 }
48 assert_ok(id_, expected, response.body)
49 assert_ok(id_, expected, response.body)
49 action = 'user_closed_pull_request:%d' % pull_request_id
50 journal = UserLog.query()\
50 journal = UserLog.query()\
51 .filter(UserLog.user_id == author)\
51 .filter(UserLog.user_id == author) \
52 .order_by('user_log_id') \
52 .filter(UserLog.repository_id == repo)\
53 .filter(UserLog.repository_id == repo)\
53 .filter(UserLog.action == action)\
54 .all()
54 .all()
55 assert len(journal) == 1
55 assert journal[-1].action == 'repo.pull_request.close'
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
59 pull_request = pr_util.create_pull_request()
59 pull_request = pr_util.create_pull_request()
60 pull_request_id = pull_request.pull_request_id
60 pull_request_id = pull_request.pull_request_id
61 pull_request_repo = pull_request.target_repo.repo_name
61 pull_request_repo = pull_request.target_repo.repo_name
62 PullRequestModel().close_pull_request(
62 PullRequestModel().close_pull_request(
63 pull_request, pull_request.author)
63 pull_request, pull_request.author)
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'close_pull_request',
65 self.apikey, 'close_pull_request',
66 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 repoid=pull_request_repo, pullrequestid=pull_request_id)
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'pull request `%s` is already closed' % pull_request_id
69 expected = 'pull request `%s` is already closed' % pull_request_id
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
73 def test_api_close_pull_request_repo_error(self):
73 def test_api_close_pull_request_repo_error(self):
74 id_, params = build_data(
74 id_, params = build_data(
75 self.apikey, 'close_pull_request',
75 self.apikey, 'close_pull_request',
76 repoid=666, pullrequestid=1)
76 repoid=666, pullrequestid=1)
77 response = api_call(self.app, params)
77 response = api_call(self.app, params)
78
78
79 expected = 'repository `666` does not exist'
79 expected = 'repository `666` does not exist'
80 assert_error(id_, expected, given=response.body)
80 assert_error(id_, expected, given=response.body)
81
81
82 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
83 def test_api_close_pull_request_non_admin_with_userid_error(self,
83 def test_api_close_pull_request_non_admin_with_userid_error(self,
84 pr_util):
84 pr_util):
85 pull_request = pr_util.create_pull_request()
85 pull_request = pr_util.create_pull_request()
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey_regular, 'close_pull_request',
87 self.apikey_regular, 'close_pull_request',
88 repoid=pull_request.target_repo.repo_name,
88 repoid=pull_request.target_repo.repo_name,
89 pullrequestid=pull_request.pull_request_id,
89 pullrequestid=pull_request.pull_request_id,
90 userid=TEST_USER_ADMIN_LOGIN)
90 userid=TEST_USER_ADMIN_LOGIN)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92
92
93 expected = 'userid is not the same as your user'
93 expected = 'userid is not the same as your user'
94 assert_error(id_, expected, given=response.body)
94 assert_error(id_, expected, given=response.body)
95
95
96 @pytest.mark.backends("git", "hg")
96 @pytest.mark.backends("git", "hg")
97 def test_api_close_pull_request_no_perms_to_close(
97 def test_api_close_pull_request_no_perms_to_close(
98 self, user_util, pr_util):
98 self, user_util, pr_util):
99 user = user_util.create_user()
99 user = user_util.create_user()
100 pull_request = pr_util.create_pull_request()
100 pull_request = pr_util.create_pull_request()
101
101
102 id_, params = build_data(
102 id_, params = build_data(
103 user.api_key, 'close_pull_request',
103 user.api_key, 'close_pull_request',
104 repoid=pull_request.target_repo.repo_name,
104 repoid=pull_request.target_repo.repo_name,
105 pullrequestid=pull_request.pull_request_id,)
105 pullrequestid=pull_request.pull_request_id,)
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107
107
108 expected = ('pull request `%s` close failed, '
108 expected = ('pull request `%s` close failed, '
109 'no permission to close.') % pull_request.pull_request_id
109 'no permission to close.') % pull_request.pull_request_id
110
110
111 response_json = response.json['error']
111 response_json = response.json['error']
112 assert response_json == expected
112 assert response_json == expected
@@ -1,209 +1,208 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.comment import CommentsModel
23 from rhodecode.model.comment import CommentsModel
24 from rhodecode.model.db import UserLog
24 from rhodecode.model.db import UserLog
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCommentPullRequest(object):
32 class TestCommentPullRequest(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 @pytest.mark.backends("git", "hg")
41 @pytest.mark.backends("git", "hg")
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 pull_request = pr_util.create_pull_request()
43 pull_request = pr_util.create_pull_request()
44 pull_request_id = pull_request.pull_request_id
44 pull_request_id = pull_request.pull_request_id
45 author = pull_request.user_id
45 author = pull_request.user_id
46 repo = pull_request.target_repo.repo_id
46 repo = pull_request.target_repo.repo_id
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'comment_pull_request',
48 self.apikey, 'comment_pull_request',
49 repoid=pull_request.target_repo.repo_name,
49 repoid=pull_request.target_repo.repo_name,
50 pullrequestid=pull_request.pull_request_id,
50 pullrequestid=pull_request.pull_request_id,
51 message='test message')
51 message='test message')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54
54
55 comments = CommentsModel().get_comments(
55 comments = CommentsModel().get_comments(
56 pull_request.target_repo.repo_id, pull_request=pull_request)
56 pull_request.target_repo.repo_id, pull_request=pull_request)
57
57
58 expected = {
58 expected = {
59 'pull_request_id': pull_request.pull_request_id,
59 'pull_request_id': pull_request.pull_request_id,
60 'comment_id': comments[-1].comment_id,
60 'comment_id': comments[-1].comment_id,
61 'status': {'given': None, 'was_changed': None}
61 'status': {'given': None, 'was_changed': None}
62 }
62 }
63 assert_ok(id_, expected, response.body)
63 assert_ok(id_, expected, response.body)
64
64
65 action = 'user_commented_pull_request:%d' % pull_request_id
66 journal = UserLog.query()\
65 journal = UserLog.query()\
67 .filter(UserLog.user_id == author)\
66 .filter(UserLog.user_id == author)\
68 .filter(UserLog.repository_id == repo)\
67 .filter(UserLog.repository_id == repo) \
69 .filter(UserLog.action == action)\
68 .order_by('user_log_id') \
70 .all()
69 .all()
71 assert len(journal) == 2
70 assert journal[-1].action == 'repo.pull_request.comment.create'
72
71
73 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
74 def test_api_comment_pull_request_change_status(
73 def test_api_comment_pull_request_change_status(
75 self, pr_util, no_notifications):
74 self, pr_util, no_notifications):
76 pull_request = pr_util.create_pull_request()
75 pull_request = pr_util.create_pull_request()
77 pull_request_id = pull_request.pull_request_id
76 pull_request_id = pull_request.pull_request_id
78 id_, params = build_data(
77 id_, params = build_data(
79 self.apikey, 'comment_pull_request',
78 self.apikey, 'comment_pull_request',
80 repoid=pull_request.target_repo.repo_name,
79 repoid=pull_request.target_repo.repo_name,
81 pullrequestid=pull_request.pull_request_id,
80 pullrequestid=pull_request.pull_request_id,
82 status='rejected')
81 status='rejected')
83 response = api_call(self.app, params)
82 response = api_call(self.app, params)
84 pull_request = PullRequestModel().get(pull_request_id)
83 pull_request = PullRequestModel().get(pull_request_id)
85
84
86 comments = CommentsModel().get_comments(
85 comments = CommentsModel().get_comments(
87 pull_request.target_repo.repo_id, pull_request=pull_request)
86 pull_request.target_repo.repo_id, pull_request=pull_request)
88 expected = {
87 expected = {
89 'pull_request_id': pull_request.pull_request_id,
88 'pull_request_id': pull_request.pull_request_id,
90 'comment_id': comments[-1].comment_id,
89 'comment_id': comments[-1].comment_id,
91 'status': {'given': 'rejected', 'was_changed': True}
90 'status': {'given': 'rejected', 'was_changed': True}
92 }
91 }
93 assert_ok(id_, expected, response.body)
92 assert_ok(id_, expected, response.body)
94
93
95 @pytest.mark.backends("git", "hg")
94 @pytest.mark.backends("git", "hg")
96 def test_api_comment_pull_request_change_status_with_specific_commit_id(
95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
97 self, pr_util, no_notifications):
96 self, pr_util, no_notifications):
98 pull_request = pr_util.create_pull_request()
97 pull_request = pr_util.create_pull_request()
99 pull_request_id = pull_request.pull_request_id
98 pull_request_id = pull_request.pull_request_id
100 latest_commit_id = 'test_commit'
99 latest_commit_id = 'test_commit'
101 # inject additional revision, to fail test the status change on
100 # inject additional revision, to fail test the status change on
102 # non-latest commit
101 # non-latest commit
103 pull_request.revisions = pull_request.revisions + ['test_commit']
102 pull_request.revisions = pull_request.revisions + ['test_commit']
104
103
105 id_, params = build_data(
104 id_, params = build_data(
106 self.apikey, 'comment_pull_request',
105 self.apikey, 'comment_pull_request',
107 repoid=pull_request.target_repo.repo_name,
106 repoid=pull_request.target_repo.repo_name,
108 pullrequestid=pull_request.pull_request_id,
107 pullrequestid=pull_request.pull_request_id,
109 status='approved', commit_id=latest_commit_id)
108 status='approved', commit_id=latest_commit_id)
110 response = api_call(self.app, params)
109 response = api_call(self.app, params)
111 pull_request = PullRequestModel().get(pull_request_id)
110 pull_request = PullRequestModel().get(pull_request_id)
112
111
113 expected = {
112 expected = {
114 'pull_request_id': pull_request.pull_request_id,
113 'pull_request_id': pull_request.pull_request_id,
115 'comment_id': None,
114 'comment_id': None,
116 'status': {'given': 'approved', 'was_changed': False}
115 'status': {'given': 'approved', 'was_changed': False}
117 }
116 }
118 assert_ok(id_, expected, response.body)
117 assert_ok(id_, expected, response.body)
119
118
120 @pytest.mark.backends("git", "hg")
119 @pytest.mark.backends("git", "hg")
121 def test_api_comment_pull_request_change_status_with_specific_commit_id(
120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
122 self, pr_util, no_notifications):
121 self, pr_util, no_notifications):
123 pull_request = pr_util.create_pull_request()
122 pull_request = pr_util.create_pull_request()
124 pull_request_id = pull_request.pull_request_id
123 pull_request_id = pull_request.pull_request_id
125 latest_commit_id = pull_request.revisions[0]
124 latest_commit_id = pull_request.revisions[0]
126
125
127 id_, params = build_data(
126 id_, params = build_data(
128 self.apikey, 'comment_pull_request',
127 self.apikey, 'comment_pull_request',
129 repoid=pull_request.target_repo.repo_name,
128 repoid=pull_request.target_repo.repo_name,
130 pullrequestid=pull_request.pull_request_id,
129 pullrequestid=pull_request.pull_request_id,
131 status='approved', commit_id=latest_commit_id)
130 status='approved', commit_id=latest_commit_id)
132 response = api_call(self.app, params)
131 response = api_call(self.app, params)
133 pull_request = PullRequestModel().get(pull_request_id)
132 pull_request = PullRequestModel().get(pull_request_id)
134
133
135 comments = CommentsModel().get_comments(
134 comments = CommentsModel().get_comments(
136 pull_request.target_repo.repo_id, pull_request=pull_request)
135 pull_request.target_repo.repo_id, pull_request=pull_request)
137 expected = {
136 expected = {
138 'pull_request_id': pull_request.pull_request_id,
137 'pull_request_id': pull_request.pull_request_id,
139 'comment_id': comments[-1].comment_id,
138 'comment_id': comments[-1].comment_id,
140 'status': {'given': 'approved', 'was_changed': True}
139 'status': {'given': 'approved', 'was_changed': True}
141 }
140 }
142 assert_ok(id_, expected, response.body)
141 assert_ok(id_, expected, response.body)
143
142
144 @pytest.mark.backends("git", "hg")
143 @pytest.mark.backends("git", "hg")
145 def test_api_comment_pull_request_missing_params_error(self, pr_util):
144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
146 pull_request = pr_util.create_pull_request()
145 pull_request = pr_util.create_pull_request()
147 pull_request_id = pull_request.pull_request_id
146 pull_request_id = pull_request.pull_request_id
148 pull_request_repo = pull_request.target_repo.repo_name
147 pull_request_repo = pull_request.target_repo.repo_name
149 id_, params = build_data(
148 id_, params = build_data(
150 self.apikey, 'comment_pull_request',
149 self.apikey, 'comment_pull_request',
151 repoid=pull_request_repo,
150 repoid=pull_request_repo,
152 pullrequestid=pull_request_id)
151 pullrequestid=pull_request_id)
153 response = api_call(self.app, params)
152 response = api_call(self.app, params)
154
153
155 expected = 'Both message and status parameters are missing. At least one is required.'
154 expected = 'Both message and status parameters are missing. At least one is required.'
156 assert_error(id_, expected, given=response.body)
155 assert_error(id_, expected, given=response.body)
157
156
158 @pytest.mark.backends("git", "hg")
157 @pytest.mark.backends("git", "hg")
159 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
160 pull_request = pr_util.create_pull_request()
159 pull_request = pr_util.create_pull_request()
161 pull_request_id = pull_request.pull_request_id
160 pull_request_id = pull_request.pull_request_id
162 pull_request_repo = pull_request.target_repo.repo_name
161 pull_request_repo = pull_request.target_repo.repo_name
163 id_, params = build_data(
162 id_, params = build_data(
164 self.apikey, 'comment_pull_request',
163 self.apikey, 'comment_pull_request',
165 repoid=pull_request_repo,
164 repoid=pull_request_repo,
166 pullrequestid=pull_request_id,
165 pullrequestid=pull_request_id,
167 status='42')
166 status='42')
168 response = api_call(self.app, params)
167 response = api_call(self.app, params)
169
168
170 expected = 'Unknown comment status: `42`'
169 expected = 'Unknown comment status: `42`'
171 assert_error(id_, expected, given=response.body)
170 assert_error(id_, expected, given=response.body)
172
171
173 @pytest.mark.backends("git", "hg")
172 @pytest.mark.backends("git", "hg")
174 def test_api_comment_pull_request_repo_error(self):
173 def test_api_comment_pull_request_repo_error(self):
175 id_, params = build_data(
174 id_, params = build_data(
176 self.apikey, 'comment_pull_request',
175 self.apikey, 'comment_pull_request',
177 repoid=666, pullrequestid=1)
176 repoid=666, pullrequestid=1)
178 response = api_call(self.app, params)
177 response = api_call(self.app, params)
179
178
180 expected = 'repository `666` does not exist'
179 expected = 'repository `666` does not exist'
181 assert_error(id_, expected, given=response.body)
180 assert_error(id_, expected, given=response.body)
182
181
183 @pytest.mark.backends("git", "hg")
182 @pytest.mark.backends("git", "hg")
184 def test_api_comment_pull_request_non_admin_with_userid_error(
183 def test_api_comment_pull_request_non_admin_with_userid_error(
185 self, pr_util):
184 self, pr_util):
186 pull_request = pr_util.create_pull_request()
185 pull_request = pr_util.create_pull_request()
187 id_, params = build_data(
186 id_, params = build_data(
188 self.apikey_regular, 'comment_pull_request',
187 self.apikey_regular, 'comment_pull_request',
189 repoid=pull_request.target_repo.repo_name,
188 repoid=pull_request.target_repo.repo_name,
190 pullrequestid=pull_request.pull_request_id,
189 pullrequestid=pull_request.pull_request_id,
191 userid=TEST_USER_ADMIN_LOGIN)
190 userid=TEST_USER_ADMIN_LOGIN)
192 response = api_call(self.app, params)
191 response = api_call(self.app, params)
193
192
194 expected = 'userid is not the same as your user'
193 expected = 'userid is not the same as your user'
195 assert_error(id_, expected, given=response.body)
194 assert_error(id_, expected, given=response.body)
196
195
197 @pytest.mark.backends("git", "hg")
196 @pytest.mark.backends("git", "hg")
198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
197 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
199 pull_request = pr_util.create_pull_request()
198 pull_request = pr_util.create_pull_request()
200 id_, params = build_data(
199 id_, params = build_data(
201 self.apikey_regular, 'comment_pull_request',
200 self.apikey_regular, 'comment_pull_request',
202 repoid=pull_request.target_repo.repo_name,
201 repoid=pull_request.target_repo.repo_name,
203 status='approved',
202 status='approved',
204 pullrequestid=pull_request.pull_request_id,
203 pullrequestid=pull_request.pull_request_id,
205 commit_id='XXX')
204 commit_id='XXX')
206 response = api_call(self.app, params)
205 response = api_call(self.app, params)
207
206
208 expected = 'Invalid commit_id `XXX` for this pull request.'
207 expected = 'Invalid commit_id `XXX` for this pull request.'
209 assert_error(id_, expected, given=response.body)
208 assert_error(id_, expected, given=response.body)
@@ -1,279 +1,297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCreatePullRequestApi(object):
32 class TestCreatePullRequestApi(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 def test_create_with_wrong_data(self):
41 def test_create_with_wrong_data(self):
42 required_data = {
42 required_data = {
43 'source_repo': 'tests/source_repo',
43 'source_repo': 'tests/source_repo',
44 'target_repo': 'tests/target_repo',
44 'target_repo': 'tests/target_repo',
45 'source_ref': 'branch:default:initial',
45 'source_ref': 'branch:default:initial',
46 'target_ref': 'branch:default:new-feature',
46 'target_ref': 'branch:default:new-feature',
47 'title': 'Test PR 1'
47 'title': 'Test PR 1'
48 }
48 }
49 for key in required_data:
49 for key in required_data:
50 data = required_data.copy()
50 data = required_data.copy()
51 data.pop(key)
51 data.pop(key)
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'create_pull_request', **data)
53 self.apikey, 'create_pull_request', **data)
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55
55
56 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
56 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 @pytest.mark.backends("git", "hg")
59 @pytest.mark.backends("git", "hg")
60 def test_create_with_correct_data(self, backend):
60 def test_create_with_correct_data(self, backend):
61 data = self._prepare_data(backend)
61 data = self._prepare_data(backend)
62 RepoModel().revoke_user_permission(
62 RepoModel().revoke_user_permission(
63 self.source.repo_name, User.DEFAULT_USER)
63 self.source.repo_name, User.DEFAULT_USER)
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey_regular, 'create_pull_request', **data)
65 self.apikey_regular, 'create_pull_request', **data)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67 expected_message = "Created new pull request `{title}`".format(
67 expected_message = "Created new pull request `{title}`".format(
68 title=data['title'])
68 title=data['title'])
69 result = response.json
69 result = response.json
70 assert result['result']['msg'] == expected_message
70 assert result['result']['msg'] == expected_message
71 pull_request_id = result['result']['pull_request_id']
71 pull_request_id = result['result']['pull_request_id']
72 pull_request = PullRequestModel().get(pull_request_id)
72 pull_request = PullRequestModel().get(pull_request_id)
73 assert pull_request.title == data['title']
73 assert pull_request.title == data['title']
74 assert pull_request.description == data['description']
74 assert pull_request.description == data['description']
75 assert pull_request.source_ref == data['source_ref']
75 assert pull_request.source_ref == data['source_ref']
76 assert pull_request.target_ref == data['target_ref']
76 assert pull_request.target_ref == data['target_ref']
77 assert pull_request.source_repo.repo_name == data['source_repo']
77 assert pull_request.source_repo.repo_name == data['source_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
79 assert pull_request.revisions == [self.commit_ids['change']]
79 assert pull_request.revisions == [self.commit_ids['change']]
80 assert pull_request.reviewers == []
80 assert len(pull_request.reviewers) == 1
81
81
82 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
83 def test_create_with_empty_description(self, backend):
83 def test_create_with_empty_description(self, backend):
84 data = self._prepare_data(backend)
84 data = self._prepare_data(backend)
85 data.pop('description')
85 data.pop('description')
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey_regular, 'create_pull_request', **data)
87 self.apikey_regular, 'create_pull_request', **data)
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 expected_message = "Created new pull request `{title}`".format(
89 expected_message = "Created new pull request `{title}`".format(
90 title=data['title'])
90 title=data['title'])
91 result = response.json
91 result = response.json
92 assert result['result']['msg'] == expected_message
92 assert result['result']['msg'] == expected_message
93 pull_request_id = result['result']['pull_request_id']
93 pull_request_id = result['result']['pull_request_id']
94 pull_request = PullRequestModel().get(pull_request_id)
94 pull_request = PullRequestModel().get(pull_request_id)
95 assert pull_request.description == ''
95 assert pull_request.description == ''
96
96
97 @pytest.mark.backends("git", "hg")
97 @pytest.mark.backends("git", "hg")
98 def test_create_with_reviewers_specified_by_names(
98 def test_create_with_reviewers_specified_by_names(
99 self, backend, no_notifications):
99 self, backend, no_notifications):
100 data = self._prepare_data(backend)
100 data = self._prepare_data(backend)
101 reviewers = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
101 reviewers = [
102 {'username': TEST_USER_REGULAR_LOGIN,
103 'reasons': ['added manually']},
104 {'username': TEST_USER_ADMIN_LOGIN,
105 'reasons': ['added manually']},
106 ]
102 data['reviewers'] = reviewers
107 data['reviewers'] = reviewers
103 id_, params = build_data(
108 id_, params = build_data(
104 self.apikey_regular, 'create_pull_request', **data)
109 self.apikey_regular, 'create_pull_request', **data)
105 response = api_call(self.app, params)
110 response = api_call(self.app, params)
106
111
107 expected_message = "Created new pull request `{title}`".format(
112 expected_message = "Created new pull request `{title}`".format(
108 title=data['title'])
113 title=data['title'])
109 result = response.json
114 result = response.json
110 assert result['result']['msg'] == expected_message
115 assert result['result']['msg'] == expected_message
111 pull_request_id = result['result']['pull_request_id']
116 pull_request_id = result['result']['pull_request_id']
112 pull_request = PullRequestModel().get(pull_request_id)
117 pull_request = PullRequestModel().get(pull_request_id)
113 actual_reviewers = [r.user.username for r in pull_request.reviewers]
118 actual_reviewers = [
119 {'username': r.user.username,
120 'reasons': ['added manually'],
121 } for r in pull_request.reviewers
122 ]
114 assert sorted(actual_reviewers) == sorted(reviewers)
123 assert sorted(actual_reviewers) == sorted(reviewers)
115
124
116 @pytest.mark.backends("git", "hg")
125 @pytest.mark.backends("git", "hg")
117 def test_create_with_reviewers_specified_by_ids(
126 def test_create_with_reviewers_specified_by_ids(
118 self, backend, no_notifications):
127 self, backend, no_notifications):
119 data = self._prepare_data(backend)
128 data = self._prepare_data(backend)
120 reviewer_names = [TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]
121 reviewers = [
129 reviewers = [
122 UserModel().get_by_username(n).user_id for n in reviewer_names]
130 {'username': UserModel().get_by_username(
131 TEST_USER_REGULAR_LOGIN).user_id,
132 'reasons': ['added manually']},
133 {'username': UserModel().get_by_username(
134 TEST_USER_ADMIN_LOGIN).user_id,
135 'reasons': ['added manually']},
136 ]
137
123 data['reviewers'] = reviewers
138 data['reviewers'] = reviewers
124 id_, params = build_data(
139 id_, params = build_data(
125 self.apikey_regular, 'create_pull_request', **data)
140 self.apikey_regular, 'create_pull_request', **data)
126 response = api_call(self.app, params)
141 response = api_call(self.app, params)
127
142
128 expected_message = "Created new pull request `{title}`".format(
143 expected_message = "Created new pull request `{title}`".format(
129 title=data['title'])
144 title=data['title'])
130 result = response.json
145 result = response.json
131 assert result['result']['msg'] == expected_message
146 assert result['result']['msg'] == expected_message
132 pull_request_id = result['result']['pull_request_id']
147 pull_request_id = result['result']['pull_request_id']
133 pull_request = PullRequestModel().get(pull_request_id)
148 pull_request = PullRequestModel().get(pull_request_id)
134 actual_reviewers = [r.user.username for r in pull_request.reviewers]
149 actual_reviewers = [
135 assert sorted(actual_reviewers) == sorted(reviewer_names)
150 {'username': r.user.user_id,
151 'reasons': ['added manually'],
152 } for r in pull_request.reviewers
153 ]
154 assert sorted(actual_reviewers) == sorted(reviewers)
136
155
137 @pytest.mark.backends("git", "hg")
156 @pytest.mark.backends("git", "hg")
138 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
157 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
139 data = self._prepare_data(backend)
158 data = self._prepare_data(backend)
140 reviewers = ['somebody']
159 data['reviewers'] = [{'username': 'somebody'}]
141 data['reviewers'] = reviewers
142 id_, params = build_data(
160 id_, params = build_data(
143 self.apikey_regular, 'create_pull_request', **data)
161 self.apikey_regular, 'create_pull_request', **data)
144 response = api_call(self.app, params)
162 response = api_call(self.app, params)
145 expected_message = 'user `somebody` does not exist'
163 expected_message = 'user `somebody` does not exist'
146 assert_error(id_, expected_message, given=response.body)
164 assert_error(id_, expected_message, given=response.body)
147
165
148 @pytest.mark.backends("git", "hg")
166 @pytest.mark.backends("git", "hg")
149 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
167 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
150 data = self._prepare_data(backend)
168 data = self._prepare_data(backend)
151 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
169 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
152 data['reviewers'] = reviewers
170 data['reviewers'] = reviewers
153 id_, params = build_data(
171 id_, params = build_data(
154 self.apikey_regular, 'create_pull_request', **data)
172 self.apikey_regular, 'create_pull_request', **data)
155 response = api_call(self.app, params)
173 response = api_call(self.app, params)
156 expected_message = 'reviewers should be specified as a list'
174 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
157 assert_error(id_, expected_message, given=response.body)
175 assert_error(id_, expected_message, given=response.body)
158
176
159 @pytest.mark.backends("git", "hg")
177 @pytest.mark.backends("git", "hg")
160 def test_create_with_no_commit_hashes(self, backend):
178 def test_create_with_no_commit_hashes(self, backend):
161 data = self._prepare_data(backend)
179 data = self._prepare_data(backend)
162 expected_source_ref = data['source_ref']
180 expected_source_ref = data['source_ref']
163 expected_target_ref = data['target_ref']
181 expected_target_ref = data['target_ref']
164 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
182 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
165 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
183 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
166 id_, params = build_data(
184 id_, params = build_data(
167 self.apikey_regular, 'create_pull_request', **data)
185 self.apikey_regular, 'create_pull_request', **data)
168 response = api_call(self.app, params)
186 response = api_call(self.app, params)
169 expected_message = "Created new pull request `{title}`".format(
187 expected_message = "Created new pull request `{title}`".format(
170 title=data['title'])
188 title=data['title'])
171 result = response.json
189 result = response.json
172 assert result['result']['msg'] == expected_message
190 assert result['result']['msg'] == expected_message
173 pull_request_id = result['result']['pull_request_id']
191 pull_request_id = result['result']['pull_request_id']
174 pull_request = PullRequestModel().get(pull_request_id)
192 pull_request = PullRequestModel().get(pull_request_id)
175 assert pull_request.source_ref == expected_source_ref
193 assert pull_request.source_ref == expected_source_ref
176 assert pull_request.target_ref == expected_target_ref
194 assert pull_request.target_ref == expected_target_ref
177
195
178 @pytest.mark.backends("git", "hg")
196 @pytest.mark.backends("git", "hg")
179 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
197 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
180 def test_create_fails_with_wrong_repo(self, backend, data_key):
198 def test_create_fails_with_wrong_repo(self, backend, data_key):
181 repo_name = 'fake-repo'
199 repo_name = 'fake-repo'
182 data = self._prepare_data(backend)
200 data = self._prepare_data(backend)
183 data[data_key] = repo_name
201 data[data_key] = repo_name
184 id_, params = build_data(
202 id_, params = build_data(
185 self.apikey_regular, 'create_pull_request', **data)
203 self.apikey_regular, 'create_pull_request', **data)
186 response = api_call(self.app, params)
204 response = api_call(self.app, params)
187 expected_message = 'repository `{}` does not exist'.format(repo_name)
205 expected_message = 'repository `{}` does not exist'.format(repo_name)
188 assert_error(id_, expected_message, given=response.body)
206 assert_error(id_, expected_message, given=response.body)
189
207
190 @pytest.mark.backends("git", "hg")
208 @pytest.mark.backends("git", "hg")
191 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
209 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
192 def test_create_fails_with_non_existing_branch(self, backend, data_key):
210 def test_create_fails_with_non_existing_branch(self, backend, data_key):
193 branch_name = 'test-branch'
211 branch_name = 'test-branch'
194 data = self._prepare_data(backend)
212 data = self._prepare_data(backend)
195 data[data_key] = "branch:{}".format(branch_name)
213 data[data_key] = "branch:{}".format(branch_name)
196 id_, params = build_data(
214 id_, params = build_data(
197 self.apikey_regular, 'create_pull_request', **data)
215 self.apikey_regular, 'create_pull_request', **data)
198 response = api_call(self.app, params)
216 response = api_call(self.app, params)
199 expected_message = 'The specified branch `{}` does not exist'.format(
217 expected_message = 'The specified branch `{}` does not exist'.format(
200 branch_name)
218 branch_name)
201 assert_error(id_, expected_message, given=response.body)
219 assert_error(id_, expected_message, given=response.body)
202
220
203 @pytest.mark.backends("git", "hg")
221 @pytest.mark.backends("git", "hg")
204 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
222 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
205 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
223 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
206 data = self._prepare_data(backend)
224 data = self._prepare_data(backend)
207 ref = 'stange-ref'
225 ref = 'stange-ref'
208 data[data_key] = ref
226 data[data_key] = ref
209 id_, params = build_data(
227 id_, params = build_data(
210 self.apikey_regular, 'create_pull_request', **data)
228 self.apikey_regular, 'create_pull_request', **data)
211 response = api_call(self.app, params)
229 response = api_call(self.app, params)
212 expected_message = (
230 expected_message = (
213 'Ref `{ref}` given in a wrong format. Please check the API'
231 'Ref `{ref}` given in a wrong format. Please check the API'
214 ' documentation for more details'.format(ref=ref))
232 ' documentation for more details'.format(ref=ref))
215 assert_error(id_, expected_message, given=response.body)
233 assert_error(id_, expected_message, given=response.body)
216
234
217 @pytest.mark.backends("git", "hg")
235 @pytest.mark.backends("git", "hg")
218 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
236 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
219 def test_create_fails_with_non_existing_ref(self, backend, data_key):
237 def test_create_fails_with_non_existing_ref(self, backend, data_key):
220 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
238 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
221 ref = self._get_full_ref(backend, commit_id)
239 ref = self._get_full_ref(backend, commit_id)
222 data = self._prepare_data(backend)
240 data = self._prepare_data(backend)
223 data[data_key] = ref
241 data[data_key] = ref
224 id_, params = build_data(
242 id_, params = build_data(
225 self.apikey_regular, 'create_pull_request', **data)
243 self.apikey_regular, 'create_pull_request', **data)
226 response = api_call(self.app, params)
244 response = api_call(self.app, params)
227 expected_message = 'Ref `{}` does not exist'.format(ref)
245 expected_message = 'Ref `{}` does not exist'.format(ref)
228 assert_error(id_, expected_message, given=response.body)
246 assert_error(id_, expected_message, given=response.body)
229
247
230 @pytest.mark.backends("git", "hg")
248 @pytest.mark.backends("git", "hg")
231 def test_create_fails_when_no_revisions(self, backend):
249 def test_create_fails_when_no_revisions(self, backend):
232 data = self._prepare_data(backend, source_head='initial')
250 data = self._prepare_data(backend, source_head='initial')
233 id_, params = build_data(
251 id_, params = build_data(
234 self.apikey_regular, 'create_pull_request', **data)
252 self.apikey_regular, 'create_pull_request', **data)
235 response = api_call(self.app, params)
253 response = api_call(self.app, params)
236 expected_message = 'no commits found'
254 expected_message = 'no commits found'
237 assert_error(id_, expected_message, given=response.body)
255 assert_error(id_, expected_message, given=response.body)
238
256
239 @pytest.mark.backends("git", "hg")
257 @pytest.mark.backends("git", "hg")
240 def test_create_fails_when_no_permissions(self, backend):
258 def test_create_fails_when_no_permissions(self, backend):
241 data = self._prepare_data(backend)
259 data = self._prepare_data(backend)
242 RepoModel().revoke_user_permission(
260 RepoModel().revoke_user_permission(
243 self.source.repo_name, User.DEFAULT_USER)
261 self.source.repo_name, User.DEFAULT_USER)
244 RepoModel().revoke_user_permission(
262 RepoModel().revoke_user_permission(
245 self.source.repo_name, self.test_user)
263 self.source.repo_name, self.test_user)
246 id_, params = build_data(
264 id_, params = build_data(
247 self.apikey_regular, 'create_pull_request', **data)
265 self.apikey_regular, 'create_pull_request', **data)
248 response = api_call(self.app, params)
266 response = api_call(self.app, params)
249 expected_message = 'repository `{}` does not exist'.format(
267 expected_message = 'repository `{}` does not exist'.format(
250 self.source.repo_name)
268 self.source.repo_name)
251 assert_error(id_, expected_message, given=response.body)
269 assert_error(id_, expected_message, given=response.body)
252
270
253 def _prepare_data(
271 def _prepare_data(
254 self, backend, source_head='change', target_head='initial'):
272 self, backend, source_head='change', target_head='initial'):
255 commits = [
273 commits = [
256 {'message': 'initial'},
274 {'message': 'initial'},
257 {'message': 'change'},
275 {'message': 'change'},
258 {'message': 'new-feature', 'parents': ['initial']},
276 {'message': 'new-feature', 'parents': ['initial']},
259 ]
277 ]
260 self.commit_ids = backend.create_master_repo(commits)
278 self.commit_ids = backend.create_master_repo(commits)
261 self.source = backend.create_repo(heads=[source_head])
279 self.source = backend.create_repo(heads=[source_head])
262 self.target = backend.create_repo(heads=[target_head])
280 self.target = backend.create_repo(heads=[target_head])
263 data = {
281 data = {
264 'source_repo': self.source.repo_name,
282 'source_repo': self.source.repo_name,
265 'target_repo': self.target.repo_name,
283 'target_repo': self.target.repo_name,
266 'source_ref': self._get_full_ref(
284 'source_ref': self._get_full_ref(
267 backend, self.commit_ids[source_head]),
285 backend, self.commit_ids[source_head]),
268 'target_ref': self._get_full_ref(
286 'target_ref': self._get_full_ref(
269 backend, self.commit_ids[target_head]),
287 backend, self.commit_ids[target_head]),
270 'title': 'Test PR 1',
288 'title': 'Test PR 1',
271 'description': 'Test'
289 'description': 'Test'
272 }
290 }
273 RepoModel().grant_user_permission(
291 RepoModel().grant_user_permission(
274 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
292 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
275 return data
293 return data
276
294
277 def _get_full_ref(self, backend, commit_id):
295 def _get_full_ref(self, backend, commit_id):
278 return 'branch:{branch}:{commit_id}'.format(
296 return 'branch:{branch}:{commit_id}'.format(
279 branch=backend.default_branch_name, commit_id=commit_id)
297 branch=backend.default_branch_name, commit_id=commit_id)
@@ -1,192 +1,206 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.model.db import RepoGroup
31 from rhodecode.model.db import RepoGroup
32
32
33
33
34 # TODO: mikhail: remove fixture from here
34 # TODO: mikhail: remove fixture from here
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestCreateUser(object):
39 class TestCreateUser(object):
40 def test_api_create_existing_user(self):
40 def test_api_create_existing_user(self):
41 id_, params = build_data(
41 id_, params = build_data(
42 self.apikey, 'create_user',
42 self.apikey, 'create_user',
43 username=TEST_USER_ADMIN_LOGIN,
43 username=TEST_USER_ADMIN_LOGIN,
44 email='test@foo.com',
44 email='test@foo.com',
45 password='trololo')
45 password='trololo')
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
49 assert_error(id_, expected, given=response.body)
49 assert_error(id_, expected, given=response.body)
50
50
51 def test_api_create_user_with_existing_email(self):
51 def test_api_create_user_with_existing_email(self):
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'create_user',
53 self.apikey, 'create_user',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
55 email=TEST_USER_REGULAR_EMAIL,
55 email=TEST_USER_REGULAR_EMAIL,
56 password='trololo')
56 password='trololo')
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58
58
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 assert_error(id_, expected, given=response.body)
60 assert_error(id_, expected, given=response.body)
61
61
62 def test_api_create_user_with_wrong_username(self):
63 bad_username = '<> HELLO WORLD <>'
64 id_, params = build_data(
65 self.apikey, 'create_user',
66 username=bad_username,
67 email='new@email.com',
68 password='trololo')
69 response = api_call(self.app, params)
70
71 expected = {'username':
72 "Username may only contain alphanumeric characters "
73 "underscores, periods or dashes and must begin with "
74 "alphanumeric character or underscore"}
75 assert_error(id_, expected, given=response.body)
76
62 def test_api_create_user(self):
77 def test_api_create_user(self):
63 username = 'test_new_api_user'
78 username = 'test_new_api_user'
64 email = username + "@foo.com"
79 email = username + "@foo.com"
65
80
66 id_, params = build_data(
81 id_, params = build_data(
67 self.apikey, 'create_user',
82 self.apikey, 'create_user',
68 username=username,
83 username=username,
69 email=email,
84 email=email,
70 password='example')
85 password='example')
71 response = api_call(self.app, params)
86 response = api_call(self.app, params)
72
87
73 usr = UserModel().get_by_username(username)
88 usr = UserModel().get_by_username(username)
74 ret = {
89 ret = {
75 'msg': 'created new user `%s`' % (username,),
90 'msg': 'created new user `%s`' % (username,),
76 'user': jsonify(usr.get_api_data(include_secrets=True)),
91 'user': jsonify(usr.get_api_data(include_secrets=True)),
77 }
92 }
78 try:
93 try:
79 expected = ret
94 expected = ret
80 assert check_password('example', usr.password)
95 assert check_password('example', usr.password)
81 assert_ok(id_, expected, given=response.body)
96 assert_ok(id_, expected, given=response.body)
82 finally:
97 finally:
83 fixture.destroy_user(usr.user_id)
98 fixture.destroy_user(usr.user_id)
84
99
85 def test_api_create_user_without_password(self):
100 def test_api_create_user_without_password(self):
86 username = 'test_new_api_user_passwordless'
101 username = 'test_new_api_user_passwordless'
87 email = username + "@foo.com"
102 email = username + "@foo.com"
88
103
89 id_, params = build_data(
104 id_, params = build_data(
90 self.apikey, 'create_user',
105 self.apikey, 'create_user',
91 username=username,
106 username=username,
92 email=email)
107 email=email)
93 response = api_call(self.app, params)
108 response = api_call(self.app, params)
94
109
95 usr = UserModel().get_by_username(username)
110 usr = UserModel().get_by_username(username)
96 ret = {
111 ret = {
97 'msg': 'created new user `%s`' % (username,),
112 'msg': 'created new user `%s`' % (username,),
98 'user': jsonify(usr.get_api_data(include_secrets=True)),
113 'user': jsonify(usr.get_api_data(include_secrets=True)),
99 }
114 }
100 try:
115 try:
101 expected = ret
116 expected = ret
102 assert_ok(id_, expected, given=response.body)
117 assert_ok(id_, expected, given=response.body)
103 finally:
118 finally:
104 fixture.destroy_user(usr.user_id)
119 fixture.destroy_user(usr.user_id)
105
120
106 def test_api_create_user_with_extern_name(self):
121 def test_api_create_user_with_extern_name(self):
107 username = 'test_new_api_user_passwordless'
122 username = 'test_new_api_user_passwordless'
108 email = username + "@foo.com"
123 email = username + "@foo.com"
109
124
110 id_, params = build_data(
125 id_, params = build_data(
111 self.apikey, 'create_user',
126 self.apikey, 'create_user',
112 username=username,
127 username=username,
113 email=email, extern_name='rhodecode')
128 email=email, extern_name='rhodecode')
114 response = api_call(self.app, params)
129 response = api_call(self.app, params)
115
130
116 usr = UserModel().get_by_username(username)
131 usr = UserModel().get_by_username(username)
117 ret = {
132 ret = {
118 'msg': 'created new user `%s`' % (username,),
133 'msg': 'created new user `%s`' % (username,),
119 'user': jsonify(usr.get_api_data(include_secrets=True)),
134 'user': jsonify(usr.get_api_data(include_secrets=True)),
120 }
135 }
121 try:
136 try:
122 expected = ret
137 expected = ret
123 assert_ok(id_, expected, given=response.body)
138 assert_ok(id_, expected, given=response.body)
124 finally:
139 finally:
125 fixture.destroy_user(usr.user_id)
140 fixture.destroy_user(usr.user_id)
126
141
127 def test_api_create_user_with_password_change(self):
142 def test_api_create_user_with_password_change(self):
128 username = 'test_new_api_user_password_change'
143 username = 'test_new_api_user_password_change'
129 email = username + "@foo.com"
144 email = username + "@foo.com"
130
145
131 id_, params = build_data(
146 id_, params = build_data(
132 self.apikey, 'create_user',
147 self.apikey, 'create_user',
133 username=username,
148 username=username,
134 email=email, extern_name='rhodecode',
149 email=email, extern_name='rhodecode',
135 force_password_change=True)
150 force_password_change=True)
136 response = api_call(self.app, params)
151 response = api_call(self.app, params)
137
152
138 usr = UserModel().get_by_username(username)
153 usr = UserModel().get_by_username(username)
139 ret = {
154 ret = {
140 'msg': 'created new user `%s`' % (username,),
155 'msg': 'created new user `%s`' % (username,),
141 'user': jsonify(usr.get_api_data(include_secrets=True)),
156 'user': jsonify(usr.get_api_data(include_secrets=True)),
142 }
157 }
143 try:
158 try:
144 expected = ret
159 expected = ret
145 assert_ok(id_, expected, given=response.body)
160 assert_ok(id_, expected, given=response.body)
146 finally:
161 finally:
147 fixture.destroy_user(usr.user_id)
162 fixture.destroy_user(usr.user_id)
148
163
149 def test_api_create_user_with_personal_repo_group(self):
164 def test_api_create_user_with_personal_repo_group(self):
150 username = 'test_new_api_user_personal_group'
165 username = 'test_new_api_user_personal_group'
151 email = username + "@foo.com"
166 email = username + "@foo.com"
152
167
153 id_, params = build_data(
168 id_, params = build_data(
154 self.apikey, 'create_user',
169 self.apikey, 'create_user',
155 username=username,
170 username=username,
156 email=email, extern_name='rhodecode',
171 email=email, extern_name='rhodecode',
157 create_personal_repo_group=True)
172 create_personal_repo_group=True)
158 response = api_call(self.app, params)
173 response = api_call(self.app, params)
159
174
160 usr = UserModel().get_by_username(username)
175 usr = UserModel().get_by_username(username)
161 ret = {
176 ret = {
162 'msg': 'created new user `%s`' % (username,),
177 'msg': 'created new user `%s`' % (username,),
163 'user': jsonify(usr.get_api_data(include_secrets=True)),
178 'user': jsonify(usr.get_api_data(include_secrets=True)),
164 }
179 }
165
180
166 personal_group = RepoGroup.get_by_group_name(username)
181 personal_group = RepoGroup.get_by_group_name(username)
167 assert personal_group
182 assert personal_group
168 assert personal_group.personal == True
183 assert personal_group.personal == True
169 assert personal_group.user.username == username
184 assert personal_group.user.username == username
170
185
171 try:
186 try:
172 expected = ret
187 expected = ret
173 assert_ok(id_, expected, given=response.body)
188 assert_ok(id_, expected, given=response.body)
174 finally:
189 finally:
175 fixture.destroy_repo_group(username)
190 fixture.destroy_repo_group(username)
176 fixture.destroy_user(usr.user_id)
191 fixture.destroy_user(usr.user_id)
177
192
178
179 @mock.patch.object(UserModel, 'create_or_update', crash)
193 @mock.patch.object(UserModel, 'create_or_update', crash)
180 def test_api_create_user_when_exception_happened(self):
194 def test_api_create_user_when_exception_happened(self):
181
195
182 username = 'test_new_api_user'
196 username = 'test_new_api_user'
183 email = username + "@foo.com"
197 email = username + "@foo.com"
184
198
185 id_, params = build_data(
199 id_, params = build_data(
186 self.apikey, 'create_user',
200 self.apikey, 'create_user',
187 username=username,
201 username=username,
188 email=email,
202 email=email,
189 password='trololo')
203 password='trololo')
190 response = api_call(self.app, params)
204 response = api_call(self.app, params)
191 expected = 'failed to create user `%s`' % (username,)
205 expected = 'failed to create user `%s`' % (username,)
192 assert_error(id_, expected, given=response.body)
206 assert_error(id_, expected, given=response.body)
@@ -1,114 +1,127 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.model.user_group import UserGroupModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30
30
31
31
32 @pytest.mark.usefixtures("testuser_api", "app")
32 @pytest.mark.usefixtures("testuser_api", "app")
33 class TestCreateUserGroup(object):
33 class TestCreateUserGroup(object):
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36 def test_api_create_user_group(self):
36 def test_api_create_user_group(self):
37 group_name = 'some_new_group'
37 group_name = 'some_new_group'
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'create_user_group', group_name=group_name)
39 self.apikey, 'create_user_group', group_name=group_name)
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41
41
42 ret = {
42 ret = {
43 'msg': 'created new user group `%s`' % (group_name,),
43 'msg': 'created new user group `%s`' % (group_name,),
44 'user_group': jsonify(
44 'user_group': jsonify(
45 UserGroupModel()
45 UserGroupModel()
46 .get_by_name(group_name)
46 .get_by_name(group_name)
47 .get_api_data()
47 .get_api_data()
48 )
48 )
49 }
49 }
50 expected = ret
50 expected = ret
51 assert_ok(id_, expected, given=response.body)
51 assert_ok(id_, expected, given=response.body)
52 self.fixture.destroy_user_group(group_name)
52 self.fixture.destroy_user_group(group_name)
53
53
54 def test_api_create_user_group_regular_user(self):
54 def test_api_create_user_group_regular_user(self):
55 group_name = 'some_new_group'
55 group_name = 'some_new_group'
56
56
57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
58 usr.inherit_default_permissions = False
58 usr.inherit_default_permissions = False
59 Session().add(usr)
59 Session().add(usr)
60 UserModel().grant_perm(
60 UserModel().grant_perm(
61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
62 Session().commit()
62 Session().commit()
63
63
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey_regular, 'create_user_group', group_name=group_name)
65 self.apikey_regular, 'create_user_group', group_name=group_name)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'msg': 'created new user group `%s`' % (group_name,),
69 'msg': 'created new user group `%s`' % (group_name,),
70 'user_group': jsonify(
70 'user_group': jsonify(
71 UserGroupModel()
71 UserGroupModel()
72 .get_by_name(group_name)
72 .get_by_name(group_name)
73 .get_api_data()
73 .get_api_data()
74 )
74 )
75 }
75 }
76 try:
76 try:
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78 finally:
78 finally:
79 self.fixture.destroy_user_group(group_name)
79 self.fixture.destroy_user_group(group_name)
80 UserModel().revoke_perm(
80 UserModel().revoke_perm(
81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
83 usr.inherit_default_permissions = True
83 usr.inherit_default_permissions = True
84 Session().add(usr)
84 Session().add(usr)
85 Session().commit()
85 Session().commit()
86
86
87 def test_api_create_user_group_regular_user_no_permission(self):
87 def test_api_create_user_group_regular_user_no_permission(self):
88 group_name = 'some_new_group'
88 group_name = 'some_new_group'
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey_regular, 'create_user_group', group_name=group_name)
90 self.apikey_regular, 'create_user_group', group_name=group_name)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 expected = "Access was denied to this resource."
92 expected = "Access was denied to this resource."
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94
94
95 def test_api_create_user_group_that_exist(self, user_util):
95 def test_api_create_user_group_that_exist(self, user_util):
96 group = user_util.create_user_group()
96 group = user_util.create_user_group()
97 group_name = group.users_group_name
97 group_name = group.users_group_name
98
98
99 id_, params = build_data(
99 id_, params = build_data(
100 self.apikey, 'create_user_group', group_name=group_name)
100 self.apikey, 'create_user_group', group_name=group_name)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102
102
103 expected = "user group `%s` already exist" % (group_name,)
103 expected = "user group `%s` already exist" % (group_name,)
104 assert_error(id_, expected, given=response.body)
104 assert_error(id_, expected, given=response.body)
105
105
106 @mock.patch.object(UserGroupModel, 'create', crash)
106 @mock.patch.object(UserGroupModel, 'create', crash)
107 def test_api_create_user_group_exception_occurred(self):
107 def test_api_create_user_group_exception_occurred(self):
108 group_name = 'exception_happens'
108 group_name = 'exception_happens'
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey, 'create_user_group', group_name=group_name)
110 self.apikey, 'create_user_group', group_name=group_name)
111 response = api_call(self.app, params)
111 response = api_call(self.app, params)
112
112
113 expected = 'failed to create group `%s`' % (group_name,)
113 expected = 'failed to create group `%s`' % (group_name,)
114 assert_error(id_, expected, given=response.body)
114 assert_error(id_, expected, given=response.body)
115
116 def test_api_create_user_group_with_wrong_name(self, user_util):
117
118 group_name = 'wrong NAME <>'
119 id_, params = build_data(
120 self.apikey, 'create_user_group', group_name=group_name)
121 response = api_call(self.app, params)
122
123 expected = {"user_group_name":
124 "Allowed in name are letters, numbers, and `-`, `_`, "
125 "`.` Name must start with a letter or number. "
126 "Got `{}`".format(group_name)}
127 assert_error(id_, expected, given=response.body)
@@ -1,72 +1,74 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiDeleteRepo(object):
30 class TestApiDeleteRepo(object):
31 def test_api_delete_repo(self, backend):
31 def test_api_delete_repo(self, backend):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33
33 repo_name = repo.repo_name
34 id_, params = build_data(
34 id_, params = build_data(
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37
37
38 expected = {
38 expected = {
39 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
39 'msg': 'Deleted repository `%s`' % (repo_name,),
40 'success': True
40 'success': True
41 }
41 }
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 repo = backend.create_repo(cur_user=user_regular.username)
45 repo = backend.create_repo(cur_user=user_regular.username)
46 repo_name = repo.repo_name
46 id_, params = build_data(
47 id_, params = build_data(
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 response = api_call(self.app, params)
49 response = api_call(self.app, params)
49
50
50 expected = {
51 expected = {
51 'msg': 'Deleted repository `%s`' % (repo.repo_name,),
52 'msg': 'Deleted repository `%s`' % (repo_name,),
52 'success': True
53 'success': True
53 }
54 }
54 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
55
56
56 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 repo = backend.create_repo()
58 repo = backend.create_repo()
59 repo_name = repo.repo_name
58 id_, params = build_data(
60 id_, params = build_data(
59 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
61 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
60 response = api_call(self.app, params)
62 response = api_call(self.app, params)
61 expected = 'repository `%s` does not exist' % (repo.repo_name)
63 expected = 'repository `%s` does not exist' % (repo_name)
62 assert_error(id_, expected, given=response.body)
64 assert_error(id_, expected, given=response.body)
63
65
64 def test_api_delete_repo_exception_occurred(self, backend):
66 def test_api_delete_repo_exception_occurred(self, backend):
65 repo = backend.create_repo()
67 repo = backend.create_repo()
68 repo_name = repo.repo_name
66 id_, params = build_data(
69 id_, params = build_data(
67 self.apikey, 'delete_repo', repoid=repo.repo_name, )
70 self.apikey, 'delete_repo', repoid=repo.repo_name, )
68 with mock.patch.object(RepoModel, 'delete', crash):
71 with mock.patch.object(RepoModel, 'delete', crash):
69 response = api_call(self.app, params)
72 response = api_call(self.app, params)
70 expected = 'failed to delete repository `%s`' % (
73 expected = 'failed to delete repository `%s`' % (repo_name,)
71 repo.repo_name,)
72 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
@@ -1,101 +1,101 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Gist
24 from rhodecode.model.db import Gist
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gist(self, gist_util):
31 def test_api_get_gist(self, gist_util, http_host_stub):
32 gist = gist_util.create_gist()
32 gist = gist_util.create_gist()
33 gist_id = gist.gist_access_id
33 gist_id = gist.gist_access_id
34 gist_created_on = gist.created_on
34 gist_created_on = gist.created_on
35 gist_modified_at = gist.modified_at
35 gist_modified_at = gist.modified_at
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'get_gist', gistid=gist_id, )
37 self.apikey, 'get_gist', gistid=gist_id, )
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = {
40 expected = {
41 'access_id': gist_id,
41 'access_id': gist_id,
42 'created_on': gist_created_on,
42 'created_on': gist_created_on,
43 'modified_at': gist_modified_at,
43 'modified_at': gist_modified_at,
44 'description': 'new-gist',
44 'description': 'new-gist',
45 'expires': -1.0,
45 'expires': -1.0,
46 'gist_id': int(gist_id),
46 'gist_id': int(gist_id),
47 'type': 'public',
47 'type': 'public',
48 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
48 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 'content': None,
50 'content': None,
51 }
51 }
52
52
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_get_gist_with_content(self, gist_util):
55 def test_api_get_gist_with_content(self, gist_util, http_host_stub):
56 mapping = {
56 mapping = {
57 u'filename1.txt': {'content': u'hello world'},
57 u'filename1.txt': {'content': u'hello world'},
58 u'filename1ą.txt': {'content': u'hello worldę'}
58 u'filename1ą.txt': {'content': u'hello worldę'}
59 }
59 }
60 gist = gist_util.create_gist(gist_mapping=mapping)
60 gist = gist_util.create_gist(gist_mapping=mapping)
61 gist_id = gist.gist_access_id
61 gist_id = gist.gist_access_id
62 gist_created_on = gist.created_on
62 gist_created_on = gist.created_on
63 gist_modified_at = gist.modified_at
63 gist_modified_at = gist.modified_at
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'access_id': gist_id,
69 'access_id': gist_id,
70 'created_on': gist_created_on,
70 'created_on': gist_created_on,
71 'modified_at': gist_modified_at,
71 'modified_at': gist_modified_at,
72 'description': 'new-gist',
72 'description': 'new-gist',
73 'expires': -1.0,
73 'expires': -1.0,
74 'gist_id': int(gist_id),
74 'gist_id': int(gist_id),
75 'type': 'public',
75 'type': 'public',
76 'url': 'http://test.example.com:80/_admin/gists/%s' % (gist_id,),
76 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 'content': {
78 'content': {
79 u'filename1.txt': u'hello world',
79 u'filename1.txt': u'hello world',
80 u'filename1ą.txt': u'hello worldę'
80 u'filename1ą.txt': u'hello worldę'
81 },
81 },
82 }
82 }
83
83
84 assert_ok(id_, expected, given=response.body)
84 assert_ok(id_, expected, given=response.body)
85
85
86 def test_api_get_gist_not_existing(self):
86 def test_api_get_gist_not_existing(self):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'get_gist', gistid='12345', )
88 self.apikey_regular, 'get_gist', gistid='12345', )
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 expected = 'gist `%s` does not exist' % ('12345',)
90 expected = 'gist `%s` does not exist' % ('12345',)
91 assert_error(id_, expected, given=response.body)
91 assert_error(id_, expected, given=response.body)
92
92
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
94 gist = gist_util.create_gist()
94 gist = gist_util.create_gist()
95 gist_id = gist.gist_access_id
95 gist_id = gist.gist_access_id
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99
99
100 expected = 'gist `%s` does not exist' % (gist_id,)
100 expected = 'gist `%s` does not exist' % (gist_id,)
101 assert_error(id_, expected, given=response.body)
101 assert_error(id_, expected, given=response.body)
@@ -1,133 +1,134 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
23 import pytest
22 import pytest
24 import urlobject
23 import urlobject
25 from pylons import url
24 from pylons import url
26
25
27 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28 from rhodecode.lib.utils2 import safe_unicode
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequest(object):
34 class TestGetPullRequest(object):
35
35
36 def test_api_get_pull_request(self, pr_util):
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38 pull_request = pr_util.create_pull_request(mergeable=True)
38 pull_request = pr_util.create_pull_request(mergeable=True)
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'get_pull_request',
40 self.apikey, 'get_pull_request',
41 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43
43
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 assert response.status == '200 OK'
46 assert response.status == '200 OK'
47
47
48 url_obj = urlobject.URLObject(
48 url_obj = urlobject.URLObject(
49 url(
49 url(
50 'pullrequest_show',
50 'pullrequest_show',
51 repo_name=pull_request.target_repo.repo_name,
51 repo_name=pull_request.target_repo.repo_name,
52 pull_request_id=pull_request.pull_request_id, qualified=True))
52 pull_request_id=pull_request.pull_request_id, qualified=True))
53 pr_url = unicode(
53
54 url_obj.with_netloc('test.example.com:80'))
54 pr_url = safe_unicode(
55 source_url = unicode(
55 url_obj.with_netloc(http_host_only_stub))
56 pull_request.source_repo.clone_url()
56 source_url = safe_unicode(
57 .with_netloc('test.example.com:80'))
57 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
58 target_url = unicode(
58 target_url = safe_unicode(
59 pull_request.target_repo.clone_url()
59 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
60 .with_netloc('test.example.com:80'))
60 shadow_url = safe_unicode(
61 shadow_url = unicode(
62 PullRequestModel().get_shadow_clone_url(pull_request))
61 PullRequestModel().get_shadow_clone_url(pull_request))
62
63 expected = {
63 expected = {
64 'pull_request_id': pull_request.pull_request_id,
64 'pull_request_id': pull_request.pull_request_id,
65 'url': pr_url,
65 'url': pr_url,
66 'title': pull_request.title,
66 'title': pull_request.title,
67 'description': pull_request.description,
67 'description': pull_request.description,
68 'status': pull_request.status,
68 'status': pull_request.status,
69 'created_on': pull_request.created_on,
69 'created_on': pull_request.created_on,
70 'updated_on': pull_request.updated_on,
70 'updated_on': pull_request.updated_on,
71 'commit_ids': pull_request.revisions,
71 'commit_ids': pull_request.revisions,
72 'review_status': pull_request.calculated_review_status(),
72 'review_status': pull_request.calculated_review_status(),
73 'mergeable': {
73 'mergeable': {
74 'status': True,
74 'status': True,
75 'message': 'This pull request can be automatically merged.',
75 'message': 'This pull request can be automatically merged.',
76 },
76 },
77 'source': {
77 'source': {
78 'clone_url': source_url,
78 'clone_url': source_url,
79 'repository': pull_request.source_repo.repo_name,
79 'repository': pull_request.source_repo.repo_name,
80 'reference': {
80 'reference': {
81 'name': pull_request.source_ref_parts.name,
81 'name': pull_request.source_ref_parts.name,
82 'type': pull_request.source_ref_parts.type,
82 'type': pull_request.source_ref_parts.type,
83 'commit_id': pull_request.source_ref_parts.commit_id,
83 'commit_id': pull_request.source_ref_parts.commit_id,
84 },
84 },
85 },
85 },
86 'target': {
86 'target': {
87 'clone_url': target_url,
87 'clone_url': target_url,
88 'repository': pull_request.target_repo.repo_name,
88 'repository': pull_request.target_repo.repo_name,
89 'reference': {
89 'reference': {
90 'name': pull_request.target_ref_parts.name,
90 'name': pull_request.target_ref_parts.name,
91 'type': pull_request.target_ref_parts.type,
91 'type': pull_request.target_ref_parts.type,
92 'commit_id': pull_request.target_ref_parts.commit_id,
92 'commit_id': pull_request.target_ref_parts.commit_id,
93 },
93 },
94 },
94 },
95 'merge': {
95 'merge': {
96 'clone_url': shadow_url,
96 'clone_url': shadow_url,
97 'reference': {
97 'reference': {
98 'name': pull_request.shadow_merge_ref.name,
98 'name': pull_request.shadow_merge_ref.name,
99 'type': pull_request.shadow_merge_ref.type,
99 'type': pull_request.shadow_merge_ref.type,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 },
101 },
102 },
102 },
103 'author': pull_request.author.get_api_data(include_secrets=False,
103 'author': pull_request.author.get_api_data(include_secrets=False,
104 details='basic'),
104 details='basic'),
105 'reviewers': [
105 'reviewers': [
106 {
106 {
107 'user': reviewer.get_api_data(include_secrets=False,
107 'user': reviewer.get_api_data(include_secrets=False,
108 details='basic'),
108 details='basic'),
109 'reasons': reasons,
109 'reasons': reasons,
110 'review_status': st[0][1].status if st else 'not_reviewed',
110 'review_status': st[0][1].status if st else 'not_reviewed',
111 }
111 }
112 for reviewer, reasons, st in pull_request.reviewers_statuses()
112 for reviewer, reasons, mandatory, st in
113 pull_request.reviewers_statuses()
113 ]
114 ]
114 }
115 }
115 assert_ok(id_, expected, response.body)
116 assert_ok(id_, expected, response.body)
116
117
117 def test_api_get_pull_request_repo_error(self):
118 def test_api_get_pull_request_repo_error(self):
118 id_, params = build_data(
119 id_, params = build_data(
119 self.apikey, 'get_pull_request',
120 self.apikey, 'get_pull_request',
120 repoid=666, pullrequestid=1)
121 repoid=666, pullrequestid=1)
121 response = api_call(self.app, params)
122 response = api_call(self.app, params)
122
123
123 expected = 'repository `666` does not exist'
124 expected = 'repository `666` does not exist'
124 assert_error(id_, expected, given=response.body)
125 assert_error(id_, expected, given=response.body)
125
126
126 def test_api_get_pull_request_pull_request_error(self):
127 def test_api_get_pull_request_pull_request_error(self):
127 id_, params = build_data(
128 id_, params = build_data(
128 self.apikey, 'get_pull_request',
129 self.apikey, 'get_pull_request',
129 repoid=1, pullrequestid=666)
130 repoid=1, pullrequestid=666)
130 response = api_call(self.app, params)
131 response = api_call(self.app, params)
131
132
132 expected = 'pull request `666` does not exist'
133 expected = 'pull request `666` does not exist'
133 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
@@ -1,136 +1,136 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog, PullRequest
23 from rhodecode.model.db import UserLog, PullRequest
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestMergePullRequest(object):
31 class TestMergePullRequest(object):
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 pull_request = pr_util.create_pull_request(mergeable=True)
34 pull_request = pr_util.create_pull_request(mergeable=True)
35 author = pull_request.user_id
35 author = pull_request.user_id
36 repo = pull_request.target_repo.repo_id
36 repo = pull_request.target_repo.repo_id
37 pull_request_id = pull_request.pull_request_id
37 pull_request_id = pull_request.pull_request_id
38 pull_request_repo = pull_request.target_repo.repo_name
38 pull_request_repo = pull_request.target_repo.repo_name
39
39
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'merge_pull_request',
41 self.apikey, 'merge_pull_request',
42 repoid=pull_request_repo,
42 repoid=pull_request_repo,
43 pullrequestid=pull_request_id)
43 pullrequestid=pull_request_id)
44
44
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 # The above api call detaches the pull request DB object from the
47 # The above api call detaches the pull request DB object from the
48 # session because of an unconditional transaction rollback in our
48 # session because of an unconditional transaction rollback in our
49 # middleware. Therefore we need to add it back here if we want to use
49 # middleware. Therefore we need to add it back here if we want to use
50 # it.
50 # it.
51 Session().add(pull_request)
51 Session().add(pull_request)
52
52
53 expected = 'merge not possible for following reasons: ' \
53 expected = 'merge not possible for following reasons: ' \
54 'Pull request reviewer approval is pending.'
54 'Pull request reviewer approval is pending.'
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 def test_api_merge_pull_request(self, pr_util, no_notifications):
58 def test_api_merge_pull_request(self, pr_util, no_notifications):
59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
60 author = pull_request.user_id
60 author = pull_request.user_id
61 repo = pull_request.target_repo.repo_id
61 repo = pull_request.target_repo.repo_id
62 pull_request_id = pull_request.pull_request_id
62 pull_request_id = pull_request.pull_request_id
63 pull_request_repo = pull_request.target_repo.repo_name
63 pull_request_repo = pull_request.target_repo.repo_name
64
64
65 id_, params = build_data(
65 id_, params = build_data(
66 self.apikey, 'comment_pull_request',
66 self.apikey, 'comment_pull_request',
67 repoid=pull_request_repo,
67 repoid=pull_request_repo,
68 pullrequestid=pull_request_id,
68 pullrequestid=pull_request_id,
69 status='approved')
69 status='approved')
70
70
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 expected = {
72 expected = {
73 'comment_id': response.json.get('result', {}).get('comment_id'),
73 'comment_id': response.json.get('result', {}).get('comment_id'),
74 'pull_request_id': pull_request_id,
74 'pull_request_id': pull_request_id,
75 'status': {'given': 'approved', 'was_changed': True}
75 'status': {'given': 'approved', 'was_changed': True}
76 }
76 }
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78
78
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey, 'merge_pull_request',
80 self.apikey, 'merge_pull_request',
81 repoid=pull_request_repo,
81 repoid=pull_request_repo,
82 pullrequestid=pull_request_id)
82 pullrequestid=pull_request_id)
83
83
84 response = api_call(self.app, params)
84 response = api_call(self.app, params)
85
85
86 pull_request = PullRequest.get(pull_request_id)
86 pull_request = PullRequest.get(pull_request_id)
87
87
88 expected = {
88 expected = {
89 'executed': True,
89 'executed': True,
90 'failure_reason': 0,
90 'failure_reason': 0,
91 'possible': True,
91 'possible': True,
92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
93 'merge_ref': pull_request.shadow_merge_ref._asdict()
93 'merge_ref': pull_request.shadow_merge_ref._asdict()
94 }
94 }
95
95
96 assert_ok(id_, expected, response.body)
96 assert_ok(id_, expected, response.body)
97
97
98 action = 'user_merged_pull_request:%d' % (pull_request_id, )
99 journal = UserLog.query()\
98 journal = UserLog.query()\
100 .filter(UserLog.user_id == author)\
99 .filter(UserLog.user_id == author)\
101 .filter(UserLog.repository_id == repo)\
100 .filter(UserLog.repository_id == repo) \
102 .filter(UserLog.action == action)\
101 .order_by('user_log_id') \
103 .all()
102 .all()
104 assert len(journal) == 1
103 assert journal[-2].action == 'repo.pull_request.merge'
104 assert journal[-1].action == 'repo.pull_request.close'
105
105
106 id_, params = build_data(
106 id_, params = build_data(
107 self.apikey, 'merge_pull_request',
107 self.apikey, 'merge_pull_request',
108 repoid=pull_request_repo, pullrequestid=pull_request_id)
108 repoid=pull_request_repo, pullrequestid=pull_request_id)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110
110
111 expected = 'merge not possible for following reasons: This pull request is closed.'
111 expected = 'merge not possible for following reasons: This pull request is closed.'
112 assert_error(id_, expected, given=response.body)
112 assert_error(id_, expected, given=response.body)
113
113
114 @pytest.mark.backends("git", "hg")
114 @pytest.mark.backends("git", "hg")
115 def test_api_merge_pull_request_repo_error(self):
115 def test_api_merge_pull_request_repo_error(self):
116 id_, params = build_data(
116 id_, params = build_data(
117 self.apikey, 'merge_pull_request',
117 self.apikey, 'merge_pull_request',
118 repoid=666, pullrequestid=1)
118 repoid=666, pullrequestid=1)
119 response = api_call(self.app, params)
119 response = api_call(self.app, params)
120
120
121 expected = 'repository `666` does not exist'
121 expected = 'repository `666` does not exist'
122 assert_error(id_, expected, given=response.body)
122 assert_error(id_, expected, given=response.body)
123
123
124 @pytest.mark.backends("git", "hg")
124 @pytest.mark.backends("git", "hg")
125 def test_api_merge_pull_request_non_admin_with_userid_error(self,
125 def test_api_merge_pull_request_non_admin_with_userid_error(self,
126 pr_util):
126 pr_util):
127 pull_request = pr_util.create_pull_request(mergeable=True)
127 pull_request = pr_util.create_pull_request(mergeable=True)
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey_regular, 'merge_pull_request',
129 self.apikey_regular, 'merge_pull_request',
130 repoid=pull_request.target_repo.repo_name,
130 repoid=pull_request.target_repo.repo_name,
131 pullrequestid=pull_request.pull_request_id,
131 pullrequestid=pull_request.pull_request_id,
132 userid=TEST_USER_ADMIN_LOGIN)
132 userid=TEST_USER_ADMIN_LOGIN)
133 response = api_call(self.app, params)
133 response = api_call(self.app, params)
134
134
135 expected = 'userid is not the same as your user'
135 expected = 'userid is not the same as your user'
136 assert_error(id_, expected, given=response.body)
136 assert_error(id_, expected, given=response.body)
@@ -1,205 +1,212 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.vcs.nodes import FileNode
23 from rhodecode.lib.vcs.nodes import FileNode
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error)
28 build_data, api_call, assert_ok, assert_error)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdatePullRequest(object):
32 class TestUpdatePullRequest(object):
33
33
34 @pytest.mark.backends("git", "hg")
34 @pytest.mark.backends("git", "hg")
35 def test_api_update_pull_request_title_or_description(
35 def test_api_update_pull_request_title_or_description(
36 self, pr_util, silence_action_logger, no_notifications):
36 self, pr_util, no_notifications):
37 pull_request = pr_util.create_pull_request()
37 pull_request = pr_util.create_pull_request()
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'update_pull_request',
40 self.apikey, 'update_pull_request',
41 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id,
42 pullrequestid=pull_request.pull_request_id,
43 title='New TITLE OF A PR',
43 title='New TITLE OF A PR',
44 description='New DESC OF A PR',
44 description='New DESC OF A PR',
45 )
45 )
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = {
48 expected = {
49 "msg": "Updated pull request `{}`".format(
49 "msg": "Updated pull request `{}`".format(
50 pull_request.pull_request_id),
50 pull_request.pull_request_id),
51 "pull_request": response.json['result']['pull_request'],
51 "pull_request": response.json['result']['pull_request'],
52 "updated_commits": {"added": [], "common": [], "removed": []},
52 "updated_commits": {"added": [], "common": [], "removed": []},
53 "updated_reviewers": {"added": [], "removed": []},
53 "updated_reviewers": {"added": [], "removed": []},
54 }
54 }
55
55
56 response_json = response.json['result']
56 response_json = response.json['result']
57 assert response_json == expected
57 assert response_json == expected
58 pr = response_json['pull_request']
58 pr = response_json['pull_request']
59 assert pr['title'] == 'New TITLE OF A PR'
59 assert pr['title'] == 'New TITLE OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
61
61
62 @pytest.mark.backends("git", "hg")
62 @pytest.mark.backends("git", "hg")
63 def test_api_try_update_closed_pull_request(
63 def test_api_try_update_closed_pull_request(
64 self, pr_util, silence_action_logger, no_notifications):
64 self, pr_util, no_notifications):
65 pull_request = pr_util.create_pull_request()
65 pull_request = pr_util.create_pull_request()
66 PullRequestModel().close_pull_request(
66 PullRequestModel().close_pull_request(
67 pull_request, TEST_USER_ADMIN_LOGIN)
67 pull_request, TEST_USER_ADMIN_LOGIN)
68
68
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'update_pull_request',
70 self.apikey, 'update_pull_request',
71 repoid=pull_request.target_repo.repo_name,
71 repoid=pull_request.target_repo.repo_name,
72 pullrequestid=pull_request.pull_request_id)
72 pullrequestid=pull_request.pull_request_id)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74
74
75 expected = 'pull request `{}` update failed, pull request ' \
75 expected = 'pull request `{}` update failed, pull request ' \
76 'is closed'.format(pull_request.pull_request_id)
76 'is closed'.format(pull_request.pull_request_id)
77
77
78 assert_error(id_, expected, response.body)
78 assert_error(id_, expected, response.body)
79
79
80 @pytest.mark.backends("git", "hg")
80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(
81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 self, pr_util, silence_action_logger, no_notifications):
83 commits = [
82 commits = [
84 {'message': 'a'},
83 {'message': 'a'},
85 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
86 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
85 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
87 ]
86 ]
88 pull_request = pr_util.create_pull_request(
87 pull_request = pr_util.create_pull_request(
89 commits=commits, target_head='a', source_head='b', revisions=['b'])
88 commits=commits, target_head='a', source_head='b', revisions=['b'])
90 pr_util.update_source_repository(head='c')
89 pr_util.update_source_repository(head='c')
91 repo = pull_request.source_repo.scm_instance()
90 repo = pull_request.source_repo.scm_instance()
92 commits = [x for x in repo.get_commits()]
91 commits = [x for x in repo.get_commits()]
93 print commits
92 print commits
94
93
95 added_commit_id = commits[-1].raw_id # c commit
94 added_commit_id = commits[-1].raw_id # c commit
96 common_commit_id = commits[1].raw_id # b commit is common ancestor
95 common_commit_id = commits[1].raw_id # b commit is common ancestor
97 total_commits = [added_commit_id, common_commit_id]
96 total_commits = [added_commit_id, common_commit_id]
98
97
99 id_, params = build_data(
98 id_, params = build_data(
100 self.apikey, 'update_pull_request',
99 self.apikey, 'update_pull_request',
101 repoid=pull_request.target_repo.repo_name,
100 repoid=pull_request.target_repo.repo_name,
102 pullrequestid=pull_request.pull_request_id,
101 pullrequestid=pull_request.pull_request_id,
103 update_commits=True
102 update_commits=True
104 )
103 )
105 response = api_call(self.app, params)
104 response = api_call(self.app, params)
106
105
107 expected = {
106 expected = {
108 "msg": "Updated pull request `{}`".format(
107 "msg": "Updated pull request `{}`".format(
109 pull_request.pull_request_id),
108 pull_request.pull_request_id),
110 "pull_request": response.json['result']['pull_request'],
109 "pull_request": response.json['result']['pull_request'],
111 "updated_commits": {"added": [added_commit_id],
110 "updated_commits": {"added": [added_commit_id],
112 "common": [common_commit_id],
111 "common": [common_commit_id],
113 "total": total_commits,
112 "total": total_commits,
114 "removed": []},
113 "removed": []},
115 "updated_reviewers": {"added": [], "removed": []},
114 "updated_reviewers": {"added": [], "removed": []},
116 }
115 }
117
116
118 assert_ok(id_, expected, response.body)
117 assert_ok(id_, expected, response.body)
119
118
120 @pytest.mark.backends("git", "hg")
119 @pytest.mark.backends("git", "hg")
121 def test_api_update_change_reviewers(
120 def test_api_update_change_reviewers(
122 self, pr_util, silence_action_logger, no_notifications):
121 self, user_util, pr_util, no_notifications):
122 a = user_util.create_user()
123 b = user_util.create_user()
124 c = user_util.create_user()
125 new_reviewers = [
126 {'username': b.username,'reasons': ['updated via API'],
127 'mandatory':False},
128 {'username': c.username, 'reasons': ['updated via API'],
129 'mandatory':False},
130 ]
123
131
124 users = [x.username for x in User.get_all()]
132 added = [b.username, c.username]
125 new = [users.pop(0)]
133 removed = [a.username]
126 removed = sorted(new)
127 added = sorted(users)
128
134
129 pull_request = pr_util.create_pull_request(reviewers=new)
135 pull_request = pr_util.create_pull_request(
136 reviewers=[(a.username, ['added via API'], False)])
130
137
131 id_, params = build_data(
138 id_, params = build_data(
132 self.apikey, 'update_pull_request',
139 self.apikey, 'update_pull_request',
133 repoid=pull_request.target_repo.repo_name,
140 repoid=pull_request.target_repo.repo_name,
134 pullrequestid=pull_request.pull_request_id,
141 pullrequestid=pull_request.pull_request_id,
135 reviewers=added)
142 reviewers=new_reviewers)
136 response = api_call(self.app, params)
143 response = api_call(self.app, params)
137 expected = {
144 expected = {
138 "msg": "Updated pull request `{}`".format(
145 "msg": "Updated pull request `{}`".format(
139 pull_request.pull_request_id),
146 pull_request.pull_request_id),
140 "pull_request": response.json['result']['pull_request'],
147 "pull_request": response.json['result']['pull_request'],
141 "updated_commits": {"added": [], "common": [], "removed": []},
148 "updated_commits": {"added": [], "common": [], "removed": []},
142 "updated_reviewers": {"added": added, "removed": removed},
149 "updated_reviewers": {"added": added, "removed": removed},
143 }
150 }
144
151
145 assert_ok(id_, expected, response.body)
152 assert_ok(id_, expected, response.body)
146
153
147 @pytest.mark.backends("git", "hg")
154 @pytest.mark.backends("git", "hg")
148 def test_api_update_bad_user_in_reviewers(self, pr_util):
155 def test_api_update_bad_user_in_reviewers(self, pr_util):
149 pull_request = pr_util.create_pull_request()
156 pull_request = pr_util.create_pull_request()
150
157
151 id_, params = build_data(
158 id_, params = build_data(
152 self.apikey, 'update_pull_request',
159 self.apikey, 'update_pull_request',
153 repoid=pull_request.target_repo.repo_name,
160 repoid=pull_request.target_repo.repo_name,
154 pullrequestid=pull_request.pull_request_id,
161 pullrequestid=pull_request.pull_request_id,
155 reviewers=['bad_name'])
162 reviewers=[{'username': 'bad_name'}])
156 response = api_call(self.app, params)
163 response = api_call(self.app, params)
157
164
158 expected = 'user `bad_name` does not exist'
165 expected = 'user `bad_name` does not exist'
159
166
160 assert_error(id_, expected, response.body)
167 assert_error(id_, expected, response.body)
161
168
162 @pytest.mark.backends("git", "hg")
169 @pytest.mark.backends("git", "hg")
163 def test_api_update_repo_error(self, pr_util):
170 def test_api_update_repo_error(self, pr_util):
164 id_, params = build_data(
171 id_, params = build_data(
165 self.apikey, 'update_pull_request',
172 self.apikey, 'update_pull_request',
166 repoid='fake',
173 repoid='fake',
167 pullrequestid='fake',
174 pullrequestid='fake',
168 reviewers=['bad_name'])
175 reviewers=[{'username': 'bad_name'}])
169 response = api_call(self.app, params)
176 response = api_call(self.app, params)
170
177
171 expected = 'repository `fake` does not exist'
178 expected = 'repository `fake` does not exist'
172
179
173 response_json = response.json['error']
180 response_json = response.json['error']
174 assert response_json == expected
181 assert response_json == expected
175
182
176 @pytest.mark.backends("git", "hg")
183 @pytest.mark.backends("git", "hg")
177 def test_api_update_pull_request_error(self, pr_util):
184 def test_api_update_pull_request_error(self, pr_util):
178 pull_request = pr_util.create_pull_request()
185 pull_request = pr_util.create_pull_request()
179
186
180 id_, params = build_data(
187 id_, params = build_data(
181 self.apikey, 'update_pull_request',
188 self.apikey, 'update_pull_request',
182 repoid=pull_request.target_repo.repo_name,
189 repoid=pull_request.target_repo.repo_name,
183 pullrequestid=999999,
190 pullrequestid=999999,
184 reviewers=['bad_name'])
191 reviewers=[{'username': 'bad_name'}])
185 response = api_call(self.app, params)
192 response = api_call(self.app, params)
186
193
187 expected = 'pull request `999999` does not exist'
194 expected = 'pull request `999999` does not exist'
188 assert_error(id_, expected, response.body)
195 assert_error(id_, expected, response.body)
189
196
190 @pytest.mark.backends("git", "hg")
197 @pytest.mark.backends("git", "hg")
191 def test_api_update_pull_request_no_perms_to_update(
198 def test_api_update_pull_request_no_perms_to_update(
192 self, user_util, pr_util):
199 self, user_util, pr_util):
193 user = user_util.create_user()
200 user = user_util.create_user()
194 pull_request = pr_util.create_pull_request()
201 pull_request = pr_util.create_pull_request()
195
202
196 id_, params = build_data(
203 id_, params = build_data(
197 user.api_key, 'update_pull_request',
204 user.api_key, 'update_pull_request',
198 repoid=pull_request.target_repo.repo_name,
205 repoid=pull_request.target_repo.repo_name,
199 pullrequestid=pull_request.pull_request_id,)
206 pullrequestid=pull_request.pull_request_id,)
200 response = api_call(self.app, params)
207 response = api_call(self.app, params)
201
208
202 expected = ('pull request `%s` update failed, '
209 expected = ('pull request `%s` update failed, '
203 'no permission to update.') % pull_request.pull_request_id
210 'no permission to update.') % pull_request.pull_request_id
204
211
205 assert_error(id_, expected, response.body)
212 assert_error(id_, expected, response.body)
@@ -1,189 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29 from rhodecode.tests.plugin import http_host_stub, http_host_only_stub
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33 UPDATE_REPO_NAME = 'api_update_me'
33 UPDATE_REPO_NAME = 'api_update_me'
34
34
35
35
36 class SAME_AS_UPDATES(object):
36 class SAME_AS_UPDATES(object):
37 """ Constant used for tests below """
37 """ Constant used for tests below """
38
38
39
39
40 @pytest.mark.usefixtures("testuser_api", "app")
40 @pytest.mark.usefixtures("testuser_api", "app")
41 class TestApiUpdateRepo(object):
41 class TestApiUpdateRepo(object):
42
42
43 @pytest.mark.parametrize("updates, expected", [
43 @pytest.mark.parametrize("updates, expected", [
44 ({'owner': TEST_USER_REGULAR_LOGIN},
44 ({'owner': TEST_USER_REGULAR_LOGIN},
45 SAME_AS_UPDATES),
45 SAME_AS_UPDATES),
46
46
47 ({'description': 'new description'},
47 ({'description': 'new description'},
48 SAME_AS_UPDATES),
48 SAME_AS_UPDATES),
49
49
50 ({'clone_uri': 'http://foo.com/repo'},
50 ({'clone_uri': 'http://foo.com/repo'},
51 SAME_AS_UPDATES),
51 SAME_AS_UPDATES),
52
52
53 ({'clone_uri': None},
53 ({'clone_uri': None},
54 {'clone_uri': ''}),
54 {'clone_uri': ''}),
55
55
56 ({'clone_uri': ''},
56 ({'clone_uri': ''},
57 {'clone_uri': ''}),
57 {'clone_uri': ''}),
58
58
59 ({'landing_rev': 'rev:tip'},
59 ({'landing_rev': 'rev:tip'},
60 {'landing_rev': ['rev', 'tip']}),
60 {'landing_rev': ['rev', 'tip']}),
61
61
62 ({'enable_statistics': True},
62 ({'enable_statistics': True},
63 SAME_AS_UPDATES),
63 SAME_AS_UPDATES),
64
64
65 ({'enable_locking': True},
65 ({'enable_locking': True},
66 SAME_AS_UPDATES),
66 SAME_AS_UPDATES),
67
67
68 ({'enable_downloads': True},
68 ({'enable_downloads': True},
69 SAME_AS_UPDATES),
69 SAME_AS_UPDATES),
70
70
71 ({'repo_name': 'new_repo_name'},
71 ({'repo_name': 'new_repo_name'},
72 {
72 {
73 'repo_name': 'new_repo_name',
73 'repo_name': 'new_repo_name',
74 'url': 'http://test.example.com:80/new_repo_name'
74 'url': 'http://{}/new_repo_name'.format(http_host_only_stub())
75 }),
75 }),
76
76
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
78 '_group': 'test_group_for_update'},
78 '_group': 'test_group_for_update'},
79 {
79 {
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
81 'url': 'http://test.example.com:80/test_group_for_update/{}'.format(UPDATE_REPO_NAME)
81 'url': 'http://{}/test_group_for_update/{}'.format(
82 http_host_only_stub(), UPDATE_REPO_NAME)
82 }),
83 }),
83 ])
84 ])
84 def test_api_update_repo(self, updates, expected, backend):
85 def test_api_update_repo(self, updates, expected, backend):
85 repo_name = UPDATE_REPO_NAME
86 repo_name = UPDATE_REPO_NAME
86 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
87 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
87 if updates.get('_group'):
88 if updates.get('_group'):
88 fixture.create_repo_group(updates['_group'])
89 fixture.create_repo_group(updates['_group'])
89
90
90 expected_api_data = repo.get_api_data(include_secrets=True)
91 expected_api_data = repo.get_api_data(include_secrets=True)
91 if expected is SAME_AS_UPDATES:
92 if expected is SAME_AS_UPDATES:
92 expected_api_data.update(updates)
93 expected_api_data.update(updates)
93 else:
94 else:
94 expected_api_data.update(expected)
95 expected_api_data.update(expected)
95
96
96 id_, params = build_data(
97 id_, params = build_data(
97 self.apikey, 'update_repo', repoid=repo_name, **updates)
98 self.apikey, 'update_repo', repoid=repo_name, **updates)
98 response = api_call(self.app, params)
99 response = api_call(self.app, params)
99
100
100 if updates.get('repo_name'):
101 if updates.get('repo_name'):
101 repo_name = updates['repo_name']
102 repo_name = updates['repo_name']
102
103
103 try:
104 try:
104 expected = {
105 expected = {
105 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
106 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
106 'repository': jsonify(expected_api_data)
107 'repository': jsonify(expected_api_data)
107 }
108 }
108 assert_ok(id_, expected, given=response.body)
109 assert_ok(id_, expected, given=response.body)
109 finally:
110 finally:
110 fixture.destroy_repo(repo_name)
111 fixture.destroy_repo(repo_name)
111 if updates.get('_group'):
112 if updates.get('_group'):
112 fixture.destroy_repo_group(updates['_group'])
113 fixture.destroy_repo_group(updates['_group'])
113
114
114 def test_api_update_repo_fork_of_field(self, backend):
115 def test_api_update_repo_fork_of_field(self, backend):
115 master_repo = backend.create_repo()
116 master_repo = backend.create_repo()
116 repo = backend.create_repo()
117 repo = backend.create_repo()
117 updates = {
118 updates = {
118 'fork_of': master_repo.repo_name
119 'fork_of': master_repo.repo_name,
120 'fork_of_id': master_repo.repo_id
119 }
121 }
120 expected_api_data = repo.get_api_data(include_secrets=True)
122 expected_api_data = repo.get_api_data(include_secrets=True)
121 expected_api_data.update(updates)
123 expected_api_data.update(updates)
122
124
123 id_, params = build_data(
125 id_, params = build_data(
124 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
126 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
125 response = api_call(self.app, params)
127 response = api_call(self.app, params)
126 expected = {
128 expected = {
127 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
129 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
128 'repository': jsonify(expected_api_data)
130 'repository': jsonify(expected_api_data)
129 }
131 }
130 assert_ok(id_, expected, given=response.body)
132 assert_ok(id_, expected, given=response.body)
131 result = response.json['result']['repository']
133 result = response.json['result']['repository']
132 assert result['fork_of'] == master_repo.repo_name
134 assert result['fork_of'] == master_repo.repo_name
135 assert result['fork_of_id'] == master_repo.repo_id
133
136
134 def test_api_update_repo_fork_of_not_found(self, backend):
137 def test_api_update_repo_fork_of_not_found(self, backend):
135 master_repo_name = 'fake-parent-repo'
138 master_repo_name = 'fake-parent-repo'
136 repo = backend.create_repo()
139 repo = backend.create_repo()
137 updates = {
140 updates = {
138 'fork_of': master_repo_name
141 'fork_of': master_repo_name
139 }
142 }
140 id_, params = build_data(
143 id_, params = build_data(
141 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
144 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
142 response = api_call(self.app, params)
145 response = api_call(self.app, params)
143 expected = {
146 expected = {
144 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
147 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
145 master_repo_name)}
148 master_repo_name)}
146 assert_error(id_, expected, given=response.body)
149 assert_error(id_, expected, given=response.body)
147
150
148 def test_api_update_repo_with_repo_group_not_existing(self):
151 def test_api_update_repo_with_repo_group_not_existing(self):
149 repo_name = 'admin_owned'
152 repo_name = 'admin_owned'
150 fake_repo_group = 'test_group_for_update'
153 fake_repo_group = 'test_group_for_update'
151 fixture.create_repo(repo_name)
154 fixture.create_repo(repo_name)
152 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
155 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
153 id_, params = build_data(
156 id_, params = build_data(
154 self.apikey, 'update_repo', repoid=repo_name, **updates)
157 self.apikey, 'update_repo', repoid=repo_name, **updates)
155 response = api_call(self.app, params)
158 response = api_call(self.app, params)
156 try:
159 try:
157 expected = {
160 expected = {
158 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
161 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
159 }
162 }
160 assert_error(id_, expected, given=response.body)
163 assert_error(id_, expected, given=response.body)
161 finally:
164 finally:
162 fixture.destroy_repo(repo_name)
165 fixture.destroy_repo(repo_name)
163
166
164 def test_api_update_repo_regular_user_not_allowed(self):
167 def test_api_update_repo_regular_user_not_allowed(self):
165 repo_name = 'admin_owned'
168 repo_name = 'admin_owned'
166 fixture.create_repo(repo_name)
169 fixture.create_repo(repo_name)
167 updates = {'active': False}
170 updates = {'active': False}
168 id_, params = build_data(
171 id_, params = build_data(
169 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
172 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
170 response = api_call(self.app, params)
173 response = api_call(self.app, params)
171 try:
174 try:
172 expected = 'repository `%s` does not exist' % (repo_name,)
175 expected = 'repository `%s` does not exist' % (repo_name,)
173 assert_error(id_, expected, given=response.body)
176 assert_error(id_, expected, given=response.body)
174 finally:
177 finally:
175 fixture.destroy_repo(repo_name)
178 fixture.destroy_repo(repo_name)
176
179
177 @mock.patch.object(RepoModel, 'update', crash)
180 @mock.patch.object(RepoModel, 'update', crash)
178 def test_api_update_repo_exception_occurred(self, backend):
181 def test_api_update_repo_exception_occurred(self, backend):
179 repo_name = UPDATE_REPO_NAME
182 repo_name = UPDATE_REPO_NAME
180 fixture.create_repo(repo_name, repo_type=backend.alias)
183 fixture.create_repo(repo_name, repo_type=backend.alias)
181 id_, params = build_data(
184 id_, params = build_data(
182 self.apikey, 'update_repo', repoid=repo_name,
185 self.apikey, 'update_repo', repoid=repo_name,
183 owner=TEST_USER_ADMIN_LOGIN,)
186 owner=TEST_USER_ADMIN_LOGIN,)
184 response = api_call(self.app, params)
187 response = api_call(self.app, params)
185 try:
188 try:
186 expected = 'failed to update repo `%s`' % (repo_name,)
189 expected = 'failed to update repo `%s`' % (repo_name,)
187 assert_error(id_, expected, given=response.body)
190 assert_error(id_, expected, given=response.body)
188 finally:
191 finally:
189 fixture.destroy_repo(repo_name)
192 fixture.destroy_repo(repo_name)
@@ -1,725 +1,779 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError
24 from rhodecode import events
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
25 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 validate_repo_permissions, resolve_ref_or_error)
29 validate_repo_permissions, resolve_ref_or_error)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.comment import CommentsModel
34 from rhodecode.model.comment import CommentsModel
34 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 ReviewerListSchema)
37
41
38 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
39
43
40
44
41 @jsonrpc_method()
45 @jsonrpc_method()
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
46 def get_pull_request(request, apiuser, repoid, pullrequestid):
43 """
47 """
44 Get a pull request based on the given ID.
48 Get a pull request based on the given ID.
45
49
46 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
47 :type apiuser: AuthUser
51 :type apiuser: AuthUser
48 :param repoid: Repository name or repository ID from where the pull
52 :param repoid: Repository name or repository ID from where the pull
49 request was opened.
53 request was opened.
50 :type repoid: str or int
54 :type repoid: str or int
51 :param pullrequestid: ID of the requested pull request.
55 :param pullrequestid: ID of the requested pull request.
52 :type pullrequestid: int
56 :type pullrequestid: int
53
57
54 Example output:
58 Example output:
55
59
56 .. code-block:: bash
60 .. code-block:: bash
57
61
58 "id": <id_given_in_input>,
62 "id": <id_given_in_input>,
59 "result":
63 "result":
60 {
64 {
61 "pull_request_id": "<pull_request_id>",
65 "pull_request_id": "<pull_request_id>",
62 "url": "<url>",
66 "url": "<url>",
63 "title": "<title>",
67 "title": "<title>",
64 "description": "<description>",
68 "description": "<description>",
65 "status" : "<status>",
69 "status" : "<status>",
66 "created_on": "<date_time_created>",
70 "created_on": "<date_time_created>",
67 "updated_on": "<date_time_updated>",
71 "updated_on": "<date_time_updated>",
68 "commit_ids": [
72 "commit_ids": [
69 ...
73 ...
70 "<commit_id>",
74 "<commit_id>",
71 "<commit_id>",
75 "<commit_id>",
72 ...
76 ...
73 ],
77 ],
74 "review_status": "<review_status>",
78 "review_status": "<review_status>",
75 "mergeable": {
79 "mergeable": {
76 "status": "<bool>",
80 "status": "<bool>",
77 "message": "<message>",
81 "message": "<message>",
78 },
82 },
79 "source": {
83 "source": {
80 "clone_url": "<clone_url>",
84 "clone_url": "<clone_url>",
81 "repository": "<repository_name>",
85 "repository": "<repository_name>",
82 "reference":
86 "reference":
83 {
87 {
84 "name": "<name>",
88 "name": "<name>",
85 "type": "<type>",
89 "type": "<type>",
86 "commit_id": "<commit_id>",
90 "commit_id": "<commit_id>",
87 }
91 }
88 },
92 },
89 "target": {
93 "target": {
90 "clone_url": "<clone_url>",
94 "clone_url": "<clone_url>",
91 "repository": "<repository_name>",
95 "repository": "<repository_name>",
92 "reference":
96 "reference":
93 {
97 {
94 "name": "<name>",
98 "name": "<name>",
95 "type": "<type>",
99 "type": "<type>",
96 "commit_id": "<commit_id>",
100 "commit_id": "<commit_id>",
97 }
101 }
98 },
102 },
99 "merge": {
103 "merge": {
100 "clone_url": "<clone_url>",
104 "clone_url": "<clone_url>",
101 "reference":
105 "reference":
102 {
106 {
103 "name": "<name>",
107 "name": "<name>",
104 "type": "<type>",
108 "type": "<type>",
105 "commit_id": "<commit_id>",
109 "commit_id": "<commit_id>",
106 }
110 }
107 },
111 },
108 "author": <user_obj>,
112 "author": <user_obj>,
109 "reviewers": [
113 "reviewers": [
110 ...
114 ...
111 {
115 {
112 "user": "<user_obj>",
116 "user": "<user_obj>",
113 "review_status": "<review_status>",
117 "review_status": "<review_status>",
114 }
118 }
115 ...
119 ...
116 ]
120 ]
117 },
121 },
118 "error": null
122 "error": null
119 """
123 """
120 get_repo_or_error(repoid)
124 get_repo_or_error(repoid)
121 pull_request = get_pull_request_or_error(pullrequestid)
125 pull_request = get_pull_request_or_error(pullrequestid)
122 if not PullRequestModel().check_user_read(
126 if not PullRequestModel().check_user_read(
123 pull_request, apiuser, api=True):
127 pull_request, apiuser, api=True):
124 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
128 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
125 data = pull_request.get_api_data()
129 data = pull_request.get_api_data()
126 return data
130 return data
127
131
128
132
129 @jsonrpc_method()
133 @jsonrpc_method()
130 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
134 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
131 """
135 """
132 Get all pull requests from the repository specified in `repoid`.
136 Get all pull requests from the repository specified in `repoid`.
133
137
134 :param apiuser: This is filled automatically from the |authtoken|.
138 :param apiuser: This is filled automatically from the |authtoken|.
135 :type apiuser: AuthUser
139 :type apiuser: AuthUser
136 :param repoid: Repository name or repository ID.
140 :param repoid: Repository name or repository ID.
137 :type repoid: str or int
141 :type repoid: str or int
138 :param status: Only return pull requests with the specified status.
142 :param status: Only return pull requests with the specified status.
139 Valid options are.
143 Valid options are.
140 * ``new`` (default)
144 * ``new`` (default)
141 * ``open``
145 * ``open``
142 * ``closed``
146 * ``closed``
143 :type status: str
147 :type status: str
144
148
145 Example output:
149 Example output:
146
150
147 .. code-block:: bash
151 .. code-block:: bash
148
152
149 "id": <id_given_in_input>,
153 "id": <id_given_in_input>,
150 "result":
154 "result":
151 [
155 [
152 ...
156 ...
153 {
157 {
154 "pull_request_id": "<pull_request_id>",
158 "pull_request_id": "<pull_request_id>",
155 "url": "<url>",
159 "url": "<url>",
156 "title" : "<title>",
160 "title" : "<title>",
157 "description": "<description>",
161 "description": "<description>",
158 "status": "<status>",
162 "status": "<status>",
159 "created_on": "<date_time_created>",
163 "created_on": "<date_time_created>",
160 "updated_on": "<date_time_updated>",
164 "updated_on": "<date_time_updated>",
161 "commit_ids": [
165 "commit_ids": [
162 ...
166 ...
163 "<commit_id>",
167 "<commit_id>",
164 "<commit_id>",
168 "<commit_id>",
165 ...
169 ...
166 ],
170 ],
167 "review_status": "<review_status>",
171 "review_status": "<review_status>",
168 "mergeable": {
172 "mergeable": {
169 "status": "<bool>",
173 "status": "<bool>",
170 "message: "<message>",
174 "message: "<message>",
171 },
175 },
172 "source": {
176 "source": {
173 "clone_url": "<clone_url>",
177 "clone_url": "<clone_url>",
174 "reference":
178 "reference":
175 {
179 {
176 "name": "<name>",
180 "name": "<name>",
177 "type": "<type>",
181 "type": "<type>",
178 "commit_id": "<commit_id>",
182 "commit_id": "<commit_id>",
179 }
183 }
180 },
184 },
181 "target": {
185 "target": {
182 "clone_url": "<clone_url>",
186 "clone_url": "<clone_url>",
183 "reference":
187 "reference":
184 {
188 {
185 "name": "<name>",
189 "name": "<name>",
186 "type": "<type>",
190 "type": "<type>",
187 "commit_id": "<commit_id>",
191 "commit_id": "<commit_id>",
188 }
192 }
189 },
193 },
190 "merge": {
194 "merge": {
191 "clone_url": "<clone_url>",
195 "clone_url": "<clone_url>",
192 "reference":
196 "reference":
193 {
197 {
194 "name": "<name>",
198 "name": "<name>",
195 "type": "<type>",
199 "type": "<type>",
196 "commit_id": "<commit_id>",
200 "commit_id": "<commit_id>",
197 }
201 }
198 },
202 },
199 "author": <user_obj>,
203 "author": <user_obj>,
200 "reviewers": [
204 "reviewers": [
201 ...
205 ...
202 {
206 {
203 "user": "<user_obj>",
207 "user": "<user_obj>",
204 "review_status": "<review_status>",
208 "review_status": "<review_status>",
205 }
209 }
206 ...
210 ...
207 ]
211 ]
208 }
212 }
209 ...
213 ...
210 ],
214 ],
211 "error": null
215 "error": null
212
216
213 """
217 """
214 repo = get_repo_or_error(repoid)
218 repo = get_repo_or_error(repoid)
215 if not has_superadmin_permission(apiuser):
219 if not has_superadmin_permission(apiuser):
216 _perms = (
220 _perms = (
217 'repository.admin', 'repository.write', 'repository.read',)
221 'repository.admin', 'repository.write', 'repository.read',)
218 validate_repo_permissions(apiuser, repoid, repo, _perms)
222 validate_repo_permissions(apiuser, repoid, repo, _perms)
219
223
220 status = Optional.extract(status)
224 status = Optional.extract(status)
221 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
225 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
222 data = [pr.get_api_data() for pr in pull_requests]
226 data = [pr.get_api_data() for pr in pull_requests]
223 return data
227 return data
224
228
225
229
226 @jsonrpc_method()
230 @jsonrpc_method()
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
231 def merge_pull_request(
228 userid=Optional(OAttr('apiuser'))):
232 request, apiuser, repoid, pullrequestid,
233 userid=Optional(OAttr('apiuser'))):
229 """
234 """
230 Merge the pull request specified by `pullrequestid` into its target
235 Merge the pull request specified by `pullrequestid` into its target
231 repository.
236 repository.
232
237
233 :param apiuser: This is filled automatically from the |authtoken|.
238 :param apiuser: This is filled automatically from the |authtoken|.
234 :type apiuser: AuthUser
239 :type apiuser: AuthUser
235 :param repoid: The Repository name or repository ID of the
240 :param repoid: The Repository name or repository ID of the
236 target repository to which the |pr| is to be merged.
241 target repository to which the |pr| is to be merged.
237 :type repoid: str or int
242 :type repoid: str or int
238 :param pullrequestid: ID of the pull request which shall be merged.
243 :param pullrequestid: ID of the pull request which shall be merged.
239 :type pullrequestid: int
244 :type pullrequestid: int
240 :param userid: Merge the pull request as this user.
245 :param userid: Merge the pull request as this user.
241 :type userid: Optional(str or int)
246 :type userid: Optional(str or int)
242
247
243 Example output:
248 Example output:
244
249
245 .. code-block:: bash
250 .. code-block:: bash
246
251
247 "id": <id_given_in_input>,
252 "id": <id_given_in_input>,
248 "result": {
253 "result": {
249 "executed": "<bool>",
254 "executed": "<bool>",
250 "failure_reason": "<int>",
255 "failure_reason": "<int>",
251 "merge_commit_id": "<merge_commit_id>",
256 "merge_commit_id": "<merge_commit_id>",
252 "possible": "<bool>",
257 "possible": "<bool>",
253 "merge_ref": {
258 "merge_ref": {
254 "commit_id": "<commit_id>",
259 "commit_id": "<commit_id>",
255 "type": "<type>",
260 "type": "<type>",
256 "name": "<name>"
261 "name": "<name>"
257 }
262 }
258 },
263 },
259 "error": null
264 "error": null
260 """
265 """
261 repo = get_repo_or_error(repoid)
266 repo = get_repo_or_error(repoid)
262 if not isinstance(userid, Optional):
267 if not isinstance(userid, Optional):
263 if (has_superadmin_permission(apiuser) or
268 if (has_superadmin_permission(apiuser) or
264 HasRepoPermissionAnyApi('repository.admin')(
269 HasRepoPermissionAnyApi('repository.admin')(
265 user=apiuser, repo_name=repo.repo_name)):
270 user=apiuser, repo_name=repo.repo_name)):
266 apiuser = get_user_or_error(userid)
271 apiuser = get_user_or_error(userid)
267 else:
272 else:
268 raise JSONRPCError('userid is not the same as your user')
273 raise JSONRPCError('userid is not the same as your user')
269
274
270 pull_request = get_pull_request_or_error(pullrequestid)
275 pull_request = get_pull_request_or_error(pullrequestid)
271
276
272 check = MergeCheck.validate(pull_request, user=apiuser)
277 check = MergeCheck.validate(pull_request, user=apiuser)
273 merge_possible = not check.failed
278 merge_possible = not check.failed
274
279
275 if not merge_possible:
280 if not merge_possible:
276 reasons = ','.join([msg for _e, msg in check.errors])
281 error_messages = []
282 for err_type, error_msg in check.errors:
283 error_msg = request.translate(error_msg)
284 error_messages.append(error_msg)
285
286 reasons = ','.join(error_messages)
277 raise JSONRPCError(
287 raise JSONRPCError(
278 'merge not possible for following reasons: {}'.format(reasons))
288 'merge not possible for following reasons: {}'.format(reasons))
279
289
280 target_repo = pull_request.target_repo
290 target_repo = pull_request.target_repo
281 extras = vcs_operation_context(
291 extras = vcs_operation_context(
282 request.environ, repo_name=target_repo.repo_name,
292 request.environ, repo_name=target_repo.repo_name,
283 username=apiuser.username, action='push',
293 username=apiuser.username, action='push',
284 scm=target_repo.repo_type)
294 scm=target_repo.repo_type)
285 merge_response = PullRequestModel().merge(
295 merge_response = PullRequestModel().merge(
286 pull_request, apiuser, extras=extras)
296 pull_request, apiuser, extras=extras)
287 if merge_response.executed:
297 if merge_response.executed:
288 PullRequestModel().close_pull_request(
298 PullRequestModel().close_pull_request(
289 pull_request.pull_request_id, apiuser)
299 pull_request.pull_request_id, apiuser)
290
300
291 Session().commit()
301 Session().commit()
292
302
293 # In previous versions the merge response directly contained the merge
303 # In previous versions the merge response directly contained the merge
294 # commit id. It is now contained in the merge reference object. To be
304 # commit id. It is now contained in the merge reference object. To be
295 # backwards compatible we have to extract it again.
305 # backwards compatible we have to extract it again.
296 merge_response = merge_response._asdict()
306 merge_response = merge_response._asdict()
297 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
307 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
298
308
299 return merge_response
309 return merge_response
300
310
301
311
302 @jsonrpc_method()
312 @jsonrpc_method()
303 def close_pull_request(request, apiuser, repoid, pullrequestid,
304 userid=Optional(OAttr('apiuser'))):
305 """
306 Close the pull request specified by `pullrequestid`.
307
308 :param apiuser: This is filled automatically from the |authtoken|.
309 :type apiuser: AuthUser
310 :param repoid: Repository name or repository ID to which the pull
311 request belongs.
312 :type repoid: str or int
313 :param pullrequestid: ID of the pull request to be closed.
314 :type pullrequestid: int
315 :param userid: Close the pull request as this user.
316 :type userid: Optional(str or int)
317
318 Example output:
319
320 .. code-block:: bash
321
322 "id": <id_given_in_input>,
323 "result": {
324 "pull_request_id": "<int>",
325 "closed": "<bool>"
326 },
327 "error": null
328
329 """
330 repo = get_repo_or_error(repoid)
331 if not isinstance(userid, Optional):
332 if (has_superadmin_permission(apiuser) or
333 HasRepoPermissionAnyApi('repository.admin')(
334 user=apiuser, repo_name=repo.repo_name)):
335 apiuser = get_user_or_error(userid)
336 else:
337 raise JSONRPCError('userid is not the same as your user')
338
339 pull_request = get_pull_request_or_error(pullrequestid)
340 if not PullRequestModel().check_user_update(
341 pull_request, apiuser, api=True):
342 raise JSONRPCError(
343 'pull request `%s` close failed, no permission to close.' % (
344 pullrequestid,))
345 if pull_request.is_closed():
346 raise JSONRPCError(
347 'pull request `%s` is already closed' % (pullrequestid,))
348
349 PullRequestModel().close_pull_request(
350 pull_request.pull_request_id, apiuser)
351 Session().commit()
352 data = {
353 'pull_request_id': pull_request.pull_request_id,
354 'closed': True,
355 }
356 return data
357
358
359 @jsonrpc_method()
360 def comment_pull_request(
313 def comment_pull_request(
361 request, apiuser, repoid, pullrequestid, message=Optional(None),
314 request, apiuser, repoid, pullrequestid, message=Optional(None),
362 commit_id=Optional(None), status=Optional(None),
315 commit_id=Optional(None), status=Optional(None),
363 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
316 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
364 resolves_comment_id=Optional(None),
317 resolves_comment_id=Optional(None),
365 userid=Optional(OAttr('apiuser'))):
318 userid=Optional(OAttr('apiuser'))):
366 """
319 """
367 Comment on the pull request specified with the `pullrequestid`,
320 Comment on the pull request specified with the `pullrequestid`,
368 in the |repo| specified by the `repoid`, and optionally change the
321 in the |repo| specified by the `repoid`, and optionally change the
369 review status.
322 review status.
370
323
371 :param apiuser: This is filled automatically from the |authtoken|.
324 :param apiuser: This is filled automatically from the |authtoken|.
372 :type apiuser: AuthUser
325 :type apiuser: AuthUser
373 :param repoid: The repository name or repository ID.
326 :param repoid: The repository name or repository ID.
374 :type repoid: str or int
327 :type repoid: str or int
375 :param pullrequestid: The pull request ID.
328 :param pullrequestid: The pull request ID.
376 :type pullrequestid: int
329 :type pullrequestid: int
377 :param commit_id: Specify the commit_id for which to set a comment. If
330 :param commit_id: Specify the commit_id for which to set a comment. If
378 given commit_id is different than latest in the PR status
331 given commit_id is different than latest in the PR status
379 change won't be performed.
332 change won't be performed.
380 :type commit_id: str
333 :type commit_id: str
381 :param message: The text content of the comment.
334 :param message: The text content of the comment.
382 :type message: str
335 :type message: str
383 :param status: (**Optional**) Set the approval status of the pull
336 :param status: (**Optional**) Set the approval status of the pull
384 request. One of: 'not_reviewed', 'approved', 'rejected',
337 request. One of: 'not_reviewed', 'approved', 'rejected',
385 'under_review'
338 'under_review'
386 :type status: str
339 :type status: str
387 :param comment_type: Comment type, one of: 'note', 'todo'
340 :param comment_type: Comment type, one of: 'note', 'todo'
388 :type comment_type: Optional(str), default: 'note'
341 :type comment_type: Optional(str), default: 'note'
389 :param userid: Comment on the pull request as this user
342 :param userid: Comment on the pull request as this user
390 :type userid: Optional(str or int)
343 :type userid: Optional(str or int)
391
344
392 Example output:
345 Example output:
393
346
394 .. code-block:: bash
347 .. code-block:: bash
395
348
396 id : <id_given_in_input>
349 id : <id_given_in_input>
397 result : {
350 result : {
398 "pull_request_id": "<Integer>",
351 "pull_request_id": "<Integer>",
399 "comment_id": "<Integer>",
352 "comment_id": "<Integer>",
400 "status": {"given": <given_status>,
353 "status": {"given": <given_status>,
401 "was_changed": <bool status_was_actually_changed> },
354 "was_changed": <bool status_was_actually_changed> },
402 },
355 },
403 error : null
356 error : null
404 """
357 """
405 repo = get_repo_or_error(repoid)
358 repo = get_repo_or_error(repoid)
406 if not isinstance(userid, Optional):
359 if not isinstance(userid, Optional):
407 if (has_superadmin_permission(apiuser) or
360 if (has_superadmin_permission(apiuser) or
408 HasRepoPermissionAnyApi('repository.admin')(
361 HasRepoPermissionAnyApi('repository.admin')(
409 user=apiuser, repo_name=repo.repo_name)):
362 user=apiuser, repo_name=repo.repo_name)):
410 apiuser = get_user_or_error(userid)
363 apiuser = get_user_or_error(userid)
411 else:
364 else:
412 raise JSONRPCError('userid is not the same as your user')
365 raise JSONRPCError('userid is not the same as your user')
413
366
414 pull_request = get_pull_request_or_error(pullrequestid)
367 pull_request = get_pull_request_or_error(pullrequestid)
415 if not PullRequestModel().check_user_read(
368 if not PullRequestModel().check_user_read(
416 pull_request, apiuser, api=True):
369 pull_request, apiuser, api=True):
417 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
370 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
418 message = Optional.extract(message)
371 message = Optional.extract(message)
419 status = Optional.extract(status)
372 status = Optional.extract(status)
420 commit_id = Optional.extract(commit_id)
373 commit_id = Optional.extract(commit_id)
421 comment_type = Optional.extract(comment_type)
374 comment_type = Optional.extract(comment_type)
422 resolves_comment_id = Optional.extract(resolves_comment_id)
375 resolves_comment_id = Optional.extract(resolves_comment_id)
423
376
424 if not message and not status:
377 if not message and not status:
425 raise JSONRPCError(
378 raise JSONRPCError(
426 'Both message and status parameters are missing. '
379 'Both message and status parameters are missing. '
427 'At least one is required.')
380 'At least one is required.')
428
381
429 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
382 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
430 status is not None):
383 status is not None):
431 raise JSONRPCError('Unknown comment status: `%s`' % status)
384 raise JSONRPCError('Unknown comment status: `%s`' % status)
432
385
433 if commit_id and commit_id not in pull_request.revisions:
386 if commit_id and commit_id not in pull_request.revisions:
434 raise JSONRPCError(
387 raise JSONRPCError(
435 'Invalid commit_id `%s` for this pull request.' % commit_id)
388 'Invalid commit_id `%s` for this pull request.' % commit_id)
436
389
437 allowed_to_change_status = PullRequestModel().check_user_change_status(
390 allowed_to_change_status = PullRequestModel().check_user_change_status(
438 pull_request, apiuser)
391 pull_request, apiuser)
439
392
440 # if commit_id is passed re-validated if user is allowed to change status
393 # if commit_id is passed re-validated if user is allowed to change status
441 # based on latest commit_id from the PR
394 # based on latest commit_id from the PR
442 if commit_id:
395 if commit_id:
443 commit_idx = pull_request.revisions.index(commit_id)
396 commit_idx = pull_request.revisions.index(commit_id)
444 if commit_idx != 0:
397 if commit_idx != 0:
445 allowed_to_change_status = False
398 allowed_to_change_status = False
446
399
447 if resolves_comment_id:
400 if resolves_comment_id:
448 comment = ChangesetComment.get(resolves_comment_id)
401 comment = ChangesetComment.get(resolves_comment_id)
449 if not comment:
402 if not comment:
450 raise JSONRPCError(
403 raise JSONRPCError(
451 'Invalid resolves_comment_id `%s` for this pull request.'
404 'Invalid resolves_comment_id `%s` for this pull request.'
452 % resolves_comment_id)
405 % resolves_comment_id)
453 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
406 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
454 raise JSONRPCError(
407 raise JSONRPCError(
455 'Comment `%s` is wrong type for setting status to resolved.'
408 'Comment `%s` is wrong type for setting status to resolved.'
456 % resolves_comment_id)
409 % resolves_comment_id)
457
410
458 text = message
411 text = message
459 status_label = ChangesetStatus.get_status_lbl(status)
412 status_label = ChangesetStatus.get_status_lbl(status)
460 if status and allowed_to_change_status:
413 if status and allowed_to_change_status:
461 st_message = ('Status change %(transition_icon)s %(status)s'
414 st_message = ('Status change %(transition_icon)s %(status)s'
462 % {'transition_icon': '>', 'status': status_label})
415 % {'transition_icon': '>', 'status': status_label})
463 text = message or st_message
416 text = message or st_message
464
417
465 rc_config = SettingsModel().get_all_settings()
418 rc_config = SettingsModel().get_all_settings()
466 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
419 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
467
420
468 status_change = status and allowed_to_change_status
421 status_change = status and allowed_to_change_status
469 comment = CommentsModel().create(
422 comment = CommentsModel().create(
470 text=text,
423 text=text,
471 repo=pull_request.target_repo.repo_id,
424 repo=pull_request.target_repo.repo_id,
472 user=apiuser.user_id,
425 user=apiuser.user_id,
473 pull_request=pull_request.pull_request_id,
426 pull_request=pull_request.pull_request_id,
474 f_path=None,
427 f_path=None,
475 line_no=None,
428 line_no=None,
476 status_change=(status_label if status_change else None),
429 status_change=(status_label if status_change else None),
477 status_change_type=(status if status_change else None),
430 status_change_type=(status if status_change else None),
478 closing_pr=False,
431 closing_pr=False,
479 renderer=renderer,
432 renderer=renderer,
480 comment_type=comment_type,
433 comment_type=comment_type,
481 resolves_comment_id=resolves_comment_id
434 resolves_comment_id=resolves_comment_id
482 )
435 )
483
436
484 if allowed_to_change_status and status:
437 if allowed_to_change_status and status:
485 ChangesetStatusModel().set_status(
438 ChangesetStatusModel().set_status(
486 pull_request.target_repo.repo_id,
439 pull_request.target_repo.repo_id,
487 status,
440 status,
488 apiuser.user_id,
441 apiuser.user_id,
489 comment,
442 comment,
490 pull_request=pull_request.pull_request_id
443 pull_request=pull_request.pull_request_id
491 )
444 )
492 Session().flush()
445 Session().flush()
493
446
494 Session().commit()
447 Session().commit()
495 data = {
448 data = {
496 'pull_request_id': pull_request.pull_request_id,
449 'pull_request_id': pull_request.pull_request_id,
497 'comment_id': comment.comment_id if comment else None,
450 'comment_id': comment.comment_id if comment else None,
498 'status': {'given': status, 'was_changed': status_change},
451 'status': {'given': status, 'was_changed': status_change},
499 }
452 }
500 return data
453 return data
501
454
502
455
503 @jsonrpc_method()
456 @jsonrpc_method()
504 def create_pull_request(
457 def create_pull_request(
505 request, apiuser, source_repo, target_repo, source_ref, target_ref,
458 request, apiuser, source_repo, target_repo, source_ref, target_ref,
506 title, description=Optional(''), reviewers=Optional(None)):
459 title, description=Optional(''), reviewers=Optional(None)):
507 """
460 """
508 Creates a new pull request.
461 Creates a new pull request.
509
462
510 Accepts refs in the following formats:
463 Accepts refs in the following formats:
511
464
512 * branch:<branch_name>:<sha>
465 * branch:<branch_name>:<sha>
513 * branch:<branch_name>
466 * branch:<branch_name>
514 * bookmark:<bookmark_name>:<sha> (Mercurial only)
467 * bookmark:<bookmark_name>:<sha> (Mercurial only)
515 * bookmark:<bookmark_name> (Mercurial only)
468 * bookmark:<bookmark_name> (Mercurial only)
516
469
517 :param apiuser: This is filled automatically from the |authtoken|.
470 :param apiuser: This is filled automatically from the |authtoken|.
518 :type apiuser: AuthUser
471 :type apiuser: AuthUser
519 :param source_repo: Set the source repository name.
472 :param source_repo: Set the source repository name.
520 :type source_repo: str
473 :type source_repo: str
521 :param target_repo: Set the target repository name.
474 :param target_repo: Set the target repository name.
522 :type target_repo: str
475 :type target_repo: str
523 :param source_ref: Set the source ref name.
476 :param source_ref: Set the source ref name.
524 :type source_ref: str
477 :type source_ref: str
525 :param target_ref: Set the target ref name.
478 :param target_ref: Set the target ref name.
526 :type target_ref: str
479 :type target_ref: str
527 :param title: Set the pull request title.
480 :param title: Set the pull request title.
528 :type title: str
481 :type title: str
529 :param description: Set the pull request description.
482 :param description: Set the pull request description.
530 :type description: Optional(str)
483 :type description: Optional(str)
531 :param reviewers: Set the new pull request reviewers list.
484 :param reviewers: Set the new pull request reviewers list.
485 Reviewer defined by review rules will be added automatically to the
486 defined list.
532 :type reviewers: Optional(list)
487 :type reviewers: Optional(list)
533 Accepts username strings or objects of the format:
488 Accepts username strings or objects of the format:
534
489
535 {'username': 'nick', 'reasons': ['original author']}
490 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
536 """
491 """
537
492
538 source = get_repo_or_error(source_repo)
493 source_db_repo = get_repo_or_error(source_repo)
539 target = get_repo_or_error(target_repo)
494 target_db_repo = get_repo_or_error(target_repo)
540 if not has_superadmin_permission(apiuser):
495 if not has_superadmin_permission(apiuser):
541 _perms = ('repository.admin', 'repository.write', 'repository.read',)
496 _perms = ('repository.admin', 'repository.write', 'repository.read',)
542 validate_repo_permissions(apiuser, source_repo, source, _perms)
497 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
543
498
544 full_source_ref = resolve_ref_or_error(source_ref, source)
499 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
545 full_target_ref = resolve_ref_or_error(target_ref, target)
500 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
546 source_commit = get_commit_or_error(full_source_ref, source)
501 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
547 target_commit = get_commit_or_error(full_target_ref, target)
502 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
548 source_scm = source.scm_instance()
503 source_scm = source_db_repo.scm_instance()
549 target_scm = target.scm_instance()
504 target_scm = target_db_repo.scm_instance()
550
505
551 commit_ranges = target_scm.compare(
506 commit_ranges = target_scm.compare(
552 target_commit.raw_id, source_commit.raw_id, source_scm,
507 target_commit.raw_id, source_commit.raw_id, source_scm,
553 merge=True, pre_load=[])
508 merge=True, pre_load=[])
554
509
555 ancestor = target_scm.get_common_ancestor(
510 ancestor = target_scm.get_common_ancestor(
556 target_commit.raw_id, source_commit.raw_id, source_scm)
511 target_commit.raw_id, source_commit.raw_id, source_scm)
557
512
558 if not commit_ranges:
513 if not commit_ranges:
559 raise JSONRPCError('no commits found')
514 raise JSONRPCError('no commits found')
560
515
561 if not ancestor:
516 if not ancestor:
562 raise JSONRPCError('no common ancestor found')
517 raise JSONRPCError('no common ancestor found')
563
518
564 reviewer_objects = Optional.extract(reviewers) or []
519 reviewer_objects = Optional.extract(reviewers) or []
565 if not isinstance(reviewer_objects, list):
520
566 raise JSONRPCError('reviewers should be specified as a list')
521 if reviewer_objects:
522 schema = ReviewerListSchema()
523 try:
524 reviewer_objects = schema.deserialize(reviewer_objects)
525 except Invalid as err:
526 raise JSONRPCValidationError(colander_exc=err)
527
528 # validate users
529 for reviewer_object in reviewer_objects:
530 user = get_user_or_error(reviewer_object['username'])
531 reviewer_object['user_id'] = user.user_id
567
532
568 reviewers_reasons = []
533 get_default_reviewers_data, get_validated_reviewers = \
569 for reviewer_object in reviewer_objects:
534 PullRequestModel().get_reviewer_functions()
570 reviewer_reasons = []
535
571 if isinstance(reviewer_object, (basestring, int)):
536 reviewer_rules = get_default_reviewers_data(
572 reviewer_username = reviewer_object
537 apiuser.get_instance(), source_db_repo,
573 else:
538 source_commit, target_db_repo, target_commit)
574 reviewer_username = reviewer_object['username']
575 reviewer_reasons = reviewer_object.get('reasons', [])
576
539
577 user = get_user_or_error(reviewer_username)
540 # specified rules are later re-validated, thus we can assume users will
578 reviewers_reasons.append((user.user_id, reviewer_reasons))
541 # eventually provide those that meet the reviewer criteria.
542 if not reviewer_objects:
543 reviewer_objects = reviewer_rules['reviewers']
544
545 try:
546 reviewers = get_validated_reviewers(
547 reviewer_objects, reviewer_rules)
548 except ValueError as e:
549 raise JSONRPCError('Reviewers Validation: {}'.format(e))
579
550
580 pull_request_model = PullRequestModel()
551 pull_request_model = PullRequestModel()
581 pull_request = pull_request_model.create(
552 pull_request = pull_request_model.create(
582 created_by=apiuser.user_id,
553 created_by=apiuser.user_id,
583 source_repo=source_repo,
554 source_repo=source_repo,
584 source_ref=full_source_ref,
555 source_ref=full_source_ref,
585 target_repo=target_repo,
556 target_repo=target_repo,
586 target_ref=full_target_ref,
557 target_ref=full_target_ref,
587 revisions=reversed(
558 revisions=reversed(
588 [commit.raw_id for commit in reversed(commit_ranges)]),
559 [commit.raw_id for commit in reversed(commit_ranges)]),
589 reviewers=reviewers_reasons,
560 reviewers=reviewers,
590 title=title,
561 title=title,
591 description=Optional.extract(description)
562 description=Optional.extract(description)
592 )
563 )
593
564
594 Session().commit()
565 Session().commit()
595 data = {
566 data = {
596 'msg': 'Created new pull request `{}`'.format(title),
567 'msg': 'Created new pull request `{}`'.format(title),
597 'pull_request_id': pull_request.pull_request_id,
568 'pull_request_id': pull_request.pull_request_id,
598 }
569 }
599 return data
570 return data
600
571
601
572
602 @jsonrpc_method()
573 @jsonrpc_method()
603 def update_pull_request(
574 def update_pull_request(
604 request, apiuser, repoid, pullrequestid, title=Optional(''),
575 request, apiuser, repoid, pullrequestid, title=Optional(''),
605 description=Optional(''), reviewers=Optional(None),
576 description=Optional(''), reviewers=Optional(None),
606 update_commits=Optional(None), close_pull_request=Optional(None)):
577 update_commits=Optional(None)):
607 """
578 """
608 Updates a pull request.
579 Updates a pull request.
609
580
610 :param apiuser: This is filled automatically from the |authtoken|.
581 :param apiuser: This is filled automatically from the |authtoken|.
611 :type apiuser: AuthUser
582 :type apiuser: AuthUser
612 :param repoid: The repository name or repository ID.
583 :param repoid: The repository name or repository ID.
613 :type repoid: str or int
584 :type repoid: str or int
614 :param pullrequestid: The pull request ID.
585 :param pullrequestid: The pull request ID.
615 :type pullrequestid: int
586 :type pullrequestid: int
616 :param title: Set the pull request title.
587 :param title: Set the pull request title.
617 :type title: str
588 :type title: str
618 :param description: Update pull request description.
589 :param description: Update pull request description.
619 :type description: Optional(str)
590 :type description: Optional(str)
620 :param reviewers: Update pull request reviewers list with new value.
591 :param reviewers: Update pull request reviewers list with new value.
621 :type reviewers: Optional(list)
592 :type reviewers: Optional(list)
593 Accepts username strings or objects of the format:
594
595 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
596
622 :param update_commits: Trigger update of commits for this pull request
597 :param update_commits: Trigger update of commits for this pull request
623 :type: update_commits: Optional(bool)
598 :type: update_commits: Optional(bool)
624 :param close_pull_request: Close this pull request with rejected state
625 :type: close_pull_request: Optional(bool)
626
599
627 Example output:
600 Example output:
628
601
629 .. code-block:: bash
602 .. code-block:: bash
630
603
631 id : <id_given_in_input>
604 id : <id_given_in_input>
632 result : {
605 result : {
633 "msg": "Updated pull request `63`",
606 "msg": "Updated pull request `63`",
634 "pull_request": <pull_request_object>,
607 "pull_request": <pull_request_object>,
635 "updated_reviewers": {
608 "updated_reviewers": {
636 "added": [
609 "added": [
637 "username"
610 "username"
638 ],
611 ],
639 "removed": []
612 "removed": []
640 },
613 },
641 "updated_commits": {
614 "updated_commits": {
642 "added": [
615 "added": [
643 "<sha1_hash>"
616 "<sha1_hash>"
644 ],
617 ],
645 "common": [
618 "common": [
646 "<sha1_hash>",
619 "<sha1_hash>",
647 "<sha1_hash>",
620 "<sha1_hash>",
648 ],
621 ],
649 "removed": []
622 "removed": []
650 }
623 }
651 }
624 }
652 error : null
625 error : null
653 """
626 """
654
627
655 repo = get_repo_or_error(repoid)
628 repo = get_repo_or_error(repoid)
656 pull_request = get_pull_request_or_error(pullrequestid)
629 pull_request = get_pull_request_or_error(pullrequestid)
657 if not PullRequestModel().check_user_update(
630 if not PullRequestModel().check_user_update(
658 pull_request, apiuser, api=True):
631 pull_request, apiuser, api=True):
659 raise JSONRPCError(
632 raise JSONRPCError(
660 'pull request `%s` update failed, no permission to update.' % (
633 'pull request `%s` update failed, no permission to update.' % (
661 pullrequestid,))
634 pullrequestid,))
662 if pull_request.is_closed():
635 if pull_request.is_closed():
663 raise JSONRPCError(
636 raise JSONRPCError(
664 'pull request `%s` update failed, pull request is closed' % (
637 'pull request `%s` update failed, pull request is closed' % (
665 pullrequestid,))
638 pullrequestid,))
666
639
667 reviewer_objects = Optional.extract(reviewers) or []
640 reviewer_objects = Optional.extract(reviewers) or []
668 if not isinstance(reviewer_objects, list):
641
669 raise JSONRPCError('reviewers should be specified as a list')
642 if reviewer_objects:
643 schema = ReviewerListSchema()
644 try:
645 reviewer_objects = schema.deserialize(reviewer_objects)
646 except Invalid as err:
647 raise JSONRPCValidationError(colander_exc=err)
648
649 # validate users
650 for reviewer_object in reviewer_objects:
651 user = get_user_or_error(reviewer_object['username'])
652 reviewer_object['user_id'] = user.user_id
670
653
671 reviewers_reasons = []
654 get_default_reviewers_data, get_validated_reviewers = \
672 reviewer_ids = set()
655 PullRequestModel().get_reviewer_functions()
673 for reviewer_object in reviewer_objects:
674 reviewer_reasons = []
675 if isinstance(reviewer_object, (int, basestring)):
676 reviewer_username = reviewer_object
677 else:
678 reviewer_username = reviewer_object['username']
679 reviewer_reasons = reviewer_object.get('reasons', [])
680
656
681 user = get_user_or_error(reviewer_username)
657 # re-use stored rules
682 reviewer_ids.add(user.user_id)
658 reviewer_rules = pull_request.reviewer_data
683 reviewers_reasons.append((user.user_id, reviewer_reasons))
659 try:
660 reviewers = get_validated_reviewers(
661 reviewer_objects, reviewer_rules)
662 except ValueError as e:
663 raise JSONRPCError('Reviewers Validation: {}'.format(e))
664 else:
665 reviewers = []
684
666
685 title = Optional.extract(title)
667 title = Optional.extract(title)
686 description = Optional.extract(description)
668 description = Optional.extract(description)
687 if title or description:
669 if title or description:
688 PullRequestModel().edit(
670 PullRequestModel().edit(
689 pull_request, title or pull_request.title,
671 pull_request, title or pull_request.title,
690 description or pull_request.description)
672 description or pull_request.description, apiuser)
691 Session().commit()
673 Session().commit()
692
674
693 commit_changes = {"added": [], "common": [], "removed": []}
675 commit_changes = {"added": [], "common": [], "removed": []}
694 if str2bool(Optional.extract(update_commits)):
676 if str2bool(Optional.extract(update_commits)):
695 if PullRequestModel().has_valid_update_type(pull_request):
677 if PullRequestModel().has_valid_update_type(pull_request):
696 update_response = PullRequestModel().update_commits(
678 update_response = PullRequestModel().update_commits(
697 pull_request)
679 pull_request)
698 commit_changes = update_response.changes or commit_changes
680 commit_changes = update_response.changes or commit_changes
699 Session().commit()
681 Session().commit()
700
682
701 reviewers_changes = {"added": [], "removed": []}
683 reviewers_changes = {"added": [], "removed": []}
702 if reviewer_ids:
684 if reviewers:
703 added_reviewers, removed_reviewers = \
685 added_reviewers, removed_reviewers = \
704 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
686 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
705
687
706 reviewers_changes['added'] = sorted(
688 reviewers_changes['added'] = sorted(
707 [get_user_or_error(n).username for n in added_reviewers])
689 [get_user_or_error(n).username for n in added_reviewers])
708 reviewers_changes['removed'] = sorted(
690 reviewers_changes['removed'] = sorted(
709 [get_user_or_error(n).username for n in removed_reviewers])
691 [get_user_or_error(n).username for n in removed_reviewers])
710 Session().commit()
692 Session().commit()
711
693
712 if str2bool(Optional.extract(close_pull_request)):
713 PullRequestModel().close_pull_request_with_comment(
714 pull_request, apiuser, repo)
715 Session().commit()
716
717 data = {
694 data = {
718 'msg': 'Updated pull request `{}`'.format(
695 'msg': 'Updated pull request `{}`'.format(
719 pull_request.pull_request_id),
696 pull_request.pull_request_id),
720 'pull_request': pull_request.get_api_data(),
697 'pull_request': pull_request.get_api_data(),
721 'updated_commits': commit_changes,
698 'updated_commits': commit_changes,
722 'updated_reviewers': reviewers_changes
699 'updated_reviewers': reviewers_changes
723 }
700 }
724
701
725 return data
702 return data
703
704
705 @jsonrpc_method()
706 def close_pull_request(
707 request, apiuser, repoid, pullrequestid,
708 userid=Optional(OAttr('apiuser')), message=Optional('')):
709 """
710 Close the pull request specified by `pullrequestid`.
711
712 :param apiuser: This is filled automatically from the |authtoken|.
713 :type apiuser: AuthUser
714 :param repoid: Repository name or repository ID to which the pull
715 request belongs.
716 :type repoid: str or int
717 :param pullrequestid: ID of the pull request to be closed.
718 :type pullrequestid: int
719 :param userid: Close the pull request as this user.
720 :type userid: Optional(str or int)
721 :param message: Optional message to close the Pull Request with. If not
722 specified it will be generated automatically.
723 :type message: Optional(str)
724
725 Example output:
726
727 .. code-block:: bash
728
729 "id": <id_given_in_input>,
730 "result": {
731 "pull_request_id": "<int>",
732 "close_status": "<str:status_lbl>,
733 "closed": "<bool>"
734 },
735 "error": null
736
737 """
738 _ = request.translate
739
740 repo = get_repo_or_error(repoid)
741 if not isinstance(userid, Optional):
742 if (has_superadmin_permission(apiuser) or
743 HasRepoPermissionAnyApi('repository.admin')(
744 user=apiuser, repo_name=repo.repo_name)):
745 apiuser = get_user_or_error(userid)
746 else:
747 raise JSONRPCError('userid is not the same as your user')
748
749 pull_request = get_pull_request_or_error(pullrequestid)
750
751 if pull_request.is_closed():
752 raise JSONRPCError(
753 'pull request `%s` is already closed' % (pullrequestid,))
754
755 # only owner or admin or person with write permissions
756 allowed_to_close = PullRequestModel().check_user_update(
757 pull_request, apiuser, api=True)
758
759 if not allowed_to_close:
760 raise JSONRPCError(
761 'pull request `%s` close failed, no permission to close.' % (
762 pullrequestid,))
763
764 # message we're using to close the PR, else it's automatically generated
765 message = Optional.extract(message)
766
767 # finally close the PR, with proper message comment
768 comment, status = PullRequestModel().close_pull_request_with_comment(
769 pull_request, apiuser, repo, message=message)
770 status_lbl = ChangesetStatus.get_status_lbl(status)
771
772 Session().commit()
773
774 data = {
775 'pull_request_id': pull_request.pull_request_id,
776 'close_status': status_lbl,
777 'closed': True,
778 }
779 return data
@@ -1,1987 +1,2070 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib import repo_maintenance
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
36 from rhodecode.model.changeset_status import ChangesetStatusModel
38 from rhodecode.model.changeset_status import ChangesetStatusModel
37 from rhodecode.model.comment import CommentsModel
39 from rhodecode.model.comment import CommentsModel
38 from rhodecode.model.db import (
40 from rhodecode.model.db import (
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
41 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
40 ChangesetComment)
42 ChangesetComment)
41 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.scm import ScmModel, RepoList
44 from rhodecode.model.scm import ScmModel, RepoList
43 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
45 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
44 from rhodecode.model import validation_schema
46 from rhodecode.model import validation_schema
45 from rhodecode.model.validation_schema.schemas import repo_schema
47 from rhodecode.model.validation_schema.schemas import repo_schema
46
48
47 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
48
50
49
51
50 @jsonrpc_method()
52 @jsonrpc_method()
51 def get_repo(request, apiuser, repoid, cache=Optional(True)):
53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
52 """
54 """
53 Gets an existing repository by its name or repository_id.
55 Gets an existing repository by its name or repository_id.
54
56
55 The members section so the output returns users groups or users
57 The members section so the output returns users groups or users
56 associated with that repository.
58 associated with that repository.
57
59
58 This command can only be run using an |authtoken| with admin rights,
60 This command can only be run using an |authtoken| with admin rights,
59 or users with at least read rights to the |repo|.
61 or users with at least read rights to the |repo|.
60
62
61 :param apiuser: This is filled automatically from the |authtoken|.
63 :param apiuser: This is filled automatically from the |authtoken|.
62 :type apiuser: AuthUser
64 :type apiuser: AuthUser
63 :param repoid: The repository name or repository id.
65 :param repoid: The repository name or repository id.
64 :type repoid: str or int
66 :type repoid: str or int
65 :param cache: use the cached value for last changeset
67 :param cache: use the cached value for last changeset
66 :type: cache: Optional(bool)
68 :type: cache: Optional(bool)
67
69
68 Example output:
70 Example output:
69
71
70 .. code-block:: bash
72 .. code-block:: bash
71
73
72 {
74 {
73 "error": null,
75 "error": null,
74 "id": <repo_id>,
76 "id": <repo_id>,
75 "result": {
77 "result": {
76 "clone_uri": null,
78 "clone_uri": null,
77 "created_on": "timestamp",
79 "created_on": "timestamp",
78 "description": "repo description",
80 "description": "repo description",
79 "enable_downloads": false,
81 "enable_downloads": false,
80 "enable_locking": false,
82 "enable_locking": false,
81 "enable_statistics": false,
83 "enable_statistics": false,
82 "followers": [
84 "followers": [
83 {
85 {
84 "active": true,
86 "active": true,
85 "admin": false,
87 "admin": false,
86 "api_key": "****************************************",
88 "api_key": "****************************************",
87 "api_keys": [
89 "api_keys": [
88 "****************************************"
90 "****************************************"
89 ],
91 ],
90 "email": "user@example.com",
92 "email": "user@example.com",
91 "emails": [
93 "emails": [
92 "user@example.com"
94 "user@example.com"
93 ],
95 ],
94 "extern_name": "rhodecode",
96 "extern_name": "rhodecode",
95 "extern_type": "rhodecode",
97 "extern_type": "rhodecode",
96 "firstname": "username",
98 "firstname": "username",
97 "ip_addresses": [],
99 "ip_addresses": [],
98 "language": null,
100 "language": null,
99 "last_login": "2015-09-16T17:16:35.854",
101 "last_login": "2015-09-16T17:16:35.854",
100 "lastname": "surname",
102 "lastname": "surname",
101 "user_id": <user_id>,
103 "user_id": <user_id>,
102 "username": "name"
104 "username": "name"
103 }
105 }
104 ],
106 ],
105 "fork_of": "parent-repo",
107 "fork_of": "parent-repo",
106 "landing_rev": [
108 "landing_rev": [
107 "rev",
109 "rev",
108 "tip"
110 "tip"
109 ],
111 ],
110 "last_changeset": {
112 "last_changeset": {
111 "author": "User <user@example.com>",
113 "author": "User <user@example.com>",
112 "branch": "default",
114 "branch": "default",
113 "date": "timestamp",
115 "date": "timestamp",
114 "message": "last commit message",
116 "message": "last commit message",
115 "parents": [
117 "parents": [
116 {
118 {
117 "raw_id": "commit-id"
119 "raw_id": "commit-id"
118 }
120 }
119 ],
121 ],
120 "raw_id": "commit-id",
122 "raw_id": "commit-id",
121 "revision": <revision number>,
123 "revision": <revision number>,
122 "short_id": "short id"
124 "short_id": "short id"
123 },
125 },
124 "lock_reason": null,
126 "lock_reason": null,
125 "locked_by": null,
127 "locked_by": null,
126 "locked_date": null,
128 "locked_date": null,
127 "members": [
129 "members": [
128 {
130 {
129 "name": "super-admin-name",
131 "name": "super-admin-name",
130 "origin": "super-admin",
132 "origin": "super-admin",
131 "permission": "repository.admin",
133 "permission": "repository.admin",
132 "type": "user"
134 "type": "user"
133 },
135 },
134 {
136 {
135 "name": "owner-name",
137 "name": "owner-name",
136 "origin": "owner",
138 "origin": "owner",
137 "permission": "repository.admin",
139 "permission": "repository.admin",
138 "type": "user"
140 "type": "user"
139 },
141 },
140 {
142 {
141 "name": "user-group-name",
143 "name": "user-group-name",
142 "origin": "permission",
144 "origin": "permission",
143 "permission": "repository.write",
145 "permission": "repository.write",
144 "type": "user_group"
146 "type": "user_group"
145 }
147 }
146 ],
148 ],
147 "owner": "owner-name",
149 "owner": "owner-name",
148 "permissions": [
150 "permissions": [
149 {
151 {
150 "name": "super-admin-name",
152 "name": "super-admin-name",
151 "origin": "super-admin",
153 "origin": "super-admin",
152 "permission": "repository.admin",
154 "permission": "repository.admin",
153 "type": "user"
155 "type": "user"
154 },
156 },
155 {
157 {
156 "name": "owner-name",
158 "name": "owner-name",
157 "origin": "owner",
159 "origin": "owner",
158 "permission": "repository.admin",
160 "permission": "repository.admin",
159 "type": "user"
161 "type": "user"
160 },
162 },
161 {
163 {
162 "name": "user-group-name",
164 "name": "user-group-name",
163 "origin": "permission",
165 "origin": "permission",
164 "permission": "repository.write",
166 "permission": "repository.write",
165 "type": "user_group"
167 "type": "user_group"
166 }
168 }
167 ],
169 ],
168 "private": true,
170 "private": true,
169 "repo_id": 676,
171 "repo_id": 676,
170 "repo_name": "user-group/repo-name",
172 "repo_name": "user-group/repo-name",
171 "repo_type": "hg"
173 "repo_type": "hg"
172 }
174 }
173 }
175 }
174 """
176 """
175
177
176 repo = get_repo_or_error(repoid)
178 repo = get_repo_or_error(repoid)
177 cache = Optional.extract(cache)
179 cache = Optional.extract(cache)
178
180
179 include_secrets = False
181 include_secrets = False
180 if has_superadmin_permission(apiuser):
182 if has_superadmin_permission(apiuser):
181 include_secrets = True
183 include_secrets = True
182 else:
184 else:
183 # check if we have at least read permission for this repo !
185 # check if we have at least read permission for this repo !
184 _perms = (
186 _perms = (
185 'repository.admin', 'repository.write', 'repository.read',)
187 'repository.admin', 'repository.write', 'repository.read',)
186 validate_repo_permissions(apiuser, repoid, repo, _perms)
188 validate_repo_permissions(apiuser, repoid, repo, _perms)
187
189
188 permissions = []
190 permissions = []
189 for _user in repo.permissions():
191 for _user in repo.permissions():
190 user_data = {
192 user_data = {
191 'name': _user.username,
193 'name': _user.username,
192 'permission': _user.permission,
194 'permission': _user.permission,
193 'origin': get_origin(_user),
195 'origin': get_origin(_user),
194 'type': "user",
196 'type': "user",
195 }
197 }
196 permissions.append(user_data)
198 permissions.append(user_data)
197
199
198 for _user_group in repo.permission_user_groups():
200 for _user_group in repo.permission_user_groups():
199 user_group_data = {
201 user_group_data = {
200 'name': _user_group.users_group_name,
202 'name': _user_group.users_group_name,
201 'permission': _user_group.permission,
203 'permission': _user_group.permission,
202 'origin': get_origin(_user_group),
204 'origin': get_origin(_user_group),
203 'type': "user_group",
205 'type': "user_group",
204 }
206 }
205 permissions.append(user_group_data)
207 permissions.append(user_group_data)
206
208
207 following_users = [
209 following_users = [
208 user.user.get_api_data(include_secrets=include_secrets)
210 user.user.get_api_data(include_secrets=include_secrets)
209 for user in repo.followers]
211 for user in repo.followers]
210
212
211 if not cache:
213 if not cache:
212 repo.update_commit_cache()
214 repo.update_commit_cache()
213 data = repo.get_api_data(include_secrets=include_secrets)
215 data = repo.get_api_data(include_secrets=include_secrets)
214 data['members'] = permissions # TODO: this should be deprecated soon
216 data['members'] = permissions # TODO: this should be deprecated soon
215 data['permissions'] = permissions
217 data['permissions'] = permissions
216 data['followers'] = following_users
218 data['followers'] = following_users
217 return data
219 return data
218
220
219
221
220 @jsonrpc_method()
222 @jsonrpc_method()
221 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
223 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
222 """
224 """
223 Lists all existing repositories.
225 Lists all existing repositories.
224
226
225 This command can only be run using an |authtoken| with admin rights,
227 This command can only be run using an |authtoken| with admin rights,
226 or users with at least read rights to |repos|.
228 or users with at least read rights to |repos|.
227
229
228 :param apiuser: This is filled automatically from the |authtoken|.
230 :param apiuser: This is filled automatically from the |authtoken|.
229 :type apiuser: AuthUser
231 :type apiuser: AuthUser
230 :param root: specify root repository group to fetch repositories.
232 :param root: specify root repository group to fetch repositories.
231 filters the returned repositories to be members of given root group.
233 filters the returned repositories to be members of given root group.
232 :type root: Optional(None)
234 :type root: Optional(None)
233 :param traverse: traverse given root into subrepositories. With this flag
235 :param traverse: traverse given root into subrepositories. With this flag
234 set to False, it will only return top-level repositories from `root`.
236 set to False, it will only return top-level repositories from `root`.
235 if root is empty it will return just top-level repositories.
237 if root is empty it will return just top-level repositories.
236 :type traverse: Optional(True)
238 :type traverse: Optional(True)
237
239
238
240
239 Example output:
241 Example output:
240
242
241 .. code-block:: bash
243 .. code-block:: bash
242
244
243 id : <id_given_in_input>
245 id : <id_given_in_input>
244 result: [
246 result: [
245 {
247 {
246 "repo_id" : "<repo_id>",
248 "repo_id" : "<repo_id>",
247 "repo_name" : "<reponame>"
249 "repo_name" : "<reponame>"
248 "repo_type" : "<repo_type>",
250 "repo_type" : "<repo_type>",
249 "clone_uri" : "<clone_uri>",
251 "clone_uri" : "<clone_uri>",
250 "private": : "<bool>",
252 "private": : "<bool>",
251 "created_on" : "<datetimecreated>",
253 "created_on" : "<datetimecreated>",
252 "description" : "<description>",
254 "description" : "<description>",
253 "landing_rev": "<landing_rev>",
255 "landing_rev": "<landing_rev>",
254 "owner": "<repo_owner>",
256 "owner": "<repo_owner>",
255 "fork_of": "<name_of_fork_parent>",
257 "fork_of": "<name_of_fork_parent>",
256 "enable_downloads": "<bool>",
258 "enable_downloads": "<bool>",
257 "enable_locking": "<bool>",
259 "enable_locking": "<bool>",
258 "enable_statistics": "<bool>",
260 "enable_statistics": "<bool>",
259 },
261 },
260 ...
262 ...
261 ]
263 ]
262 error: null
264 error: null
263 """
265 """
264
266
265 include_secrets = has_superadmin_permission(apiuser)
267 include_secrets = has_superadmin_permission(apiuser)
266 _perms = ('repository.read', 'repository.write', 'repository.admin',)
268 _perms = ('repository.read', 'repository.write', 'repository.admin',)
267 extras = {'user': apiuser}
269 extras = {'user': apiuser}
268
270
269 root = Optional.extract(root)
271 root = Optional.extract(root)
270 traverse = Optional.extract(traverse, binary=True)
272 traverse = Optional.extract(traverse, binary=True)
271
273
272 if root:
274 if root:
273 # verify parent existance, if it's empty return an error
275 # verify parent existance, if it's empty return an error
274 parent = RepoGroup.get_by_group_name(root)
276 parent = RepoGroup.get_by_group_name(root)
275 if not parent:
277 if not parent:
276 raise JSONRPCError(
278 raise JSONRPCError(
277 'Root repository group `{}` does not exist'.format(root))
279 'Root repository group `{}` does not exist'.format(root))
278
280
279 if traverse:
281 if traverse:
280 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
282 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
281 else:
283 else:
282 repos = RepoModel().get_repos_for_root(root=parent)
284 repos = RepoModel().get_repos_for_root(root=parent)
283 else:
285 else:
284 if traverse:
286 if traverse:
285 repos = RepoModel().get_all()
287 repos = RepoModel().get_all()
286 else:
288 else:
287 # return just top-level
289 # return just top-level
288 repos = RepoModel().get_repos_for_root(root=None)
290 repos = RepoModel().get_repos_for_root(root=None)
289
291
290 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
292 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
291 return [repo.get_api_data(include_secrets=include_secrets)
293 return [repo.get_api_data(include_secrets=include_secrets)
292 for repo in repo_list]
294 for repo in repo_list]
293
295
294
296
295 @jsonrpc_method()
297 @jsonrpc_method()
296 def get_repo_changeset(request, apiuser, repoid, revision,
298 def get_repo_changeset(request, apiuser, repoid, revision,
297 details=Optional('basic')):
299 details=Optional('basic')):
298 """
300 """
299 Returns information about a changeset.
301 Returns information about a changeset.
300
302
301 Additionally parameters define the amount of details returned by
303 Additionally parameters define the amount of details returned by
302 this function.
304 this function.
303
305
304 This command can only be run using an |authtoken| with admin rights,
306 This command can only be run using an |authtoken| with admin rights,
305 or users with at least read rights to the |repo|.
307 or users with at least read rights to the |repo|.
306
308
307 :param apiuser: This is filled automatically from the |authtoken|.
309 :param apiuser: This is filled automatically from the |authtoken|.
308 :type apiuser: AuthUser
310 :type apiuser: AuthUser
309 :param repoid: The repository name or repository id
311 :param repoid: The repository name or repository id
310 :type repoid: str or int
312 :type repoid: str or int
311 :param revision: revision for which listing should be done
313 :param revision: revision for which listing should be done
312 :type revision: str
314 :type revision: str
313 :param details: details can be 'basic|extended|full' full gives diff
315 :param details: details can be 'basic|extended|full' full gives diff
314 info details like the diff itself, and number of changed files etc.
316 info details like the diff itself, and number of changed files etc.
315 :type details: Optional(str)
317 :type details: Optional(str)
316
318
317 """
319 """
318 repo = get_repo_or_error(repoid)
320 repo = get_repo_or_error(repoid)
319 if not has_superadmin_permission(apiuser):
321 if not has_superadmin_permission(apiuser):
320 _perms = (
322 _perms = (
321 'repository.admin', 'repository.write', 'repository.read',)
323 'repository.admin', 'repository.write', 'repository.read',)
322 validate_repo_permissions(apiuser, repoid, repo, _perms)
324 validate_repo_permissions(apiuser, repoid, repo, _perms)
323
325
324 changes_details = Optional.extract(details)
326 changes_details = Optional.extract(details)
325 _changes_details_types = ['basic', 'extended', 'full']
327 _changes_details_types = ['basic', 'extended', 'full']
326 if changes_details not in _changes_details_types:
328 if changes_details not in _changes_details_types:
327 raise JSONRPCError(
329 raise JSONRPCError(
328 'ret_type must be one of %s' % (
330 'ret_type must be one of %s' % (
329 ','.join(_changes_details_types)))
331 ','.join(_changes_details_types)))
330
332
331 pre_load = ['author', 'branch', 'date', 'message', 'parents',
333 pre_load = ['author', 'branch', 'date', 'message', 'parents',
332 'status', '_commit', '_file_paths']
334 'status', '_commit', '_file_paths']
333
335
334 try:
336 try:
335 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
337 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
336 except TypeError as e:
338 except TypeError as e:
337 raise JSONRPCError(e.message)
339 raise JSONRPCError(e.message)
338 _cs_json = cs.__json__()
340 _cs_json = cs.__json__()
339 _cs_json['diff'] = build_commit_data(cs, changes_details)
341 _cs_json['diff'] = build_commit_data(cs, changes_details)
340 if changes_details == 'full':
342 if changes_details == 'full':
341 _cs_json['refs'] = {
343 _cs_json['refs'] = {
342 'branches': [cs.branch],
344 'branches': [cs.branch],
343 'bookmarks': getattr(cs, 'bookmarks', []),
345 'bookmarks': getattr(cs, 'bookmarks', []),
344 'tags': cs.tags
346 'tags': cs.tags
345 }
347 }
346 return _cs_json
348 return _cs_json
347
349
348
350
349 @jsonrpc_method()
351 @jsonrpc_method()
350 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
352 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
351 details=Optional('basic')):
353 details=Optional('basic')):
352 """
354 """
353 Returns a set of commits limited by the number starting
355 Returns a set of commits limited by the number starting
354 from the `start_rev` option.
356 from the `start_rev` option.
355
357
356 Additional parameters define the amount of details returned by this
358 Additional parameters define the amount of details returned by this
357 function.
359 function.
358
360
359 This command can only be run using an |authtoken| with admin rights,
361 This command can only be run using an |authtoken| with admin rights,
360 or users with at least read rights to |repos|.
362 or users with at least read rights to |repos|.
361
363
362 :param apiuser: This is filled automatically from the |authtoken|.
364 :param apiuser: This is filled automatically from the |authtoken|.
363 :type apiuser: AuthUser
365 :type apiuser: AuthUser
364 :param repoid: The repository name or repository ID.
366 :param repoid: The repository name or repository ID.
365 :type repoid: str or int
367 :type repoid: str or int
366 :param start_rev: The starting revision from where to get changesets.
368 :param start_rev: The starting revision from where to get changesets.
367 :type start_rev: str
369 :type start_rev: str
368 :param limit: Limit the number of commits to this amount
370 :param limit: Limit the number of commits to this amount
369 :type limit: str or int
371 :type limit: str or int
370 :param details: Set the level of detail returned. Valid option are:
372 :param details: Set the level of detail returned. Valid option are:
371 ``basic``, ``extended`` and ``full``.
373 ``basic``, ``extended`` and ``full``.
372 :type details: Optional(str)
374 :type details: Optional(str)
373
375
374 .. note::
376 .. note::
375
377
376 Setting the parameter `details` to the value ``full`` is extensive
378 Setting the parameter `details` to the value ``full`` is extensive
377 and returns details like the diff itself, and the number
379 and returns details like the diff itself, and the number
378 of changed files.
380 of changed files.
379
381
380 """
382 """
381 repo = get_repo_or_error(repoid)
383 repo = get_repo_or_error(repoid)
382 if not has_superadmin_permission(apiuser):
384 if not has_superadmin_permission(apiuser):
383 _perms = (
385 _perms = (
384 'repository.admin', 'repository.write', 'repository.read',)
386 'repository.admin', 'repository.write', 'repository.read',)
385 validate_repo_permissions(apiuser, repoid, repo, _perms)
387 validate_repo_permissions(apiuser, repoid, repo, _perms)
386
388
387 changes_details = Optional.extract(details)
389 changes_details = Optional.extract(details)
388 _changes_details_types = ['basic', 'extended', 'full']
390 _changes_details_types = ['basic', 'extended', 'full']
389 if changes_details not in _changes_details_types:
391 if changes_details not in _changes_details_types:
390 raise JSONRPCError(
392 raise JSONRPCError(
391 'ret_type must be one of %s' % (
393 'ret_type must be one of %s' % (
392 ','.join(_changes_details_types)))
394 ','.join(_changes_details_types)))
393
395
394 limit = int(limit)
396 limit = int(limit)
395 pre_load = ['author', 'branch', 'date', 'message', 'parents',
397 pre_load = ['author', 'branch', 'date', 'message', 'parents',
396 'status', '_commit', '_file_paths']
398 'status', '_commit', '_file_paths']
397
399
398 vcs_repo = repo.scm_instance()
400 vcs_repo = repo.scm_instance()
399 # SVN needs a special case to distinguish its index and commit id
401 # SVN needs a special case to distinguish its index and commit id
400 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
402 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
401 start_rev = vcs_repo.commit_ids[0]
403 start_rev = vcs_repo.commit_ids[0]
402
404
403 try:
405 try:
404 commits = vcs_repo.get_commits(
406 commits = vcs_repo.get_commits(
405 start_id=start_rev, pre_load=pre_load)
407 start_id=start_rev, pre_load=pre_load)
406 except TypeError as e:
408 except TypeError as e:
407 raise JSONRPCError(e.message)
409 raise JSONRPCError(e.message)
408 except Exception:
410 except Exception:
409 log.exception('Fetching of commits failed')
411 log.exception('Fetching of commits failed')
410 raise JSONRPCError('Error occurred during commit fetching')
412 raise JSONRPCError('Error occurred during commit fetching')
411
413
412 ret = []
414 ret = []
413 for cnt, commit in enumerate(commits):
415 for cnt, commit in enumerate(commits):
414 if cnt >= limit != -1:
416 if cnt >= limit != -1:
415 break
417 break
416 _cs_json = commit.__json__()
418 _cs_json = commit.__json__()
417 _cs_json['diff'] = build_commit_data(commit, changes_details)
419 _cs_json['diff'] = build_commit_data(commit, changes_details)
418 if changes_details == 'full':
420 if changes_details == 'full':
419 _cs_json['refs'] = {
421 _cs_json['refs'] = {
420 'branches': [commit.branch],
422 'branches': [commit.branch],
421 'bookmarks': getattr(commit, 'bookmarks', []),
423 'bookmarks': getattr(commit, 'bookmarks', []),
422 'tags': commit.tags
424 'tags': commit.tags
423 }
425 }
424 ret.append(_cs_json)
426 ret.append(_cs_json)
425 return ret
427 return ret
426
428
427
429
428 @jsonrpc_method()
430 @jsonrpc_method()
429 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
431 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
430 ret_type=Optional('all'), details=Optional('basic'),
432 ret_type=Optional('all'), details=Optional('basic'),
431 max_file_bytes=Optional(None)):
433 max_file_bytes=Optional(None)):
432 """
434 """
433 Returns a list of nodes and children in a flat list for a given
435 Returns a list of nodes and children in a flat list for a given
434 path at given revision.
436 path at given revision.
435
437
436 It's possible to specify ret_type to show only `files` or `dirs`.
438 It's possible to specify ret_type to show only `files` or `dirs`.
437
439
438 This command can only be run using an |authtoken| with admin rights,
440 This command can only be run using an |authtoken| with admin rights,
439 or users with at least read rights to |repos|.
441 or users with at least read rights to |repos|.
440
442
441 :param apiuser: This is filled automatically from the |authtoken|.
443 :param apiuser: This is filled automatically from the |authtoken|.
442 :type apiuser: AuthUser
444 :type apiuser: AuthUser
443 :param repoid: The repository name or repository ID.
445 :param repoid: The repository name or repository ID.
444 :type repoid: str or int
446 :type repoid: str or int
445 :param revision: The revision for which listing should be done.
447 :param revision: The revision for which listing should be done.
446 :type revision: str
448 :type revision: str
447 :param root_path: The path from which to start displaying.
449 :param root_path: The path from which to start displaying.
448 :type root_path: str
450 :type root_path: str
449 :param ret_type: Set the return type. Valid options are
451 :param ret_type: Set the return type. Valid options are
450 ``all`` (default), ``files`` and ``dirs``.
452 ``all`` (default), ``files`` and ``dirs``.
451 :type ret_type: Optional(str)
453 :type ret_type: Optional(str)
452 :param details: Returns extended information about nodes, such as
454 :param details: Returns extended information about nodes, such as
453 md5, binary, and or content. The valid options are ``basic`` and
455 md5, binary, and or content. The valid options are ``basic`` and
454 ``full``.
456 ``full``.
455 :type details: Optional(str)
457 :type details: Optional(str)
456 :param max_file_bytes: Only return file content under this file size bytes
458 :param max_file_bytes: Only return file content under this file size bytes
457 :type details: Optional(int)
459 :type details: Optional(int)
458
460
459 Example output:
461 Example output:
460
462
461 .. code-block:: bash
463 .. code-block:: bash
462
464
463 id : <id_given_in_input>
465 id : <id_given_in_input>
464 result: [
466 result: [
465 {
467 {
466 "name" : "<name>"
468 "name" : "<name>"
467 "type" : "<type>",
469 "type" : "<type>",
468 "binary": "<true|false>" (only in extended mode)
470 "binary": "<true|false>" (only in extended mode)
469 "md5" : "<md5 of file content>" (only in extended mode)
471 "md5" : "<md5 of file content>" (only in extended mode)
470 },
472 },
471 ...
473 ...
472 ]
474 ]
473 error: null
475 error: null
474 """
476 """
475
477
476 repo = get_repo_or_error(repoid)
478 repo = get_repo_or_error(repoid)
477 if not has_superadmin_permission(apiuser):
479 if not has_superadmin_permission(apiuser):
478 _perms = (
480 _perms = (
479 'repository.admin', 'repository.write', 'repository.read',)
481 'repository.admin', 'repository.write', 'repository.read',)
480 validate_repo_permissions(apiuser, repoid, repo, _perms)
482 validate_repo_permissions(apiuser, repoid, repo, _perms)
481
483
482 ret_type = Optional.extract(ret_type)
484 ret_type = Optional.extract(ret_type)
483 details = Optional.extract(details)
485 details = Optional.extract(details)
484 _extended_types = ['basic', 'full']
486 _extended_types = ['basic', 'full']
485 if details not in _extended_types:
487 if details not in _extended_types:
486 raise JSONRPCError(
488 raise JSONRPCError(
487 'ret_type must be one of %s' % (','.join(_extended_types)))
489 'ret_type must be one of %s' % (','.join(_extended_types)))
488 extended_info = False
490 extended_info = False
489 content = False
491 content = False
490 if details == 'basic':
492 if details == 'basic':
491 extended_info = True
493 extended_info = True
492
494
493 if details == 'full':
495 if details == 'full':
494 extended_info = content = True
496 extended_info = content = True
495
497
496 _map = {}
498 _map = {}
497 try:
499 try:
498 # check if repo is not empty by any chance, skip quicker if it is.
500 # check if repo is not empty by any chance, skip quicker if it is.
499 _scm = repo.scm_instance()
501 _scm = repo.scm_instance()
500 if _scm.is_empty():
502 if _scm.is_empty():
501 return []
503 return []
502
504
503 _d, _f = ScmModel().get_nodes(
505 _d, _f = ScmModel().get_nodes(
504 repo, revision, root_path, flat=False,
506 repo, revision, root_path, flat=False,
505 extended_info=extended_info, content=content,
507 extended_info=extended_info, content=content,
506 max_file_bytes=max_file_bytes)
508 max_file_bytes=max_file_bytes)
507 _map = {
509 _map = {
508 'all': _d + _f,
510 'all': _d + _f,
509 'files': _f,
511 'files': _f,
510 'dirs': _d,
512 'dirs': _d,
511 }
513 }
512 return _map[ret_type]
514 return _map[ret_type]
513 except KeyError:
515 except KeyError:
514 raise JSONRPCError(
516 raise JSONRPCError(
515 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
517 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
516 except Exception:
518 except Exception:
517 log.exception("Exception occurred while trying to get repo nodes")
519 log.exception("Exception occurred while trying to get repo nodes")
518 raise JSONRPCError(
520 raise JSONRPCError(
519 'failed to get repo: `%s` nodes' % repo.repo_name
521 'failed to get repo: `%s` nodes' % repo.repo_name
520 )
522 )
521
523
522
524
523 @jsonrpc_method()
525 @jsonrpc_method()
524 def get_repo_refs(request, apiuser, repoid):
526 def get_repo_refs(request, apiuser, repoid):
525 """
527 """
526 Returns a dictionary of current references. It returns
528 Returns a dictionary of current references. It returns
527 bookmarks, branches, closed_branches, and tags for given repository
529 bookmarks, branches, closed_branches, and tags for given repository
528
530
529 It's possible to specify ret_type to show only `files` or `dirs`.
531 It's possible to specify ret_type to show only `files` or `dirs`.
530
532
531 This command can only be run using an |authtoken| with admin rights,
533 This command can only be run using an |authtoken| with admin rights,
532 or users with at least read rights to |repos|.
534 or users with at least read rights to |repos|.
533
535
534 :param apiuser: This is filled automatically from the |authtoken|.
536 :param apiuser: This is filled automatically from the |authtoken|.
535 :type apiuser: AuthUser
537 :type apiuser: AuthUser
536 :param repoid: The repository name or repository ID.
538 :param repoid: The repository name or repository ID.
537 :type repoid: str or int
539 :type repoid: str or int
538
540
539 Example output:
541 Example output:
540
542
541 .. code-block:: bash
543 .. code-block:: bash
542
544
543 id : <id_given_in_input>
545 id : <id_given_in_input>
544 "result": {
546 "result": {
545 "bookmarks": {
547 "bookmarks": {
546 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
548 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
547 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
549 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
548 },
550 },
549 "branches": {
551 "branches": {
550 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
552 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
551 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
553 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
552 },
554 },
553 "branches_closed": {},
555 "branches_closed": {},
554 "tags": {
556 "tags": {
555 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
557 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
556 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
558 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
557 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
559 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
558 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
560 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
559 }
561 }
560 }
562 }
561 error: null
563 error: null
562 """
564 """
563
565
564 repo = get_repo_or_error(repoid)
566 repo = get_repo_or_error(repoid)
565 if not has_superadmin_permission(apiuser):
567 if not has_superadmin_permission(apiuser):
566 _perms = ('repository.admin', 'repository.write', 'repository.read',)
568 _perms = ('repository.admin', 'repository.write', 'repository.read',)
567 validate_repo_permissions(apiuser, repoid, repo, _perms)
569 validate_repo_permissions(apiuser, repoid, repo, _perms)
568
570
569 try:
571 try:
570 # check if repo is not empty by any chance, skip quicker if it is.
572 # check if repo is not empty by any chance, skip quicker if it is.
571 vcs_instance = repo.scm_instance()
573 vcs_instance = repo.scm_instance()
572 refs = vcs_instance.refs()
574 refs = vcs_instance.refs()
573 return refs
575 return refs
574 except Exception:
576 except Exception:
575 log.exception("Exception occurred while trying to get repo refs")
577 log.exception("Exception occurred while trying to get repo refs")
576 raise JSONRPCError(
578 raise JSONRPCError(
577 'failed to get repo: `%s` references' % repo.repo_name
579 'failed to get repo: `%s` references' % repo.repo_name
578 )
580 )
579
581
580
582
581 @jsonrpc_method()
583 @jsonrpc_method()
582 def create_repo(
584 def create_repo(
583 request, apiuser, repo_name, repo_type,
585 request, apiuser, repo_name, repo_type,
584 owner=Optional(OAttr('apiuser')),
586 owner=Optional(OAttr('apiuser')),
585 description=Optional(''),
587 description=Optional(''),
586 private=Optional(False),
588 private=Optional(False),
587 clone_uri=Optional(None),
589 clone_uri=Optional(None),
588 landing_rev=Optional('rev:tip'),
590 landing_rev=Optional('rev:tip'),
589 enable_statistics=Optional(False),
591 enable_statistics=Optional(False),
590 enable_locking=Optional(False),
592 enable_locking=Optional(False),
591 enable_downloads=Optional(False),
593 enable_downloads=Optional(False),
592 copy_permissions=Optional(False)):
594 copy_permissions=Optional(False)):
593 """
595 """
594 Creates a repository.
596 Creates a repository.
595
597
596 * If the repository name contains "/", repository will be created inside
598 * If the repository name contains "/", repository will be created inside
597 a repository group or nested repository groups
599 a repository group or nested repository groups
598
600
599 For example "foo/bar/repo1" will create |repo| called "repo1" inside
601 For example "foo/bar/repo1" will create |repo| called "repo1" inside
600 group "foo/bar". You have to have permissions to access and write to
602 group "foo/bar". You have to have permissions to access and write to
601 the last repository group ("bar" in this example)
603 the last repository group ("bar" in this example)
602
604
603 This command can only be run using an |authtoken| with at least
605 This command can only be run using an |authtoken| with at least
604 permissions to create repositories, or write permissions to
606 permissions to create repositories, or write permissions to
605 parent repository groups.
607 parent repository groups.
606
608
607 :param apiuser: This is filled automatically from the |authtoken|.
609 :param apiuser: This is filled automatically from the |authtoken|.
608 :type apiuser: AuthUser
610 :type apiuser: AuthUser
609 :param repo_name: Set the repository name.
611 :param repo_name: Set the repository name.
610 :type repo_name: str
612 :type repo_name: str
611 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
613 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
612 :type repo_type: str
614 :type repo_type: str
613 :param owner: user_id or username
615 :param owner: user_id or username
614 :type owner: Optional(str)
616 :type owner: Optional(str)
615 :param description: Set the repository description.
617 :param description: Set the repository description.
616 :type description: Optional(str)
618 :type description: Optional(str)
617 :param private: set repository as private
619 :param private: set repository as private
618 :type private: bool
620 :type private: bool
619 :param clone_uri: set clone_uri
621 :param clone_uri: set clone_uri
620 :type clone_uri: str
622 :type clone_uri: str
621 :param landing_rev: <rev_type>:<rev>
623 :param landing_rev: <rev_type>:<rev>
622 :type landing_rev: str
624 :type landing_rev: str
623 :param enable_locking:
625 :param enable_locking:
624 :type enable_locking: bool
626 :type enable_locking: bool
625 :param enable_downloads:
627 :param enable_downloads:
626 :type enable_downloads: bool
628 :type enable_downloads: bool
627 :param enable_statistics:
629 :param enable_statistics:
628 :type enable_statistics: bool
630 :type enable_statistics: bool
629 :param copy_permissions: Copy permission from group in which the
631 :param copy_permissions: Copy permission from group in which the
630 repository is being created.
632 repository is being created.
631 :type copy_permissions: bool
633 :type copy_permissions: bool
632
634
633
635
634 Example output:
636 Example output:
635
637
636 .. code-block:: bash
638 .. code-block:: bash
637
639
638 id : <id_given_in_input>
640 id : <id_given_in_input>
639 result: {
641 result: {
640 "msg": "Created new repository `<reponame>`",
642 "msg": "Created new repository `<reponame>`",
641 "success": true,
643 "success": true,
642 "task": "<celery task id or None if done sync>"
644 "task": "<celery task id or None if done sync>"
643 }
645 }
644 error: null
646 error: null
645
647
646
648
647 Example error output:
649 Example error output:
648
650
649 .. code-block:: bash
651 .. code-block:: bash
650
652
651 id : <id_given_in_input>
653 id : <id_given_in_input>
652 result : null
654 result : null
653 error : {
655 error : {
654 'failed to create repository `<repo_name>`'
656 'failed to create repository `<repo_name>`'
655 }
657 }
656
658
657 """
659 """
658
660
659 owner = validate_set_owner_permissions(apiuser, owner)
661 owner = validate_set_owner_permissions(apiuser, owner)
660
662
661 description = Optional.extract(description)
663 description = Optional.extract(description)
662 copy_permissions = Optional.extract(copy_permissions)
664 copy_permissions = Optional.extract(copy_permissions)
663 clone_uri = Optional.extract(clone_uri)
665 clone_uri = Optional.extract(clone_uri)
664 landing_commit_ref = Optional.extract(landing_rev)
666 landing_commit_ref = Optional.extract(landing_rev)
665
667
666 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
668 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
667 if isinstance(private, Optional):
669 if isinstance(private, Optional):
668 private = defs.get('repo_private') or Optional.extract(private)
670 private = defs.get('repo_private') or Optional.extract(private)
669 if isinstance(repo_type, Optional):
671 if isinstance(repo_type, Optional):
670 repo_type = defs.get('repo_type')
672 repo_type = defs.get('repo_type')
671 if isinstance(enable_statistics, Optional):
673 if isinstance(enable_statistics, Optional):
672 enable_statistics = defs.get('repo_enable_statistics')
674 enable_statistics = defs.get('repo_enable_statistics')
673 if isinstance(enable_locking, Optional):
675 if isinstance(enable_locking, Optional):
674 enable_locking = defs.get('repo_enable_locking')
676 enable_locking = defs.get('repo_enable_locking')
675 if isinstance(enable_downloads, Optional):
677 if isinstance(enable_downloads, Optional):
676 enable_downloads = defs.get('repo_enable_downloads')
678 enable_downloads = defs.get('repo_enable_downloads')
677
679
678 schema = repo_schema.RepoSchema().bind(
680 schema = repo_schema.RepoSchema().bind(
679 repo_type_options=rhodecode.BACKENDS.keys(),
681 repo_type_options=rhodecode.BACKENDS.keys(),
680 # user caller
682 # user caller
681 user=apiuser)
683 user=apiuser)
682
684
683 try:
685 try:
684 schema_data = schema.deserialize(dict(
686 schema_data = schema.deserialize(dict(
685 repo_name=repo_name,
687 repo_name=repo_name,
686 repo_type=repo_type,
688 repo_type=repo_type,
687 repo_owner=owner.username,
689 repo_owner=owner.username,
688 repo_description=description,
690 repo_description=description,
689 repo_landing_commit_ref=landing_commit_ref,
691 repo_landing_commit_ref=landing_commit_ref,
690 repo_clone_uri=clone_uri,
692 repo_clone_uri=clone_uri,
691 repo_private=private,
693 repo_private=private,
692 repo_copy_permissions=copy_permissions,
694 repo_copy_permissions=copy_permissions,
693 repo_enable_statistics=enable_statistics,
695 repo_enable_statistics=enable_statistics,
694 repo_enable_downloads=enable_downloads,
696 repo_enable_downloads=enable_downloads,
695 repo_enable_locking=enable_locking))
697 repo_enable_locking=enable_locking))
696 except validation_schema.Invalid as err:
698 except validation_schema.Invalid as err:
697 raise JSONRPCValidationError(colander_exc=err)
699 raise JSONRPCValidationError(colander_exc=err)
698
700
699 try:
701 try:
700 data = {
702 data = {
701 'owner': owner,
703 'owner': owner,
702 'repo_name': schema_data['repo_group']['repo_name_without_group'],
704 'repo_name': schema_data['repo_group']['repo_name_without_group'],
703 'repo_name_full': schema_data['repo_name'],
705 'repo_name_full': schema_data['repo_name'],
704 'repo_group': schema_data['repo_group']['repo_group_id'],
706 'repo_group': schema_data['repo_group']['repo_group_id'],
705 'repo_type': schema_data['repo_type'],
707 'repo_type': schema_data['repo_type'],
706 'repo_description': schema_data['repo_description'],
708 'repo_description': schema_data['repo_description'],
707 'repo_private': schema_data['repo_private'],
709 'repo_private': schema_data['repo_private'],
708 'clone_uri': schema_data['repo_clone_uri'],
710 'clone_uri': schema_data['repo_clone_uri'],
709 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
711 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
710 'enable_statistics': schema_data['repo_enable_statistics'],
712 'enable_statistics': schema_data['repo_enable_statistics'],
711 'enable_locking': schema_data['repo_enable_locking'],
713 'enable_locking': schema_data['repo_enable_locking'],
712 'enable_downloads': schema_data['repo_enable_downloads'],
714 'enable_downloads': schema_data['repo_enable_downloads'],
713 'repo_copy_permissions': schema_data['repo_copy_permissions'],
715 'repo_copy_permissions': schema_data['repo_copy_permissions'],
714 }
716 }
715
717
716 task = RepoModel().create(form_data=data, cur_user=owner)
718 task = RepoModel().create(form_data=data, cur_user=owner)
717 from celery.result import BaseAsyncResult
719 from celery.result import BaseAsyncResult
718 task_id = None
720 task_id = None
719 if isinstance(task, BaseAsyncResult):
721 if isinstance(task, BaseAsyncResult):
720 task_id = task.task_id
722 task_id = task.task_id
721 # no commit, it's done in RepoModel, or async via celery
723 # no commit, it's done in RepoModel, or async via celery
722 return {
724 return {
723 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
725 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
724 'success': True, # cannot return the repo data here since fork
726 'success': True, # cannot return the repo data here since fork
725 # can be done async
727 # can be done async
726 'task': task_id
728 'task': task_id
727 }
729 }
728 except Exception:
730 except Exception:
729 log.exception(
731 log.exception(
730 u"Exception while trying to create the repository %s",
732 u"Exception while trying to create the repository %s",
731 schema_data['repo_name'])
733 schema_data['repo_name'])
732 raise JSONRPCError(
734 raise JSONRPCError(
733 'failed to create repository `%s`' % (schema_data['repo_name'],))
735 'failed to create repository `%s`' % (schema_data['repo_name'],))
734
736
735
737
736 @jsonrpc_method()
738 @jsonrpc_method()
737 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
739 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
738 description=Optional('')):
740 description=Optional('')):
739 """
741 """
740 Adds an extra field to a repository.
742 Adds an extra field to a repository.
741
743
742 This command can only be run using an |authtoken| with at least
744 This command can only be run using an |authtoken| with at least
743 write permissions to the |repo|.
745 write permissions to the |repo|.
744
746
745 :param apiuser: This is filled automatically from the |authtoken|.
747 :param apiuser: This is filled automatically from the |authtoken|.
746 :type apiuser: AuthUser
748 :type apiuser: AuthUser
747 :param repoid: Set the repository name or repository id.
749 :param repoid: Set the repository name or repository id.
748 :type repoid: str or int
750 :type repoid: str or int
749 :param key: Create a unique field key for this repository.
751 :param key: Create a unique field key for this repository.
750 :type key: str
752 :type key: str
751 :param label:
753 :param label:
752 :type label: Optional(str)
754 :type label: Optional(str)
753 :param description:
755 :param description:
754 :type description: Optional(str)
756 :type description: Optional(str)
755 """
757 """
756 repo = get_repo_or_error(repoid)
758 repo = get_repo_or_error(repoid)
757 if not has_superadmin_permission(apiuser):
759 if not has_superadmin_permission(apiuser):
758 _perms = ('repository.admin',)
760 _perms = ('repository.admin',)
759 validate_repo_permissions(apiuser, repoid, repo, _perms)
761 validate_repo_permissions(apiuser, repoid, repo, _perms)
760
762
761 label = Optional.extract(label) or key
763 label = Optional.extract(label) or key
762 description = Optional.extract(description)
764 description = Optional.extract(description)
763
765
764 field = RepositoryField.get_by_key_name(key, repo)
766 field = RepositoryField.get_by_key_name(key, repo)
765 if field:
767 if field:
766 raise JSONRPCError('Field with key '
768 raise JSONRPCError('Field with key '
767 '`%s` exists for repo `%s`' % (key, repoid))
769 '`%s` exists for repo `%s`' % (key, repoid))
768
770
769 try:
771 try:
770 RepoModel().add_repo_field(repo, key, field_label=label,
772 RepoModel().add_repo_field(repo, key, field_label=label,
771 field_desc=description)
773 field_desc=description)
772 Session().commit()
774 Session().commit()
773 return {
775 return {
774 'msg': "Added new repository field `%s`" % (key,),
776 'msg': "Added new repository field `%s`" % (key,),
775 'success': True,
777 'success': True,
776 }
778 }
777 except Exception:
779 except Exception:
778 log.exception("Exception occurred while trying to add field to repo")
780 log.exception("Exception occurred while trying to add field to repo")
779 raise JSONRPCError(
781 raise JSONRPCError(
780 'failed to create new field for repository `%s`' % (repoid,))
782 'failed to create new field for repository `%s`' % (repoid,))
781
783
782
784
783 @jsonrpc_method()
785 @jsonrpc_method()
784 def remove_field_from_repo(request, apiuser, repoid, key):
786 def remove_field_from_repo(request, apiuser, repoid, key):
785 """
787 """
786 Removes an extra field from a repository.
788 Removes an extra field from a repository.
787
789
788 This command can only be run using an |authtoken| with at least
790 This command can only be run using an |authtoken| with at least
789 write permissions to the |repo|.
791 write permissions to the |repo|.
790
792
791 :param apiuser: This is filled automatically from the |authtoken|.
793 :param apiuser: This is filled automatically from the |authtoken|.
792 :type apiuser: AuthUser
794 :type apiuser: AuthUser
793 :param repoid: Set the repository name or repository ID.
795 :param repoid: Set the repository name or repository ID.
794 :type repoid: str or int
796 :type repoid: str or int
795 :param key: Set the unique field key for this repository.
797 :param key: Set the unique field key for this repository.
796 :type key: str
798 :type key: str
797 """
799 """
798
800
799 repo = get_repo_or_error(repoid)
801 repo = get_repo_or_error(repoid)
800 if not has_superadmin_permission(apiuser):
802 if not has_superadmin_permission(apiuser):
801 _perms = ('repository.admin',)
803 _perms = ('repository.admin',)
802 validate_repo_permissions(apiuser, repoid, repo, _perms)
804 validate_repo_permissions(apiuser, repoid, repo, _perms)
803
805
804 field = RepositoryField.get_by_key_name(key, repo)
806 field = RepositoryField.get_by_key_name(key, repo)
805 if not field:
807 if not field:
806 raise JSONRPCError('Field with key `%s` does not '
808 raise JSONRPCError('Field with key `%s` does not '
807 'exists for repo `%s`' % (key, repoid))
809 'exists for repo `%s`' % (key, repoid))
808
810
809 try:
811 try:
810 RepoModel().delete_repo_field(repo, field_key=key)
812 RepoModel().delete_repo_field(repo, field_key=key)
811 Session().commit()
813 Session().commit()
812 return {
814 return {
813 'msg': "Deleted repository field `%s`" % (key,),
815 'msg': "Deleted repository field `%s`" % (key,),
814 'success': True,
816 'success': True,
815 }
817 }
816 except Exception:
818 except Exception:
817 log.exception(
819 log.exception(
818 "Exception occurred while trying to delete field from repo")
820 "Exception occurred while trying to delete field from repo")
819 raise JSONRPCError(
821 raise JSONRPCError(
820 'failed to delete field for repository `%s`' % (repoid,))
822 'failed to delete field for repository `%s`' % (repoid,))
821
823
822
824
823 @jsonrpc_method()
825 @jsonrpc_method()
824 def update_repo(
826 def update_repo(
825 request, apiuser, repoid, repo_name=Optional(None),
827 request, apiuser, repoid, repo_name=Optional(None),
826 owner=Optional(OAttr('apiuser')), description=Optional(''),
828 owner=Optional(OAttr('apiuser')), description=Optional(''),
827 private=Optional(False), clone_uri=Optional(None),
829 private=Optional(False), clone_uri=Optional(None),
828 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
830 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
829 enable_statistics=Optional(False),
831 enable_statistics=Optional(False),
830 enable_locking=Optional(False),
832 enable_locking=Optional(False),
831 enable_downloads=Optional(False), fields=Optional('')):
833 enable_downloads=Optional(False), fields=Optional('')):
832 """
834 """
833 Updates a repository with the given information.
835 Updates a repository with the given information.
834
836
835 This command can only be run using an |authtoken| with at least
837 This command can only be run using an |authtoken| with at least
836 admin permissions to the |repo|.
838 admin permissions to the |repo|.
837
839
838 * If the repository name contains "/", repository will be updated
840 * If the repository name contains "/", repository will be updated
839 accordingly with a repository group or nested repository groups
841 accordingly with a repository group or nested repository groups
840
842
841 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
843 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
842 called "repo-test" and place it inside group "foo/bar".
844 called "repo-test" and place it inside group "foo/bar".
843 You have to have permissions to access and write to the last repository
845 You have to have permissions to access and write to the last repository
844 group ("bar" in this example)
846 group ("bar" in this example)
845
847
846 :param apiuser: This is filled automatically from the |authtoken|.
848 :param apiuser: This is filled automatically from the |authtoken|.
847 :type apiuser: AuthUser
849 :type apiuser: AuthUser
848 :param repoid: repository name or repository ID.
850 :param repoid: repository name or repository ID.
849 :type repoid: str or int
851 :type repoid: str or int
850 :param repo_name: Update the |repo| name, including the
852 :param repo_name: Update the |repo| name, including the
851 repository group it's in.
853 repository group it's in.
852 :type repo_name: str
854 :type repo_name: str
853 :param owner: Set the |repo| owner.
855 :param owner: Set the |repo| owner.
854 :type owner: str
856 :type owner: str
855 :param fork_of: Set the |repo| as fork of another |repo|.
857 :param fork_of: Set the |repo| as fork of another |repo|.
856 :type fork_of: str
858 :type fork_of: str
857 :param description: Update the |repo| description.
859 :param description: Update the |repo| description.
858 :type description: str
860 :type description: str
859 :param private: Set the |repo| as private. (True | False)
861 :param private: Set the |repo| as private. (True | False)
860 :type private: bool
862 :type private: bool
861 :param clone_uri: Update the |repo| clone URI.
863 :param clone_uri: Update the |repo| clone URI.
862 :type clone_uri: str
864 :type clone_uri: str
863 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
865 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
864 :type landing_rev: str
866 :type landing_rev: str
865 :param enable_statistics: Enable statistics on the |repo|, (True | False).
867 :param enable_statistics: Enable statistics on the |repo|, (True | False).
866 :type enable_statistics: bool
868 :type enable_statistics: bool
867 :param enable_locking: Enable |repo| locking.
869 :param enable_locking: Enable |repo| locking.
868 :type enable_locking: bool
870 :type enable_locking: bool
869 :param enable_downloads: Enable downloads from the |repo|, (True | False).
871 :param enable_downloads: Enable downloads from the |repo|, (True | False).
870 :type enable_downloads: bool
872 :type enable_downloads: bool
871 :param fields: Add extra fields to the |repo|. Use the following
873 :param fields: Add extra fields to the |repo|. Use the following
872 example format: ``field_key=field_val,field_key2=fieldval2``.
874 example format: ``field_key=field_val,field_key2=fieldval2``.
873 Escape ', ' with \,
875 Escape ', ' with \,
874 :type fields: str
876 :type fields: str
875 """
877 """
876
878
877 repo = get_repo_or_error(repoid)
879 repo = get_repo_or_error(repoid)
878
880
879 include_secrets = False
881 include_secrets = False
880 if not has_superadmin_permission(apiuser):
882 if not has_superadmin_permission(apiuser):
881 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
883 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
882 else:
884 else:
883 include_secrets = True
885 include_secrets = True
884
886
885 updates = dict(
887 updates = dict(
886 repo_name=repo_name
888 repo_name=repo_name
887 if not isinstance(repo_name, Optional) else repo.repo_name,
889 if not isinstance(repo_name, Optional) else repo.repo_name,
888
890
889 fork_id=fork_of
891 fork_id=fork_of
890 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
892 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
891
893
892 user=owner
894 user=owner
893 if not isinstance(owner, Optional) else repo.user.username,
895 if not isinstance(owner, Optional) else repo.user.username,
894
896
895 repo_description=description
897 repo_description=description
896 if not isinstance(description, Optional) else repo.description,
898 if not isinstance(description, Optional) else repo.description,
897
899
898 repo_private=private
900 repo_private=private
899 if not isinstance(private, Optional) else repo.private,
901 if not isinstance(private, Optional) else repo.private,
900
902
901 clone_uri=clone_uri
903 clone_uri=clone_uri
902 if not isinstance(clone_uri, Optional) else repo.clone_uri,
904 if not isinstance(clone_uri, Optional) else repo.clone_uri,
903
905
904 repo_landing_rev=landing_rev
906 repo_landing_rev=landing_rev
905 if not isinstance(landing_rev, Optional) else repo._landing_revision,
907 if not isinstance(landing_rev, Optional) else repo._landing_revision,
906
908
907 repo_enable_statistics=enable_statistics
909 repo_enable_statistics=enable_statistics
908 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
910 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
909
911
910 repo_enable_locking=enable_locking
912 repo_enable_locking=enable_locking
911 if not isinstance(enable_locking, Optional) else repo.enable_locking,
913 if not isinstance(enable_locking, Optional) else repo.enable_locking,
912
914
913 repo_enable_downloads=enable_downloads
915 repo_enable_downloads=enable_downloads
914 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
916 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
915
917
916 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
917
919
920 old_values = repo.get_api_data()
918 schema = repo_schema.RepoSchema().bind(
921 schema = repo_schema.RepoSchema().bind(
919 repo_type_options=rhodecode.BACKENDS.keys(),
922 repo_type_options=rhodecode.BACKENDS.keys(),
920 repo_ref_options=ref_choices,
923 repo_ref_options=ref_choices,
921 # user caller
924 # user caller
922 user=apiuser,
925 user=apiuser,
923 old_values=repo.get_api_data())
926 old_values=old_values)
924 try:
927 try:
925 schema_data = schema.deserialize(dict(
928 schema_data = schema.deserialize(dict(
926 # we save old value, users cannot change type
929 # we save old value, users cannot change type
927 repo_type=repo.repo_type,
930 repo_type=repo.repo_type,
928
931
929 repo_name=updates['repo_name'],
932 repo_name=updates['repo_name'],
930 repo_owner=updates['user'],
933 repo_owner=updates['user'],
931 repo_description=updates['repo_description'],
934 repo_description=updates['repo_description'],
932 repo_clone_uri=updates['clone_uri'],
935 repo_clone_uri=updates['clone_uri'],
933 repo_fork_of=updates['fork_id'],
936 repo_fork_of=updates['fork_id'],
934 repo_private=updates['repo_private'],
937 repo_private=updates['repo_private'],
935 repo_landing_commit_ref=updates['repo_landing_rev'],
938 repo_landing_commit_ref=updates['repo_landing_rev'],
936 repo_enable_statistics=updates['repo_enable_statistics'],
939 repo_enable_statistics=updates['repo_enable_statistics'],
937 repo_enable_downloads=updates['repo_enable_downloads'],
940 repo_enable_downloads=updates['repo_enable_downloads'],
938 repo_enable_locking=updates['repo_enable_locking']))
941 repo_enable_locking=updates['repo_enable_locking']))
939 except validation_schema.Invalid as err:
942 except validation_schema.Invalid as err:
940 raise JSONRPCValidationError(colander_exc=err)
943 raise JSONRPCValidationError(colander_exc=err)
941
944
942 # save validated data back into the updates dict
945 # save validated data back into the updates dict
943 validated_updates = dict(
946 validated_updates = dict(
944 repo_name=schema_data['repo_group']['repo_name_without_group'],
947 repo_name=schema_data['repo_group']['repo_name_without_group'],
945 repo_group=schema_data['repo_group']['repo_group_id'],
948 repo_group=schema_data['repo_group']['repo_group_id'],
946
949
947 user=schema_data['repo_owner'],
950 user=schema_data['repo_owner'],
948 repo_description=schema_data['repo_description'],
951 repo_description=schema_data['repo_description'],
949 repo_private=schema_data['repo_private'],
952 repo_private=schema_data['repo_private'],
950 clone_uri=schema_data['repo_clone_uri'],
953 clone_uri=schema_data['repo_clone_uri'],
951 repo_landing_rev=schema_data['repo_landing_commit_ref'],
954 repo_landing_rev=schema_data['repo_landing_commit_ref'],
952 repo_enable_statistics=schema_data['repo_enable_statistics'],
955 repo_enable_statistics=schema_data['repo_enable_statistics'],
953 repo_enable_locking=schema_data['repo_enable_locking'],
956 repo_enable_locking=schema_data['repo_enable_locking'],
954 repo_enable_downloads=schema_data['repo_enable_downloads'],
957 repo_enable_downloads=schema_data['repo_enable_downloads'],
955 )
958 )
956
959
957 if schema_data['repo_fork_of']:
960 if schema_data['repo_fork_of']:
958 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
961 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
959 validated_updates['fork_id'] = fork_repo.repo_id
962 validated_updates['fork_id'] = fork_repo.repo_id
960
963
961 # extra fields
964 # extra fields
962 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
965 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
963 if fields:
966 if fields:
964 validated_updates.update(fields)
967 validated_updates.update(fields)
965
968
966 try:
969 try:
967 RepoModel().update(repo, **validated_updates)
970 RepoModel().update(repo, **validated_updates)
971 audit_logger.store_api(
972 'repo.edit', action_data={'old_data': old_values},
973 user=apiuser, repo=repo)
968 Session().commit()
974 Session().commit()
969 return {
975 return {
970 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
976 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
971 'repository': repo.get_api_data(include_secrets=include_secrets)
977 'repository': repo.get_api_data(include_secrets=include_secrets)
972 }
978 }
973 except Exception:
979 except Exception:
974 log.exception(
980 log.exception(
975 u"Exception while trying to update the repository %s",
981 u"Exception while trying to update the repository %s",
976 repoid)
982 repoid)
977 raise JSONRPCError('failed to update repo `%s`' % repoid)
983 raise JSONRPCError('failed to update repo `%s`' % repoid)
978
984
979
985
980 @jsonrpc_method()
986 @jsonrpc_method()
981 def fork_repo(request, apiuser, repoid, fork_name,
987 def fork_repo(request, apiuser, repoid, fork_name,
982 owner=Optional(OAttr('apiuser')),
988 owner=Optional(OAttr('apiuser')),
983 description=Optional(''),
989 description=Optional(''),
984 private=Optional(False),
990 private=Optional(False),
985 clone_uri=Optional(None),
991 clone_uri=Optional(None),
986 landing_rev=Optional('rev:tip'),
992 landing_rev=Optional('rev:tip'),
987 copy_permissions=Optional(False)):
993 copy_permissions=Optional(False)):
988 """
994 """
989 Creates a fork of the specified |repo|.
995 Creates a fork of the specified |repo|.
990
996
991 * If the fork_name contains "/", fork will be created inside
997 * If the fork_name contains "/", fork will be created inside
992 a repository group or nested repository groups
998 a repository group or nested repository groups
993
999
994 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1000 For example "foo/bar/fork-repo" will create fork called "fork-repo"
995 inside group "foo/bar". You have to have permissions to access and
1001 inside group "foo/bar". You have to have permissions to access and
996 write to the last repository group ("bar" in this example)
1002 write to the last repository group ("bar" in this example)
997
1003
998 This command can only be run using an |authtoken| with minimum
1004 This command can only be run using an |authtoken| with minimum
999 read permissions of the forked repo, create fork permissions for an user.
1005 read permissions of the forked repo, create fork permissions for an user.
1000
1006
1001 :param apiuser: This is filled automatically from the |authtoken|.
1007 :param apiuser: This is filled automatically from the |authtoken|.
1002 :type apiuser: AuthUser
1008 :type apiuser: AuthUser
1003 :param repoid: Set repository name or repository ID.
1009 :param repoid: Set repository name or repository ID.
1004 :type repoid: str or int
1010 :type repoid: str or int
1005 :param fork_name: Set the fork name, including it's repository group membership.
1011 :param fork_name: Set the fork name, including it's repository group membership.
1006 :type fork_name: str
1012 :type fork_name: str
1007 :param owner: Set the fork owner.
1013 :param owner: Set the fork owner.
1008 :type owner: str
1014 :type owner: str
1009 :param description: Set the fork description.
1015 :param description: Set the fork description.
1010 :type description: str
1016 :type description: str
1011 :param copy_permissions: Copy permissions from parent |repo|. The
1017 :param copy_permissions: Copy permissions from parent |repo|. The
1012 default is False.
1018 default is False.
1013 :type copy_permissions: bool
1019 :type copy_permissions: bool
1014 :param private: Make the fork private. The default is False.
1020 :param private: Make the fork private. The default is False.
1015 :type private: bool
1021 :type private: bool
1016 :param landing_rev: Set the landing revision. The default is tip.
1022 :param landing_rev: Set the landing revision. The default is tip.
1017
1023
1018 Example output:
1024 Example output:
1019
1025
1020 .. code-block:: bash
1026 .. code-block:: bash
1021
1027
1022 id : <id_for_response>
1028 id : <id_for_response>
1023 api_key : "<api_key>"
1029 api_key : "<api_key>"
1024 args: {
1030 args: {
1025 "repoid" : "<reponame or repo_id>",
1031 "repoid" : "<reponame or repo_id>",
1026 "fork_name": "<forkname>",
1032 "fork_name": "<forkname>",
1027 "owner": "<username or user_id = Optional(=apiuser)>",
1033 "owner": "<username or user_id = Optional(=apiuser)>",
1028 "description": "<description>",
1034 "description": "<description>",
1029 "copy_permissions": "<bool>",
1035 "copy_permissions": "<bool>",
1030 "private": "<bool>",
1036 "private": "<bool>",
1031 "landing_rev": "<landing_rev>"
1037 "landing_rev": "<landing_rev>"
1032 }
1038 }
1033
1039
1034 Example error output:
1040 Example error output:
1035
1041
1036 .. code-block:: bash
1042 .. code-block:: bash
1037
1043
1038 id : <id_given_in_input>
1044 id : <id_given_in_input>
1039 result: {
1045 result: {
1040 "msg": "Created fork of `<reponame>` as `<forkname>`",
1046 "msg": "Created fork of `<reponame>` as `<forkname>`",
1041 "success": true,
1047 "success": true,
1042 "task": "<celery task id or None if done sync>"
1048 "task": "<celery task id or None if done sync>"
1043 }
1049 }
1044 error: null
1050 error: null
1045
1051
1046 """
1052 """
1047
1053
1048 repo = get_repo_or_error(repoid)
1054 repo = get_repo_or_error(repoid)
1049 repo_name = repo.repo_name
1055 repo_name = repo.repo_name
1050
1056
1051 if not has_superadmin_permission(apiuser):
1057 if not has_superadmin_permission(apiuser):
1052 # check if we have at least read permission for
1058 # check if we have at least read permission for
1053 # this repo that we fork !
1059 # this repo that we fork !
1054 _perms = (
1060 _perms = (
1055 'repository.admin', 'repository.write', 'repository.read')
1061 'repository.admin', 'repository.write', 'repository.read')
1056 validate_repo_permissions(apiuser, repoid, repo, _perms)
1062 validate_repo_permissions(apiuser, repoid, repo, _perms)
1057
1063
1058 # check if the regular user has at least fork permissions as well
1064 # check if the regular user has at least fork permissions as well
1059 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1065 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1060 raise JSONRPCForbidden()
1066 raise JSONRPCForbidden()
1061
1067
1062 # check if user can set owner parameter
1068 # check if user can set owner parameter
1063 owner = validate_set_owner_permissions(apiuser, owner)
1069 owner = validate_set_owner_permissions(apiuser, owner)
1064
1070
1065 description = Optional.extract(description)
1071 description = Optional.extract(description)
1066 copy_permissions = Optional.extract(copy_permissions)
1072 copy_permissions = Optional.extract(copy_permissions)
1067 clone_uri = Optional.extract(clone_uri)
1073 clone_uri = Optional.extract(clone_uri)
1068 landing_commit_ref = Optional.extract(landing_rev)
1074 landing_commit_ref = Optional.extract(landing_rev)
1069 private = Optional.extract(private)
1075 private = Optional.extract(private)
1070
1076
1071 schema = repo_schema.RepoSchema().bind(
1077 schema = repo_schema.RepoSchema().bind(
1072 repo_type_options=rhodecode.BACKENDS.keys(),
1078 repo_type_options=rhodecode.BACKENDS.keys(),
1073 # user caller
1079 # user caller
1074 user=apiuser)
1080 user=apiuser)
1075
1081
1076 try:
1082 try:
1077 schema_data = schema.deserialize(dict(
1083 schema_data = schema.deserialize(dict(
1078 repo_name=fork_name,
1084 repo_name=fork_name,
1079 repo_type=repo.repo_type,
1085 repo_type=repo.repo_type,
1080 repo_owner=owner.username,
1086 repo_owner=owner.username,
1081 repo_description=description,
1087 repo_description=description,
1082 repo_landing_commit_ref=landing_commit_ref,
1088 repo_landing_commit_ref=landing_commit_ref,
1083 repo_clone_uri=clone_uri,
1089 repo_clone_uri=clone_uri,
1084 repo_private=private,
1090 repo_private=private,
1085 repo_copy_permissions=copy_permissions))
1091 repo_copy_permissions=copy_permissions))
1086 except validation_schema.Invalid as err:
1092 except validation_schema.Invalid as err:
1087 raise JSONRPCValidationError(colander_exc=err)
1093 raise JSONRPCValidationError(colander_exc=err)
1088
1094
1089 try:
1095 try:
1090 data = {
1096 data = {
1091 'fork_parent_id': repo.repo_id,
1097 'fork_parent_id': repo.repo_id,
1092
1098
1093 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1099 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1094 'repo_name_full': schema_data['repo_name'],
1100 'repo_name_full': schema_data['repo_name'],
1095 'repo_group': schema_data['repo_group']['repo_group_id'],
1101 'repo_group': schema_data['repo_group']['repo_group_id'],
1096 'repo_type': schema_data['repo_type'],
1102 'repo_type': schema_data['repo_type'],
1097 'description': schema_data['repo_description'],
1103 'description': schema_data['repo_description'],
1098 'private': schema_data['repo_private'],
1104 'private': schema_data['repo_private'],
1099 'copy_permissions': schema_data['repo_copy_permissions'],
1105 'copy_permissions': schema_data['repo_copy_permissions'],
1100 'landing_rev': schema_data['repo_landing_commit_ref'],
1106 'landing_rev': schema_data['repo_landing_commit_ref'],
1101 }
1107 }
1102
1108
1103 task = RepoModel().create_fork(data, cur_user=owner)
1109 task = RepoModel().create_fork(data, cur_user=owner)
1104 # no commit, it's done in RepoModel, or async via celery
1110 # no commit, it's done in RepoModel, or async via celery
1105 from celery.result import BaseAsyncResult
1111 from celery.result import BaseAsyncResult
1106 task_id = None
1112 task_id = None
1107 if isinstance(task, BaseAsyncResult):
1113 if isinstance(task, BaseAsyncResult):
1108 task_id = task.task_id
1114 task_id = task.task_id
1109 return {
1115 return {
1110 'msg': 'Created fork of `%s` as `%s`' % (
1116 'msg': 'Created fork of `%s` as `%s`' % (
1111 repo.repo_name, schema_data['repo_name']),
1117 repo.repo_name, schema_data['repo_name']),
1112 'success': True, # cannot return the repo data here since fork
1118 'success': True, # cannot return the repo data here since fork
1113 # can be done async
1119 # can be done async
1114 'task': task_id
1120 'task': task_id
1115 }
1121 }
1116 except Exception:
1122 except Exception:
1117 log.exception(
1123 log.exception(
1118 u"Exception while trying to create fork %s",
1124 u"Exception while trying to create fork %s",
1119 schema_data['repo_name'])
1125 schema_data['repo_name'])
1120 raise JSONRPCError(
1126 raise JSONRPCError(
1121 'failed to fork repository `%s` as `%s`' % (
1127 'failed to fork repository `%s` as `%s`' % (
1122 repo_name, schema_data['repo_name']))
1128 repo_name, schema_data['repo_name']))
1123
1129
1124
1130
1125 @jsonrpc_method()
1131 @jsonrpc_method()
1126 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1132 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1127 """
1133 """
1128 Deletes a repository.
1134 Deletes a repository.
1129
1135
1130 * When the `forks` parameter is set it's possible to detach or delete
1136 * When the `forks` parameter is set it's possible to detach or delete
1131 forks of deleted repository.
1137 forks of deleted repository.
1132
1138
1133 This command can only be run using an |authtoken| with admin
1139 This command can only be run using an |authtoken| with admin
1134 permissions on the |repo|.
1140 permissions on the |repo|.
1135
1141
1136 :param apiuser: This is filled automatically from the |authtoken|.
1142 :param apiuser: This is filled automatically from the |authtoken|.
1137 :type apiuser: AuthUser
1143 :type apiuser: AuthUser
1138 :param repoid: Set the repository name or repository ID.
1144 :param repoid: Set the repository name or repository ID.
1139 :type repoid: str or int
1145 :type repoid: str or int
1140 :param forks: Set to `detach` or `delete` forks from the |repo|.
1146 :param forks: Set to `detach` or `delete` forks from the |repo|.
1141 :type forks: Optional(str)
1147 :type forks: Optional(str)
1142
1148
1143 Example error output:
1149 Example error output:
1144
1150
1145 .. code-block:: bash
1151 .. code-block:: bash
1146
1152
1147 id : <id_given_in_input>
1153 id : <id_given_in_input>
1148 result: {
1154 result: {
1149 "msg": "Deleted repository `<reponame>`",
1155 "msg": "Deleted repository `<reponame>`",
1150 "success": true
1156 "success": true
1151 }
1157 }
1152 error: null
1158 error: null
1153 """
1159 """
1154
1160
1155 repo = get_repo_or_error(repoid)
1161 repo = get_repo_or_error(repoid)
1162 repo_name = repo.repo_name
1156 if not has_superadmin_permission(apiuser):
1163 if not has_superadmin_permission(apiuser):
1157 _perms = ('repository.admin',)
1164 _perms = ('repository.admin',)
1158 validate_repo_permissions(apiuser, repoid, repo, _perms)
1165 validate_repo_permissions(apiuser, repoid, repo, _perms)
1159
1166
1160 try:
1167 try:
1161 handle_forks = Optional.extract(forks)
1168 handle_forks = Optional.extract(forks)
1162 _forks_msg = ''
1169 _forks_msg = ''
1163 _forks = [f for f in repo.forks]
1170 _forks = [f for f in repo.forks]
1164 if handle_forks == 'detach':
1171 if handle_forks == 'detach':
1165 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1172 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1166 elif handle_forks == 'delete':
1173 elif handle_forks == 'delete':
1167 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1174 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1168 elif _forks:
1175 elif _forks:
1169 raise JSONRPCError(
1176 raise JSONRPCError(
1170 'Cannot delete `%s` it still contains attached forks' %
1177 'Cannot delete `%s` it still contains attached forks' %
1171 (repo.repo_name,)
1178 (repo.repo_name,)
1172 )
1179 )
1180 old_data = repo.get_api_data()
1181 RepoModel().delete(repo, forks=forks)
1173
1182
1174 RepoModel().delete(repo, forks=forks)
1183 repo = audit_logger.RepoWrap(repo_id=None,
1184 repo_name=repo.repo_name)
1185
1186 audit_logger.store_api(
1187 'repo.delete', action_data={'old_data': old_data},
1188 user=apiuser, repo=repo)
1189
1190 ScmModel().mark_for_invalidation(repo_name, delete=True)
1175 Session().commit()
1191 Session().commit()
1176 return {
1192 return {
1177 'msg': 'Deleted repository `%s`%s' % (
1193 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1178 repo.repo_name, _forks_msg),
1179 'success': True
1194 'success': True
1180 }
1195 }
1181 except Exception:
1196 except Exception:
1182 log.exception("Exception occurred while trying to delete repo")
1197 log.exception("Exception occurred while trying to delete repo")
1183 raise JSONRPCError(
1198 raise JSONRPCError(
1184 'failed to delete repository `%s`' % (repo.repo_name,)
1199 'failed to delete repository `%s`' % (repo_name,)
1185 )
1200 )
1186
1201
1187
1202
1188 #TODO: marcink, change name ?
1203 #TODO: marcink, change name ?
1189 @jsonrpc_method()
1204 @jsonrpc_method()
1190 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1205 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1191 """
1206 """
1192 Invalidates the cache for the specified repository.
1207 Invalidates the cache for the specified repository.
1193
1208
1194 This command can only be run using an |authtoken| with admin rights to
1209 This command can only be run using an |authtoken| with admin rights to
1195 the specified repository.
1210 the specified repository.
1196
1211
1197 This command takes the following options:
1212 This command takes the following options:
1198
1213
1199 :param apiuser: This is filled automatically from |authtoken|.
1214 :param apiuser: This is filled automatically from |authtoken|.
1200 :type apiuser: AuthUser
1215 :type apiuser: AuthUser
1201 :param repoid: Sets the repository name or repository ID.
1216 :param repoid: Sets the repository name or repository ID.
1202 :type repoid: str or int
1217 :type repoid: str or int
1203 :param delete_keys: This deletes the invalidated keys instead of
1218 :param delete_keys: This deletes the invalidated keys instead of
1204 just flagging them.
1219 just flagging them.
1205 :type delete_keys: Optional(``True`` | ``False``)
1220 :type delete_keys: Optional(``True`` | ``False``)
1206
1221
1207 Example output:
1222 Example output:
1208
1223
1209 .. code-block:: bash
1224 .. code-block:: bash
1210
1225
1211 id : <id_given_in_input>
1226 id : <id_given_in_input>
1212 result : {
1227 result : {
1213 'msg': Cache for repository `<repository name>` was invalidated,
1228 'msg': Cache for repository `<repository name>` was invalidated,
1214 'repository': <repository name>
1229 'repository': <repository name>
1215 }
1230 }
1216 error : null
1231 error : null
1217
1232
1218 Example error output:
1233 Example error output:
1219
1234
1220 .. code-block:: bash
1235 .. code-block:: bash
1221
1236
1222 id : <id_given_in_input>
1237 id : <id_given_in_input>
1223 result : null
1238 result : null
1224 error : {
1239 error : {
1225 'Error occurred during cache invalidation action'
1240 'Error occurred during cache invalidation action'
1226 }
1241 }
1227
1242
1228 """
1243 """
1229
1244
1230 repo = get_repo_or_error(repoid)
1245 repo = get_repo_or_error(repoid)
1231 if not has_superadmin_permission(apiuser):
1246 if not has_superadmin_permission(apiuser):
1232 _perms = ('repository.admin', 'repository.write',)
1247 _perms = ('repository.admin', 'repository.write',)
1233 validate_repo_permissions(apiuser, repoid, repo, _perms)
1248 validate_repo_permissions(apiuser, repoid, repo, _perms)
1234
1249
1235 delete = Optional.extract(delete_keys)
1250 delete = Optional.extract(delete_keys)
1236 try:
1251 try:
1237 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1252 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1238 return {
1253 return {
1239 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1254 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1240 'repository': repo.repo_name
1255 'repository': repo.repo_name
1241 }
1256 }
1242 except Exception:
1257 except Exception:
1243 log.exception(
1258 log.exception(
1244 "Exception occurred while trying to invalidate repo cache")
1259 "Exception occurred while trying to invalidate repo cache")
1245 raise JSONRPCError(
1260 raise JSONRPCError(
1246 'Error occurred during cache invalidation action'
1261 'Error occurred during cache invalidation action'
1247 )
1262 )
1248
1263
1249
1264
1250 #TODO: marcink, change name ?
1265 #TODO: marcink, change name ?
1251 @jsonrpc_method()
1266 @jsonrpc_method()
1252 def lock(request, apiuser, repoid, locked=Optional(None),
1267 def lock(request, apiuser, repoid, locked=Optional(None),
1253 userid=Optional(OAttr('apiuser'))):
1268 userid=Optional(OAttr('apiuser'))):
1254 """
1269 """
1255 Sets the lock state of the specified |repo| by the given user.
1270 Sets the lock state of the specified |repo| by the given user.
1256 From more information, see :ref:`repo-locking`.
1271 From more information, see :ref:`repo-locking`.
1257
1272
1258 * If the ``userid`` option is not set, the repository is locked to the
1273 * If the ``userid`` option is not set, the repository is locked to the
1259 user who called the method.
1274 user who called the method.
1260 * If the ``locked`` parameter is not set, the current lock state of the
1275 * If the ``locked`` parameter is not set, the current lock state of the
1261 repository is displayed.
1276 repository is displayed.
1262
1277
1263 This command can only be run using an |authtoken| with admin rights to
1278 This command can only be run using an |authtoken| with admin rights to
1264 the specified repository.
1279 the specified repository.
1265
1280
1266 This command takes the following options:
1281 This command takes the following options:
1267
1282
1268 :param apiuser: This is filled automatically from the |authtoken|.
1283 :param apiuser: This is filled automatically from the |authtoken|.
1269 :type apiuser: AuthUser
1284 :type apiuser: AuthUser
1270 :param repoid: Sets the repository name or repository ID.
1285 :param repoid: Sets the repository name or repository ID.
1271 :type repoid: str or int
1286 :type repoid: str or int
1272 :param locked: Sets the lock state.
1287 :param locked: Sets the lock state.
1273 :type locked: Optional(``True`` | ``False``)
1288 :type locked: Optional(``True`` | ``False``)
1274 :param userid: Set the repository lock to this user.
1289 :param userid: Set the repository lock to this user.
1275 :type userid: Optional(str or int)
1290 :type userid: Optional(str or int)
1276
1291
1277 Example error output:
1292 Example error output:
1278
1293
1279 .. code-block:: bash
1294 .. code-block:: bash
1280
1295
1281 id : <id_given_in_input>
1296 id : <id_given_in_input>
1282 result : {
1297 result : {
1283 'repo': '<reponame>',
1298 'repo': '<reponame>',
1284 'locked': <bool: lock state>,
1299 'locked': <bool: lock state>,
1285 'locked_since': <int: lock timestamp>,
1300 'locked_since': <int: lock timestamp>,
1286 'locked_by': <username of person who made the lock>,
1301 'locked_by': <username of person who made the lock>,
1287 'lock_reason': <str: reason for locking>,
1302 'lock_reason': <str: reason for locking>,
1288 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1303 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1289 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1304 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1290 or
1305 or
1291 'msg': 'Repo `<repository name>` not locked.'
1306 'msg': 'Repo `<repository name>` not locked.'
1292 or
1307 or
1293 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1308 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1294 }
1309 }
1295 error : null
1310 error : null
1296
1311
1297 Example error output:
1312 Example error output:
1298
1313
1299 .. code-block:: bash
1314 .. code-block:: bash
1300
1315
1301 id : <id_given_in_input>
1316 id : <id_given_in_input>
1302 result : null
1317 result : null
1303 error : {
1318 error : {
1304 'Error occurred locking repository `<reponame>`'
1319 'Error occurred locking repository `<reponame>`'
1305 }
1320 }
1306 """
1321 """
1307
1322
1308 repo = get_repo_or_error(repoid)
1323 repo = get_repo_or_error(repoid)
1309 if not has_superadmin_permission(apiuser):
1324 if not has_superadmin_permission(apiuser):
1310 # check if we have at least write permission for this repo !
1325 # check if we have at least write permission for this repo !
1311 _perms = ('repository.admin', 'repository.write',)
1326 _perms = ('repository.admin', 'repository.write',)
1312 validate_repo_permissions(apiuser, repoid, repo, _perms)
1327 validate_repo_permissions(apiuser, repoid, repo, _perms)
1313
1328
1314 # make sure normal user does not pass someone else userid,
1329 # make sure normal user does not pass someone else userid,
1315 # he is not allowed to do that
1330 # he is not allowed to do that
1316 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1331 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1317 raise JSONRPCError('userid is not the same as your user')
1332 raise JSONRPCError('userid is not the same as your user')
1318
1333
1319 if isinstance(userid, Optional):
1334 if isinstance(userid, Optional):
1320 userid = apiuser.user_id
1335 userid = apiuser.user_id
1321
1336
1322 user = get_user_or_error(userid)
1337 user = get_user_or_error(userid)
1323
1338
1324 if isinstance(locked, Optional):
1339 if isinstance(locked, Optional):
1325 lockobj = repo.locked
1340 lockobj = repo.locked
1326
1341
1327 if lockobj[0] is None:
1342 if lockobj[0] is None:
1328 _d = {
1343 _d = {
1329 'repo': repo.repo_name,
1344 'repo': repo.repo_name,
1330 'locked': False,
1345 'locked': False,
1331 'locked_since': None,
1346 'locked_since': None,
1332 'locked_by': None,
1347 'locked_by': None,
1333 'lock_reason': None,
1348 'lock_reason': None,
1334 'lock_state_changed': False,
1349 'lock_state_changed': False,
1335 'msg': 'Repo `%s` not locked.' % repo.repo_name
1350 'msg': 'Repo `%s` not locked.' % repo.repo_name
1336 }
1351 }
1337 return _d
1352 return _d
1338 else:
1353 else:
1339 _user_id, _time, _reason = lockobj
1354 _user_id, _time, _reason = lockobj
1340 lock_user = get_user_or_error(userid)
1355 lock_user = get_user_or_error(userid)
1341 _d = {
1356 _d = {
1342 'repo': repo.repo_name,
1357 'repo': repo.repo_name,
1343 'locked': True,
1358 'locked': True,
1344 'locked_since': _time,
1359 'locked_since': _time,
1345 'locked_by': lock_user.username,
1360 'locked_by': lock_user.username,
1346 'lock_reason': _reason,
1361 'lock_reason': _reason,
1347 'lock_state_changed': False,
1362 'lock_state_changed': False,
1348 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1363 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1349 % (repo.repo_name, lock_user.username,
1364 % (repo.repo_name, lock_user.username,
1350 json.dumps(time_to_datetime(_time))))
1365 json.dumps(time_to_datetime(_time))))
1351 }
1366 }
1352 return _d
1367 return _d
1353
1368
1354 # force locked state through a flag
1369 # force locked state through a flag
1355 else:
1370 else:
1356 locked = str2bool(locked)
1371 locked = str2bool(locked)
1357 lock_reason = Repository.LOCK_API
1372 lock_reason = Repository.LOCK_API
1358 try:
1373 try:
1359 if locked:
1374 if locked:
1360 lock_time = time.time()
1375 lock_time = time.time()
1361 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1376 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1362 else:
1377 else:
1363 lock_time = None
1378 lock_time = None
1364 Repository.unlock(repo)
1379 Repository.unlock(repo)
1365 _d = {
1380 _d = {
1366 'repo': repo.repo_name,
1381 'repo': repo.repo_name,
1367 'locked': locked,
1382 'locked': locked,
1368 'locked_since': lock_time,
1383 'locked_since': lock_time,
1369 'locked_by': user.username,
1384 'locked_by': user.username,
1370 'lock_reason': lock_reason,
1385 'lock_reason': lock_reason,
1371 'lock_state_changed': True,
1386 'lock_state_changed': True,
1372 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1387 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1373 % (user.username, repo.repo_name, locked))
1388 % (user.username, repo.repo_name, locked))
1374 }
1389 }
1375 return _d
1390 return _d
1376 except Exception:
1391 except Exception:
1377 log.exception(
1392 log.exception(
1378 "Exception occurred while trying to lock repository")
1393 "Exception occurred while trying to lock repository")
1379 raise JSONRPCError(
1394 raise JSONRPCError(
1380 'Error occurred locking repository `%s`' % repo.repo_name
1395 'Error occurred locking repository `%s`' % repo.repo_name
1381 )
1396 )
1382
1397
1383
1398
1384 @jsonrpc_method()
1399 @jsonrpc_method()
1385 def comment_commit(
1400 def comment_commit(
1386 request, apiuser, repoid, commit_id, message, status=Optional(None),
1401 request, apiuser, repoid, commit_id, message, status=Optional(None),
1387 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1402 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1388 resolves_comment_id=Optional(None),
1403 resolves_comment_id=Optional(None),
1389 userid=Optional(OAttr('apiuser'))):
1404 userid=Optional(OAttr('apiuser'))):
1390 """
1405 """
1391 Set a commit comment, and optionally change the status of the commit.
1406 Set a commit comment, and optionally change the status of the commit.
1392
1407
1393 :param apiuser: This is filled automatically from the |authtoken|.
1408 :param apiuser: This is filled automatically from the |authtoken|.
1394 :type apiuser: AuthUser
1409 :type apiuser: AuthUser
1395 :param repoid: Set the repository name or repository ID.
1410 :param repoid: Set the repository name or repository ID.
1396 :type repoid: str or int
1411 :type repoid: str or int
1397 :param commit_id: Specify the commit_id for which to set a comment.
1412 :param commit_id: Specify the commit_id for which to set a comment.
1398 :type commit_id: str
1413 :type commit_id: str
1399 :param message: The comment text.
1414 :param message: The comment text.
1400 :type message: str
1415 :type message: str
1401 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1416 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1402 'approved', 'rejected', 'under_review'
1417 'approved', 'rejected', 'under_review'
1403 :type status: str
1418 :type status: str
1404 :param comment_type: Comment type, one of: 'note', 'todo'
1419 :param comment_type: Comment type, one of: 'note', 'todo'
1405 :type comment_type: Optional(str), default: 'note'
1420 :type comment_type: Optional(str), default: 'note'
1406 :param userid: Set the user name of the comment creator.
1421 :param userid: Set the user name of the comment creator.
1407 :type userid: Optional(str or int)
1422 :type userid: Optional(str or int)
1408
1423
1409 Example error output:
1424 Example error output:
1410
1425
1411 .. code-block:: bash
1426 .. code-block:: bash
1412
1427
1413 {
1428 {
1414 "id" : <id_given_in_input>,
1429 "id" : <id_given_in_input>,
1415 "result" : {
1430 "result" : {
1416 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1431 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1417 "status_change": null or <status>,
1432 "status_change": null or <status>,
1418 "success": true
1433 "success": true
1419 },
1434 },
1420 "error" : null
1435 "error" : null
1421 }
1436 }
1422
1437
1423 """
1438 """
1424 repo = get_repo_or_error(repoid)
1439 repo = get_repo_or_error(repoid)
1425 if not has_superadmin_permission(apiuser):
1440 if not has_superadmin_permission(apiuser):
1426 _perms = ('repository.read', 'repository.write', 'repository.admin')
1441 _perms = ('repository.read', 'repository.write', 'repository.admin')
1427 validate_repo_permissions(apiuser, repoid, repo, _perms)
1442 validate_repo_permissions(apiuser, repoid, repo, _perms)
1428
1443
1429 try:
1444 try:
1430 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1445 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1431 except Exception as e:
1446 except Exception as e:
1432 log.exception('Failed to fetch commit')
1447 log.exception('Failed to fetch commit')
1433 raise JSONRPCError(e.message)
1448 raise JSONRPCError(e.message)
1434
1449
1435 if isinstance(userid, Optional):
1450 if isinstance(userid, Optional):
1436 userid = apiuser.user_id
1451 userid = apiuser.user_id
1437
1452
1438 user = get_user_or_error(userid)
1453 user = get_user_or_error(userid)
1439 status = Optional.extract(status)
1454 status = Optional.extract(status)
1440 comment_type = Optional.extract(comment_type)
1455 comment_type = Optional.extract(comment_type)
1441 resolves_comment_id = Optional.extract(resolves_comment_id)
1456 resolves_comment_id = Optional.extract(resolves_comment_id)
1442
1457
1443 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1458 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1444 if status and status not in allowed_statuses:
1459 if status and status not in allowed_statuses:
1445 raise JSONRPCError('Bad status, must be on '
1460 raise JSONRPCError('Bad status, must be on '
1446 'of %s got %s' % (allowed_statuses, status,))
1461 'of %s got %s' % (allowed_statuses, status,))
1447
1462
1448 if resolves_comment_id:
1463 if resolves_comment_id:
1449 comment = ChangesetComment.get(resolves_comment_id)
1464 comment = ChangesetComment.get(resolves_comment_id)
1450 if not comment:
1465 if not comment:
1451 raise JSONRPCError(
1466 raise JSONRPCError(
1452 'Invalid resolves_comment_id `%s` for this commit.'
1467 'Invalid resolves_comment_id `%s` for this commit.'
1453 % resolves_comment_id)
1468 % resolves_comment_id)
1454 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1469 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1455 raise JSONRPCError(
1470 raise JSONRPCError(
1456 'Comment `%s` is wrong type for setting status to resolved.'
1471 'Comment `%s` is wrong type for setting status to resolved.'
1457 % resolves_comment_id)
1472 % resolves_comment_id)
1458
1473
1459 try:
1474 try:
1460 rc_config = SettingsModel().get_all_settings()
1475 rc_config = SettingsModel().get_all_settings()
1461 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1476 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1462 status_change_label = ChangesetStatus.get_status_lbl(status)
1477 status_change_label = ChangesetStatus.get_status_lbl(status)
1463 comm = CommentsModel().create(
1478 comment = CommentsModel().create(
1464 message, repo, user, commit_id=commit_id,
1479 message, repo, user, commit_id=commit_id,
1465 status_change=status_change_label,
1480 status_change=status_change_label,
1466 status_change_type=status,
1481 status_change_type=status,
1467 renderer=renderer,
1482 renderer=renderer,
1468 comment_type=comment_type,
1483 comment_type=comment_type,
1469 resolves_comment_id=resolves_comment_id
1484 resolves_comment_id=resolves_comment_id
1470 )
1485 )
1471 if status:
1486 if status:
1472 # also do a status change
1487 # also do a status change
1473 try:
1488 try:
1474 ChangesetStatusModel().set_status(
1489 ChangesetStatusModel().set_status(
1475 repo, status, user, comm, revision=commit_id,
1490 repo, status, user, comment, revision=commit_id,
1476 dont_allow_on_closed_pull_request=True
1491 dont_allow_on_closed_pull_request=True
1477 )
1492 )
1478 except StatusChangeOnClosedPullRequestError:
1493 except StatusChangeOnClosedPullRequestError:
1479 log.exception(
1494 log.exception(
1480 "Exception occurred while trying to change repo commit status")
1495 "Exception occurred while trying to change repo commit status")
1481 msg = ('Changing status on a changeset associated with '
1496 msg = ('Changing status on a changeset associated with '
1482 'a closed pull request is not allowed')
1497 'a closed pull request is not allowed')
1483 raise JSONRPCError(msg)
1498 raise JSONRPCError(msg)
1484
1499
1485 Session().commit()
1500 Session().commit()
1486 return {
1501 return {
1487 'msg': (
1502 'msg': (
1488 'Commented on commit `%s` for repository `%s`' % (
1503 'Commented on commit `%s` for repository `%s`' % (
1489 comm.revision, repo.repo_name)),
1504 comment.revision, repo.repo_name)),
1490 'status_change': status,
1505 'status_change': status,
1491 'success': True,
1506 'success': True,
1492 }
1507 }
1493 except JSONRPCError:
1508 except JSONRPCError:
1494 # catch any inside errors, and re-raise them to prevent from
1509 # catch any inside errors, and re-raise them to prevent from
1495 # below global catch to silence them
1510 # below global catch to silence them
1496 raise
1511 raise
1497 except Exception:
1512 except Exception:
1498 log.exception("Exception occurred while trying to comment on commit")
1513 log.exception("Exception occurred while trying to comment on commit")
1499 raise JSONRPCError(
1514 raise JSONRPCError(
1500 'failed to set comment on repository `%s`' % (repo.repo_name,)
1515 'failed to set comment on repository `%s`' % (repo.repo_name,)
1501 )
1516 )
1502
1517
1503
1518
1504 @jsonrpc_method()
1519 @jsonrpc_method()
1505 def grant_user_permission(request, apiuser, repoid, userid, perm):
1520 def grant_user_permission(request, apiuser, repoid, userid, perm):
1506 """
1521 """
1507 Grant permissions for the specified user on the given repository,
1522 Grant permissions for the specified user on the given repository,
1508 or update existing permissions if found.
1523 or update existing permissions if found.
1509
1524
1510 This command can only be run using an |authtoken| with admin
1525 This command can only be run using an |authtoken| with admin
1511 permissions on the |repo|.
1526 permissions on the |repo|.
1512
1527
1513 :param apiuser: This is filled automatically from the |authtoken|.
1528 :param apiuser: This is filled automatically from the |authtoken|.
1514 :type apiuser: AuthUser
1529 :type apiuser: AuthUser
1515 :param repoid: Set the repository name or repository ID.
1530 :param repoid: Set the repository name or repository ID.
1516 :type repoid: str or int
1531 :type repoid: str or int
1517 :param userid: Set the user name.
1532 :param userid: Set the user name.
1518 :type userid: str
1533 :type userid: str
1519 :param perm: Set the user permissions, using the following format
1534 :param perm: Set the user permissions, using the following format
1520 ``(repository.(none|read|write|admin))``
1535 ``(repository.(none|read|write|admin))``
1521 :type perm: str
1536 :type perm: str
1522
1537
1523 Example output:
1538 Example output:
1524
1539
1525 .. code-block:: bash
1540 .. code-block:: bash
1526
1541
1527 id : <id_given_in_input>
1542 id : <id_given_in_input>
1528 result: {
1543 result: {
1529 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1544 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1530 "success": true
1545 "success": true
1531 }
1546 }
1532 error: null
1547 error: null
1533 """
1548 """
1534
1549
1535 repo = get_repo_or_error(repoid)
1550 repo = get_repo_or_error(repoid)
1536 user = get_user_or_error(userid)
1551 user = get_user_or_error(userid)
1537 perm = get_perm_or_error(perm)
1552 perm = get_perm_or_error(perm)
1538 if not has_superadmin_permission(apiuser):
1553 if not has_superadmin_permission(apiuser):
1539 _perms = ('repository.admin',)
1554 _perms = ('repository.admin',)
1540 validate_repo_permissions(apiuser, repoid, repo, _perms)
1555 validate_repo_permissions(apiuser, repoid, repo, _perms)
1541
1556
1542 try:
1557 try:
1543
1558
1544 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1559 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1545
1560
1546 Session().commit()
1561 Session().commit()
1547 return {
1562 return {
1548 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1563 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1549 perm.permission_name, user.username, repo.repo_name
1564 perm.permission_name, user.username, repo.repo_name
1550 ),
1565 ),
1551 'success': True
1566 'success': True
1552 }
1567 }
1553 except Exception:
1568 except Exception:
1554 log.exception(
1569 log.exception(
1555 "Exception occurred while trying edit permissions for repo")
1570 "Exception occurred while trying edit permissions for repo")
1556 raise JSONRPCError(
1571 raise JSONRPCError(
1557 'failed to edit permission for user: `%s` in repo: `%s`' % (
1572 'failed to edit permission for user: `%s` in repo: `%s`' % (
1558 userid, repoid
1573 userid, repoid
1559 )
1574 )
1560 )
1575 )
1561
1576
1562
1577
1563 @jsonrpc_method()
1578 @jsonrpc_method()
1564 def revoke_user_permission(request, apiuser, repoid, userid):
1579 def revoke_user_permission(request, apiuser, repoid, userid):
1565 """
1580 """
1566 Revoke permission for a user on the specified repository.
1581 Revoke permission for a user on the specified repository.
1567
1582
1568 This command can only be run using an |authtoken| with admin
1583 This command can only be run using an |authtoken| with admin
1569 permissions on the |repo|.
1584 permissions on the |repo|.
1570
1585
1571 :param apiuser: This is filled automatically from the |authtoken|.
1586 :param apiuser: This is filled automatically from the |authtoken|.
1572 :type apiuser: AuthUser
1587 :type apiuser: AuthUser
1573 :param repoid: Set the repository name or repository ID.
1588 :param repoid: Set the repository name or repository ID.
1574 :type repoid: str or int
1589 :type repoid: str or int
1575 :param userid: Set the user name of revoked user.
1590 :param userid: Set the user name of revoked user.
1576 :type userid: str or int
1591 :type userid: str or int
1577
1592
1578 Example error output:
1593 Example error output:
1579
1594
1580 .. code-block:: bash
1595 .. code-block:: bash
1581
1596
1582 id : <id_given_in_input>
1597 id : <id_given_in_input>
1583 result: {
1598 result: {
1584 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1599 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1585 "success": true
1600 "success": true
1586 }
1601 }
1587 error: null
1602 error: null
1588 """
1603 """
1589
1604
1590 repo = get_repo_or_error(repoid)
1605 repo = get_repo_or_error(repoid)
1591 user = get_user_or_error(userid)
1606 user = get_user_or_error(userid)
1592 if not has_superadmin_permission(apiuser):
1607 if not has_superadmin_permission(apiuser):
1593 _perms = ('repository.admin',)
1608 _perms = ('repository.admin',)
1594 validate_repo_permissions(apiuser, repoid, repo, _perms)
1609 validate_repo_permissions(apiuser, repoid, repo, _perms)
1595
1610
1596 try:
1611 try:
1597 RepoModel().revoke_user_permission(repo=repo, user=user)
1612 RepoModel().revoke_user_permission(repo=repo, user=user)
1598 Session().commit()
1613 Session().commit()
1599 return {
1614 return {
1600 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1615 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1601 user.username, repo.repo_name
1616 user.username, repo.repo_name
1602 ),
1617 ),
1603 'success': True
1618 'success': True
1604 }
1619 }
1605 except Exception:
1620 except Exception:
1606 log.exception(
1621 log.exception(
1607 "Exception occurred while trying revoke permissions to repo")
1622 "Exception occurred while trying revoke permissions to repo")
1608 raise JSONRPCError(
1623 raise JSONRPCError(
1609 'failed to edit permission for user: `%s` in repo: `%s`' % (
1624 'failed to edit permission for user: `%s` in repo: `%s`' % (
1610 userid, repoid
1625 userid, repoid
1611 )
1626 )
1612 )
1627 )
1613
1628
1614
1629
1615 @jsonrpc_method()
1630 @jsonrpc_method()
1616 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1631 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1617 """
1632 """
1618 Grant permission for a user group on the specified repository,
1633 Grant permission for a user group on the specified repository,
1619 or update existing permissions.
1634 or update existing permissions.
1620
1635
1621 This command can only be run using an |authtoken| with admin
1636 This command can only be run using an |authtoken| with admin
1622 permissions on the |repo|.
1637 permissions on the |repo|.
1623
1638
1624 :param apiuser: This is filled automatically from the |authtoken|.
1639 :param apiuser: This is filled automatically from the |authtoken|.
1625 :type apiuser: AuthUser
1640 :type apiuser: AuthUser
1626 :param repoid: Set the repository name or repository ID.
1641 :param repoid: Set the repository name or repository ID.
1627 :type repoid: str or int
1642 :type repoid: str or int
1628 :param usergroupid: Specify the ID of the user group.
1643 :param usergroupid: Specify the ID of the user group.
1629 :type usergroupid: str or int
1644 :type usergroupid: str or int
1630 :param perm: Set the user group permissions using the following
1645 :param perm: Set the user group permissions using the following
1631 format: (repository.(none|read|write|admin))
1646 format: (repository.(none|read|write|admin))
1632 :type perm: str
1647 :type perm: str
1633
1648
1634 Example output:
1649 Example output:
1635
1650
1636 .. code-block:: bash
1651 .. code-block:: bash
1637
1652
1638 id : <id_given_in_input>
1653 id : <id_given_in_input>
1639 result : {
1654 result : {
1640 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1655 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1641 "success": true
1656 "success": true
1642
1657
1643 }
1658 }
1644 error : null
1659 error : null
1645
1660
1646 Example error output:
1661 Example error output:
1647
1662
1648 .. code-block:: bash
1663 .. code-block:: bash
1649
1664
1650 id : <id_given_in_input>
1665 id : <id_given_in_input>
1651 result : null
1666 result : null
1652 error : {
1667 error : {
1653 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1668 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1654 }
1669 }
1655
1670
1656 """
1671 """
1657
1672
1658 repo = get_repo_or_error(repoid)
1673 repo = get_repo_or_error(repoid)
1659 perm = get_perm_or_error(perm)
1674 perm = get_perm_or_error(perm)
1660 if not has_superadmin_permission(apiuser):
1675 if not has_superadmin_permission(apiuser):
1661 _perms = ('repository.admin',)
1676 _perms = ('repository.admin',)
1662 validate_repo_permissions(apiuser, repoid, repo, _perms)
1677 validate_repo_permissions(apiuser, repoid, repo, _perms)
1663
1678
1664 user_group = get_user_group_or_error(usergroupid)
1679 user_group = get_user_group_or_error(usergroupid)
1665 if not has_superadmin_permission(apiuser):
1680 if not has_superadmin_permission(apiuser):
1666 # check if we have at least read permission for this user group !
1681 # check if we have at least read permission for this user group !
1667 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1682 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1668 if not HasUserGroupPermissionAnyApi(*_perms)(
1683 if not HasUserGroupPermissionAnyApi(*_perms)(
1669 user=apiuser, user_group_name=user_group.users_group_name):
1684 user=apiuser, user_group_name=user_group.users_group_name):
1670 raise JSONRPCError(
1685 raise JSONRPCError(
1671 'user group `%s` does not exist' % (usergroupid,))
1686 'user group `%s` does not exist' % (usergroupid,))
1672
1687
1673 try:
1688 try:
1674 RepoModel().grant_user_group_permission(
1689 RepoModel().grant_user_group_permission(
1675 repo=repo, group_name=user_group, perm=perm)
1690 repo=repo, group_name=user_group, perm=perm)
1676
1691
1677 Session().commit()
1692 Session().commit()
1678 return {
1693 return {
1679 'msg': 'Granted perm: `%s` for user group: `%s` in '
1694 'msg': 'Granted perm: `%s` for user group: `%s` in '
1680 'repo: `%s`' % (
1695 'repo: `%s`' % (
1681 perm.permission_name, user_group.users_group_name,
1696 perm.permission_name, user_group.users_group_name,
1682 repo.repo_name
1697 repo.repo_name
1683 ),
1698 ),
1684 'success': True
1699 'success': True
1685 }
1700 }
1686 except Exception:
1701 except Exception:
1687 log.exception(
1702 log.exception(
1688 "Exception occurred while trying change permission on repo")
1703 "Exception occurred while trying change permission on repo")
1689 raise JSONRPCError(
1704 raise JSONRPCError(
1690 'failed to edit permission for user group: `%s` in '
1705 'failed to edit permission for user group: `%s` in '
1691 'repo: `%s`' % (
1706 'repo: `%s`' % (
1692 usergroupid, repo.repo_name
1707 usergroupid, repo.repo_name
1693 )
1708 )
1694 )
1709 )
1695
1710
1696
1711
1697 @jsonrpc_method()
1712 @jsonrpc_method()
1698 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1713 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1699 """
1714 """
1700 Revoke the permissions of a user group on a given repository.
1715 Revoke the permissions of a user group on a given repository.
1701
1716
1702 This command can only be run using an |authtoken| with admin
1717 This command can only be run using an |authtoken| with admin
1703 permissions on the |repo|.
1718 permissions on the |repo|.
1704
1719
1705 :param apiuser: This is filled automatically from the |authtoken|.
1720 :param apiuser: This is filled automatically from the |authtoken|.
1706 :type apiuser: AuthUser
1721 :type apiuser: AuthUser
1707 :param repoid: Set the repository name or repository ID.
1722 :param repoid: Set the repository name or repository ID.
1708 :type repoid: str or int
1723 :type repoid: str or int
1709 :param usergroupid: Specify the user group ID.
1724 :param usergroupid: Specify the user group ID.
1710 :type usergroupid: str or int
1725 :type usergroupid: str or int
1711
1726
1712 Example output:
1727 Example output:
1713
1728
1714 .. code-block:: bash
1729 .. code-block:: bash
1715
1730
1716 id : <id_given_in_input>
1731 id : <id_given_in_input>
1717 result: {
1732 result: {
1718 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1733 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1719 "success": true
1734 "success": true
1720 }
1735 }
1721 error: null
1736 error: null
1722 """
1737 """
1723
1738
1724 repo = get_repo_or_error(repoid)
1739 repo = get_repo_or_error(repoid)
1725 if not has_superadmin_permission(apiuser):
1740 if not has_superadmin_permission(apiuser):
1726 _perms = ('repository.admin',)
1741 _perms = ('repository.admin',)
1727 validate_repo_permissions(apiuser, repoid, repo, _perms)
1742 validate_repo_permissions(apiuser, repoid, repo, _perms)
1728
1743
1729 user_group = get_user_group_or_error(usergroupid)
1744 user_group = get_user_group_or_error(usergroupid)
1730 if not has_superadmin_permission(apiuser):
1745 if not has_superadmin_permission(apiuser):
1731 # check if we have at least read permission for this user group !
1746 # check if we have at least read permission for this user group !
1732 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1747 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1733 if not HasUserGroupPermissionAnyApi(*_perms)(
1748 if not HasUserGroupPermissionAnyApi(*_perms)(
1734 user=apiuser, user_group_name=user_group.users_group_name):
1749 user=apiuser, user_group_name=user_group.users_group_name):
1735 raise JSONRPCError(
1750 raise JSONRPCError(
1736 'user group `%s` does not exist' % (usergroupid,))
1751 'user group `%s` does not exist' % (usergroupid,))
1737
1752
1738 try:
1753 try:
1739 RepoModel().revoke_user_group_permission(
1754 RepoModel().revoke_user_group_permission(
1740 repo=repo, group_name=user_group)
1755 repo=repo, group_name=user_group)
1741
1756
1742 Session().commit()
1757 Session().commit()
1743 return {
1758 return {
1744 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1759 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1745 user_group.users_group_name, repo.repo_name
1760 user_group.users_group_name, repo.repo_name
1746 ),
1761 ),
1747 'success': True
1762 'success': True
1748 }
1763 }
1749 except Exception:
1764 except Exception:
1750 log.exception("Exception occurred while trying revoke "
1765 log.exception("Exception occurred while trying revoke "
1751 "user group permission on repo")
1766 "user group permission on repo")
1752 raise JSONRPCError(
1767 raise JSONRPCError(
1753 'failed to edit permission for user group: `%s` in '
1768 'failed to edit permission for user group: `%s` in '
1754 'repo: `%s`' % (
1769 'repo: `%s`' % (
1755 user_group.users_group_name, repo.repo_name
1770 user_group.users_group_name, repo.repo_name
1756 )
1771 )
1757 )
1772 )
1758
1773
1759
1774
1760 @jsonrpc_method()
1775 @jsonrpc_method()
1761 def pull(request, apiuser, repoid):
1776 def pull(request, apiuser, repoid):
1762 """
1777 """
1763 Triggers a pull on the given repository from a remote location. You
1778 Triggers a pull on the given repository from a remote location. You
1764 can use this to keep remote repositories up-to-date.
1779 can use this to keep remote repositories up-to-date.
1765
1780
1766 This command can only be run using an |authtoken| with admin
1781 This command can only be run using an |authtoken| with admin
1767 rights to the specified repository. For more information,
1782 rights to the specified repository. For more information,
1768 see :ref:`config-token-ref`.
1783 see :ref:`config-token-ref`.
1769
1784
1770 This command takes the following options:
1785 This command takes the following options:
1771
1786
1772 :param apiuser: This is filled automatically from the |authtoken|.
1787 :param apiuser: This is filled automatically from the |authtoken|.
1773 :type apiuser: AuthUser
1788 :type apiuser: AuthUser
1774 :param repoid: The repository name or repository ID.
1789 :param repoid: The repository name or repository ID.
1775 :type repoid: str or int
1790 :type repoid: str or int
1776
1791
1777 Example output:
1792 Example output:
1778
1793
1779 .. code-block:: bash
1794 .. code-block:: bash
1780
1795
1781 id : <id_given_in_input>
1796 id : <id_given_in_input>
1782 result : {
1797 result : {
1783 "msg": "Pulled from `<repository name>`"
1798 "msg": "Pulled from `<repository name>`"
1784 "repository": "<repository name>"
1799 "repository": "<repository name>"
1785 }
1800 }
1786 error : null
1801 error : null
1787
1802
1788 Example error output:
1803 Example error output:
1789
1804
1790 .. code-block:: bash
1805 .. code-block:: bash
1791
1806
1792 id : <id_given_in_input>
1807 id : <id_given_in_input>
1793 result : null
1808 result : null
1794 error : {
1809 error : {
1795 "Unable to pull changes from `<reponame>`"
1810 "Unable to pull changes from `<reponame>`"
1796 }
1811 }
1797
1812
1798 """
1813 """
1799
1814
1800 repo = get_repo_or_error(repoid)
1815 repo = get_repo_or_error(repoid)
1801 if not has_superadmin_permission(apiuser):
1816 if not has_superadmin_permission(apiuser):
1802 _perms = ('repository.admin',)
1817 _perms = ('repository.admin',)
1803 validate_repo_permissions(apiuser, repoid, repo, _perms)
1818 validate_repo_permissions(apiuser, repoid, repo, _perms)
1804
1819
1805 try:
1820 try:
1806 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1821 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1807 return {
1822 return {
1808 'msg': 'Pulled from `%s`' % repo.repo_name,
1823 'msg': 'Pulled from `%s`' % repo.repo_name,
1809 'repository': repo.repo_name
1824 'repository': repo.repo_name
1810 }
1825 }
1811 except Exception:
1826 except Exception:
1812 log.exception("Exception occurred while trying to "
1827 log.exception("Exception occurred while trying to "
1813 "pull changes from remote location")
1828 "pull changes from remote location")
1814 raise JSONRPCError(
1829 raise JSONRPCError(
1815 'Unable to pull changes from `%s`' % repo.repo_name
1830 'Unable to pull changes from `%s`' % repo.repo_name
1816 )
1831 )
1817
1832
1818
1833
1819 @jsonrpc_method()
1834 @jsonrpc_method()
1820 def strip(request, apiuser, repoid, revision, branch):
1835 def strip(request, apiuser, repoid, revision, branch):
1821 """
1836 """
1822 Strips the given revision from the specified repository.
1837 Strips the given revision from the specified repository.
1823
1838
1824 * This will remove the revision and all of its decendants.
1839 * This will remove the revision and all of its decendants.
1825
1840
1826 This command can only be run using an |authtoken| with admin rights to
1841 This command can only be run using an |authtoken| with admin rights to
1827 the specified repository.
1842 the specified repository.
1828
1843
1829 This command takes the following options:
1844 This command takes the following options:
1830
1845
1831 :param apiuser: This is filled automatically from the |authtoken|.
1846 :param apiuser: This is filled automatically from the |authtoken|.
1832 :type apiuser: AuthUser
1847 :type apiuser: AuthUser
1833 :param repoid: The repository name or repository ID.
1848 :param repoid: The repository name or repository ID.
1834 :type repoid: str or int
1849 :type repoid: str or int
1835 :param revision: The revision you wish to strip.
1850 :param revision: The revision you wish to strip.
1836 :type revision: str
1851 :type revision: str
1837 :param branch: The branch from which to strip the revision.
1852 :param branch: The branch from which to strip the revision.
1838 :type branch: str
1853 :type branch: str
1839
1854
1840 Example output:
1855 Example output:
1841
1856
1842 .. code-block:: bash
1857 .. code-block:: bash
1843
1858
1844 id : <id_given_in_input>
1859 id : <id_given_in_input>
1845 result : {
1860 result : {
1846 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1861 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1847 "repository": "<repository name>"
1862 "repository": "<repository name>"
1848 }
1863 }
1849 error : null
1864 error : null
1850
1865
1851 Example error output:
1866 Example error output:
1852
1867
1853 .. code-block:: bash
1868 .. code-block:: bash
1854
1869
1855 id : <id_given_in_input>
1870 id : <id_given_in_input>
1856 result : null
1871 result : null
1857 error : {
1872 error : {
1858 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1873 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1859 }
1874 }
1860
1875
1861 """
1876 """
1862
1877
1863 repo = get_repo_or_error(repoid)
1878 repo = get_repo_or_error(repoid)
1864 if not has_superadmin_permission(apiuser):
1879 if not has_superadmin_permission(apiuser):
1865 _perms = ('repository.admin',)
1880 _perms = ('repository.admin',)
1866 validate_repo_permissions(apiuser, repoid, repo, _perms)
1881 validate_repo_permissions(apiuser, repoid, repo, _perms)
1867
1882
1868 try:
1883 try:
1869 ScmModel().strip(repo, revision, branch)
1884 ScmModel().strip(repo, revision, branch)
1885 audit_logger.store_api(
1886 'repo.commit.strip', action_data={'commit_id': revision},
1887 repo=repo,
1888 user=apiuser, commit=True)
1889
1870 return {
1890 return {
1871 'msg': 'Stripped commit %s from repo `%s`' % (
1891 'msg': 'Stripped commit %s from repo `%s`' % (
1872 revision, repo.repo_name),
1892 revision, repo.repo_name),
1873 'repository': repo.repo_name
1893 'repository': repo.repo_name
1874 }
1894 }
1875 except Exception:
1895 except Exception:
1876 log.exception("Exception while trying to strip")
1896 log.exception("Exception while trying to strip")
1877 raise JSONRPCError(
1897 raise JSONRPCError(
1878 'Unable to strip commit %s from repo `%s`' % (
1898 'Unable to strip commit %s from repo `%s`' % (
1879 revision, repo.repo_name)
1899 revision, repo.repo_name)
1880 )
1900 )
1881
1901
1882
1902
1883 @jsonrpc_method()
1903 @jsonrpc_method()
1884 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1904 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1885 """
1905 """
1886 Returns all settings for a repository. If key is given it only returns the
1906 Returns all settings for a repository. If key is given it only returns the
1887 setting identified by the key or null.
1907 setting identified by the key or null.
1888
1908
1889 :param apiuser: This is filled automatically from the |authtoken|.
1909 :param apiuser: This is filled automatically from the |authtoken|.
1890 :type apiuser: AuthUser
1910 :type apiuser: AuthUser
1891 :param repoid: The repository name or repository id.
1911 :param repoid: The repository name or repository id.
1892 :type repoid: str or int
1912 :type repoid: str or int
1893 :param key: Key of the setting to return.
1913 :param key: Key of the setting to return.
1894 :type: key: Optional(str)
1914 :type: key: Optional(str)
1895
1915
1896 Example output:
1916 Example output:
1897
1917
1898 .. code-block:: bash
1918 .. code-block:: bash
1899
1919
1900 {
1920 {
1901 "error": null,
1921 "error": null,
1902 "id": 237,
1922 "id": 237,
1903 "result": {
1923 "result": {
1904 "extensions_largefiles": true,
1924 "extensions_largefiles": true,
1925 "extensions_evolve": true,
1905 "hooks_changegroup_push_logger": true,
1926 "hooks_changegroup_push_logger": true,
1906 "hooks_changegroup_repo_size": false,
1927 "hooks_changegroup_repo_size": false,
1907 "hooks_outgoing_pull_logger": true,
1928 "hooks_outgoing_pull_logger": true,
1908 "phases_publish": "True",
1929 "phases_publish": "True",
1909 "rhodecode_hg_use_rebase_for_merging": true,
1930 "rhodecode_hg_use_rebase_for_merging": true,
1910 "rhodecode_pr_merge_enabled": true,
1931 "rhodecode_pr_merge_enabled": true,
1911 "rhodecode_use_outdated_comments": true
1932 "rhodecode_use_outdated_comments": true
1912 }
1933 }
1913 }
1934 }
1914 """
1935 """
1915
1936
1916 # Restrict access to this api method to admins only.
1937 # Restrict access to this api method to admins only.
1917 if not has_superadmin_permission(apiuser):
1938 if not has_superadmin_permission(apiuser):
1918 raise JSONRPCForbidden()
1939 raise JSONRPCForbidden()
1919
1940
1920 try:
1941 try:
1921 repo = get_repo_or_error(repoid)
1942 repo = get_repo_or_error(repoid)
1922 settings_model = VcsSettingsModel(repo=repo)
1943 settings_model = VcsSettingsModel(repo=repo)
1923 settings = settings_model.get_global_settings()
1944 settings = settings_model.get_global_settings()
1924 settings.update(settings_model.get_repo_settings())
1945 settings.update(settings_model.get_repo_settings())
1925
1946
1926 # If only a single setting is requested fetch it from all settings.
1947 # If only a single setting is requested fetch it from all settings.
1927 key = Optional.extract(key)
1948 key = Optional.extract(key)
1928 if key is not None:
1949 if key is not None:
1929 settings = settings.get(key, None)
1950 settings = settings.get(key, None)
1930 except Exception:
1951 except Exception:
1931 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1952 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1932 log.exception(msg)
1953 log.exception(msg)
1933 raise JSONRPCError(msg)
1954 raise JSONRPCError(msg)
1934
1955
1935 return settings
1956 return settings
1936
1957
1937
1958
1938 @jsonrpc_method()
1959 @jsonrpc_method()
1939 def set_repo_settings(request, apiuser, repoid, settings):
1960 def set_repo_settings(request, apiuser, repoid, settings):
1940 """
1961 """
1941 Update repository settings. Returns true on success.
1962 Update repository settings. Returns true on success.
1942
1963
1943 :param apiuser: This is filled automatically from the |authtoken|.
1964 :param apiuser: This is filled automatically from the |authtoken|.
1944 :type apiuser: AuthUser
1965 :type apiuser: AuthUser
1945 :param repoid: The repository name or repository id.
1966 :param repoid: The repository name or repository id.
1946 :type repoid: str or int
1967 :type repoid: str or int
1947 :param settings: The new settings for the repository.
1968 :param settings: The new settings for the repository.
1948 :type: settings: dict
1969 :type: settings: dict
1949
1970
1950 Example output:
1971 Example output:
1951
1972
1952 .. code-block:: bash
1973 .. code-block:: bash
1953
1974
1954 {
1975 {
1955 "error": null,
1976 "error": null,
1956 "id": 237,
1977 "id": 237,
1957 "result": true
1978 "result": true
1958 }
1979 }
1959 """
1980 """
1960 # Restrict access to this api method to admins only.
1981 # Restrict access to this api method to admins only.
1961 if not has_superadmin_permission(apiuser):
1982 if not has_superadmin_permission(apiuser):
1962 raise JSONRPCForbidden()
1983 raise JSONRPCForbidden()
1963
1984
1964 if type(settings) is not dict:
1985 if type(settings) is not dict:
1965 raise JSONRPCError('Settings have to be a JSON Object.')
1986 raise JSONRPCError('Settings have to be a JSON Object.')
1966
1987
1967 try:
1988 try:
1968 settings_model = VcsSettingsModel(repo=repoid)
1989 settings_model = VcsSettingsModel(repo=repoid)
1969
1990
1970 # Merge global, repo and incoming settings.
1991 # Merge global, repo and incoming settings.
1971 new_settings = settings_model.get_global_settings()
1992 new_settings = settings_model.get_global_settings()
1972 new_settings.update(settings_model.get_repo_settings())
1993 new_settings.update(settings_model.get_repo_settings())
1973 new_settings.update(settings)
1994 new_settings.update(settings)
1974
1995
1975 # Update the settings.
1996 # Update the settings.
1976 inherit_global_settings = new_settings.get(
1997 inherit_global_settings = new_settings.get(
1977 'inherit_global_settings', False)
1998 'inherit_global_settings', False)
1978 settings_model.create_or_update_repo_settings(
1999 settings_model.create_or_update_repo_settings(
1979 new_settings, inherit_global_settings=inherit_global_settings)
2000 new_settings, inherit_global_settings=inherit_global_settings)
1980 Session().commit()
2001 Session().commit()
1981 except Exception:
2002 except Exception:
1982 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2003 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1983 log.exception(msg)
2004 log.exception(msg)
1984 raise JSONRPCError(msg)
2005 raise JSONRPCError(msg)
1985
2006
1986 # Indicate success.
2007 # Indicate success.
1987 return True
2008 return True
2009
2010
2011 @jsonrpc_method()
2012 def maintenance(request, apiuser, repoid):
2013 """
2014 Triggers a maintenance on the given repository.
2015
2016 This command can only be run using an |authtoken| with admin
2017 rights to the specified repository. For more information,
2018 see :ref:`config-token-ref`.
2019
2020 This command takes the following options:
2021
2022 :param apiuser: This is filled automatically from the |authtoken|.
2023 :type apiuser: AuthUser
2024 :param repoid: The repository name or repository ID.
2025 :type repoid: str or int
2026
2027 Example output:
2028
2029 .. code-block:: bash
2030
2031 id : <id_given_in_input>
2032 result : {
2033 "msg": "executed maintenance command",
2034 "executed_actions": [
2035 <action_message>, <action_message2>...
2036 ],
2037 "repository": "<repository name>"
2038 }
2039 error : null
2040
2041 Example error output:
2042
2043 .. code-block:: bash
2044
2045 id : <id_given_in_input>
2046 result : null
2047 error : {
2048 "Unable to execute maintenance on `<reponame>`"
2049 }
2050
2051 """
2052
2053 repo = get_repo_or_error(repoid)
2054 if not has_superadmin_permission(apiuser):
2055 _perms = ('repository.admin',)
2056 validate_repo_permissions(apiuser, repoid, repo, _perms)
2057
2058 try:
2059 maintenance = repo_maintenance.RepoMaintenance()
2060 executed_actions = maintenance.execute(repo)
2061
2062 return {
2063 'msg': 'executed maintenance command',
2064 'executed_actions': executed_actions,
2065 'repository': repo.repo_name
2066 }
2067 except Exception:
2068 log.exception("Exception occurred while trying to run maintenance")
2069 raise JSONRPCError(
2070 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,702 +1,719 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import JSONRPCValidationError
24 from rhodecode.api import JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
31 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 from rhodecode.model.db import Session
33 from rhodecode.model.db import Session
33 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model import validation_schema
36 from rhodecode.model import validation_schema
36 from rhodecode.model.validation_schema.schemas import repo_group_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
37
38
38
39
39 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
40
41
41
42
42 @jsonrpc_method()
43 @jsonrpc_method()
43 def get_repo_group(request, apiuser, repogroupid):
44 def get_repo_group(request, apiuser, repogroupid):
44 """
45 """
45 Return the specified |repo| group, along with permissions,
46 Return the specified |repo| group, along with permissions,
46 and repositories inside the group
47 and repositories inside the group
47
48
48 :param apiuser: This is filled automatically from the |authtoken|.
49 :param apiuser: This is filled automatically from the |authtoken|.
49 :type apiuser: AuthUser
50 :type apiuser: AuthUser
50 :param repogroupid: Specify the name of ID of the repository group.
51 :param repogroupid: Specify the name of ID of the repository group.
51 :type repogroupid: str or int
52 :type repogroupid: str or int
52
53
53
54
54 Example output:
55 Example output:
55
56
56 .. code-block:: bash
57 .. code-block:: bash
57
58
58 {
59 {
59 "error": null,
60 "error": null,
60 "id": repo-group-id,
61 "id": repo-group-id,
61 "result": {
62 "result": {
62 "group_description": "repo group description",
63 "group_description": "repo group description",
63 "group_id": 14,
64 "group_id": 14,
64 "group_name": "group name",
65 "group_name": "group name",
65 "members": [
66 "members": [
66 {
67 {
67 "name": "super-admin-username",
68 "name": "super-admin-username",
68 "origin": "super-admin",
69 "origin": "super-admin",
69 "permission": "group.admin",
70 "permission": "group.admin",
70 "type": "user"
71 "type": "user"
71 },
72 },
72 {
73 {
73 "name": "owner-name",
74 "name": "owner-name",
74 "origin": "owner",
75 "origin": "owner",
75 "permission": "group.admin",
76 "permission": "group.admin",
76 "type": "user"
77 "type": "user"
77 },
78 },
78 {
79 {
79 "name": "user-group-name",
80 "name": "user-group-name",
80 "origin": "permission",
81 "origin": "permission",
81 "permission": "group.write",
82 "permission": "group.write",
82 "type": "user_group"
83 "type": "user_group"
83 }
84 }
84 ],
85 ],
85 "owner": "owner-name",
86 "owner": "owner-name",
86 "parent_group": null,
87 "parent_group": null,
87 "repositories": [ repo-list ]
88 "repositories": [ repo-list ]
88 }
89 }
89 }
90 }
90 """
91 """
91
92
92 repo_group = get_repo_group_or_error(repogroupid)
93 repo_group = get_repo_group_or_error(repogroupid)
93 if not has_superadmin_permission(apiuser):
94 if not has_superadmin_permission(apiuser):
94 # check if we have at least read permission for this repo group !
95 # check if we have at least read permission for this repo group !
95 _perms = ('group.admin', 'group.write', 'group.read',)
96 _perms = ('group.admin', 'group.write', 'group.read',)
96 if not HasRepoGroupPermissionAnyApi(*_perms)(
97 if not HasRepoGroupPermissionAnyApi(*_perms)(
97 user=apiuser, group_name=repo_group.group_name):
98 user=apiuser, group_name=repo_group.group_name):
98 raise JSONRPCError(
99 raise JSONRPCError(
99 'repository group `%s` does not exist' % (repogroupid,))
100 'repository group `%s` does not exist' % (repogroupid,))
100
101
101 permissions = []
102 permissions = []
102 for _user in repo_group.permissions():
103 for _user in repo_group.permissions():
103 user_data = {
104 user_data = {
104 'name': _user.username,
105 'name': _user.username,
105 'permission': _user.permission,
106 'permission': _user.permission,
106 'origin': get_origin(_user),
107 'origin': get_origin(_user),
107 'type': "user",
108 'type': "user",
108 }
109 }
109 permissions.append(user_data)
110 permissions.append(user_data)
110
111
111 for _user_group in repo_group.permission_user_groups():
112 for _user_group in repo_group.permission_user_groups():
112 user_group_data = {
113 user_group_data = {
113 'name': _user_group.users_group_name,
114 'name': _user_group.users_group_name,
114 'permission': _user_group.permission,
115 'permission': _user_group.permission,
115 'origin': get_origin(_user_group),
116 'origin': get_origin(_user_group),
116 'type': "user_group",
117 'type': "user_group",
117 }
118 }
118 permissions.append(user_group_data)
119 permissions.append(user_group_data)
119
120
120 data = repo_group.get_api_data()
121 data = repo_group.get_api_data()
121 data["members"] = permissions # TODO: this should be named permissions
122 data["members"] = permissions # TODO: this should be named permissions
122 return data
123 return data
123
124
124
125
125 @jsonrpc_method()
126 @jsonrpc_method()
126 def get_repo_groups(request, apiuser):
127 def get_repo_groups(request, apiuser):
127 """
128 """
128 Returns all repository groups.
129 Returns all repository groups.
129
130
130 :param apiuser: This is filled automatically from the |authtoken|.
131 :param apiuser: This is filled automatically from the |authtoken|.
131 :type apiuser: AuthUser
132 :type apiuser: AuthUser
132 """
133 """
133
134
134 result = []
135 result = []
135 _perms = ('group.read', 'group.write', 'group.admin',)
136 _perms = ('group.read', 'group.write', 'group.admin',)
136 extras = {'user': apiuser}
137 extras = {'user': apiuser}
137 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
138 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
138 perm_set=_perms, extra_kwargs=extras):
139 perm_set=_perms, extra_kwargs=extras):
139 result.append(repo_group.get_api_data())
140 result.append(repo_group.get_api_data())
140 return result
141 return result
141
142
142
143
143 @jsonrpc_method()
144 @jsonrpc_method()
144 def create_repo_group(
145 def create_repo_group(
145 request, apiuser, group_name,
146 request, apiuser, group_name,
146 owner=Optional(OAttr('apiuser')),
147 owner=Optional(OAttr('apiuser')),
147 description=Optional(''),
148 description=Optional(''),
148 copy_permissions=Optional(False)):
149 copy_permissions=Optional(False)):
149 """
150 """
150 Creates a repository group.
151 Creates a repository group.
151
152
152 * If the repository group name contains "/", repository group will be
153 * If the repository group name contains "/", repository group will be
153 created inside a repository group or nested repository groups
154 created inside a repository group or nested repository groups
154
155
155 For example "foo/bar/group1" will create repository group called "group1"
156 For example "foo/bar/group1" will create repository group called "group1"
156 inside group "foo/bar". You have to have permissions to access and
157 inside group "foo/bar". You have to have permissions to access and
157 write to the last repository group ("bar" in this example)
158 write to the last repository group ("bar" in this example)
158
159
159 This command can only be run using an |authtoken| with at least
160 This command can only be run using an |authtoken| with at least
160 permissions to create repository groups, or admin permissions to
161 permissions to create repository groups, or admin permissions to
161 parent repository groups.
162 parent repository groups.
162
163
163 :param apiuser: This is filled automatically from the |authtoken|.
164 :param apiuser: This is filled automatically from the |authtoken|.
164 :type apiuser: AuthUser
165 :type apiuser: AuthUser
165 :param group_name: Set the repository group name.
166 :param group_name: Set the repository group name.
166 :type group_name: str
167 :type group_name: str
167 :param description: Set the |repo| group description.
168 :param description: Set the |repo| group description.
168 :type description: str
169 :type description: str
169 :param owner: Set the |repo| group owner.
170 :param owner: Set the |repo| group owner.
170 :type owner: str
171 :type owner: str
171 :param copy_permissions:
172 :param copy_permissions:
172 :type copy_permissions:
173 :type copy_permissions:
173
174
174 Example output:
175 Example output:
175
176
176 .. code-block:: bash
177 .. code-block:: bash
177
178
178 id : <id_given_in_input>
179 id : <id_given_in_input>
179 result : {
180 result : {
180 "msg": "Created new repo group `<repo_group_name>`"
181 "msg": "Created new repo group `<repo_group_name>`"
181 "repo_group": <repogroup_object>
182 "repo_group": <repogroup_object>
182 }
183 }
183 error : null
184 error : null
184
185
185
186
186 Example error output:
187 Example error output:
187
188
188 .. code-block:: bash
189 .. code-block:: bash
189
190
190 id : <id_given_in_input>
191 id : <id_given_in_input>
191 result : null
192 result : null
192 error : {
193 error : {
193 failed to create repo group `<repogroupid>`
194 failed to create repo group `<repogroupid>`
194 }
195 }
195
196
196 """
197 """
197
198
198 owner = validate_set_owner_permissions(apiuser, owner)
199 owner = validate_set_owner_permissions(apiuser, owner)
199
200
200 description = Optional.extract(description)
201 description = Optional.extract(description)
201 copy_permissions = Optional.extract(copy_permissions)
202 copy_permissions = Optional.extract(copy_permissions)
202
203
203 schema = repo_group_schema.RepoGroupSchema().bind(
204 schema = repo_group_schema.RepoGroupSchema().bind(
204 # user caller
205 # user caller
205 user=apiuser)
206 user=apiuser)
206
207
207 try:
208 try:
208 schema_data = schema.deserialize(dict(
209 schema_data = schema.deserialize(dict(
209 repo_group_name=group_name,
210 repo_group_name=group_name,
210 repo_group_owner=owner.username,
211 repo_group_owner=owner.username,
211 repo_group_description=description,
212 repo_group_description=description,
212 repo_group_copy_permissions=copy_permissions,
213 repo_group_copy_permissions=copy_permissions,
213 ))
214 ))
214 except validation_schema.Invalid as err:
215 except validation_schema.Invalid as err:
215 raise JSONRPCValidationError(colander_exc=err)
216 raise JSONRPCValidationError(colander_exc=err)
216
217
217 validated_group_name = schema_data['repo_group_name']
218 validated_group_name = schema_data['repo_group_name']
218
219
219 try:
220 try:
220 repo_group = RepoGroupModel().create(
221 repo_group = RepoGroupModel().create(
221 owner=owner,
222 owner=owner,
222 group_name=validated_group_name,
223 group_name=validated_group_name,
223 group_description=schema_data['repo_group_name'],
224 group_description=schema_data['repo_group_name'],
224 copy_permissions=schema_data['repo_group_copy_permissions'])
225 copy_permissions=schema_data['repo_group_copy_permissions'])
226 Session().flush()
227
228 repo_group_data = repo_group.get_api_data()
229 audit_logger.store_api(
230 'repo_group.create', action_data={'data': repo_group_data},
231 user=apiuser)
232
225 Session().commit()
233 Session().commit()
226 return {
234 return {
227 'msg': 'Created new repo group `%s`' % validated_group_name,
235 'msg': 'Created new repo group `%s`' % validated_group_name,
228 'repo_group': repo_group.get_api_data()
236 'repo_group': repo_group.get_api_data()
229 }
237 }
230 except Exception:
238 except Exception:
231 log.exception("Exception occurred while trying create repo group")
239 log.exception("Exception occurred while trying create repo group")
232 raise JSONRPCError(
240 raise JSONRPCError(
233 'failed to create repo group `%s`' % (validated_group_name,))
241 'failed to create repo group `%s`' % (validated_group_name,))
234
242
235
243
236 @jsonrpc_method()
244 @jsonrpc_method()
237 def update_repo_group(
245 def update_repo_group(
238 request, apiuser, repogroupid, group_name=Optional(''),
246 request, apiuser, repogroupid, group_name=Optional(''),
239 description=Optional(''), owner=Optional(OAttr('apiuser')),
247 description=Optional(''), owner=Optional(OAttr('apiuser')),
240 enable_locking=Optional(False)):
248 enable_locking=Optional(False)):
241 """
249 """
242 Updates repository group with the details given.
250 Updates repository group with the details given.
243
251
244 This command can only be run using an |authtoken| with admin
252 This command can only be run using an |authtoken| with admin
245 permissions.
253 permissions.
246
254
247 * If the group_name name contains "/", repository group will be updated
255 * If the group_name name contains "/", repository group will be updated
248 accordingly with a repository group or nested repository groups
256 accordingly with a repository group or nested repository groups
249
257
250 For example repogroupid=group-test group_name="foo/bar/group-test"
258 For example repogroupid=group-test group_name="foo/bar/group-test"
251 will update repository group called "group-test" and place it
259 will update repository group called "group-test" and place it
252 inside group "foo/bar".
260 inside group "foo/bar".
253 You have to have permissions to access and write to the last repository
261 You have to have permissions to access and write to the last repository
254 group ("bar" in this example)
262 group ("bar" in this example)
255
263
256 :param apiuser: This is filled automatically from the |authtoken|.
264 :param apiuser: This is filled automatically from the |authtoken|.
257 :type apiuser: AuthUser
265 :type apiuser: AuthUser
258 :param repogroupid: Set the ID of repository group.
266 :param repogroupid: Set the ID of repository group.
259 :type repogroupid: str or int
267 :type repogroupid: str or int
260 :param group_name: Set the name of the |repo| group.
268 :param group_name: Set the name of the |repo| group.
261 :type group_name: str
269 :type group_name: str
262 :param description: Set a description for the group.
270 :param description: Set a description for the group.
263 :type description: str
271 :type description: str
264 :param owner: Set the |repo| group owner.
272 :param owner: Set the |repo| group owner.
265 :type owner: str
273 :type owner: str
266 :param enable_locking: Enable |repo| locking. The default is false.
274 :param enable_locking: Enable |repo| locking. The default is false.
267 :type enable_locking: bool
275 :type enable_locking: bool
268 """
276 """
269
277
270 repo_group = get_repo_group_or_error(repogroupid)
278 repo_group = get_repo_group_or_error(repogroupid)
271
279
272 if not has_superadmin_permission(apiuser):
280 if not has_superadmin_permission(apiuser):
273 validate_repo_group_permissions(
281 validate_repo_group_permissions(
274 apiuser, repogroupid, repo_group, ('group.admin',))
282 apiuser, repogroupid, repo_group, ('group.admin',))
275
283
276 updates = dict(
284 updates = dict(
277 group_name=group_name
285 group_name=group_name
278 if not isinstance(group_name, Optional) else repo_group.group_name,
286 if not isinstance(group_name, Optional) else repo_group.group_name,
279
287
280 group_description=description
288 group_description=description
281 if not isinstance(description, Optional) else repo_group.group_description,
289 if not isinstance(description, Optional) else repo_group.group_description,
282
290
283 user=owner
291 user=owner
284 if not isinstance(owner, Optional) else repo_group.user.username,
292 if not isinstance(owner, Optional) else repo_group.user.username,
285
293
286 enable_locking=enable_locking
294 enable_locking=enable_locking
287 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
295 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
288 )
296 )
289
297
290 schema = repo_group_schema.RepoGroupSchema().bind(
298 schema = repo_group_schema.RepoGroupSchema().bind(
291 # user caller
299 # user caller
292 user=apiuser,
300 user=apiuser,
293 old_values=repo_group.get_api_data())
301 old_values=repo_group.get_api_data())
294
302
295 try:
303 try:
296 schema_data = schema.deserialize(dict(
304 schema_data = schema.deserialize(dict(
297 repo_group_name=updates['group_name'],
305 repo_group_name=updates['group_name'],
298 repo_group_owner=updates['user'],
306 repo_group_owner=updates['user'],
299 repo_group_description=updates['group_description'],
307 repo_group_description=updates['group_description'],
300 repo_group_enable_locking=updates['enable_locking'],
308 repo_group_enable_locking=updates['enable_locking'],
301 ))
309 ))
302 except validation_schema.Invalid as err:
310 except validation_schema.Invalid as err:
303 raise JSONRPCValidationError(colander_exc=err)
311 raise JSONRPCValidationError(colander_exc=err)
304
312
305 validated_updates = dict(
313 validated_updates = dict(
306 group_name=schema_data['repo_group']['repo_group_name_without_group'],
314 group_name=schema_data['repo_group']['repo_group_name_without_group'],
307 group_parent_id=schema_data['repo_group']['repo_group_id'],
315 group_parent_id=schema_data['repo_group']['repo_group_id'],
308 user=schema_data['repo_group_owner'],
316 user=schema_data['repo_group_owner'],
309 group_description=schema_data['repo_group_description'],
317 group_description=schema_data['repo_group_description'],
310 enable_locking=schema_data['repo_group_enable_locking'],
318 enable_locking=schema_data['repo_group_enable_locking'],
311 )
319 )
312
320
321 old_data = repo_group.get_api_data()
313 try:
322 try:
314 RepoGroupModel().update(repo_group, validated_updates)
323 RepoGroupModel().update(repo_group, validated_updates)
324 audit_logger.store_api(
325 'repo_group.edit', action_data={'old_data': old_data},
326 user=apiuser)
327
315 Session().commit()
328 Session().commit()
316 return {
329 return {
317 'msg': 'updated repository group ID:%s %s' % (
330 'msg': 'updated repository group ID:%s %s' % (
318 repo_group.group_id, repo_group.group_name),
331 repo_group.group_id, repo_group.group_name),
319 'repo_group': repo_group.get_api_data()
332 'repo_group': repo_group.get_api_data()
320 }
333 }
321 except Exception:
334 except Exception:
322 log.exception(
335 log.exception(
323 u"Exception occurred while trying update repo group %s",
336 u"Exception occurred while trying update repo group %s",
324 repogroupid)
337 repogroupid)
325 raise JSONRPCError('failed to update repository group `%s`'
338 raise JSONRPCError('failed to update repository group `%s`'
326 % (repogroupid,))
339 % (repogroupid,))
327
340
328
341
329 @jsonrpc_method()
342 @jsonrpc_method()
330 def delete_repo_group(request, apiuser, repogroupid):
343 def delete_repo_group(request, apiuser, repogroupid):
331 """
344 """
332 Deletes a |repo| group.
345 Deletes a |repo| group.
333
346
334 :param apiuser: This is filled automatically from the |authtoken|.
347 :param apiuser: This is filled automatically from the |authtoken|.
335 :type apiuser: AuthUser
348 :type apiuser: AuthUser
336 :param repogroupid: Set the name or ID of repository group to be
349 :param repogroupid: Set the name or ID of repository group to be
337 deleted.
350 deleted.
338 :type repogroupid: str or int
351 :type repogroupid: str or int
339
352
340 Example output:
353 Example output:
341
354
342 .. code-block:: bash
355 .. code-block:: bash
343
356
344 id : <id_given_in_input>
357 id : <id_given_in_input>
345 result : {
358 result : {
346 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
359 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
347 'repo_group': null
360 'repo_group': null
348 }
361 }
349 error : null
362 error : null
350
363
351 Example error output:
364 Example error output:
352
365
353 .. code-block:: bash
366 .. code-block:: bash
354
367
355 id : <id_given_in_input>
368 id : <id_given_in_input>
356 result : null
369 result : null
357 error : {
370 error : {
358 "failed to delete repo group ID:<repogroupid> <repogroupname>"
371 "failed to delete repo group ID:<repogroupid> <repogroupname>"
359 }
372 }
360
373
361 """
374 """
362
375
363 repo_group = get_repo_group_or_error(repogroupid)
376 repo_group = get_repo_group_or_error(repogroupid)
364 if not has_superadmin_permission(apiuser):
377 if not has_superadmin_permission(apiuser):
365 validate_repo_group_permissions(
378 validate_repo_group_permissions(
366 apiuser, repogroupid, repo_group, ('group.admin',))
379 apiuser, repogroupid, repo_group, ('group.admin',))
367
380
381 old_data = repo_group.get_api_data()
368 try:
382 try:
369 RepoGroupModel().delete(repo_group)
383 RepoGroupModel().delete(repo_group)
384 audit_logger.store_api(
385 'repo_group.delete', action_data={'old_data': old_data},
386 user=apiuser)
370 Session().commit()
387 Session().commit()
371 return {
388 return {
372 'msg': 'deleted repo group ID:%s %s' %
389 'msg': 'deleted repo group ID:%s %s' %
373 (repo_group.group_id, repo_group.group_name),
390 (repo_group.group_id, repo_group.group_name),
374 'repo_group': None
391 'repo_group': None
375 }
392 }
376 except Exception:
393 except Exception:
377 log.exception("Exception occurred while trying to delete repo group")
394 log.exception("Exception occurred while trying to delete repo group")
378 raise JSONRPCError('failed to delete repo group ID:%s %s' %
395 raise JSONRPCError('failed to delete repo group ID:%s %s' %
379 (repo_group.group_id, repo_group.group_name))
396 (repo_group.group_id, repo_group.group_name))
380
397
381
398
382 @jsonrpc_method()
399 @jsonrpc_method()
383 def grant_user_permission_to_repo_group(
400 def grant_user_permission_to_repo_group(
384 request, apiuser, repogroupid, userid, perm,
401 request, apiuser, repogroupid, userid, perm,
385 apply_to_children=Optional('none')):
402 apply_to_children=Optional('none')):
386 """
403 """
387 Grant permission for a user on the given repository group, or update
404 Grant permission for a user on the given repository group, or update
388 existing permissions if found.
405 existing permissions if found.
389
406
390 This command can only be run using an |authtoken| with admin
407 This command can only be run using an |authtoken| with admin
391 permissions.
408 permissions.
392
409
393 :param apiuser: This is filled automatically from the |authtoken|.
410 :param apiuser: This is filled automatically from the |authtoken|.
394 :type apiuser: AuthUser
411 :type apiuser: AuthUser
395 :param repogroupid: Set the name or ID of repository group.
412 :param repogroupid: Set the name or ID of repository group.
396 :type repogroupid: str or int
413 :type repogroupid: str or int
397 :param userid: Set the user name.
414 :param userid: Set the user name.
398 :type userid: str
415 :type userid: str
399 :param perm: (group.(none|read|write|admin))
416 :param perm: (group.(none|read|write|admin))
400 :type perm: str
417 :type perm: str
401 :param apply_to_children: 'none', 'repos', 'groups', 'all'
418 :param apply_to_children: 'none', 'repos', 'groups', 'all'
402 :type apply_to_children: str
419 :type apply_to_children: str
403
420
404 Example output:
421 Example output:
405
422
406 .. code-block:: bash
423 .. code-block:: bash
407
424
408 id : <id_given_in_input>
425 id : <id_given_in_input>
409 result: {
426 result: {
410 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
427 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
411 "success": true
428 "success": true
412 }
429 }
413 error: null
430 error: null
414
431
415 Example error output:
432 Example error output:
416
433
417 .. code-block:: bash
434 .. code-block:: bash
418
435
419 id : <id_given_in_input>
436 id : <id_given_in_input>
420 result : null
437 result : null
421 error : {
438 error : {
422 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
439 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
423 }
440 }
424
441
425 """
442 """
426
443
427 repo_group = get_repo_group_or_error(repogroupid)
444 repo_group = get_repo_group_or_error(repogroupid)
428
445
429 if not has_superadmin_permission(apiuser):
446 if not has_superadmin_permission(apiuser):
430 validate_repo_group_permissions(
447 validate_repo_group_permissions(
431 apiuser, repogroupid, repo_group, ('group.admin',))
448 apiuser, repogroupid, repo_group, ('group.admin',))
432
449
433 user = get_user_or_error(userid)
450 user = get_user_or_error(userid)
434 perm = get_perm_or_error(perm, prefix='group.')
451 perm = get_perm_or_error(perm, prefix='group.')
435 apply_to_children = Optional.extract(apply_to_children)
452 apply_to_children = Optional.extract(apply_to_children)
436
453
437 perm_additions = [[user.user_id, perm, "user"]]
454 perm_additions = [[user.user_id, perm, "user"]]
438 try:
455 try:
439 RepoGroupModel().update_permissions(repo_group=repo_group,
456 RepoGroupModel().update_permissions(repo_group=repo_group,
440 perm_additions=perm_additions,
457 perm_additions=perm_additions,
441 recursive=apply_to_children,
458 recursive=apply_to_children,
442 cur_user=apiuser)
459 cur_user=apiuser)
443 Session().commit()
460 Session().commit()
444 return {
461 return {
445 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
462 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
446 '`%s` in repo group: `%s`' % (
463 '`%s` in repo group: `%s`' % (
447 perm.permission_name, apply_to_children, user.username,
464 perm.permission_name, apply_to_children, user.username,
448 repo_group.name
465 repo_group.name
449 ),
466 ),
450 'success': True
467 'success': True
451 }
468 }
452 except Exception:
469 except Exception:
453 log.exception("Exception occurred while trying to grant "
470 log.exception("Exception occurred while trying to grant "
454 "user permissions to repo group")
471 "user permissions to repo group")
455 raise JSONRPCError(
472 raise JSONRPCError(
456 'failed to edit permission for user: '
473 'failed to edit permission for user: '
457 '`%s` in repo group: `%s`' % (userid, repo_group.name))
474 '`%s` in repo group: `%s`' % (userid, repo_group.name))
458
475
459
476
460 @jsonrpc_method()
477 @jsonrpc_method()
461 def revoke_user_permission_from_repo_group(
478 def revoke_user_permission_from_repo_group(
462 request, apiuser, repogroupid, userid,
479 request, apiuser, repogroupid, userid,
463 apply_to_children=Optional('none')):
480 apply_to_children=Optional('none')):
464 """
481 """
465 Revoke permission for a user in a given repository group.
482 Revoke permission for a user in a given repository group.
466
483
467 This command can only be run using an |authtoken| with admin
484 This command can only be run using an |authtoken| with admin
468 permissions on the |repo| group.
485 permissions on the |repo| group.
469
486
470 :param apiuser: This is filled automatically from the |authtoken|.
487 :param apiuser: This is filled automatically from the |authtoken|.
471 :type apiuser: AuthUser
488 :type apiuser: AuthUser
472 :param repogroupid: Set the name or ID of the repository group.
489 :param repogroupid: Set the name or ID of the repository group.
473 :type repogroupid: str or int
490 :type repogroupid: str or int
474 :param userid: Set the user name to revoke.
491 :param userid: Set the user name to revoke.
475 :type userid: str
492 :type userid: str
476 :param apply_to_children: 'none', 'repos', 'groups', 'all'
493 :param apply_to_children: 'none', 'repos', 'groups', 'all'
477 :type apply_to_children: str
494 :type apply_to_children: str
478
495
479 Example output:
496 Example output:
480
497
481 .. code-block:: bash
498 .. code-block:: bash
482
499
483 id : <id_given_in_input>
500 id : <id_given_in_input>
484 result: {
501 result: {
485 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
502 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
486 "success": true
503 "success": true
487 }
504 }
488 error: null
505 error: null
489
506
490 Example error output:
507 Example error output:
491
508
492 .. code-block:: bash
509 .. code-block:: bash
493
510
494 id : <id_given_in_input>
511 id : <id_given_in_input>
495 result : null
512 result : null
496 error : {
513 error : {
497 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
514 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
498 }
515 }
499
516
500 """
517 """
501
518
502 repo_group = get_repo_group_or_error(repogroupid)
519 repo_group = get_repo_group_or_error(repogroupid)
503
520
504 if not has_superadmin_permission(apiuser):
521 if not has_superadmin_permission(apiuser):
505 validate_repo_group_permissions(
522 validate_repo_group_permissions(
506 apiuser, repogroupid, repo_group, ('group.admin',))
523 apiuser, repogroupid, repo_group, ('group.admin',))
507
524
508 user = get_user_or_error(userid)
525 user = get_user_or_error(userid)
509 apply_to_children = Optional.extract(apply_to_children)
526 apply_to_children = Optional.extract(apply_to_children)
510
527
511 perm_deletions = [[user.user_id, None, "user"]]
528 perm_deletions = [[user.user_id, None, "user"]]
512 try:
529 try:
513 RepoGroupModel().update_permissions(repo_group=repo_group,
530 RepoGroupModel().update_permissions(repo_group=repo_group,
514 perm_deletions=perm_deletions,
531 perm_deletions=perm_deletions,
515 recursive=apply_to_children,
532 recursive=apply_to_children,
516 cur_user=apiuser)
533 cur_user=apiuser)
517 Session().commit()
534 Session().commit()
518 return {
535 return {
519 'msg': 'Revoked perm (recursive:%s) for user: '
536 'msg': 'Revoked perm (recursive:%s) for user: '
520 '`%s` in repo group: `%s`' % (
537 '`%s` in repo group: `%s`' % (
521 apply_to_children, user.username, repo_group.name
538 apply_to_children, user.username, repo_group.name
522 ),
539 ),
523 'success': True
540 'success': True
524 }
541 }
525 except Exception:
542 except Exception:
526 log.exception("Exception occurred while trying revoke user "
543 log.exception("Exception occurred while trying revoke user "
527 "permission from repo group")
544 "permission from repo group")
528 raise JSONRPCError(
545 raise JSONRPCError(
529 'failed to edit permission for user: '
546 'failed to edit permission for user: '
530 '`%s` in repo group: `%s`' % (userid, repo_group.name))
547 '`%s` in repo group: `%s`' % (userid, repo_group.name))
531
548
532
549
533 @jsonrpc_method()
550 @jsonrpc_method()
534 def grant_user_group_permission_to_repo_group(
551 def grant_user_group_permission_to_repo_group(
535 request, apiuser, repogroupid, usergroupid, perm,
552 request, apiuser, repogroupid, usergroupid, perm,
536 apply_to_children=Optional('none'), ):
553 apply_to_children=Optional('none'), ):
537 """
554 """
538 Grant permission for a user group on given repository group, or update
555 Grant permission for a user group on given repository group, or update
539 existing permissions if found.
556 existing permissions if found.
540
557
541 This command can only be run using an |authtoken| with admin
558 This command can only be run using an |authtoken| with admin
542 permissions on the |repo| group.
559 permissions on the |repo| group.
543
560
544 :param apiuser: This is filled automatically from the |authtoken|.
561 :param apiuser: This is filled automatically from the |authtoken|.
545 :type apiuser: AuthUser
562 :type apiuser: AuthUser
546 :param repogroupid: Set the name or id of repository group
563 :param repogroupid: Set the name or id of repository group
547 :type repogroupid: str or int
564 :type repogroupid: str or int
548 :param usergroupid: id of usergroup
565 :param usergroupid: id of usergroup
549 :type usergroupid: str or int
566 :type usergroupid: str or int
550 :param perm: (group.(none|read|write|admin))
567 :param perm: (group.(none|read|write|admin))
551 :type perm: str
568 :type perm: str
552 :param apply_to_children: 'none', 'repos', 'groups', 'all'
569 :param apply_to_children: 'none', 'repos', 'groups', 'all'
553 :type apply_to_children: str
570 :type apply_to_children: str
554
571
555 Example output:
572 Example output:
556
573
557 .. code-block:: bash
574 .. code-block:: bash
558
575
559 id : <id_given_in_input>
576 id : <id_given_in_input>
560 result : {
577 result : {
561 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
578 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
562 "success": true
579 "success": true
563
580
564 }
581 }
565 error : null
582 error : null
566
583
567 Example error output:
584 Example error output:
568
585
569 .. code-block:: bash
586 .. code-block:: bash
570
587
571 id : <id_given_in_input>
588 id : <id_given_in_input>
572 result : null
589 result : null
573 error : {
590 error : {
574 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
591 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
575 }
592 }
576
593
577 """
594 """
578
595
579 repo_group = get_repo_group_or_error(repogroupid)
596 repo_group = get_repo_group_or_error(repogroupid)
580 perm = get_perm_or_error(perm, prefix='group.')
597 perm = get_perm_or_error(perm, prefix='group.')
581 user_group = get_user_group_or_error(usergroupid)
598 user_group = get_user_group_or_error(usergroupid)
582 if not has_superadmin_permission(apiuser):
599 if not has_superadmin_permission(apiuser):
583 validate_repo_group_permissions(
600 validate_repo_group_permissions(
584 apiuser, repogroupid, repo_group, ('group.admin',))
601 apiuser, repogroupid, repo_group, ('group.admin',))
585
602
586 # check if we have at least read permission for this user group !
603 # check if we have at least read permission for this user group !
587 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
604 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
588 if not HasUserGroupPermissionAnyApi(*_perms)(
605 if not HasUserGroupPermissionAnyApi(*_perms)(
589 user=apiuser, user_group_name=user_group.users_group_name):
606 user=apiuser, user_group_name=user_group.users_group_name):
590 raise JSONRPCError(
607 raise JSONRPCError(
591 'user group `%s` does not exist' % (usergroupid,))
608 'user group `%s` does not exist' % (usergroupid,))
592
609
593 apply_to_children = Optional.extract(apply_to_children)
610 apply_to_children = Optional.extract(apply_to_children)
594
611
595 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
612 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
596 try:
613 try:
597 RepoGroupModel().update_permissions(repo_group=repo_group,
614 RepoGroupModel().update_permissions(repo_group=repo_group,
598 perm_additions=perm_additions,
615 perm_additions=perm_additions,
599 recursive=apply_to_children,
616 recursive=apply_to_children,
600 cur_user=apiuser)
617 cur_user=apiuser)
601 Session().commit()
618 Session().commit()
602 return {
619 return {
603 'msg': 'Granted perm: `%s` (recursive:%s) '
620 'msg': 'Granted perm: `%s` (recursive:%s) '
604 'for user group: `%s` in repo group: `%s`' % (
621 'for user group: `%s` in repo group: `%s`' % (
605 perm.permission_name, apply_to_children,
622 perm.permission_name, apply_to_children,
606 user_group.users_group_name, repo_group.name
623 user_group.users_group_name, repo_group.name
607 ),
624 ),
608 'success': True
625 'success': True
609 }
626 }
610 except Exception:
627 except Exception:
611 log.exception("Exception occurred while trying to grant user "
628 log.exception("Exception occurred while trying to grant user "
612 "group permissions to repo group")
629 "group permissions to repo group")
613 raise JSONRPCError(
630 raise JSONRPCError(
614 'failed to edit permission for user group: `%s` in '
631 'failed to edit permission for user group: `%s` in '
615 'repo group: `%s`' % (
632 'repo group: `%s`' % (
616 usergroupid, repo_group.name
633 usergroupid, repo_group.name
617 )
634 )
618 )
635 )
619
636
620
637
621 @jsonrpc_method()
638 @jsonrpc_method()
622 def revoke_user_group_permission_from_repo_group(
639 def revoke_user_group_permission_from_repo_group(
623 request, apiuser, repogroupid, usergroupid,
640 request, apiuser, repogroupid, usergroupid,
624 apply_to_children=Optional('none')):
641 apply_to_children=Optional('none')):
625 """
642 """
626 Revoke permission for user group on given repository.
643 Revoke permission for user group on given repository.
627
644
628 This command can only be run using an |authtoken| with admin
645 This command can only be run using an |authtoken| with admin
629 permissions on the |repo| group.
646 permissions on the |repo| group.
630
647
631 :param apiuser: This is filled automatically from the |authtoken|.
648 :param apiuser: This is filled automatically from the |authtoken|.
632 :type apiuser: AuthUser
649 :type apiuser: AuthUser
633 :param repogroupid: name or id of repository group
650 :param repogroupid: name or id of repository group
634 :type repogroupid: str or int
651 :type repogroupid: str or int
635 :param usergroupid:
652 :param usergroupid:
636 :param apply_to_children: 'none', 'repos', 'groups', 'all'
653 :param apply_to_children: 'none', 'repos', 'groups', 'all'
637 :type apply_to_children: str
654 :type apply_to_children: str
638
655
639 Example output:
656 Example output:
640
657
641 .. code-block:: bash
658 .. code-block:: bash
642
659
643 id : <id_given_in_input>
660 id : <id_given_in_input>
644 result: {
661 result: {
645 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
662 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
646 "success": true
663 "success": true
647 }
664 }
648 error: null
665 error: null
649
666
650 Example error output:
667 Example error output:
651
668
652 .. code-block:: bash
669 .. code-block:: bash
653
670
654 id : <id_given_in_input>
671 id : <id_given_in_input>
655 result : null
672 result : null
656 error : {
673 error : {
657 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
674 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
658 }
675 }
659
676
660
677
661 """
678 """
662
679
663 repo_group = get_repo_group_or_error(repogroupid)
680 repo_group = get_repo_group_or_error(repogroupid)
664 user_group = get_user_group_or_error(usergroupid)
681 user_group = get_user_group_or_error(usergroupid)
665 if not has_superadmin_permission(apiuser):
682 if not has_superadmin_permission(apiuser):
666 validate_repo_group_permissions(
683 validate_repo_group_permissions(
667 apiuser, repogroupid, repo_group, ('group.admin',))
684 apiuser, repogroupid, repo_group, ('group.admin',))
668
685
669 # check if we have at least read permission for this user group !
686 # check if we have at least read permission for this user group !
670 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
687 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
671 if not HasUserGroupPermissionAnyApi(*_perms)(
688 if not HasUserGroupPermissionAnyApi(*_perms)(
672 user=apiuser, user_group_name=user_group.users_group_name):
689 user=apiuser, user_group_name=user_group.users_group_name):
673 raise JSONRPCError(
690 raise JSONRPCError(
674 'user group `%s` does not exist' % (usergroupid,))
691 'user group `%s` does not exist' % (usergroupid,))
675
692
676 apply_to_children = Optional.extract(apply_to_children)
693 apply_to_children = Optional.extract(apply_to_children)
677
694
678 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
695 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
679 try:
696 try:
680 RepoGroupModel().update_permissions(repo_group=repo_group,
697 RepoGroupModel().update_permissions(repo_group=repo_group,
681 perm_deletions=perm_deletions,
698 perm_deletions=perm_deletions,
682 recursive=apply_to_children,
699 recursive=apply_to_children,
683 cur_user=apiuser)
700 cur_user=apiuser)
684 Session().commit()
701 Session().commit()
685 return {
702 return {
686 'msg': 'Revoked perm (recursive:%s) for user group: '
703 'msg': 'Revoked perm (recursive:%s) for user group: '
687 '`%s` in repo group: `%s`' % (
704 '`%s` in repo group: `%s`' % (
688 apply_to_children, user_group.users_group_name,
705 apply_to_children, user_group.users_group_name,
689 repo_group.name
706 repo_group.name
690 ),
707 ),
691 'success': True
708 'success': True
692 }
709 }
693 except Exception:
710 except Exception:
694 log.exception("Exception occurred while trying revoke user group "
711 log.exception("Exception occurred while trying revoke user group "
695 "permissions from repo group")
712 "permissions from repo group")
696 raise JSONRPCError(
713 raise JSONRPCError(
697 'failed to edit permission for user group: '
714 'failed to edit permission for user group: '
698 '`%s` in repo group: `%s`' % (
715 '`%s` in repo group: `%s`' % (
699 user_group.users_group_name, repo_group.name
716 user_group.users_group_name, repo_group.name
700 )
717 )
701 )
718 )
702
719
@@ -1,515 +1,560 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
24 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
26 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
27 from rhodecode.lib import audit_logger
26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
28 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 from rhodecode.lib.exceptions import DefaultUserException
29 from rhodecode.lib.exceptions import DefaultUserException
28 from rhodecode.lib.utils2 import safe_int, str2bool
30 from rhodecode.lib.utils2 import safe_int, str2bool
29 from rhodecode.model.db import Session, User, Repository
31 from rhodecode.model.db import Session, User, Repository
30 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.model import validation_schema
34 from rhodecode.model.validation_schema.schemas import user_schema
31
35
32 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
33
37
34
38
35 @jsonrpc_method()
39 @jsonrpc_method()
36 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
40 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
37 """
41 """
38 Returns the information associated with a username or userid.
42 Returns the information associated with a username or userid.
39
43
40 * If the ``userid`` is not set, this command returns the information
44 * If the ``userid`` is not set, this command returns the information
41 for the ``userid`` calling the method.
45 for the ``userid`` calling the method.
42
46
43 .. note::
47 .. note::
44
48
45 Normal users may only run this command against their ``userid``. For
49 Normal users may only run this command against their ``userid``. For
46 full privileges you must run this command using an |authtoken| with
50 full privileges you must run this command using an |authtoken| with
47 admin rights.
51 admin rights.
48
52
49 :param apiuser: This is filled automatically from the |authtoken|.
53 :param apiuser: This is filled automatically from the |authtoken|.
50 :type apiuser: AuthUser
54 :type apiuser: AuthUser
51 :param userid: Sets the userid for which data will be returned.
55 :param userid: Sets the userid for which data will be returned.
52 :type userid: Optional(str or int)
56 :type userid: Optional(str or int)
53
57
54 Example output:
58 Example output:
55
59
56 .. code-block:: bash
60 .. code-block:: bash
57
61
58 {
62 {
59 "error": null,
63 "error": null,
60 "id": <id>,
64 "id": <id>,
61 "result": {
65 "result": {
62 "active": true,
66 "active": true,
63 "admin": false,
67 "admin": false,
64 "api_keys": [ list of keys ],
68 "api_keys": [ list of keys ],
65 "auth_tokens": [ list of tokens with details ],
69 "auth_tokens": [ list of tokens with details ],
66 "email": "user@example.com",
70 "email": "user@example.com",
67 "emails": [
71 "emails": [
68 "user@example.com"
72 "user@example.com"
69 ],
73 ],
70 "extern_name": "rhodecode",
74 "extern_name": "rhodecode",
71 "extern_type": "rhodecode",
75 "extern_type": "rhodecode",
72 "firstname": "username",
76 "firstname": "username",
73 "ip_addresses": [],
77 "ip_addresses": [],
74 "language": null,
78 "language": null,
75 "last_login": "Timestamp",
79 "last_login": "Timestamp",
76 "last_activity": "Timestamp",
80 "last_activity": "Timestamp",
77 "lastname": "surnae",
81 "lastname": "surnae",
78 "permissions": {
82 "permissions": {
79 "global": [
83 "global": [
80 "hg.inherit_default_perms.true",
84 "hg.inherit_default_perms.true",
81 "usergroup.read",
85 "usergroup.read",
82 "hg.repogroup.create.false",
86 "hg.repogroup.create.false",
83 "hg.create.none",
87 "hg.create.none",
84 "hg.password_reset.enabled",
88 "hg.password_reset.enabled",
85 "hg.extern_activate.manual",
89 "hg.extern_activate.manual",
86 "hg.create.write_on_repogroup.false",
90 "hg.create.write_on_repogroup.false",
87 "hg.usergroup.create.false",
91 "hg.usergroup.create.false",
88 "group.none",
92 "group.none",
89 "repository.none",
93 "repository.none",
90 "hg.register.none",
94 "hg.register.none",
91 "hg.fork.repository"
95 "hg.fork.repository"
92 ],
96 ],
93 "repositories": { "username/example": "repository.write"},
97 "repositories": { "username/example": "repository.write"},
94 "repositories_groups": { "user-group/repo": "group.none" },
98 "repositories_groups": { "user-group/repo": "group.none" },
95 "user_groups": { "user_group_name": "usergroup.read" }
99 "user_groups": { "user_group_name": "usergroup.read" }
96 },
100 },
97 "user_id": 32,
101 "user_id": 32,
98 "username": "username"
102 "username": "username"
99 }
103 }
100 }
104 }
101 """
105 """
102
106
103 if not has_superadmin_permission(apiuser):
107 if not has_superadmin_permission(apiuser):
104 # make sure normal user does not pass someone else userid,
108 # make sure normal user does not pass someone else userid,
105 # he is not allowed to do that
109 # he is not allowed to do that
106 if not isinstance(userid, Optional) and userid != apiuser.user_id:
110 if not isinstance(userid, Optional) and userid != apiuser.user_id:
107 raise JSONRPCError('userid is not the same as your user')
111 raise JSONRPCError('userid is not the same as your user')
108
112
109 userid = Optional.extract(userid, evaluate_locals=locals())
113 userid = Optional.extract(userid, evaluate_locals=locals())
110 userid = getattr(userid, 'user_id', userid)
114 userid = getattr(userid, 'user_id', userid)
111
115
112 user = get_user_or_error(userid)
116 user = get_user_or_error(userid)
113 data = user.get_api_data(include_secrets=True)
117 data = user.get_api_data(include_secrets=True)
114 data['permissions'] = AuthUser(user_id=user.user_id).permissions
118 data['permissions'] = AuthUser(user_id=user.user_id).permissions
115 return data
119 return data
116
120
117
121
118 @jsonrpc_method()
122 @jsonrpc_method()
119 def get_users(request, apiuser):
123 def get_users(request, apiuser):
120 """
124 """
121 Lists all users in the |RCE| user database.
125 Lists all users in the |RCE| user database.
122
126
123 This command can only be run using an |authtoken| with admin rights to
127 This command can only be run using an |authtoken| with admin rights to
124 the specified repository.
128 the specified repository.
125
129
126 This command takes the following options:
130 This command takes the following options:
127
131
128 :param apiuser: This is filled automatically from the |authtoken|.
132 :param apiuser: This is filled automatically from the |authtoken|.
129 :type apiuser: AuthUser
133 :type apiuser: AuthUser
130
134
131 Example output:
135 Example output:
132
136
133 .. code-block:: bash
137 .. code-block:: bash
134
138
135 id : <id_given_in_input>
139 id : <id_given_in_input>
136 result: [<user_object>, ...]
140 result: [<user_object>, ...]
137 error: null
141 error: null
138 """
142 """
139
143
140 if not has_superadmin_permission(apiuser):
144 if not has_superadmin_permission(apiuser):
141 raise JSONRPCForbidden()
145 raise JSONRPCForbidden()
142
146
143 result = []
147 result = []
144 users_list = User.query().order_by(User.username) \
148 users_list = User.query().order_by(User.username) \
145 .filter(User.username != User.DEFAULT_USER) \
149 .filter(User.username != User.DEFAULT_USER) \
146 .all()
150 .all()
147 for user in users_list:
151 for user in users_list:
148 result.append(user.get_api_data(include_secrets=True))
152 result.append(user.get_api_data(include_secrets=True))
149 return result
153 return result
150
154
151
155
152 @jsonrpc_method()
156 @jsonrpc_method()
153 def create_user(request, apiuser, username, email, password=Optional(''),
157 def create_user(request, apiuser, username, email, password=Optional(''),
154 firstname=Optional(''), lastname=Optional(''),
158 firstname=Optional(''), lastname=Optional(''),
155 active=Optional(True), admin=Optional(False),
159 active=Optional(True), admin=Optional(False),
156 extern_name=Optional('rhodecode'),
160 extern_name=Optional('rhodecode'),
157 extern_type=Optional('rhodecode'),
161 extern_type=Optional('rhodecode'),
158 force_password_change=Optional(False),
162 force_password_change=Optional(False),
159 create_personal_repo_group=Optional(None)):
163 create_personal_repo_group=Optional(None)):
160 """
164 """
161 Creates a new user and returns the new user object.
165 Creates a new user and returns the new user object.
162
166
163 This command can only be run using an |authtoken| with admin rights to
167 This command can only be run using an |authtoken| with admin rights to
164 the specified repository.
168 the specified repository.
165
169
166 This command takes the following options:
170 This command takes the following options:
167
171
168 :param apiuser: This is filled automatically from the |authtoken|.
172 :param apiuser: This is filled automatically from the |authtoken|.
169 :type apiuser: AuthUser
173 :type apiuser: AuthUser
170 :param username: Set the new username.
174 :param username: Set the new username.
171 :type username: str or int
175 :type username: str or int
172 :param email: Set the user email address.
176 :param email: Set the user email address.
173 :type email: str
177 :type email: str
174 :param password: Set the new user password.
178 :param password: Set the new user password.
175 :type password: Optional(str)
179 :type password: Optional(str)
176 :param firstname: Set the new user firstname.
180 :param firstname: Set the new user firstname.
177 :type firstname: Optional(str)
181 :type firstname: Optional(str)
178 :param lastname: Set the new user surname.
182 :param lastname: Set the new user surname.
179 :type lastname: Optional(str)
183 :type lastname: Optional(str)
180 :param active: Set the user as active.
184 :param active: Set the user as active.
181 :type active: Optional(``True`` | ``False``)
185 :type active: Optional(``True`` | ``False``)
182 :param admin: Give the new user admin rights.
186 :param admin: Give the new user admin rights.
183 :type admin: Optional(``True`` | ``False``)
187 :type admin: Optional(``True`` | ``False``)
184 :param extern_name: Set the authentication plugin name.
188 :param extern_name: Set the authentication plugin name.
185 Using LDAP this is filled with LDAP UID.
189 Using LDAP this is filled with LDAP UID.
186 :type extern_name: Optional(str)
190 :type extern_name: Optional(str)
187 :param extern_type: Set the new user authentication plugin.
191 :param extern_type: Set the new user authentication plugin.
188 :type extern_type: Optional(str)
192 :type extern_type: Optional(str)
189 :param force_password_change: Force the new user to change password
193 :param force_password_change: Force the new user to change password
190 on next login.
194 on next login.
191 :type force_password_change: Optional(``True`` | ``False``)
195 :type force_password_change: Optional(``True`` | ``False``)
192 :param create_personal_repo_group: Create personal repo group for this user
196 :param create_personal_repo_group: Create personal repo group for this user
193 :type create_personal_repo_group: Optional(``True`` | ``False``)
197 :type create_personal_repo_group: Optional(``True`` | ``False``)
194
198
195 Example output:
199 Example output:
196
200
197 .. code-block:: bash
201 .. code-block:: bash
198
202
199 id : <id_given_in_input>
203 id : <id_given_in_input>
200 result: {
204 result: {
201 "msg" : "created new user `<username>`",
205 "msg" : "created new user `<username>`",
202 "user": <user_obj>
206 "user": <user_obj>
203 }
207 }
204 error: null
208 error: null
205
209
206 Example error output:
210 Example error output:
207
211
208 .. code-block:: bash
212 .. code-block:: bash
209
213
210 id : <id_given_in_input>
214 id : <id_given_in_input>
211 result : null
215 result : null
212 error : {
216 error : {
213 "user `<username>` already exist"
217 "user `<username>` already exist"
214 or
218 or
215 "email `<email>` already exist"
219 "email `<email>` already exist"
216 or
220 or
217 "failed to create user `<username>`"
221 "failed to create user `<username>`"
218 }
222 }
219
223
220 """
224 """
221 if not has_superadmin_permission(apiuser):
225 if not has_superadmin_permission(apiuser):
222 raise JSONRPCForbidden()
226 raise JSONRPCForbidden()
223
227
224 if UserModel().get_by_username(username):
228 if UserModel().get_by_username(username):
225 raise JSONRPCError("user `%s` already exist" % (username,))
229 raise JSONRPCError("user `%s` already exist" % (username,))
226
230
227 if UserModel().get_by_email(email, case_insensitive=True):
231 if UserModel().get_by_email(email, case_insensitive=True):
228 raise JSONRPCError("email `%s` already exist" % (email,))
232 raise JSONRPCError("email `%s` already exist" % (email,))
229
233
230 # generate random password if we actually given the
234 # generate random password if we actually given the
231 # extern_name and it's not rhodecode
235 # extern_name and it's not rhodecode
232 if (not isinstance(extern_name, Optional) and
236 if (not isinstance(extern_name, Optional) and
233 Optional.extract(extern_name) != 'rhodecode'):
237 Optional.extract(extern_name) != 'rhodecode'):
234 # generate temporary password if user is external
238 # generate temporary password if user is external
235 password = PasswordGenerator().gen_password(length=16)
239 password = PasswordGenerator().gen_password(length=16)
236 create_repo_group = Optional.extract(create_personal_repo_group)
240 create_repo_group = Optional.extract(create_personal_repo_group)
237 if isinstance(create_repo_group, basestring):
241 if isinstance(create_repo_group, basestring):
238 create_repo_group = str2bool(create_repo_group)
242 create_repo_group = str2bool(create_repo_group)
239
243
244 username = Optional.extract(username)
245 password = Optional.extract(password)
246 email = Optional.extract(email)
247 first_name = Optional.extract(firstname)
248 last_name = Optional.extract(lastname)
249 active = Optional.extract(active)
250 admin = Optional.extract(admin)
251 extern_type = Optional.extract(extern_type)
252 extern_name = Optional.extract(extern_name)
253
254 schema = user_schema.UserSchema().bind(
255 # user caller
256 user=apiuser)
257 try:
258 schema_data = schema.deserialize(dict(
259 username=username,
260 email=email,
261 password=password,
262 first_name=first_name,
263 last_name=last_name,
264 active=active,
265 admin=admin,
266 extern_type=extern_type,
267 extern_name=extern_name,
268 ))
269 except validation_schema.Invalid as err:
270 raise JSONRPCValidationError(colander_exc=err)
271
240 try:
272 try:
241 user = UserModel().create_or_update(
273 user = UserModel().create_or_update(
242 username=Optional.extract(username),
274 username=schema_data['username'],
243 password=Optional.extract(password),
275 password=schema_data['password'],
244 email=Optional.extract(email),
276 email=schema_data['email'],
245 firstname=Optional.extract(firstname),
277 firstname=schema_data['first_name'],
246 lastname=Optional.extract(lastname),
278 lastname=schema_data['last_name'],
247 active=Optional.extract(active),
279 active=schema_data['active'],
248 admin=Optional.extract(admin),
280 admin=schema_data['admin'],
249 extern_type=Optional.extract(extern_type),
281 extern_type=schema_data['extern_type'],
250 extern_name=Optional.extract(extern_name),
282 extern_name=schema_data['extern_name'],
251 force_password_change=Optional.extract(force_password_change),
283 force_password_change=Optional.extract(force_password_change),
252 create_repo_group=create_repo_group
284 create_repo_group=create_repo_group
253 )
285 )
286 Session().flush()
287 creation_data = user.get_api_data()
288 audit_logger.store_api(
289 'user.create', action_data={'data': creation_data},
290 user=apiuser)
291
254 Session().commit()
292 Session().commit()
255 return {
293 return {
256 'msg': 'created new user `%s`' % username,
294 'msg': 'created new user `%s`' % username,
257 'user': user.get_api_data(include_secrets=True)
295 'user': user.get_api_data(include_secrets=True)
258 }
296 }
259 except Exception:
297 except Exception:
260 log.exception('Error occurred during creation of user')
298 log.exception('Error occurred during creation of user')
261 raise JSONRPCError('failed to create user `%s`' % (username,))
299 raise JSONRPCError('failed to create user `%s`' % (username,))
262
300
263
301
264 @jsonrpc_method()
302 @jsonrpc_method()
265 def update_user(request, apiuser, userid, username=Optional(None),
303 def update_user(request, apiuser, userid, username=Optional(None),
266 email=Optional(None), password=Optional(None),
304 email=Optional(None), password=Optional(None),
267 firstname=Optional(None), lastname=Optional(None),
305 firstname=Optional(None), lastname=Optional(None),
268 active=Optional(None), admin=Optional(None),
306 active=Optional(None), admin=Optional(None),
269 extern_type=Optional(None), extern_name=Optional(None), ):
307 extern_type=Optional(None), extern_name=Optional(None), ):
270 """
308 """
271 Updates the details for the specified user, if that user exists.
309 Updates the details for the specified user, if that user exists.
272
310
273 This command can only be run using an |authtoken| with admin rights to
311 This command can only be run using an |authtoken| with admin rights to
274 the specified repository.
312 the specified repository.
275
313
276 This command takes the following options:
314 This command takes the following options:
277
315
278 :param apiuser: This is filled automatically from |authtoken|.
316 :param apiuser: This is filled automatically from |authtoken|.
279 :type apiuser: AuthUser
317 :type apiuser: AuthUser
280 :param userid: Set the ``userid`` to update.
318 :param userid: Set the ``userid`` to update.
281 :type userid: str or int
319 :type userid: str or int
282 :param username: Set the new username.
320 :param username: Set the new username.
283 :type username: str or int
321 :type username: str or int
284 :param email: Set the new email.
322 :param email: Set the new email.
285 :type email: str
323 :type email: str
286 :param password: Set the new password.
324 :param password: Set the new password.
287 :type password: Optional(str)
325 :type password: Optional(str)
288 :param firstname: Set the new first name.
326 :param firstname: Set the new first name.
289 :type firstname: Optional(str)
327 :type firstname: Optional(str)
290 :param lastname: Set the new surname.
328 :param lastname: Set the new surname.
291 :type lastname: Optional(str)
329 :type lastname: Optional(str)
292 :param active: Set the new user as active.
330 :param active: Set the new user as active.
293 :type active: Optional(``True`` | ``False``)
331 :type active: Optional(``True`` | ``False``)
294 :param admin: Give the user admin rights.
332 :param admin: Give the user admin rights.
295 :type admin: Optional(``True`` | ``False``)
333 :type admin: Optional(``True`` | ``False``)
296 :param extern_name: Set the authentication plugin user name.
334 :param extern_name: Set the authentication plugin user name.
297 Using LDAP this is filled with LDAP UID.
335 Using LDAP this is filled with LDAP UID.
298 :type extern_name: Optional(str)
336 :type extern_name: Optional(str)
299 :param extern_type: Set the authentication plugin type.
337 :param extern_type: Set the authentication plugin type.
300 :type extern_type: Optional(str)
338 :type extern_type: Optional(str)
301
339
302
340
303 Example output:
341 Example output:
304
342
305 .. code-block:: bash
343 .. code-block:: bash
306
344
307 id : <id_given_in_input>
345 id : <id_given_in_input>
308 result: {
346 result: {
309 "msg" : "updated user ID:<userid> <username>",
347 "msg" : "updated user ID:<userid> <username>",
310 "user": <user_object>,
348 "user": <user_object>,
311 }
349 }
312 error: null
350 error: null
313
351
314 Example error output:
352 Example error output:
315
353
316 .. code-block:: bash
354 .. code-block:: bash
317
355
318 id : <id_given_in_input>
356 id : <id_given_in_input>
319 result : null
357 result : null
320 error : {
358 error : {
321 "failed to update user `<username>`"
359 "failed to update user `<username>`"
322 }
360 }
323
361
324 """
362 """
325 if not has_superadmin_permission(apiuser):
363 if not has_superadmin_permission(apiuser):
326 raise JSONRPCForbidden()
364 raise JSONRPCForbidden()
327
365
328 user = get_user_or_error(userid)
366 user = get_user_or_error(userid)
329
367 old_data = user.get_api_data()
330 # only non optional arguments will be stored in updates
368 # only non optional arguments will be stored in updates
331 updates = {}
369 updates = {}
332
370
333 try:
371 try:
334
372
335 store_update(updates, username, 'username')
373 store_update(updates, username, 'username')
336 store_update(updates, password, 'password')
374 store_update(updates, password, 'password')
337 store_update(updates, email, 'email')
375 store_update(updates, email, 'email')
338 store_update(updates, firstname, 'name')
376 store_update(updates, firstname, 'name')
339 store_update(updates, lastname, 'lastname')
377 store_update(updates, lastname, 'lastname')
340 store_update(updates, active, 'active')
378 store_update(updates, active, 'active')
341 store_update(updates, admin, 'admin')
379 store_update(updates, admin, 'admin')
342 store_update(updates, extern_name, 'extern_name')
380 store_update(updates, extern_name, 'extern_name')
343 store_update(updates, extern_type, 'extern_type')
381 store_update(updates, extern_type, 'extern_type')
344
382
345 user = UserModel().update_user(user, **updates)
383 user = UserModel().update_user(user, **updates)
384 audit_logger.store_api(
385 'user.edit', action_data={'old_data': old_data},
386 user=apiuser)
346 Session().commit()
387 Session().commit()
347 return {
388 return {
348 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
389 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
349 'user': user.get_api_data(include_secrets=True)
390 'user': user.get_api_data(include_secrets=True)
350 }
391 }
351 except DefaultUserException:
392 except DefaultUserException:
352 log.exception("Default user edit exception")
393 log.exception("Default user edit exception")
353 raise JSONRPCError('editing default user is forbidden')
394 raise JSONRPCError('editing default user is forbidden')
354 except Exception:
395 except Exception:
355 log.exception("Error occurred during update of user")
396 log.exception("Error occurred during update of user")
356 raise JSONRPCError('failed to update user `%s`' % (userid,))
397 raise JSONRPCError('failed to update user `%s`' % (userid,))
357
398
358
399
359 @jsonrpc_method()
400 @jsonrpc_method()
360 def delete_user(request, apiuser, userid):
401 def delete_user(request, apiuser, userid):
361 """
402 """
362 Deletes the specified user from the |RCE| user database.
403 Deletes the specified user from the |RCE| user database.
363
404
364 This command can only be run using an |authtoken| with admin rights to
405 This command can only be run using an |authtoken| with admin rights to
365 the specified repository.
406 the specified repository.
366
407
367 .. important::
408 .. important::
368
409
369 Ensure all open pull requests and open code review
410 Ensure all open pull requests and open code review
370 requests to this user are close.
411 requests to this user are close.
371
412
372 Also ensure all repositories, or repository groups owned by this
413 Also ensure all repositories, or repository groups owned by this
373 user are reassigned before deletion.
414 user are reassigned before deletion.
374
415
375 This command takes the following options:
416 This command takes the following options:
376
417
377 :param apiuser: This is filled automatically from the |authtoken|.
418 :param apiuser: This is filled automatically from the |authtoken|.
378 :type apiuser: AuthUser
419 :type apiuser: AuthUser
379 :param userid: Set the user to delete.
420 :param userid: Set the user to delete.
380 :type userid: str or int
421 :type userid: str or int
381
422
382 Example output:
423 Example output:
383
424
384 .. code-block:: bash
425 .. code-block:: bash
385
426
386 id : <id_given_in_input>
427 id : <id_given_in_input>
387 result: {
428 result: {
388 "msg" : "deleted user ID:<userid> <username>",
429 "msg" : "deleted user ID:<userid> <username>",
389 "user": null
430 "user": null
390 }
431 }
391 error: null
432 error: null
392
433
393 Example error output:
434 Example error output:
394
435
395 .. code-block:: bash
436 .. code-block:: bash
396
437
397 id : <id_given_in_input>
438 id : <id_given_in_input>
398 result : null
439 result : null
399 error : {
440 error : {
400 "failed to delete user ID:<userid> <username>"
441 "failed to delete user ID:<userid> <username>"
401 }
442 }
402
443
403 """
444 """
404 if not has_superadmin_permission(apiuser):
445 if not has_superadmin_permission(apiuser):
405 raise JSONRPCForbidden()
446 raise JSONRPCForbidden()
406
447
407 user = get_user_or_error(userid)
448 user = get_user_or_error(userid)
408
449 old_data = user.get_api_data()
409 try:
450 try:
410 UserModel().delete(userid)
451 UserModel().delete(userid)
452 audit_logger.store_api(
453 'user.delete', action_data={'old_data': old_data},
454 user=apiuser)
455
411 Session().commit()
456 Session().commit()
412 return {
457 return {
413 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
458 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
414 'user': None
459 'user': None
415 }
460 }
416 except Exception:
461 except Exception:
417 log.exception("Error occurred during deleting of user")
462 log.exception("Error occurred during deleting of user")
418 raise JSONRPCError(
463 raise JSONRPCError(
419 'failed to delete user ID:%s %s' % (user.user_id, user.username))
464 'failed to delete user ID:%s %s' % (user.user_id, user.username))
420
465
421
466
422 @jsonrpc_method()
467 @jsonrpc_method()
423 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
468 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
424 """
469 """
425 Displays all repositories locked by the specified user.
470 Displays all repositories locked by the specified user.
426
471
427 * If this command is run by a non-admin user, it returns
472 * If this command is run by a non-admin user, it returns
428 a list of |repos| locked by that user.
473 a list of |repos| locked by that user.
429
474
430 This command takes the following options:
475 This command takes the following options:
431
476
432 :param apiuser: This is filled automatically from the |authtoken|.
477 :param apiuser: This is filled automatically from the |authtoken|.
433 :type apiuser: AuthUser
478 :type apiuser: AuthUser
434 :param userid: Sets the userid whose list of locked |repos| will be
479 :param userid: Sets the userid whose list of locked |repos| will be
435 displayed.
480 displayed.
436 :type userid: Optional(str or int)
481 :type userid: Optional(str or int)
437
482
438 Example output:
483 Example output:
439
484
440 .. code-block:: bash
485 .. code-block:: bash
441
486
442 id : <id_given_in_input>
487 id : <id_given_in_input>
443 result : {
488 result : {
444 [repo_object, repo_object,...]
489 [repo_object, repo_object,...]
445 }
490 }
446 error : null
491 error : null
447 """
492 """
448
493
449 include_secrets = False
494 include_secrets = False
450 if not has_superadmin_permission(apiuser):
495 if not has_superadmin_permission(apiuser):
451 # make sure normal user does not pass someone else userid,
496 # make sure normal user does not pass someone else userid,
452 # he is not allowed to do that
497 # he is not allowed to do that
453 if not isinstance(userid, Optional) and userid != apiuser.user_id:
498 if not isinstance(userid, Optional) and userid != apiuser.user_id:
454 raise JSONRPCError('userid is not the same as your user')
499 raise JSONRPCError('userid is not the same as your user')
455 else:
500 else:
456 include_secrets = True
501 include_secrets = True
457
502
458 userid = Optional.extract(userid, evaluate_locals=locals())
503 userid = Optional.extract(userid, evaluate_locals=locals())
459 userid = getattr(userid, 'user_id', userid)
504 userid = getattr(userid, 'user_id', userid)
460 user = get_user_or_error(userid)
505 user = get_user_or_error(userid)
461
506
462 ret = []
507 ret = []
463
508
464 # show all locks
509 # show all locks
465 for r in Repository.getAll():
510 for r in Repository.getAll():
466 _user_id, _time, _reason = r.locked
511 _user_id, _time, _reason = r.locked
467 if _user_id and _time:
512 if _user_id and _time:
468 _api_data = r.get_api_data(include_secrets=include_secrets)
513 _api_data = r.get_api_data(include_secrets=include_secrets)
469 # if we use user filter just show the locks for this user
514 # if we use user filter just show the locks for this user
470 if safe_int(_user_id) == user.user_id:
515 if safe_int(_user_id) == user.user_id:
471 ret.append(_api_data)
516 ret.append(_api_data)
472
517
473 return ret
518 return ret
474
519
475
520
476 @jsonrpc_method()
521 @jsonrpc_method()
477 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
522 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
478 """
523 """
479 Fetches all action logs made by the specified user.
524 Fetches all action logs made by the specified user.
480
525
481 This command takes the following options:
526 This command takes the following options:
482
527
483 :param apiuser: This is filled automatically from the |authtoken|.
528 :param apiuser: This is filled automatically from the |authtoken|.
484 :type apiuser: AuthUser
529 :type apiuser: AuthUser
485 :param userid: Sets the userid whose list of locked |repos| will be
530 :param userid: Sets the userid whose list of locked |repos| will be
486 displayed.
531 displayed.
487 :type userid: Optional(str or int)
532 :type userid: Optional(str or int)
488
533
489 Example output:
534 Example output:
490
535
491 .. code-block:: bash
536 .. code-block:: bash
492
537
493 id : <id_given_in_input>
538 id : <id_given_in_input>
494 result : {
539 result : {
495 [action, action,...]
540 [action, action,...]
496 }
541 }
497 error : null
542 error : null
498 """
543 """
499
544
500 if not has_superadmin_permission(apiuser):
545 if not has_superadmin_permission(apiuser):
501 # make sure normal user does not pass someone else userid,
546 # make sure normal user does not pass someone else userid,
502 # he is not allowed to do that
547 # he is not allowed to do that
503 if not isinstance(userid, Optional) and userid != apiuser.user_id:
548 if not isinstance(userid, Optional) and userid != apiuser.user_id:
504 raise JSONRPCError('userid is not the same as your user')
549 raise JSONRPCError('userid is not the same as your user')
505
550
506 userid = Optional.extract(userid, evaluate_locals=locals())
551 userid = Optional.extract(userid, evaluate_locals=locals())
507 userid = getattr(userid, 'user_id', userid)
552 userid = getattr(userid, 'user_id', userid)
508 user = get_user_or_error(userid)
553 user = get_user_or_error(userid)
509
554
510 ret = []
555 ret = []
511
556
512 # show all user actions
557 # show all user actions
513 for entry in UserModel().get_user_log(user, filter_term=None):
558 for entry in UserModel().get_user_log(user, filter_term=None):
514 ret.append(entry)
559 ret.append(entry)
515 return ret
560 return ret
@@ -1,773 +1,818 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
24 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
25 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 get_user_or_error, get_user_group_or_error, get_perm_or_error)
27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
28 from rhodecode.lib import audit_logger
27 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
28 from rhodecode.lib.exceptions import UserGroupAssignedException
30 from rhodecode.lib.exceptions import UserGroupAssignedException
29 from rhodecode.model.db import Session
31 from rhodecode.model.db import Session
30 from rhodecode.model.scm import UserGroupList
32 from rhodecode.model.scm import UserGroupList
31 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.user_group import UserGroupModel
34 from rhodecode.model import validation_schema
35 from rhodecode.model.validation_schema.schemas import user_group_schema
32
36
33 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
34
38
35
39
36 @jsonrpc_method()
40 @jsonrpc_method()
37 def get_user_group(request, apiuser, usergroupid):
41 def get_user_group(request, apiuser, usergroupid):
38 """
42 """
39 Returns the data of an existing user group.
43 Returns the data of an existing user group.
40
44
41 This command can only be run using an |authtoken| with admin rights to
45 This command can only be run using an |authtoken| with admin rights to
42 the specified repository.
46 the specified repository.
43
47
44 :param apiuser: This is filled automatically from the |authtoken|.
48 :param apiuser: This is filled automatically from the |authtoken|.
45 :type apiuser: AuthUser
49 :type apiuser: AuthUser
46 :param usergroupid: Set the user group from which to return data.
50 :param usergroupid: Set the user group from which to return data.
47 :type usergroupid: str or int
51 :type usergroupid: str or int
48
52
49 Example error output:
53 Example error output:
50
54
51 .. code-block:: bash
55 .. code-block:: bash
52
56
53 {
57 {
54 "error": null,
58 "error": null,
55 "id": <id>,
59 "id": <id>,
56 "result": {
60 "result": {
57 "active": true,
61 "active": true,
58 "group_description": "group description",
62 "group_description": "group description",
59 "group_name": "group name",
63 "group_name": "group name",
60 "members": [
64 "members": [
61 {
65 {
62 "name": "owner-name",
66 "name": "owner-name",
63 "origin": "owner",
67 "origin": "owner",
64 "permission": "usergroup.admin",
68 "permission": "usergroup.admin",
65 "type": "user"
69 "type": "user"
66 },
70 },
67 {
71 {
68 {
72 {
69 "name": "user name",
73 "name": "user name",
70 "origin": "permission",
74 "origin": "permission",
71 "permission": "usergroup.admin",
75 "permission": "usergroup.admin",
72 "type": "user"
76 "type": "user"
73 },
77 },
74 {
78 {
75 "name": "user group name",
79 "name": "user group name",
76 "origin": "permission",
80 "origin": "permission",
77 "permission": "usergroup.write",
81 "permission": "usergroup.write",
78 "type": "user_group"
82 "type": "user_group"
79 }
83 }
80 ],
84 ],
81 "owner": "owner name",
85 "owner": "owner name",
82 "users": [],
86 "users": [],
83 "users_group_id": 2
87 "users_group_id": 2
84 }
88 }
85 }
89 }
86
90
87 """
91 """
88
92
89 user_group = get_user_group_or_error(usergroupid)
93 user_group = get_user_group_or_error(usergroupid)
90 if not has_superadmin_permission(apiuser):
94 if not has_superadmin_permission(apiuser):
91 # check if we have at least read permission for this user group !
95 # check if we have at least read permission for this user group !
92 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
96 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
93 if not HasUserGroupPermissionAnyApi(*_perms)(
97 if not HasUserGroupPermissionAnyApi(*_perms)(
94 user=apiuser, user_group_name=user_group.users_group_name):
98 user=apiuser, user_group_name=user_group.users_group_name):
95 raise JSONRPCError('user group `%s` does not exist' % (
99 raise JSONRPCError('user group `%s` does not exist' % (
96 usergroupid,))
100 usergroupid,))
97
101
98 permissions = []
102 permissions = []
99 for _user in user_group.permissions():
103 for _user in user_group.permissions():
100 user_data = {
104 user_data = {
101 'name': _user.username,
105 'name': _user.username,
102 'permission': _user.permission,
106 'permission': _user.permission,
103 'origin': get_origin(_user),
107 'origin': get_origin(_user),
104 'type': "user",
108 'type': "user",
105 }
109 }
106 permissions.append(user_data)
110 permissions.append(user_data)
107
111
108 for _user_group in user_group.permission_user_groups():
112 for _user_group in user_group.permission_user_groups():
109 user_group_data = {
113 user_group_data = {
110 'name': _user_group.users_group_name,
114 'name': _user_group.users_group_name,
111 'permission': _user_group.permission,
115 'permission': _user_group.permission,
112 'origin': get_origin(_user_group),
116 'origin': get_origin(_user_group),
113 'type': "user_group",
117 'type': "user_group",
114 }
118 }
115 permissions.append(user_group_data)
119 permissions.append(user_group_data)
116
120
117 data = user_group.get_api_data()
121 data = user_group.get_api_data()
118 data['members'] = permissions
122 data['members'] = permissions
119
123
120 return data
124 return data
121
125
122
126
123 @jsonrpc_method()
127 @jsonrpc_method()
124 def get_user_groups(request, apiuser):
128 def get_user_groups(request, apiuser):
125 """
129 """
126 Lists all the existing user groups within RhodeCode.
130 Lists all the existing user groups within RhodeCode.
127
131
128 This command can only be run using an |authtoken| with admin rights to
132 This command can only be run using an |authtoken| with admin rights to
129 the specified repository.
133 the specified repository.
130
134
131 This command takes the following options:
135 This command takes the following options:
132
136
133 :param apiuser: This is filled automatically from the |authtoken|.
137 :param apiuser: This is filled automatically from the |authtoken|.
134 :type apiuser: AuthUser
138 :type apiuser: AuthUser
135
139
136 Example error output:
140 Example error output:
137
141
138 .. code-block:: bash
142 .. code-block:: bash
139
143
140 id : <id_given_in_input>
144 id : <id_given_in_input>
141 result : [<user_group_obj>,...]
145 result : [<user_group_obj>,...]
142 error : null
146 error : null
143 """
147 """
144
148
145 include_secrets = has_superadmin_permission(apiuser)
149 include_secrets = has_superadmin_permission(apiuser)
146
150
147 result = []
151 result = []
148 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
152 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
149 extras = {'user': apiuser}
153 extras = {'user': apiuser}
150 for user_group in UserGroupList(UserGroupModel().get_all(),
154 for user_group in UserGroupList(UserGroupModel().get_all(),
151 perm_set=_perms, extra_kwargs=extras):
155 perm_set=_perms, extra_kwargs=extras):
152 result.append(
156 result.append(
153 user_group.get_api_data(include_secrets=include_secrets))
157 user_group.get_api_data(include_secrets=include_secrets))
154 return result
158 return result
155
159
156
160
157 @jsonrpc_method()
161 @jsonrpc_method()
158 def create_user_group(
162 def create_user_group(
159 request, apiuser, group_name, description=Optional(''),
163 request, apiuser, group_name, description=Optional(''),
160 owner=Optional(OAttr('apiuser')), active=Optional(True)):
164 owner=Optional(OAttr('apiuser')), active=Optional(True)):
161 """
165 """
162 Creates a new user group.
166 Creates a new user group.
163
167
164 This command can only be run using an |authtoken| with admin rights to
168 This command can only be run using an |authtoken| with admin rights to
165 the specified repository.
169 the specified repository.
166
170
167 This command takes the following options:
171 This command takes the following options:
168
172
169 :param apiuser: This is filled automatically from the |authtoken|.
173 :param apiuser: This is filled automatically from the |authtoken|.
170 :type apiuser: AuthUser
174 :type apiuser: AuthUser
171 :param group_name: Set the name of the new user group.
175 :param group_name: Set the name of the new user group.
172 :type group_name: str
176 :type group_name: str
173 :param description: Give a description of the new user group.
177 :param description: Give a description of the new user group.
174 :type description: str
178 :type description: str
175 :param owner: Set the owner of the new user group.
179 :param owner: Set the owner of the new user group.
176 If not set, the owner is the |authtoken| user.
180 If not set, the owner is the |authtoken| user.
177 :type owner: Optional(str or int)
181 :type owner: Optional(str or int)
178 :param active: Set this group as active.
182 :param active: Set this group as active.
179 :type active: Optional(``True`` | ``False``)
183 :type active: Optional(``True`` | ``False``)
180
184
181 Example output:
185 Example output:
182
186
183 .. code-block:: bash
187 .. code-block:: bash
184
188
185 id : <id_given_in_input>
189 id : <id_given_in_input>
186 result: {
190 result: {
187 "msg": "created new user group `<groupname>`",
191 "msg": "created new user group `<groupname>`",
188 "user_group": <user_group_object>
192 "user_group": <user_group_object>
189 }
193 }
190 error: null
194 error: null
191
195
192 Example error output:
196 Example error output:
193
197
194 .. code-block:: bash
198 .. code-block:: bash
195
199
196 id : <id_given_in_input>
200 id : <id_given_in_input>
197 result : null
201 result : null
198 error : {
202 error : {
199 "user group `<group name>` already exist"
203 "user group `<group name>` already exist"
200 or
204 or
201 "failed to create group `<group name>`"
205 "failed to create group `<group name>`"
202 }
206 }
203
207
204 """
208 """
205
209
206 if not has_superadmin_permission(apiuser):
210 if not has_superadmin_permission(apiuser):
207 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
211 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
208 raise JSONRPCForbidden()
212 raise JSONRPCForbidden()
209
213
210 if UserGroupModel().get_by_name(group_name):
214 if UserGroupModel().get_by_name(group_name):
211 raise JSONRPCError("user group `%s` already exist" % (group_name,))
215 raise JSONRPCError("user group `%s` already exist" % (group_name,))
212
216
213 try:
217 if isinstance(owner, Optional):
214 if isinstance(owner, Optional):
218 owner = apiuser.user_id
215 owner = apiuser.user_id
219
220 owner = get_user_or_error(owner)
221 active = Optional.extract(active)
222 description = Optional.extract(description)
216
223
217 owner = get_user_or_error(owner)
224 schema = user_group_schema.UserGroupSchema().bind(
218 active = Optional.extract(active)
225 # user caller
219 description = Optional.extract(description)
226 user=apiuser)
220 ug = UserGroupModel().create(
227 try:
221 name=group_name, description=description, owner=owner,
228 schema_data = schema.deserialize(dict(
222 active=active)
229 user_group_name=group_name,
230 user_group_description=description,
231 user_group_owner=owner.username,
232 user_group_active=active,
233 ))
234 except validation_schema.Invalid as err:
235 raise JSONRPCValidationError(colander_exc=err)
236
237 try:
238 user_group = UserGroupModel().create(
239 name=schema_data['user_group_name'],
240 description=schema_data['user_group_description'],
241 owner=owner,
242 active=schema_data['user_group_active'])
243 Session().flush()
244 creation_data = user_group.get_api_data()
245 audit_logger.store_api(
246 'user_group.create', action_data={'data': creation_data},
247 user=apiuser)
223 Session().commit()
248 Session().commit()
224 return {
249 return {
225 'msg': 'created new user group `%s`' % group_name,
250 'msg': 'created new user group `%s`' % group_name,
226 'user_group': ug.get_api_data()
251 'user_group': creation_data
227 }
252 }
228 except Exception:
253 except Exception:
229 log.exception("Error occurred during creation of user group")
254 log.exception("Error occurred during creation of user group")
230 raise JSONRPCError('failed to create group `%s`' % (group_name,))
255 raise JSONRPCError('failed to create group `%s`' % (group_name,))
231
256
232
257
233 @jsonrpc_method()
258 @jsonrpc_method()
234 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
259 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
235 description=Optional(''), owner=Optional(None),
260 description=Optional(''), owner=Optional(None),
236 active=Optional(True)):
261 active=Optional(True)):
237 """
262 """
238 Updates the specified `user group` with the details provided.
263 Updates the specified `user group` with the details provided.
239
264
240 This command can only be run using an |authtoken| with admin rights to
265 This command can only be run using an |authtoken| with admin rights to
241 the specified repository.
266 the specified repository.
242
267
243 :param apiuser: This is filled automatically from the |authtoken|.
268 :param apiuser: This is filled automatically from the |authtoken|.
244 :type apiuser: AuthUser
269 :type apiuser: AuthUser
245 :param usergroupid: Set the id of the `user group` to update.
270 :param usergroupid: Set the id of the `user group` to update.
246 :type usergroupid: str or int
271 :type usergroupid: str or int
247 :param group_name: Set the new name the `user group`
272 :param group_name: Set the new name the `user group`
248 :type group_name: str
273 :type group_name: str
249 :param description: Give a description for the `user group`
274 :param description: Give a description for the `user group`
250 :type description: str
275 :type description: str
251 :param owner: Set the owner of the `user group`.
276 :param owner: Set the owner of the `user group`.
252 :type owner: Optional(str or int)
277 :type owner: Optional(str or int)
253 :param active: Set the group as active.
278 :param active: Set the group as active.
254 :type active: Optional(``True`` | ``False``)
279 :type active: Optional(``True`` | ``False``)
255
280
256 Example output:
281 Example output:
257
282
258 .. code-block:: bash
283 .. code-block:: bash
259
284
260 id : <id_given_in_input>
285 id : <id_given_in_input>
261 result : {
286 result : {
262 "msg": 'updated user group ID:<user group id> <user group name>',
287 "msg": 'updated user group ID:<user group id> <user group name>',
263 "user_group": <user_group_object>
288 "user_group": <user_group_object>
264 }
289 }
265 error : null
290 error : null
266
291
267 Example error output:
292 Example error output:
268
293
269 .. code-block:: bash
294 .. code-block:: bash
270
295
271 id : <id_given_in_input>
296 id : <id_given_in_input>
272 result : null
297 result : null
273 error : {
298 error : {
274 "failed to update user group `<user group name>`"
299 "failed to update user group `<user group name>`"
275 }
300 }
276
301
277 """
302 """
278
303
279 user_group = get_user_group_or_error(usergroupid)
304 user_group = get_user_group_or_error(usergroupid)
280 include_secrets = False
305 include_secrets = False
281 if not has_superadmin_permission(apiuser):
306 if not has_superadmin_permission(apiuser):
282 # check if we have admin permission for this user group !
307 # check if we have admin permission for this user group !
283 _perms = ('usergroup.admin',)
308 _perms = ('usergroup.admin',)
284 if not HasUserGroupPermissionAnyApi(*_perms)(
309 if not HasUserGroupPermissionAnyApi(*_perms)(
285 user=apiuser, user_group_name=user_group.users_group_name):
310 user=apiuser, user_group_name=user_group.users_group_name):
286 raise JSONRPCError(
311 raise JSONRPCError(
287 'user group `%s` does not exist' % (usergroupid,))
312 'user group `%s` does not exist' % (usergroupid,))
288 else:
313 else:
289 include_secrets = True
314 include_secrets = True
290
315
291 if not isinstance(owner, Optional):
316 if not isinstance(owner, Optional):
292 owner = get_user_or_error(owner)
317 owner = get_user_or_error(owner)
293
318
319 old_data = user_group.get_api_data()
294 updates = {}
320 updates = {}
295 store_update(updates, group_name, 'users_group_name')
321 store_update(updates, group_name, 'users_group_name')
296 store_update(updates, description, 'user_group_description')
322 store_update(updates, description, 'user_group_description')
297 store_update(updates, owner, 'user')
323 store_update(updates, owner, 'user')
298 store_update(updates, active, 'users_group_active')
324 store_update(updates, active, 'users_group_active')
299 try:
325 try:
300 UserGroupModel().update(user_group, updates)
326 UserGroupModel().update(user_group, updates)
327 audit_logger.store_api(
328 'user_group.edit', action_data={'old_data': old_data},
329 user=apiuser)
301 Session().commit()
330 Session().commit()
302 return {
331 return {
303 'msg': 'updated user group ID:%s %s' % (
332 'msg': 'updated user group ID:%s %s' % (
304 user_group.users_group_id, user_group.users_group_name),
333 user_group.users_group_id, user_group.users_group_name),
305 'user_group': user_group.get_api_data(
334 'user_group': user_group.get_api_data(
306 include_secrets=include_secrets)
335 include_secrets=include_secrets)
307 }
336 }
308 except Exception:
337 except Exception:
309 log.exception("Error occurred during update of user group")
338 log.exception("Error occurred during update of user group")
310 raise JSONRPCError(
339 raise JSONRPCError(
311 'failed to update user group `%s`' % (usergroupid,))
340 'failed to update user group `%s`' % (usergroupid,))
312
341
313
342
314 @jsonrpc_method()
343 @jsonrpc_method()
315 def delete_user_group(request, apiuser, usergroupid):
344 def delete_user_group(request, apiuser, usergroupid):
316 """
345 """
317 Deletes the specified `user group`.
346 Deletes the specified `user group`.
318
347
319 This command can only be run using an |authtoken| with admin rights to
348 This command can only be run using an |authtoken| with admin rights to
320 the specified repository.
349 the specified repository.
321
350
322 This command takes the following options:
351 This command takes the following options:
323
352
324 :param apiuser: filled automatically from apikey
353 :param apiuser: filled automatically from apikey
325 :type apiuser: AuthUser
354 :type apiuser: AuthUser
326 :param usergroupid:
355 :param usergroupid:
327 :type usergroupid: int
356 :type usergroupid: int
328
357
329 Example output:
358 Example output:
330
359
331 .. code-block:: bash
360 .. code-block:: bash
332
361
333 id : <id_given_in_input>
362 id : <id_given_in_input>
334 result : {
363 result : {
335 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
364 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
336 }
365 }
337 error : null
366 error : null
338
367
339 Example error output:
368 Example error output:
340
369
341 .. code-block:: bash
370 .. code-block:: bash
342
371
343 id : <id_given_in_input>
372 id : <id_given_in_input>
344 result : null
373 result : null
345 error : {
374 error : {
346 "failed to delete user group ID:<user_group_id> <user_group_name>"
375 "failed to delete user group ID:<user_group_id> <user_group_name>"
347 or
376 or
348 "RepoGroup assigned to <repo_groups_list>"
377 "RepoGroup assigned to <repo_groups_list>"
349 }
378 }
350
379
351 """
380 """
352
381
353 user_group = get_user_group_or_error(usergroupid)
382 user_group = get_user_group_or_error(usergroupid)
354 if not has_superadmin_permission(apiuser):
383 if not has_superadmin_permission(apiuser):
355 # check if we have admin permission for this user group !
384 # check if we have admin permission for this user group !
356 _perms = ('usergroup.admin',)
385 _perms = ('usergroup.admin',)
357 if not HasUserGroupPermissionAnyApi(*_perms)(
386 if not HasUserGroupPermissionAnyApi(*_perms)(
358 user=apiuser, user_group_name=user_group.users_group_name):
387 user=apiuser, user_group_name=user_group.users_group_name):
359 raise JSONRPCError(
388 raise JSONRPCError(
360 'user group `%s` does not exist' % (usergroupid,))
389 'user group `%s` does not exist' % (usergroupid,))
361
390
391 old_data = user_group.get_api_data()
362 try:
392 try:
363 UserGroupModel().delete(user_group)
393 UserGroupModel().delete(user_group)
394 audit_logger.store_api(
395 'user_group.delete', action_data={'old_data': old_data},
396 user=apiuser)
364 Session().commit()
397 Session().commit()
365 return {
398 return {
366 'msg': 'deleted user group ID:%s %s' % (
399 'msg': 'deleted user group ID:%s %s' % (
367 user_group.users_group_id, user_group.users_group_name),
400 user_group.users_group_id, user_group.users_group_name),
368 'user_group': None
401 'user_group': None
369 }
402 }
370 except UserGroupAssignedException as e:
403 except UserGroupAssignedException as e:
371 log.exception("UserGroupAssigned error")
404 log.exception("UserGroupAssigned error")
372 raise JSONRPCError(str(e))
405 raise JSONRPCError(str(e))
373 except Exception:
406 except Exception:
374 log.exception("Error occurred during deletion of user group")
407 log.exception("Error occurred during deletion of user group")
375 raise JSONRPCError(
408 raise JSONRPCError(
376 'failed to delete user group ID:%s %s' %(
409 'failed to delete user group ID:%s %s' %(
377 user_group.users_group_id, user_group.users_group_name))
410 user_group.users_group_id, user_group.users_group_name))
378
411
379
412
380 @jsonrpc_method()
413 @jsonrpc_method()
381 def add_user_to_user_group(request, apiuser, usergroupid, userid):
414 def add_user_to_user_group(request, apiuser, usergroupid, userid):
382 """
415 """
383 Adds a user to a `user group`. If the user already exists in the group
416 Adds a user to a `user group`. If the user already exists in the group
384 this command will return false.
417 this command will return false.
385
418
386 This command can only be run using an |authtoken| with admin rights to
419 This command can only be run using an |authtoken| with admin rights to
387 the specified user group.
420 the specified user group.
388
421
389 This command takes the following options:
422 This command takes the following options:
390
423
391 :param apiuser: This is filled automatically from the |authtoken|.
424 :param apiuser: This is filled automatically from the |authtoken|.
392 :type apiuser: AuthUser
425 :type apiuser: AuthUser
393 :param usergroupid: Set the name of the `user group` to which a
426 :param usergroupid: Set the name of the `user group` to which a
394 user will be added.
427 user will be added.
395 :type usergroupid: int
428 :type usergroupid: int
396 :param userid: Set the `user_id` of the user to add to the group.
429 :param userid: Set the `user_id` of the user to add to the group.
397 :type userid: int
430 :type userid: int
398
431
399 Example output:
432 Example output:
400
433
401 .. code-block:: bash
434 .. code-block:: bash
402
435
403 id : <id_given_in_input>
436 id : <id_given_in_input>
404 result : {
437 result : {
405 "success": True|False # depends on if member is in group
438 "success": True|False # depends on if member is in group
406 "msg": "added member `<username>` to user group `<groupname>` |
439 "msg": "added member `<username>` to user group `<groupname>` |
407 User is already in that group"
440 User is already in that group"
408
441
409 }
442 }
410 error : null
443 error : null
411
444
412 Example error output:
445 Example error output:
413
446
414 .. code-block:: bash
447 .. code-block:: bash
415
448
416 id : <id_given_in_input>
449 id : <id_given_in_input>
417 result : null
450 result : null
418 error : {
451 error : {
419 "failed to add member to user group `<user_group_name>`"
452 "failed to add member to user group `<user_group_name>`"
420 }
453 }
421
454
422 """
455 """
423
456
424 user = get_user_or_error(userid)
457 user = get_user_or_error(userid)
425 user_group = get_user_group_or_error(usergroupid)
458 user_group = get_user_group_or_error(usergroupid)
426 if not has_superadmin_permission(apiuser):
459 if not has_superadmin_permission(apiuser):
427 # check if we have admin permission for this user group !
460 # check if we have admin permission for this user group !
428 _perms = ('usergroup.admin',)
461 _perms = ('usergroup.admin',)
429 if not HasUserGroupPermissionAnyApi(*_perms)(
462 if not HasUserGroupPermissionAnyApi(*_perms)(
430 user=apiuser, user_group_name=user_group.users_group_name):
463 user=apiuser, user_group_name=user_group.users_group_name):
431 raise JSONRPCError('user group `%s` does not exist' % (
464 raise JSONRPCError('user group `%s` does not exist' % (
432 usergroupid,))
465 usergroupid,))
433
466
434 try:
467 try:
435 ugm = UserGroupModel().add_user_to_group(user_group, user)
468 ugm = UserGroupModel().add_user_to_group(user_group, user)
436 success = True if ugm is not True else False
469 success = True if ugm is not True else False
437 msg = 'added member `%s` to user group `%s`' % (
470 msg = 'added member `%s` to user group `%s`' % (
438 user.username, user_group.users_group_name
471 user.username, user_group.users_group_name
439 )
472 )
440 msg = msg if success else 'User is already in that group'
473 msg = msg if success else 'User is already in that group'
474 if success:
475 user_data = user.get_api_data()
476 audit_logger.store_api(
477 'user_group.edit.member.add', action_data={'user': user_data},
478 user=apiuser)
479
441 Session().commit()
480 Session().commit()
442
481
443 return {
482 return {
444 'success': success,
483 'success': success,
445 'msg': msg
484 'msg': msg
446 }
485 }
447 except Exception:
486 except Exception:
448 log.exception("Error occurred during adding a member to user group")
487 log.exception("Error occurred during adding a member to user group")
449 raise JSONRPCError(
488 raise JSONRPCError(
450 'failed to add member to user group `%s`' % (
489 'failed to add member to user group `%s`' % (
451 user_group.users_group_name,
490 user_group.users_group_name,
452 )
491 )
453 )
492 )
454
493
455
494
456 @jsonrpc_method()
495 @jsonrpc_method()
457 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
496 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
458 """
497 """
459 Removes a user from a user group.
498 Removes a user from a user group.
460
499
461 * If the specified user is not in the group, this command will return
500 * If the specified user is not in the group, this command will return
462 `false`.
501 `false`.
463
502
464 This command can only be run using an |authtoken| with admin rights to
503 This command can only be run using an |authtoken| with admin rights to
465 the specified user group.
504 the specified user group.
466
505
467 :param apiuser: This is filled automatically from the |authtoken|.
506 :param apiuser: This is filled automatically from the |authtoken|.
468 :type apiuser: AuthUser
507 :type apiuser: AuthUser
469 :param usergroupid: Sets the user group name.
508 :param usergroupid: Sets the user group name.
470 :type usergroupid: str or int
509 :type usergroupid: str or int
471 :param userid: The user you wish to remove from |RCE|.
510 :param userid: The user you wish to remove from |RCE|.
472 :type userid: str or int
511 :type userid: str or int
473
512
474 Example output:
513 Example output:
475
514
476 .. code-block:: bash
515 .. code-block:: bash
477
516
478 id : <id_given_in_input>
517 id : <id_given_in_input>
479 result: {
518 result: {
480 "success": True|False, # depends on if member is in group
519 "success": True|False, # depends on if member is in group
481 "msg": "removed member <username> from user group <groupname> |
520 "msg": "removed member <username> from user group <groupname> |
482 User wasn't in group"
521 User wasn't in group"
483 }
522 }
484 error: null
523 error: null
485
524
486 """
525 """
487
526
488 user = get_user_or_error(userid)
527 user = get_user_or_error(userid)
489 user_group = get_user_group_or_error(usergroupid)
528 user_group = get_user_group_or_error(usergroupid)
490 if not has_superadmin_permission(apiuser):
529 if not has_superadmin_permission(apiuser):
491 # check if we have admin permission for this user group !
530 # check if we have admin permission for this user group !
492 _perms = ('usergroup.admin',)
531 _perms = ('usergroup.admin',)
493 if not HasUserGroupPermissionAnyApi(*_perms)(
532 if not HasUserGroupPermissionAnyApi(*_perms)(
494 user=apiuser, user_group_name=user_group.users_group_name):
533 user=apiuser, user_group_name=user_group.users_group_name):
495 raise JSONRPCError(
534 raise JSONRPCError(
496 'user group `%s` does not exist' % (usergroupid,))
535 'user group `%s` does not exist' % (usergroupid,))
497
536
498 try:
537 try:
499 success = UserGroupModel().remove_user_from_group(user_group, user)
538 success = UserGroupModel().remove_user_from_group(user_group, user)
500 msg = 'removed member `%s` from user group `%s`' % (
539 msg = 'removed member `%s` from user group `%s`' % (
501 user.username, user_group.users_group_name
540 user.username, user_group.users_group_name
502 )
541 )
503 msg = msg if success else "User wasn't in group"
542 msg = msg if success else "User wasn't in group"
543 if success:
544 user_data = user.get_api_data()
545 audit_logger.store_api(
546 'user_group.edit.member.delete', action_data={'user': user_data},
547 user=apiuser)
548
504 Session().commit()
549 Session().commit()
505 return {'success': success, 'msg': msg}
550 return {'success': success, 'msg': msg}
506 except Exception:
551 except Exception:
507 log.exception("Error occurred during removing an member from user group")
552 log.exception("Error occurred during removing an member from user group")
508 raise JSONRPCError(
553 raise JSONRPCError(
509 'failed to remove member from user group `%s`' % (
554 'failed to remove member from user group `%s`' % (
510 user_group.users_group_name,
555 user_group.users_group_name,
511 )
556 )
512 )
557 )
513
558
514
559
515 @jsonrpc_method()
560 @jsonrpc_method()
516 def grant_user_permission_to_user_group(
561 def grant_user_permission_to_user_group(
517 request, apiuser, usergroupid, userid, perm):
562 request, apiuser, usergroupid, userid, perm):
518 """
563 """
519 Set permissions for a user in a user group.
564 Set permissions for a user in a user group.
520
565
521 :param apiuser: This is filled automatically from the |authtoken|.
566 :param apiuser: This is filled automatically from the |authtoken|.
522 :type apiuser: AuthUser
567 :type apiuser: AuthUser
523 :param usergroupid: Set the user group to edit permissions on.
568 :param usergroupid: Set the user group to edit permissions on.
524 :type usergroupid: str or int
569 :type usergroupid: str or int
525 :param userid: Set the user from whom you wish to set permissions.
570 :param userid: Set the user from whom you wish to set permissions.
526 :type userid: str
571 :type userid: str
527 :param perm: (usergroup.(none|read|write|admin))
572 :param perm: (usergroup.(none|read|write|admin))
528 :type perm: str
573 :type perm: str
529
574
530 Example output:
575 Example output:
531
576
532 .. code-block:: bash
577 .. code-block:: bash
533
578
534 id : <id_given_in_input>
579 id : <id_given_in_input>
535 result : {
580 result : {
536 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
581 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
537 "success": true
582 "success": true
538 }
583 }
539 error : null
584 error : null
540 """
585 """
541
586
542 user_group = get_user_group_or_error(usergroupid)
587 user_group = get_user_group_or_error(usergroupid)
543
588
544 if not has_superadmin_permission(apiuser):
589 if not has_superadmin_permission(apiuser):
545 # check if we have admin permission for this user group !
590 # check if we have admin permission for this user group !
546 _perms = ('usergroup.admin',)
591 _perms = ('usergroup.admin',)
547 if not HasUserGroupPermissionAnyApi(*_perms)(
592 if not HasUserGroupPermissionAnyApi(*_perms)(
548 user=apiuser, user_group_name=user_group.users_group_name):
593 user=apiuser, user_group_name=user_group.users_group_name):
549 raise JSONRPCError(
594 raise JSONRPCError(
550 'user group `%s` does not exist' % (usergroupid,))
595 'user group `%s` does not exist' % (usergroupid,))
551
596
552 user = get_user_or_error(userid)
597 user = get_user_or_error(userid)
553 perm = get_perm_or_error(perm, prefix='usergroup.')
598 perm = get_perm_or_error(perm, prefix='usergroup.')
554
599
555 try:
600 try:
556 UserGroupModel().grant_user_permission(
601 UserGroupModel().grant_user_permission(
557 user_group=user_group, user=user, perm=perm)
602 user_group=user_group, user=user, perm=perm)
558 Session().commit()
603 Session().commit()
559 return {
604 return {
560 'msg':
605 'msg':
561 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
606 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
562 perm.permission_name, user.username,
607 perm.permission_name, user.username,
563 user_group.users_group_name
608 user_group.users_group_name
564 ),
609 ),
565 'success': True
610 'success': True
566 }
611 }
567 except Exception:
612 except Exception:
568 log.exception("Error occurred during editing permissions "
613 log.exception("Error occurred during editing permissions "
569 "for user in user group")
614 "for user in user group")
570 raise JSONRPCError(
615 raise JSONRPCError(
571 'failed to edit permission for user: '
616 'failed to edit permission for user: '
572 '`%s` in user group: `%s`' % (
617 '`%s` in user group: `%s`' % (
573 userid, user_group.users_group_name))
618 userid, user_group.users_group_name))
574
619
575
620
576 @jsonrpc_method()
621 @jsonrpc_method()
577 def revoke_user_permission_from_user_group(
622 def revoke_user_permission_from_user_group(
578 request, apiuser, usergroupid, userid):
623 request, apiuser, usergroupid, userid):
579 """
624 """
580 Revoke a users permissions in a user group.
625 Revoke a users permissions in a user group.
581
626
582 :param apiuser: This is filled automatically from the |authtoken|.
627 :param apiuser: This is filled automatically from the |authtoken|.
583 :type apiuser: AuthUser
628 :type apiuser: AuthUser
584 :param usergroupid: Set the user group from which to revoke the user
629 :param usergroupid: Set the user group from which to revoke the user
585 permissions.
630 permissions.
586 :type: usergroupid: str or int
631 :type: usergroupid: str or int
587 :param userid: Set the userid of the user whose permissions will be
632 :param userid: Set the userid of the user whose permissions will be
588 revoked.
633 revoked.
589 :type userid: str
634 :type userid: str
590
635
591 Example output:
636 Example output:
592
637
593 .. code-block:: bash
638 .. code-block:: bash
594
639
595 id : <id_given_in_input>
640 id : <id_given_in_input>
596 result : {
641 result : {
597 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
642 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
598 "success": true
643 "success": true
599 }
644 }
600 error : null
645 error : null
601 """
646 """
602
647
603 user_group = get_user_group_or_error(usergroupid)
648 user_group = get_user_group_or_error(usergroupid)
604
649
605 if not has_superadmin_permission(apiuser):
650 if not has_superadmin_permission(apiuser):
606 # check if we have admin permission for this user group !
651 # check if we have admin permission for this user group !
607 _perms = ('usergroup.admin',)
652 _perms = ('usergroup.admin',)
608 if not HasUserGroupPermissionAnyApi(*_perms)(
653 if not HasUserGroupPermissionAnyApi(*_perms)(
609 user=apiuser, user_group_name=user_group.users_group_name):
654 user=apiuser, user_group_name=user_group.users_group_name):
610 raise JSONRPCError(
655 raise JSONRPCError(
611 'user group `%s` does not exist' % (usergroupid,))
656 'user group `%s` does not exist' % (usergroupid,))
612
657
613 user = get_user_or_error(userid)
658 user = get_user_or_error(userid)
614
659
615 try:
660 try:
616 UserGroupModel().revoke_user_permission(
661 UserGroupModel().revoke_user_permission(
617 user_group=user_group, user=user)
662 user_group=user_group, user=user)
618 Session().commit()
663 Session().commit()
619 return {
664 return {
620 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
665 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
621 user.username, user_group.users_group_name
666 user.username, user_group.users_group_name
622 ),
667 ),
623 'success': True
668 'success': True
624 }
669 }
625 except Exception:
670 except Exception:
626 log.exception("Error occurred during editing permissions "
671 log.exception("Error occurred during editing permissions "
627 "for user in user group")
672 "for user in user group")
628 raise JSONRPCError(
673 raise JSONRPCError(
629 'failed to edit permission for user: `%s` in user group: `%s`'
674 'failed to edit permission for user: `%s` in user group: `%s`'
630 % (userid, user_group.users_group_name))
675 % (userid, user_group.users_group_name))
631
676
632
677
633 @jsonrpc_method()
678 @jsonrpc_method()
634 def grant_user_group_permission_to_user_group(
679 def grant_user_group_permission_to_user_group(
635 request, apiuser, usergroupid, sourceusergroupid, perm):
680 request, apiuser, usergroupid, sourceusergroupid, perm):
636 """
681 """
637 Give one user group permissions to another user group.
682 Give one user group permissions to another user group.
638
683
639 :param apiuser: This is filled automatically from the |authtoken|.
684 :param apiuser: This is filled automatically from the |authtoken|.
640 :type apiuser: AuthUser
685 :type apiuser: AuthUser
641 :param usergroupid: Set the user group on which to edit permissions.
686 :param usergroupid: Set the user group on which to edit permissions.
642 :type usergroupid: str or int
687 :type usergroupid: str or int
643 :param sourceusergroupid: Set the source user group to which
688 :param sourceusergroupid: Set the source user group to which
644 access/permissions will be granted.
689 access/permissions will be granted.
645 :type sourceusergroupid: str or int
690 :type sourceusergroupid: str or int
646 :param perm: (usergroup.(none|read|write|admin))
691 :param perm: (usergroup.(none|read|write|admin))
647 :type perm: str
692 :type perm: str
648
693
649 Example output:
694 Example output:
650
695
651 .. code-block:: bash
696 .. code-block:: bash
652
697
653 id : <id_given_in_input>
698 id : <id_given_in_input>
654 result : {
699 result : {
655 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
700 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
656 "success": true
701 "success": true
657 }
702 }
658 error : null
703 error : null
659 """
704 """
660
705
661 user_group = get_user_group_or_error(sourceusergroupid)
706 user_group = get_user_group_or_error(sourceusergroupid)
662 target_user_group = get_user_group_or_error(usergroupid)
707 target_user_group = get_user_group_or_error(usergroupid)
663 perm = get_perm_or_error(perm, prefix='usergroup.')
708 perm = get_perm_or_error(perm, prefix='usergroup.')
664
709
665 if not has_superadmin_permission(apiuser):
710 if not has_superadmin_permission(apiuser):
666 # check if we have admin permission for this user group !
711 # check if we have admin permission for this user group !
667 _perms = ('usergroup.admin',)
712 _perms = ('usergroup.admin',)
668 if not HasUserGroupPermissionAnyApi(*_perms)(
713 if not HasUserGroupPermissionAnyApi(*_perms)(
669 user=apiuser,
714 user=apiuser,
670 user_group_name=target_user_group.users_group_name):
715 user_group_name=target_user_group.users_group_name):
671 raise JSONRPCError(
716 raise JSONRPCError(
672 'to user group `%s` does not exist' % (usergroupid,))
717 'to user group `%s` does not exist' % (usergroupid,))
673
718
674 # check if we have at least read permission for source user group !
719 # check if we have at least read permission for source user group !
675 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
720 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
676 if not HasUserGroupPermissionAnyApi(*_perms)(
721 if not HasUserGroupPermissionAnyApi(*_perms)(
677 user=apiuser, user_group_name=user_group.users_group_name):
722 user=apiuser, user_group_name=user_group.users_group_name):
678 raise JSONRPCError(
723 raise JSONRPCError(
679 'user group `%s` does not exist' % (sourceusergroupid,))
724 'user group `%s` does not exist' % (sourceusergroupid,))
680
725
681 try:
726 try:
682 UserGroupModel().grant_user_group_permission(
727 UserGroupModel().grant_user_group_permission(
683 target_user_group=target_user_group,
728 target_user_group=target_user_group,
684 user_group=user_group, perm=perm)
729 user_group=user_group, perm=perm)
685 Session().commit()
730 Session().commit()
686
731
687 return {
732 return {
688 'msg': 'Granted perm: `%s` for user group: `%s` '
733 'msg': 'Granted perm: `%s` for user group: `%s` '
689 'in user group: `%s`' % (
734 'in user group: `%s`' % (
690 perm.permission_name, user_group.users_group_name,
735 perm.permission_name, user_group.users_group_name,
691 target_user_group.users_group_name
736 target_user_group.users_group_name
692 ),
737 ),
693 'success': True
738 'success': True
694 }
739 }
695 except Exception:
740 except Exception:
696 log.exception("Error occurred during editing permissions "
741 log.exception("Error occurred during editing permissions "
697 "for user group in user group")
742 "for user group in user group")
698 raise JSONRPCError(
743 raise JSONRPCError(
699 'failed to edit permission for user group: `%s` in '
744 'failed to edit permission for user group: `%s` in '
700 'user group: `%s`' % (
745 'user group: `%s`' % (
701 sourceusergroupid, target_user_group.users_group_name
746 sourceusergroupid, target_user_group.users_group_name
702 )
747 )
703 )
748 )
704
749
705
750
706 @jsonrpc_method()
751 @jsonrpc_method()
707 def revoke_user_group_permission_from_user_group(
752 def revoke_user_group_permission_from_user_group(
708 request, apiuser, usergroupid, sourceusergroupid):
753 request, apiuser, usergroupid, sourceusergroupid):
709 """
754 """
710 Revoke the permissions that one user group has to another.
755 Revoke the permissions that one user group has to another.
711
756
712 :param apiuser: This is filled automatically from the |authtoken|.
757 :param apiuser: This is filled automatically from the |authtoken|.
713 :type apiuser: AuthUser
758 :type apiuser: AuthUser
714 :param usergroupid: Set the user group on which to edit permissions.
759 :param usergroupid: Set the user group on which to edit permissions.
715 :type usergroupid: str or int
760 :type usergroupid: str or int
716 :param sourceusergroupid: Set the user group from which permissions
761 :param sourceusergroupid: Set the user group from which permissions
717 are revoked.
762 are revoked.
718 :type sourceusergroupid: str or int
763 :type sourceusergroupid: str or int
719
764
720 Example output:
765 Example output:
721
766
722 .. code-block:: bash
767 .. code-block:: bash
723
768
724 id : <id_given_in_input>
769 id : <id_given_in_input>
725 result : {
770 result : {
726 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
771 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
727 "success": true
772 "success": true
728 }
773 }
729 error : null
774 error : null
730 """
775 """
731
776
732 user_group = get_user_group_or_error(sourceusergroupid)
777 user_group = get_user_group_or_error(sourceusergroupid)
733 target_user_group = get_user_group_or_error(usergroupid)
778 target_user_group = get_user_group_or_error(usergroupid)
734
779
735 if not has_superadmin_permission(apiuser):
780 if not has_superadmin_permission(apiuser):
736 # check if we have admin permission for this user group !
781 # check if we have admin permission for this user group !
737 _perms = ('usergroup.admin',)
782 _perms = ('usergroup.admin',)
738 if not HasUserGroupPermissionAnyApi(*_perms)(
783 if not HasUserGroupPermissionAnyApi(*_perms)(
739 user=apiuser,
784 user=apiuser,
740 user_group_name=target_user_group.users_group_name):
785 user_group_name=target_user_group.users_group_name):
741 raise JSONRPCError(
786 raise JSONRPCError(
742 'to user group `%s` does not exist' % (usergroupid,))
787 'to user group `%s` does not exist' % (usergroupid,))
743
788
744 # check if we have at least read permission
789 # check if we have at least read permission
745 # for the source user group !
790 # for the source user group !
746 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
791 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
747 if not HasUserGroupPermissionAnyApi(*_perms)(
792 if not HasUserGroupPermissionAnyApi(*_perms)(
748 user=apiuser, user_group_name=user_group.users_group_name):
793 user=apiuser, user_group_name=user_group.users_group_name):
749 raise JSONRPCError(
794 raise JSONRPCError(
750 'user group `%s` does not exist' % (sourceusergroupid,))
795 'user group `%s` does not exist' % (sourceusergroupid,))
751
796
752 try:
797 try:
753 UserGroupModel().revoke_user_group_permission(
798 UserGroupModel().revoke_user_group_permission(
754 target_user_group=target_user_group, user_group=user_group)
799 target_user_group=target_user_group, user_group=user_group)
755 Session().commit()
800 Session().commit()
756
801
757 return {
802 return {
758 'msg': 'Revoked perm for user group: '
803 'msg': 'Revoked perm for user group: '
759 '`%s` in user group: `%s`' % (
804 '`%s` in user group: `%s`' % (
760 user_group.users_group_name,
805 user_group.users_group_name,
761 target_user_group.users_group_name
806 target_user_group.users_group_name
762 ),
807 ),
763 'success': True
808 'success': True
764 }
809 }
765 except Exception:
810 except Exception:
766 log.exception("Error occurred during editing permissions "
811 log.exception("Error occurred during editing permissions "
767 "for user group in user group")
812 "for user group in user group")
768 raise JSONRPCError(
813 raise JSONRPCError(
769 'failed to edit permission for user group: '
814 'failed to edit permission for user group: '
770 '`%s` in user group: `%s`' % (
815 '`%s` in user group: `%s`' % (
771 sourceusergroupid, target_user_group.users_group_name
816 sourceusergroupid, target_user_group.users_group_name
772 )
817 )
773 )
818 )
@@ -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/
@@ -1,163 +1,357 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 from pylons import tmpl_context as c
23 from pylons import tmpl_context as c
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int
27 from rhodecode.lib.utils import PartialRenderer
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.ext_json import json
28 from rhodecode.model import repo
31 from rhodecode.model import repo
32 from rhodecode.model import repo_group
29 from rhodecode.model.db import User
33 from rhodecode.model.db import User
30 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
31
35
32 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
33
37
34
38
35 ADMIN_PREFIX = '/_admin'
39 ADMIN_PREFIX = '/_admin'
36 STATIC_FILE_PREFIX = '/_static'
40 STATIC_FILE_PREFIX = '/_static'
37
41
38
42
43 def add_route_with_slash(config,name, pattern, **kw):
44 config.add_route(name, pattern, **kw)
45 if not pattern.endswith('/'):
46 config.add_route(name + '_slash', pattern + '/', **kw)
47
48
49 def get_format_ref_id(repo):
50 """Returns a `repo` specific reference formatter function"""
51 if h.is_svn(repo):
52 return _format_ref_id_svn
53 else:
54 return _format_ref_id
55
56
57 def _format_ref_id(name, raw_id):
58 """Default formatting of a given reference `name`"""
59 return name
60
61
62 def _format_ref_id_svn(name, raw_id):
63 """Special way of formatting a reference for Subversion including path"""
64 return '%s@%s' % (name, raw_id)
65
66
39 class TemplateArgs(StrictAttributeDict):
67 class TemplateArgs(StrictAttributeDict):
40 pass
68 pass
41
69
42
70
43 class BaseAppView(object):
71 class BaseAppView(object):
44
72
45 def __init__(self, context, request):
73 def __init__(self, context, request):
46 self.request = request
74 self.request = request
47 self.context = context
75 self.context = context
48 self.session = request.session
76 self.session = request.session
49 self._rhodecode_user = request.user # auth user
77 self._rhodecode_user = request.user # auth user
50 self._rhodecode_db_user = self._rhodecode_user.get_instance()
78 self._rhodecode_db_user = self._rhodecode_user.get_instance()
51 self._maybe_needs_password_change(
79 self._maybe_needs_password_change(
52 request.matched_route.name, self._rhodecode_db_user)
80 request.matched_route.name, self._rhodecode_db_user)
53
81
54 def _maybe_needs_password_change(self, view_name, user_obj):
82 def _maybe_needs_password_change(self, view_name, user_obj):
55 log.debug('Checking if user %s needs password change on view %s',
83 log.debug('Checking if user %s needs password change on view %s',
56 user_obj, view_name)
84 user_obj, view_name)
57 skip_user_views = [
85 skip_user_views = [
58 'logout', 'login',
86 'logout', 'login',
59 'my_account_password', 'my_account_password_update'
87 'my_account_password', 'my_account_password_update'
60 ]
88 ]
61
89
62 if not user_obj:
90 if not user_obj:
63 return
91 return
64
92
65 if user_obj.username == User.DEFAULT_USER:
93 if user_obj.username == User.DEFAULT_USER:
66 return
94 return
67
95
68 now = time.time()
96 now = time.time()
69 should_change = user_obj.user_data.get('force_password_change')
97 should_change = user_obj.user_data.get('force_password_change')
70 change_after = safe_int(should_change) or 0
98 change_after = safe_int(should_change) or 0
71 if should_change and now > change_after:
99 if should_change and now > change_after:
72 log.debug('User %s requires password change', user_obj)
100 log.debug('User %s requires password change', user_obj)
73 h.flash('You are required to change your password', 'warning',
101 h.flash('You are required to change your password', 'warning',
74 ignore_duplicate=True)
102 ignore_duplicate=True)
75
103
76 if view_name not in skip_user_views:
104 if view_name not in skip_user_views:
77 raise HTTPFound(
105 raise HTTPFound(
78 self.request.route_path('my_account_password'))
106 self.request.route_path('my_account_password'))
79
107
80 def _get_local_tmpl_context(self):
108 def _get_local_tmpl_context(self, include_app_defaults=False):
81 c = TemplateArgs()
109 c = TemplateArgs()
82 c.auth_user = self.request.user
110 c.auth_user = self.request.user
111 if include_app_defaults:
112 # NOTE(marcink): after full pyramid migration include_app_defaults
113 # should be turned on by default
114 from rhodecode.lib.base import attach_context_attributes
115 attach_context_attributes(c, self.request, self.request.user.user_id)
83 return c
116 return c
84
117
85 def _register_global_c(self, tmpl_args):
118 def _register_global_c(self, tmpl_args):
86 """
119 """
87 Registers attributes to pylons global `c`
120 Registers attributes to pylons global `c`
88 """
121 """
89 # TODO(marcink): remove once pyramid migration is finished
122 # TODO(marcink): remove once pyramid migration is finished
90 for k, v in tmpl_args.items():
123 for k, v in tmpl_args.items():
91 setattr(c, k, v)
124 setattr(c, k, v)
92
125
93 def _get_template_context(self, tmpl_args):
126 def _get_template_context(self, tmpl_args):
94 self._register_global_c(tmpl_args)
127 self._register_global_c(tmpl_args)
95
128
96 local_tmpl_args = {
129 local_tmpl_args = {
97 'defaults': {},
130 'defaults': {},
98 'errors': {},
131 'errors': {},
99 }
132 }
100 local_tmpl_args.update(tmpl_args)
133 local_tmpl_args.update(tmpl_args)
101 return local_tmpl_args
134 return local_tmpl_args
102
135
103 def load_default_context(self):
136 def load_default_context(self):
104 """
137 """
105 example:
138 example:
106
139
107 def load_default_context(self):
140 def load_default_context(self):
108 c = self._get_local_tmpl_context()
141 c = self._get_local_tmpl_context()
109 c.custom_var = 'foobar'
142 c.custom_var = 'foobar'
110 self._register_global_c(c)
143 self._register_global_c(c)
111 return c
144 return c
112 """
145 """
113 raise NotImplementedError('Needs implementation in view class')
146 raise NotImplementedError('Needs implementation in view class')
114
147
115
148
116 class RepoAppView(BaseAppView):
149 class RepoAppView(BaseAppView):
117
150
118 def __init__(self, context, request):
151 def __init__(self, context, request):
119 super(RepoAppView, self).__init__(context, request)
152 super(RepoAppView, self).__init__(context, request)
120 self.db_repo = request.db_repo
153 self.db_repo = request.db_repo
121 self.db_repo_name = self.db_repo.repo_name
154 self.db_repo_name = self.db_repo.repo_name
122 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
155 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
123
156
124 def _get_local_tmpl_context(self):
157 def _handle_missing_requirements(self, error):
125 c = super(RepoAppView, self)._get_local_tmpl_context()
158 log.error(
159 'Requirements are missing for repository %s: %s',
160 self.db_repo_name, error.message)
161
162 def _get_local_tmpl_context(self, include_app_defaults=False):
163 c = super(RepoAppView, self)._get_local_tmpl_context(
164 include_app_defaults=include_app_defaults)
165
126 # register common vars for this type of view
166 # register common vars for this type of view
127 c.rhodecode_db_repo = self.db_repo
167 c.rhodecode_db_repo = self.db_repo
128 c.repo_name = self.db_repo_name
168 c.repo_name = self.db_repo_name
129 c.repository_pull_requests = self.db_repo_pull_requests
169 c.repository_pull_requests = self.db_repo_pull_requests
170
171 c.repository_requirements_missing = False
172 try:
173 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
174 except RepositoryRequirementError as e:
175 c.repository_requirements_missing = True
176 self._handle_missing_requirements(e)
177
130 return c
178 return c
131
179
132
180
181 class DataGridAppView(object):
182 """
183 Common class to have re-usable grid rendering components
184 """
185
186 def _extract_ordering(self, request, column_map=None):
187 column_map = column_map or {}
188 column_index = safe_int(request.GET.get('order[0][column]'))
189 order_dir = request.GET.get(
190 'order[0][dir]', 'desc')
191 order_by = request.GET.get(
192 'columns[%s][data][sort]' % column_index, 'name_raw')
193
194 # translate datatable to DB columns
195 order_by = column_map.get(order_by) or order_by
196
197 search_q = request.GET.get('search[value]')
198 return search_q, order_by, order_dir
199
200 def _extract_chunk(self, request):
201 start = safe_int(request.GET.get('start'), 0)
202 length = safe_int(request.GET.get('length'), 25)
203 draw = safe_int(request.GET.get('draw'))
204 return draw, start, length
205
206
207 class BaseReferencesView(RepoAppView):
208 """
209 Base for reference view for branches, tags and bookmarks.
210 """
211 def load_default_context(self):
212 c = self._get_local_tmpl_context()
213
214 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
215 c.repo_info = self.db_repo
216
217 self._register_global_c(c)
218 return c
219
220 def load_refs_context(self, ref_items, partials_template):
221 _render = PartialRenderer(partials_template)
222 _data = []
223 pre_load = ["author", "date", "message"]
224
225 is_svn = h.is_svn(self.rhodecode_vcs_repo)
226 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
227
228 for ref_name, commit_id in ref_items:
229 commit = self.rhodecode_vcs_repo.get_commit(
230 commit_id=commit_id, pre_load=pre_load)
231
232 # TODO: johbo: Unify generation of reference links
233 use_commit_id = '/' in ref_name or is_svn
234 files_url = h.url(
235 'files_home',
236 repo_name=c.repo_name,
237 f_path=ref_name if is_svn else '',
238 revision=commit_id if use_commit_id else ref_name,
239 at=ref_name)
240
241 _data.append({
242 "name": _render('name', ref_name, files_url),
243 "name_raw": ref_name,
244 "date": _render('date', commit.date),
245 "date_raw": datetime_to_time(commit.date),
246 "author": _render('author', commit.author),
247 "commit": _render(
248 'commit', commit.message, commit.raw_id, commit.idx),
249 "commit_raw": commit.idx,
250 "compare": _render(
251 'compare', format_ref_id(ref_name, commit.raw_id)),
252 })
253 c.has_references = bool(_data)
254 c.data = json.dumps(_data)
255
256
133 class RepoRoutePredicate(object):
257 class RepoRoutePredicate(object):
134 def __init__(self, val, config):
258 def __init__(self, val, config):
135 self.val = val
259 self.val = val
136
260
137 def text(self):
261 def text(self):
138 return 'repo_route = %s' % self.val
262 return 'repo_route = %s' % self.val
139
263
140 phash = text
264 phash = text
141
265
142 def __call__(self, info, request):
266 def __call__(self, info, request):
267
268 if hasattr(request, 'vcs_call'):
269 # skip vcs calls
270 return
271
143 repo_name = info['match']['repo_name']
272 repo_name = info['match']['repo_name']
144 repo_model = repo.RepoModel()
273 repo_model = repo.RepoModel()
145 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
274 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
146 # if we match quickly from database, short circuit the operation,
275
147 # and validate repo based on the type.
148 if by_name_match:
276 if by_name_match:
149 # register this as request object we can re-use later
277 # register this as request object we can re-use later
150 request.db_repo = by_name_match
278 request.db_repo = by_name_match
151 return True
279 return True
152
280
153 by_id_match = repo_model.get_repo_by_id(repo_name)
281 by_id_match = repo_model.get_repo_by_id(repo_name)
154 if by_id_match:
282 if by_id_match:
155 request.db_repo = by_id_match
283 request.db_repo = by_id_match
156 return True
284 return True
157
285
158 return False
286 return False
159
287
160
288
289 class RepoTypeRoutePredicate(object):
290 def __init__(self, val, config):
291 self.val = val or ['hg', 'git', 'svn']
292
293 def text(self):
294 return 'repo_accepted_type = %s' % self.val
295
296 phash = text
297
298 def __call__(self, info, request):
299 if hasattr(request, 'vcs_call'):
300 # skip vcs calls
301 return
302
303 rhodecode_db_repo = request.db_repo
304
305 log.debug(
306 '%s checking repo type for %s in %s',
307 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
308
309 if rhodecode_db_repo.repo_type in self.val:
310 return True
311 else:
312 log.warning('Current view is not supported for repo type:%s',
313 rhodecode_db_repo.repo_type)
314 #
315 # h.flash(h.literal(
316 # _('Action not supported for %s.' % rhodecode_repo.alias)),
317 # category='warning')
318 # return redirect(
319 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
320
321 return False
322
323
324 class RepoGroupRoutePredicate(object):
325 def __init__(self, val, config):
326 self.val = val
327
328 def text(self):
329 return 'repo_group_route = %s' % self.val
330
331 phash = text
332
333 def __call__(self, info, request):
334 if hasattr(request, 'vcs_call'):
335 # skip vcs calls
336 return
337
338 repo_group_name = info['match']['repo_group_name']
339 repo_group_model = repo_group.RepoGroupModel()
340 by_name_match = repo_group_model.get_by_group_name(
341 repo_group_name, cache=True)
342
343 if by_name_match:
344 # register this as request object we can re-use later
345 request.db_repo_group = by_name_match
346 return True
347
348 return False
349
350
161 def includeme(config):
351 def includeme(config):
162 config.add_route_predicate(
352 config.add_route_predicate(
163 'repo_route', RepoRoutePredicate)
353 'repo_route', RepoRoutePredicate)
354 config.add_route_predicate(
355 'repo_accepted_types', RepoTypeRoutePredicate)
356 config.add_route_predicate(
357 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,99 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
34 pattern='/audit_logs')
35
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:[0-9]+}')
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:[0-9]+}')
42 config.add_route(
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:[0-9]+}')
45
46 config.add_route(
33 name='admin_settings_open_source',
47 name='admin_settings_open_source',
34 pattern='/settings/open_source')
48 pattern='/settings/open_source')
35 config.add_route(
49 config.add_route(
36 name='admin_settings_vcs_svn_generate_cfg',
50 name='admin_settings_vcs_svn_generate_cfg',
37 pattern='/settings/vcs/svn_generate_cfg')
51 pattern='/settings/vcs/svn_generate_cfg')
38
52
39 config.add_route(
53 config.add_route(
40 name='admin_settings_system',
54 name='admin_settings_system',
41 pattern='/settings/system')
55 pattern='/settings/system')
42 config.add_route(
56 config.add_route(
43 name='admin_settings_system_update',
57 name='admin_settings_system_update',
44 pattern='/settings/system/updates')
58 pattern='/settings/system/updates')
45
59
46 config.add_route(
60 config.add_route(
47 name='admin_settings_sessions',
61 name='admin_settings_sessions',
48 pattern='/settings/sessions')
62 pattern='/settings/sessions')
49 config.add_route(
63 config.add_route(
50 name='admin_settings_sessions_cleanup',
64 name='admin_settings_sessions_cleanup',
51 pattern='/settings/sessions/cleanup')
65 pattern='/settings/sessions/cleanup')
52
66
67 # global permissions
68 config.add_route(
69 name='admin_permissions_ips',
70 pattern='/permissions/ips')
71
53 # users admin
72 # users admin
54 config.add_route(
73 config.add_route(
55 name='users',
74 name='users',
56 pattern='/users')
75 pattern='/users')
57
76
58 config.add_route(
77 config.add_route(
59 name='users_data',
78 name='users_data',
60 pattern='/users_data')
79 pattern='/users_data')
61
80
62 # user auth tokens
81 # user auth tokens
63 config.add_route(
82 config.add_route(
64 name='edit_user_auth_tokens',
83 name='edit_user_auth_tokens',
65 pattern='/users/{user_id:\d+}/edit/auth_tokens')
84 pattern='/users/{user_id:\d+}/edit/auth_tokens')
66 config.add_route(
85 config.add_route(
67 name='edit_user_auth_tokens_add',
86 name='edit_user_auth_tokens_add',
68 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
87 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
69 config.add_route(
88 config.add_route(
70 name='edit_user_auth_tokens_delete',
89 name='edit_user_auth_tokens_delete',
71 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
90 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
72
91
92 # user emails
93 config.add_route(
94 name='edit_user_emails',
95 pattern='/users/{user_id:\d+}/edit/emails')
96 config.add_route(
97 name='edit_user_emails_add',
98 pattern='/users/{user_id:\d+}/edit/emails/new')
99 config.add_route(
100 name='edit_user_emails_delete',
101 pattern='/users/{user_id:\d+}/edit/emails/delete')
102
103 # user IPs
104 config.add_route(
105 name='edit_user_ips',
106 pattern='/users/{user_id:\d+}/edit/ips')
107 config.add_route(
108 name='edit_user_ips_add',
109 pattern='/users/{user_id:\d+}/edit/ips/new')
110 config.add_route(
111 name='edit_user_ips_delete',
112 pattern='/users/{user_id:\d+}/edit/ips/delete')
113
73 # user groups management
114 # user groups management
74 config.add_route(
115 config.add_route(
75 name='edit_user_groups_management',
116 name='edit_user_groups_management',
76 pattern='/users/{user_id:\d+}/edit/groups_management')
117 pattern='/users/{user_id:\d+}/edit/groups_management')
77
118
78 config.add_route(
119 config.add_route(
79 name='edit_user_groups_management_updates',
120 name='edit_user_groups_management_updates',
80 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
121 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
81
122
82 # user audit logs
123 # user audit logs
83 config.add_route(
124 config.add_route(
84 name='edit_user_audit_logs',
125 name='edit_user_audit_logs',
85 pattern='/users/{user_id:\d+}/edit/audit')
126 pattern='/users/{user_id:\d+}/edit/audit')
86
127
87
128
88 def includeme(config):
129 def includeme(config):
89 settings = config.get_settings()
130 settings = config.get_settings()
90
131
91 # Create admin navigation registry and add it to the pyramid registry.
132 # Create admin navigation registry and add it to the pyramid registry.
92 labs_active = str2bool(settings.get('labs_settings_active', False))
133 labs_active = str2bool(settings.get('labs_settings_active', False))
93 navigation_registry = NavigationRegistry(labs_active=labs_active)
134 navigation_registry = NavigationRegistry(labs_active=labs_active)
94 config.registry.registerUtility(navigation_registry)
135 config.registry.registerUtility(navigation_registry)
95
136
137 # main admin routes
138 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
96 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
139 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
97
140
98 # Scan module for configuration decorators.
141 # Scan module for configuration decorators.
99 config.scan()
142 config.scan()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/tests/functional/test_home.py to rhodecode/apps/home/tests/test_home.py
NO CONTENT: file renamed from rhodecode/tests/functional/test_home.py to rhodecode/apps/home/tests/test_home.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/tests/functional/test_bookmarks.py to rhodecode/apps/repository/tests/test_repo_bookmarks.py
NO CONTENT: file renamed from rhodecode/tests/functional/test_bookmarks.py to rhodecode/apps/repository/tests/test_repo_bookmarks.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/tests/functional/test_branches.py to rhodecode/apps/repository/tests/test_repo_branches.py
NO CONTENT: file renamed from rhodecode/tests/functional/test_branches.py to rhodecode/apps/repository/tests/test_repo_branches.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/tests/functional/test_summary.py to rhodecode/apps/repository/tests/test_repo_summary.py
NO CONTENT: file renamed from rhodecode/tests/functional/test_summary.py to rhodecode/apps/repository/tests/test_repo_summary.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/tests/functional/test_tags.py to rhodecode/apps/repository/tests/test_repo_tags.py
NO CONTENT: file renamed from rhodecode/tests/functional/test_tags.py to rhodecode/apps/repository/tests/test_repo_tags.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/apps/repository/views/strip.py to rhodecode/apps/repository/views/repo_strip.py
NO CONTENT: file renamed from rhodecode/apps/repository/views/strip.py to rhodecode/apps/repository/views/repo_strip.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/templates/admin/admin.mako to rhodecode/templates/admin/admin_audit_logs.mako
NO CONTENT: file renamed from rhodecode/templates/admin/admin.mako to rhodecode/templates/admin/admin_audit_logs.mako
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/tests/controllers/test_search.py to rhodecode/tests/lib/test_search.py
NO CONTENT: file renamed from rhodecode/tests/controllers/test_search.py to rhodecode/tests/lib/test_search.py
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now