##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r4456:279b3293 merge stable
parent child Browse files
Show More

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

@@ -0,0 +1,74 b''
1 |RCE| 4.20.0 |RNS|
2 ------------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2020-07-20
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13 - Comments: users can now edit comments body.
14 Editing is versioned and all older versions are kept for auditing.
15 - Pull requests: changed the order of close-branch after merge,
16 so branch heads are no longer left open after the merge.
17 - Diffs: added diff navigation to improve UX when browsing the full context diffs.
18 - Emails: set the `References` header for threading in emails with different subjects.
19 Only some Email clients supports this.
20 - Emails: added logic to allow overwriting the default email titles via rcextensions.
21 - Markdown: support summary/details tags to allow setting a link with expansion menu.
22 - Integrations: added `store_file` integration. This allows storing
23 selected files from repository on disk on push.
24
25
26 General
27 ^^^^^^^
28
29 - License: individual users can hide license flash messages warning about upcoming
30 license expiration.
31 - Downloads: the default download commit is now the landing revision set in repo settings.
32 - Auth-tokens: expose all roles with explanation to help users understand it better.
33 - Pull requests: make auto generated title for pull requests show also source Ref type
34 eg. branch feature1, instead of just name of the branch.
35 - UI: added secondary action instead of two buttons on files page, and download page.
36 - Emails: reduce excessive warning logs on pre-mailer.
37
38
39 Security
40 ^^^^^^^^
41
42 - Branch permissions: protect from XSS on branch rules forbidden flash message.
43
44
45 Performance
46 ^^^^^^^^^^^
47
48
49
50 Fixes
51 ^^^^^
52
53 - Pull requests: detect missing commits on diffs from new PR ancestor logic. This fixes
54 problem with older PRs opened before 4.19.X that had special ancestor set, which could
55 lead in some cases to crash when viewing older pull requests.
56 - Permissions: fixed a case when a duplicate permission made repository settings active on archived repository.
57 - Permissions: fixed missing user info on global and repository permissions pages.
58 - Permissions: allow users to update settings for repository groups they still own,
59 or have admin perms, when they don't change their name.
60 - Permissions: flush all when running remap and rescan.
61 - Repositories: fixed a bug for repo groups that didn't pre-fill the repo group from GET param.
62 - Repositories: allow updating repository settings for users without
63 store-in-root permissions in case repository name didn't change.
64 - Comments: fixed line display icons.
65 - Summary: fixed summary page total commits count.
66
67
68 Upgrade notes
69 ^^^^^^^^^^^^^
70
71 - Schedule feature update.
72 - On Mercurial repositories we changed the order of commits when the close branch on merge features is used.
73 Before the commits was made after a merge leaving an open head.
74 This backward incompatible change now reverses that order, which is the correct way of doing it.
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,6 +1,5 b''
1 1 [bumpversion]
2 current_version = 4.19.3
2 current_version = 4.20.0
3 3 message = release: Bump version {current_version} to {new_version}
4 4
5 5 [bumpversion:file:rhodecode/VERSION]
6
@@ -1,33 +1,28 b''
1 1 [DEFAULT]
2 2 done = false
3 3
4 4 [task:bump_version]
5 5 done = true
6 6
7 7 [task:rc_tools_pinned]
8 done = true
9 8
10 9 [task:fixes_on_stable]
11 done = true
12 10
13 11 [task:pip2nix_generated]
14 done = true
15 12
16 13 [task:changelog_updated]
17 done = true
18 14
19 15 [task:generate_api_docs]
20 done = true
16
17 [task:updated_translation]
21 18
22 19 [release]
23 state = prepared
24 version = 4.19.3
25
26 [task:updated_translation]
20 state = in_progress
21 version = 4.20.0
27 22
28 23 [task:generate_js_routes]
29 24
30 25 [task:updated_trial_license]
31 26
32 27 [task:generate_oss_licenses]
33 28
@@ -1,264 +1,264 b''
1 1 .. _admin-tricks:
2 2
3 3 One-time Admin Tasks
4 4 --------------------
5 5
6 6 * :ref:`web-analytics`
7 7 * :ref:`admin-tricks-license`
8 8 * :ref:`announcements`
9 9 * :ref:`md-rst`
10 10 * :ref:`repo-stats`
11 11 * :ref:`server-side-merge`
12 12 * :ref:`remap-rescan`
13 13 * :ref:`custom-hooks`
14 14 * :ref:`clear-repo-cache`
15 15 * :ref:`set-repo-pub`
16 16 * :ref:`ping`
17 17
18 18 .. _web-analytics:
19 19
20 20 Adding Web Analytics
21 21 ^^^^^^^^^^^^^^^^^^^^
22 22
23 23 If you wish to add a Google Analytics, or any other kind of tracker to your
24 24 |RCE| instance you can add the necessary codes to the header or footer
25 25 section of each instance using the following steps:
26 26
27 27 1. From the |RCE| interface, select
28 28 :menuselection:`Admin --> Settings --> Global`
29 29 2. To add a tracking code to you instance, enter it in the header or footer
30 30 section and select **Save**
31 31
32 32 Use the example templates in the drop-down menu to set up your configuration.
33 33
34 34 .. _admin-tricks-license:
35 35
36 36 Licence Key Management
37 37 ^^^^^^^^^^^^^^^^^^^^^^
38 38
39 39 To manage your license key, go to
40 40 :menuselection:`Admin --> Settings --> License`.
41 41 On this page you can see the license key details. If you need a new license,
42 42 or have questions about your current one, contact support@rhodecode.com
43 43
44 44 .. _announcements:
45 45
46 46 Server-wide Announcements
47 47 ^^^^^^^^^^^^^^^^^^^^^^^^^
48 48
49 49 If you need to make a server-wide announcement to all users,
50 50 you can add a message to be displayed using the following steps:
51 51
52 52 1. From the |RCE| interface, select
53 53 :menuselection:`Admin --> Settings --> Global`
54 54 2. To add a message that will be displayed to all users,
55 55 select :guilabel:`Server Announcement` from the drop-down menu and
56 56 change the ``var message = "TYPE YOUR MESSAGE HERE";`` example line.
57 57 3. Select :guilabel:`Save`, and you will see the message once your page
58 58 refreshes.
59 59
60 60 .. image:: ../../images/server-wide-announcement.png
61 61 :alt: Server Wide Announcement
62 62
63 63 .. _md-rst:
64 64
65 65
66 66 Suppress license warnings or errors
67 67 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
68 68
69 69 In case you're running on maximum allowed users, RhodeCode will display a
70 70 warning message on pages that you're close to the license limits.
71 71 It's often not desired to show that all the time. Here's how you can suppress
72 72 the license messages.
73 73
74 74 1. From the |RCE| interface, select
75 75 :menuselection:`Admin --> Settings --> Global`
76 76 2. Select :guilabel:`Flash message filtering` from the drop-down menu.
77 77 3. Select :guilabel:`Save`, and you will no longer see the license message
78 78 once your page refreshes.
79 79
80 80 .. _admin-tricks-suppress-license-messages:
81 81
82 82
83 83 Markdown or RST Rendering
84 84 ^^^^^^^^^^^^^^^^^^^^^^^^^
85 85
86 86 |RCE| can use `Markdown`_ or `reStructured Text`_ in commit message,
87 87 code review messages, and inline comments. To set the default to either,
88 88 select your preference from the drop-down menu on the
89 89 :menuselection:`Admin --> Settings --> Visual` page and select
90 90 :guilabel:`Save settings`.
91 91
92 92 .. _repo-stats:
93 93
94 94 Enabling Repository Statistics
95 95 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
96 96
97 97 To enable |repo| statistics, use the following steps:
98 98
99 99 1. From the |RCE| interface, open
100 100 :menuselection:`Admin --> Repositories` and select
101 101 :guilabel:`Edit` beside the |repo| for which you wish to enable statistics.
102 102 2. Check the :guilabel:`Enable statistics` box, and select :guilabel:`Save`
103 103
104 104 .. _server-side-merge:
105 105
106 106 Enabling Server-side Merging
107 107 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108 108
109 109 To enable server-side merging, use the following steps:
110 110
111 111 1. From the |RCE| interface, open :menuselection:`Admin --> Settings --> VCS`
112 112 2. Check the :guilabel:`Server-side merge` box, and select
113 113 :guilabel:`Save Settings`
114 114
115 115 If you encounter slow performance with server-side merging enabled, check the
116 116 speed at which your server is performing actions. When server-side merging is
117 117 enabled, the following actions occurs on the server.
118 118
119 119 * A |pr| is created in the database.
120 120 * A shadow |repo| is created as a working environment for the |pr|.
121 121 * On display, |RCE| checks if the |pr| can be merged.
122 122
123 123 To check how fast the shadow |repo| creation is occurring on your server, use
124 124 the following steps:
125 125
126 126 1. Log into your server and create a directory in your |repos| folder.
127 127 2. Clone a |repo| that is showing slow performance and time the action.
128 128
129 129 .. code-block:: bash
130 130
131 131 # One option is to use the time command
132 132 $ time hg clone SOURCE_REPO TARGET
133 133
134 134 .. _remap-rescan:
135 135
136 136 Remap and Rescan Repositories
137 137 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
138 138
139 139 You may want to Remap and rescan the |repos| that |RCE| is managing to ensure
140 140 the system is always up-to-date. This is useful after importing, deleting,
141 141 or carrying out general cleaning up operations. To do this use the
142 142 following steps:
143 143
144 144 1. From the |RCE|, open
145 145 :menuselection:`Admin --> Settings --> Remap and rescan`
146 146 2. Click :guilabel:`Rescan Repositories`
147 147
148 148 Check the additional options if needed:
149 149
150 150 * :guilabel:`Destroy old data`: Useful for purging deleted repository
151 151 information from the database.
152 152 * :guilabel:`Invalidate cache for all repositories`: Use this to completely
153 153 remap all |repos|. Useful when importing or migrating |repos| to ensure all
154 154 new information is picked up.
155 155
156 156 .. _custom-hooks:
157 157
158 158 Adding Custom Hooks
159 159 ^^^^^^^^^^^^^^^^^^^
160 160
161 161 To add custom hooks to your instance, use the following steps:
162 162
163 163 1. Open :menuselection:`Admin --> Settings --> Hooks`
164 164 2. Add your custom hook details, you can use a file path to specify custom
165 165 hook scripts, for example:
166 166 ``pretxnchangegroup.example`` with value ``python:/path/to/custom_hook.py:my_func_name``
167 167 3. Select :guilabel:`Save`
168 168
169 169 Also, see the RhodeCode Extensions section of the :ref:`rc-tools` guide. RhodeCode
170 170 Extensions can be used to add additional hooks to your instance and comes
171 171 with a number of pre-built plugins if you chose to install them.
172 172
173 173 .. _clear-repo-cache:
174 174
175 175 Clearing |repo| cache
176 176 ^^^^^^^^^^^^^^^^^^^^^
177 177
178 178 If you need to clear the cache for a particular |repo|, use the following steps:
179 179
180 180 1. Open :menuselection:`Admin --> Repositories` and select :guilabel:`Edit`
181 181 beside the |repo| whose cache you wish to clear.
182 182 2. On the |repo| settings page, go to the :guilabel:`Caches` tab and select
183 183 :guilabel:`Invalidate repository cache`.
184 184
185 185 .. _set-lang:
186 186
187 187 Changing Default Language
188 188 ^^^^^^^^^^^^^^^^^^^^^^^^^
189 189
190 190 To change the default language of a |RCE| instance, change the language code
191 191 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To
192 192 do this, use the following steps.
193 193
194 194 1. Open the :file:`rhodecode.ini` file and set the required language code.
195 195
196 196 .. code-block:: ini
197 197
198 198 ## Optional Languages
199 199 ## en(default), de, fr, it, ja, pl, pt, ru, zh
200 200 lang = de
201 201
202 202 2. Restart the |RCE| instance and check that the language has been updated.
203 203
204 204 .. code-block:: bash
205 205
206 206 $ rccontrol restart enterprise-2
207 207 Instance "enterprise-2" successfully stopped.
208 208 Instance "enterprise-2" successfully started.
209 209
210 210 .. image:: ../../images/language.png
211 211
212 212 .. _set-repo-pub:
213 213
214 214 Setting Repositories to Publish
215 215 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
216 216
217 217 To automatically promote your local |repos| to public after pushing to |RCE|,
218 218 enable the :guilabel:`Set repositories as publishing` option on the
219 219 :menuselection:`Admin --> Settings --> VCS` page.
220 220
221 221 .. note::
222 222
223 223 This option is enabled by default on most |RCE| versions, but if upgrading
224 224 from a 1.7.x version it could be disabled on upgrade due to inheriting
225 225 older default settings.
226 226
227 227 .. _ping:
228 228
229 229 Pinging the |RCE| Server
230 230 ^^^^^^^^^^^^^^^^^^^^^^^^
231 231
232 232 You can check the IP Address of your |RCE| instance using the
233 233 following URL: ``{instance-URL}/_admin/ping``.
234 234
235 235 .. code-block:: bash
236 236
237 237 $ curl https://your.rhodecode.url/_admin/ping
238 238 pong[rce-7880] => 203.0.113.23
239 239
240 240 .. _Markdown: http://daringfireball.net/projects/markdown/
241 .. _reStructured Text: http://docutils.sourceforge.net/docs/index.html
241 .. _reStructured Text: http://docutils.sourceforge.io/docs/index.html
242 242
243 243
244 244 Unarchiving a repository
245 245 ^^^^^^^^^^^^^^^^^^^^^^^^^
246 246
247 247 Archive operation for the repository is similar as delete. Archive keeps the data for future references
248 248 but makes the repository read-only. After archiving the repository it shouldn't be modified in any way.
249 249 This is why repository settings are disabled for an archived repository.
250 250
251 251 If there's a need for unarchiving a repository for some reasons, the interactive
252 252 ishell interface should be used.
253 253
254 254 .. code-block:: bash
255 255
256 256 # Open iShell from the terminal
257 257 $ rccontrol ishell enterprise-1/community-1
258 258
259 259 .. code-block:: python
260 260
261 261 # Set repository as un-archived
262 262 In [1]: repo = Repository.get_by_repo_name('SOME_REPO_NAME')
263 263 In [2]: repo.archived = False
264 264 In [3]: Session().add(repo);Session().commit()
@@ -1,107 +1,107 b''
1 1 .. _apache-conf-eg:
2 2
3 3 Apache Configuration Example
4 4 ----------------------------
5 5
6 6 Use the following example to configure Apache as a your web server.
7 7 Below config if for an Apache Reverse Proxy configuration.
8 8
9 9 .. note::
10 10
11 11 Apache requires the following modules to be enabled. Below is an example
12 12 how to enable them on Ubuntu Server
13 13
14 14
15 15 .. code-block:: bash
16 16
17 17 $ sudo a2enmod proxy
18 18 $ sudo a2enmod proxy_http
19 19 $ sudo a2enmod proxy_balancer
20 20 $ sudo a2enmod headers
21 21 $ sudo a2enmod ssl
22 22 $ sudo a2enmod rewrite
23 23
24 24 # requires Apache 2.4+, required to handle websockets/channelstream
25 25 $ sudo a2enmod proxy_wstunnel
26 26
27 27
28 28 .. code-block:: apache
29 29
30 30 ## HTTP to HTTPS rewrite
31 31 <VirtualHost *:80>
32 32 ServerName rhodecode.myserver.com
33 33 DocumentRoot /var/www/html
34 34 Redirect permanent / https://rhodecode.myserver.com/
35 35 </VirtualHost>
36 36
37 37 ## MAIN SSL enabled server
38 38 <VirtualHost *:443>
39 39
40 40 ServerName rhodecode.myserver.com
41 41 ServerAlias rhodecode.myserver.com
42 42
43 43 ## Skip ProxyPass the _static to backend server
44 44 #ProxyPass /_static !
45 45
46 46 ## serve static files by Apache, recommended for performance
47 47 #Alias /_static/rhodecode /home/ubuntu/.rccontrol/community-1/static
48 48
49 49 ## Allow Apache to access the static files in this directory
50 50 #<Directory /home/ubuntu/.rccontrol/community-1/static/>
51 51 # AllowOverride none
52 52 # Require all granted
53 53 #</Directory>
54 54
55 55 RequestHeader set X-Forwarded-Proto "https"
56 56
57 57 ## channelstream websocket handling
58 58 ProxyPass /_channelstream ws://localhost:9800
59 59 ProxyPassReverse /_channelstream ws://localhost:9800
60 60
61 61 <Proxy *>
62 62 Order allow,deny
63 63 Allow from all
64 64 </Proxy>
65 65
66 66 # Directive to properly generate url (clone url) for RhodeCode
67 67 ProxyPreserveHost On
68 68
69 69 # It allows request bodies to be sent to the backend using chunked transfer encoding.
70 70 SetEnv proxy-sendchunked 1
71 71
72 72 # Increase headers size for large Mercurial headers sent with many branches
73 73 LimitRequestLine 16380
74 74
75 75 # Url to running RhodeCode instance. This is shown as `- URL:` when
76 76 # running rccontrol status.
77 77
78 ProxyPass / http://127.0.0.1:10002/ timeout=7200 Keepalive=On
78 ProxyPass / http://127.0.0.1:10002/ connectiontimeout=7200 timeout=7200 Keepalive=On
79 79 ProxyPassReverse / http://127.0.0.1:10002/
80 80
81 81 # strict http prevents from https -> http downgrade
82 82 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
83 83
84 84 # Set x-frame options
85 85 Header always append X-Frame-Options SAMEORIGIN
86 86
87 87 # To enable https use line below
88 88 # SetEnvIf X-Url-Scheme https HTTPS=1
89 89
90 90 # SSL setup
91 91 SSLEngine On
92 92 SSLCertificateFile /etc/apache2/ssl/rhodecode.myserver.pem
93 93 SSLCertificateKeyFile /etc/apache2/ssl/rhodecode.myserver.key
94 94
95 95 SSLProtocol all -SSLv2 -SSLv3
96 96 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
97 97 SSLHonorCipherOrder on
98 98
99 99 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
100 100 #SSLOpenSSLConfCmd DHParameters "/etc/apache2/dhparam.pem"
101 101
102 102 ## custom 502 error page. Will be displayed while RhodeCode server
103 103 ## is turned off
104 104 ErrorDocument 502 /path/to/.rccontrol/enterprise-1/static/502.html
105 105
106 106 </VirtualHost>
107 107
@@ -1,447 +1,448 b''
1 1 .. _pull-request-methods-ref:
2 2
3 3 pull_request methods
4 4 ====================
5 5
6 6 close_pull_request
7 7 ------------------
8 8
9 9 .. py:function:: close_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>, message=<Optional:''>)
10 10
11 11 Close the pull request specified by `pullrequestid`.
12 12
13 13 :param apiuser: This is filled automatically from the |authtoken|.
14 14 :type apiuser: AuthUser
15 15 :param repoid: Repository name or repository ID to which the pull
16 16 request belongs.
17 17 :type repoid: str or int
18 18 :param pullrequestid: ID of the pull request to be closed.
19 19 :type pullrequestid: int
20 20 :param userid: Close the pull request as this user.
21 21 :type userid: Optional(str or int)
22 22 :param message: Optional message to close the Pull Request with. If not
23 23 specified it will be generated automatically.
24 24 :type message: Optional(str)
25 25
26 26 Example output:
27 27
28 28 .. code-block:: bash
29 29
30 30 "id": <id_given_in_input>,
31 31 "result": {
32 32 "pull_request_id": "<int>",
33 33 "close_status": "<str:status_lbl>,
34 34 "closed": "<bool>"
35 35 },
36 36 "error": null
37 37
38 38
39 39 comment_pull_request
40 40 --------------------
41 41
42 42 .. py:function:: comment_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, message=<Optional:None>, commit_id=<Optional:None>, status=<Optional:None>, comment_type=<Optional:u'note'>, resolves_comment_id=<Optional:None>, extra_recipients=<Optional:[]>, userid=<Optional:<OptionalAttr:apiuser>>, send_email=<Optional:True>)
43 43
44 44 Comment on the pull request specified with the `pullrequestid`,
45 45 in the |repo| specified by the `repoid`, and optionally change the
46 46 review status.
47 47
48 48 :param apiuser: This is filled automatically from the |authtoken|.
49 49 :type apiuser: AuthUser
50 50 :param repoid: Optional repository name or repository ID.
51 51 :type repoid: str or int
52 52 :param pullrequestid: The pull request ID.
53 53 :type pullrequestid: int
54 54 :param commit_id: Specify the commit_id for which to set a comment. If
55 55 given commit_id is different than latest in the PR status
56 56 change won't be performed.
57 57 :type commit_id: str
58 58 :param message: The text content of the comment.
59 59 :type message: str
60 60 :param status: (**Optional**) Set the approval status of the pull
61 61 request. One of: 'not_reviewed', 'approved', 'rejected',
62 62 'under_review'
63 63 :type status: str
64 64 :param comment_type: Comment type, one of: 'note', 'todo'
65 65 :type comment_type: Optional(str), default: 'note'
66 66 :param resolves_comment_id: id of comment which this one will resolve
67 67 :type resolves_comment_id: Optional(int)
68 68 :param extra_recipients: list of user ids or usernames to add
69 69 notifications for this comment. Acts like a CC for notification
70 70 :type extra_recipients: Optional(list)
71 71 :param userid: Comment on the pull request as this user
72 72 :type userid: Optional(str or int)
73 73 :param send_email: Define if this comment should also send email notification
74 74 :type send_email: Optional(bool)
75 75
76 76 Example output:
77 77
78 78 .. code-block:: bash
79 79
80 80 id : <id_given_in_input>
81 81 result : {
82 82 "pull_request_id": "<Integer>",
83 83 "comment_id": "<Integer>",
84 84 "status": {"given": <given_status>,
85 85 "was_changed": <bool status_was_actually_changed> },
86 86 },
87 87 error : null
88 88
89 89
90 90 create_pull_request
91 91 -------------------
92 92
93 93 .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, owner=<Optional:<OptionalAttr:apiuser>>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>)
94 94
95 95 Creates a new pull request.
96 96
97 97 Accepts refs in the following formats:
98 98
99 99 * branch:<branch_name>:<sha>
100 100 * branch:<branch_name>
101 101 * bookmark:<bookmark_name>:<sha> (Mercurial only)
102 102 * bookmark:<bookmark_name> (Mercurial only)
103 103
104 104 :param apiuser: This is filled automatically from the |authtoken|.
105 105 :type apiuser: AuthUser
106 106 :param source_repo: Set the source repository name.
107 107 :type source_repo: str
108 108 :param target_repo: Set the target repository name.
109 109 :type target_repo: str
110 110 :param source_ref: Set the source ref name.
111 111 :type source_ref: str
112 112 :param target_ref: Set the target ref name.
113 113 :type target_ref: str
114 114 :param owner: user_id or username
115 115 :type owner: Optional(str)
116 116 :param title: Optionally Set the pull request title, it's generated otherwise
117 117 :type title: str
118 118 :param description: Set the pull request description.
119 119 :type description: Optional(str)
120 120 :type description_renderer: Optional(str)
121 121 :param description_renderer: Set pull request renderer for the description.
122 122 It should be 'rst', 'markdown' or 'plain'. If not give default
123 123 system renderer will be used
124 124 :param reviewers: Set the new pull request reviewers list.
125 125 Reviewer defined by review rules will be added automatically to the
126 126 defined list.
127 127 :type reviewers: Optional(list)
128 128 Accepts username strings or objects of the format:
129 129
130 130 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
131 131
132 132
133 133 get_pull_request
134 134 ----------------
135 135
136 136 .. py:function:: get_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, merge_state=<Optional:False>)
137 137
138 138 Get a pull request based on the given ID.
139 139
140 140 :param apiuser: This is filled automatically from the |authtoken|.
141 141 :type apiuser: AuthUser
142 142 :param repoid: Optional, repository name or repository ID from where
143 143 the pull request was opened.
144 144 :type repoid: str or int
145 145 :param pullrequestid: ID of the requested pull request.
146 146 :type pullrequestid: int
147 147 :param merge_state: Optional calculate merge state for each repository.
148 148 This could result in longer time to fetch the data
149 149 :type merge_state: bool
150 150
151 151 Example output:
152 152
153 153 .. code-block:: bash
154 154
155 155 "id": <id_given_in_input>,
156 156 "result":
157 157 {
158 158 "pull_request_id": "<pull_request_id>",
159 159 "url": "<url>",
160 160 "title": "<title>",
161 161 "description": "<description>",
162 162 "status" : "<status>",
163 163 "created_on": "<date_time_created>",
164 164 "updated_on": "<date_time_updated>",
165 165 "versions": "<number_or_versions_of_pr>",
166 166 "commit_ids": [
167 167 ...
168 168 "<commit_id>",
169 169 "<commit_id>",
170 170 ...
171 171 ],
172 172 "review_status": "<review_status>",
173 173 "mergeable": {
174 174 "status": "<bool>",
175 175 "message": "<message>",
176 176 },
177 177 "source": {
178 178 "clone_url": "<clone_url>",
179 179 "repository": "<repository_name>",
180 180 "reference":
181 181 {
182 182 "name": "<name>",
183 183 "type": "<type>",
184 184 "commit_id": "<commit_id>",
185 185 }
186 186 },
187 187 "target": {
188 188 "clone_url": "<clone_url>",
189 189 "repository": "<repository_name>",
190 190 "reference":
191 191 {
192 192 "name": "<name>",
193 193 "type": "<type>",
194 194 "commit_id": "<commit_id>",
195 195 }
196 196 },
197 197 "merge": {
198 198 "clone_url": "<clone_url>",
199 199 "reference":
200 200 {
201 201 "name": "<name>",
202 202 "type": "<type>",
203 203 "commit_id": "<commit_id>",
204 204 }
205 205 },
206 206 "author": <user_obj>,
207 207 "reviewers": [
208 208 ...
209 209 {
210 210 "user": "<user_obj>",
211 211 "review_status": "<review_status>",
212 212 }
213 213 ...
214 214 ]
215 215 },
216 216 "error": null
217 217
218 218
219 219 get_pull_request_comments
220 220 -------------------------
221 221
222 222 .. py:function:: get_pull_request_comments(apiuser, pullrequestid, repoid=<Optional:None>)
223 223
224 224 Get all comments of pull request specified with the `pullrequestid`
225 225
226 226 :param apiuser: This is filled automatically from the |authtoken|.
227 227 :type apiuser: AuthUser
228 228 :param repoid: Optional repository name or repository ID.
229 229 :type repoid: str or int
230 230 :param pullrequestid: The pull request ID.
231 231 :type pullrequestid: int
232 232
233 233 Example output:
234 234
235 235 .. code-block:: bash
236 236
237 237 id : <id_given_in_input>
238 238 result : [
239 239 {
240 240 "comment_author": {
241 241 "active": true,
242 242 "full_name_or_username": "Tom Gore",
243 243 "username": "admin"
244 244 },
245 245 "comment_created_on": "2017-01-02T18:43:45.533",
246 246 "comment_f_path": null,
247 247 "comment_id": 25,
248 248 "comment_lineno": null,
249 249 "comment_status": {
250 250 "status": "under_review",
251 251 "status_lbl": "Under Review"
252 252 },
253 253 "comment_text": "Example text",
254 254 "comment_type": null,
255 "comment_last_version: 0,
255 256 "pull_request_version": null,
256 257 "comment_commit_id": None,
257 258 "comment_pull_request_id": <pull_request_id>
258 259 }
259 260 ],
260 261 error : null
261 262
262 263
263 264 get_pull_requests
264 265 -----------------
265 266
266 267 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>, merge_state=<Optional:False>)
267 268
268 269 Get all pull requests from the repository specified in `repoid`.
269 270
270 271 :param apiuser: This is filled automatically from the |authtoken|.
271 272 :type apiuser: AuthUser
272 273 :param repoid: Optional repository name or repository ID.
273 274 :type repoid: str or int
274 275 :param status: Only return pull requests with the specified status.
275 276 Valid options are.
276 277 * ``new`` (default)
277 278 * ``open``
278 279 * ``closed``
279 280 :type status: str
280 281 :param merge_state: Optional calculate merge state for each repository.
281 282 This could result in longer time to fetch the data
282 283 :type merge_state: bool
283 284
284 285 Example output:
285 286
286 287 .. code-block:: bash
287 288
288 289 "id": <id_given_in_input>,
289 290 "result":
290 291 [
291 292 ...
292 293 {
293 294 "pull_request_id": "<pull_request_id>",
294 295 "url": "<url>",
295 296 "title" : "<title>",
296 297 "description": "<description>",
297 298 "status": "<status>",
298 299 "created_on": "<date_time_created>",
299 300 "updated_on": "<date_time_updated>",
300 301 "commit_ids": [
301 302 ...
302 303 "<commit_id>",
303 304 "<commit_id>",
304 305 ...
305 306 ],
306 307 "review_status": "<review_status>",
307 308 "mergeable": {
308 309 "status": "<bool>",
309 310 "message: "<message>",
310 311 },
311 312 "source": {
312 313 "clone_url": "<clone_url>",
313 314 "reference":
314 315 {
315 316 "name": "<name>",
316 317 "type": "<type>",
317 318 "commit_id": "<commit_id>",
318 319 }
319 320 },
320 321 "target": {
321 322 "clone_url": "<clone_url>",
322 323 "reference":
323 324 {
324 325 "name": "<name>",
325 326 "type": "<type>",
326 327 "commit_id": "<commit_id>",
327 328 }
328 329 },
329 330 "merge": {
330 331 "clone_url": "<clone_url>",
331 332 "reference":
332 333 {
333 334 "name": "<name>",
334 335 "type": "<type>",
335 336 "commit_id": "<commit_id>",
336 337 }
337 338 },
338 339 "author": <user_obj>,
339 340 "reviewers": [
340 341 ...
341 342 {
342 343 "user": "<user_obj>",
343 344 "review_status": "<review_status>",
344 345 }
345 346 ...
346 347 ]
347 348 }
348 349 ...
349 350 ],
350 351 "error": null
351 352
352 353
353 354 merge_pull_request
354 355 ------------------
355 356
356 357 .. py:function:: merge_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
357 358
358 359 Merge the pull request specified by `pullrequestid` into its target
359 360 repository.
360 361
361 362 :param apiuser: This is filled automatically from the |authtoken|.
362 363 :type apiuser: AuthUser
363 364 :param repoid: Optional, repository name or repository ID of the
364 365 target repository to which the |pr| is to be merged.
365 366 :type repoid: str or int
366 367 :param pullrequestid: ID of the pull request which shall be merged.
367 368 :type pullrequestid: int
368 369 :param userid: Merge the pull request as this user.
369 370 :type userid: Optional(str or int)
370 371
371 372 Example output:
372 373
373 374 .. code-block:: bash
374 375
375 376 "id": <id_given_in_input>,
376 377 "result": {
377 378 "executed": "<bool>",
378 379 "failure_reason": "<int>",
379 380 "merge_status_message": "<str>",
380 381 "merge_commit_id": "<merge_commit_id>",
381 382 "possible": "<bool>",
382 383 "merge_ref": {
383 384 "commit_id": "<commit_id>",
384 385 "type": "<type>",
385 386 "name": "<name>"
386 387 }
387 388 },
388 389 "error": null
389 390
390 391
391 392 update_pull_request
392 393 -------------------
393 394
394 395 .. py:function:: update_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>)
395 396
396 397 Updates a pull request.
397 398
398 399 :param apiuser: This is filled automatically from the |authtoken|.
399 400 :type apiuser: AuthUser
400 401 :param repoid: Optional repository name or repository ID.
401 402 :type repoid: str or int
402 403 :param pullrequestid: The pull request ID.
403 404 :type pullrequestid: int
404 405 :param title: Set the pull request title.
405 406 :type title: str
406 407 :param description: Update pull request description.
407 408 :type description: Optional(str)
408 409 :type description_renderer: Optional(str)
409 410 :param description_renderer: Update pull request renderer for the description.
410 411 It should be 'rst', 'markdown' or 'plain'
411 412 :param reviewers: Update pull request reviewers list with new value.
412 413 :type reviewers: Optional(list)
413 414 Accepts username strings or objects of the format:
414 415
415 416 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
416 417
417 418 :param update_commits: Trigger update of commits for this pull request
418 419 :type: update_commits: Optional(bool)
419 420
420 421 Example output:
421 422
422 423 .. code-block:: bash
423 424
424 425 id : <id_given_in_input>
425 426 result : {
426 427 "msg": "Updated pull request `63`",
427 428 "pull_request": <pull_request_object>,
428 429 "updated_reviewers": {
429 430 "added": [
430 431 "username"
431 432 ],
432 433 "removed": []
433 434 },
434 435 "updated_commits": {
435 436 "added": [
436 437 "<sha1_hash>"
437 438 ],
438 439 "common": [
439 440 "<sha1_hash>",
440 441 "<sha1_hash>",
441 442 ],
442 443 "removed": []
443 444 }
444 445 }
445 446 error : null
446 447
447 448
@@ -1,1140 +1,1206 b''
1 1 .. _repo-methods-ref:
2 2
3 3 repo methods
4 4 ============
5 5
6 6 add_field_to_repo
7 7 -----------------
8 8
9 9 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
10 10
11 11 Adds an extra field to a repository.
12 12
13 13 This command can only be run using an |authtoken| with at least
14 14 write permissions to the |repo|.
15 15
16 16 :param apiuser: This is filled automatically from the |authtoken|.
17 17 :type apiuser: AuthUser
18 18 :param repoid: Set the repository name or repository id.
19 19 :type repoid: str or int
20 20 :param key: Create a unique field key for this repository.
21 21 :type key: str
22 22 :param label:
23 23 :type label: Optional(str)
24 24 :param description:
25 25 :type description: Optional(str)
26 26
27 27
28 28 comment_commit
29 29 --------------
30 30
31 31 .. py:function:: comment_commit(apiuser, repoid, commit_id, message, status=<Optional:None>, comment_type=<Optional:u'note'>, resolves_comment_id=<Optional:None>, extra_recipients=<Optional:[]>, userid=<Optional:<OptionalAttr:apiuser>>, send_email=<Optional:True>)
32 32
33 33 Set a commit comment, and optionally change the status of the commit.
34 34
35 35 :param apiuser: This is filled automatically from the |authtoken|.
36 36 :type apiuser: AuthUser
37 37 :param repoid: Set the repository name or repository ID.
38 38 :type repoid: str or int
39 39 :param commit_id: Specify the commit_id for which to set a comment.
40 40 :type commit_id: str
41 41 :param message: The comment text.
42 42 :type message: str
43 43 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
44 44 'approved', 'rejected', 'under_review'
45 45 :type status: str
46 46 :param comment_type: Comment type, one of: 'note', 'todo'
47 47 :type comment_type: Optional(str), default: 'note'
48 48 :param resolves_comment_id: id of comment which this one will resolve
49 49 :type resolves_comment_id: Optional(int)
50 50 :param extra_recipients: list of user ids or usernames to add
51 51 notifications for this comment. Acts like a CC for notification
52 52 :type extra_recipients: Optional(list)
53 53 :param userid: Set the user name of the comment creator.
54 54 :type userid: Optional(str or int)
55 55 :param send_email: Define if this comment should also send email notification
56 56 :type send_email: Optional(bool)
57 57
58 58 Example error output:
59 59
60 60 .. code-block:: bash
61 61
62 62 {
63 63 "id" : <id_given_in_input>,
64 64 "result" : {
65 65 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
66 66 "status_change": null or <status>,
67 67 "success": true
68 68 },
69 69 "error" : null
70 70 }
71 71
72 72
73 73 create_repo
74 74 -----------
75 75
76 76 .. py:function:: create_repo(apiuser, repo_name, repo_type, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, push_uri=<Optional:None>, landing_rev=<Optional:None>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, copy_permissions=<Optional:False>)
77 77
78 78 Creates a repository.
79 79
80 80 * If the repository name contains "/", repository will be created inside
81 81 a repository group or nested repository groups
82 82
83 83 For example "foo/bar/repo1" will create |repo| called "repo1" inside
84 84 group "foo/bar". You have to have permissions to access and write to
85 85 the last repository group ("bar" in this example)
86 86
87 87 This command can only be run using an |authtoken| with at least
88 88 permissions to create repositories, or write permissions to
89 89 parent repository groups.
90 90
91 91 :param apiuser: This is filled automatically from the |authtoken|.
92 92 :type apiuser: AuthUser
93 93 :param repo_name: Set the repository name.
94 94 :type repo_name: str
95 95 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
96 96 :type repo_type: str
97 97 :param owner: user_id or username
98 98 :type owner: Optional(str)
99 99 :param description: Set the repository description.
100 100 :type description: Optional(str)
101 101 :param private: set repository as private
102 102 :type private: bool
103 103 :param clone_uri: set clone_uri
104 104 :type clone_uri: str
105 105 :param push_uri: set push_uri
106 106 :type push_uri: str
107 107 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
108 108 :type landing_rev: str
109 109 :param enable_locking:
110 110 :type enable_locking: bool
111 111 :param enable_downloads:
112 112 :type enable_downloads: bool
113 113 :param enable_statistics:
114 114 :type enable_statistics: bool
115 115 :param copy_permissions: Copy permission from group in which the
116 116 repository is being created.
117 117 :type copy_permissions: bool
118 118
119 119
120 120 Example output:
121 121
122 122 .. code-block:: bash
123 123
124 124 id : <id_given_in_input>
125 125 result: {
126 126 "msg": "Created new repository `<reponame>`",
127 127 "success": true,
128 128 "task": "<celery task id or None if done sync>"
129 129 }
130 130 error: null
131 131
132 132
133 133 Example error output:
134 134
135 135 .. code-block:: bash
136 136
137 137 id : <id_given_in_input>
138 138 result : null
139 139 error : {
140 140 'failed to create repository `<repo_name>`'
141 141 }
142 142
143 143
144 144 delete_repo
145 145 -----------
146 146
147 147 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
148 148
149 149 Deletes a repository.
150 150
151 151 * When the `forks` parameter is set it's possible to detach or delete
152 152 forks of deleted repository.
153 153
154 154 This command can only be run using an |authtoken| with admin
155 155 permissions on the |repo|.
156 156
157 157 :param apiuser: This is filled automatically from the |authtoken|.
158 158 :type apiuser: AuthUser
159 159 :param repoid: Set the repository name or repository ID.
160 160 :type repoid: str or int
161 161 :param forks: Set to `detach` or `delete` forks from the |repo|.
162 162 :type forks: Optional(str)
163 163
164 164 Example error output:
165 165
166 166 .. code-block:: bash
167 167
168 168 id : <id_given_in_input>
169 169 result: {
170 170 "msg": "Deleted repository `<reponame>`",
171 171 "success": true
172 172 }
173 173 error: null
174 174
175 175
176 edit_comment
177 ------------
178
179 .. py:function:: edit_comment(apiuser, message, comment_id, version, userid=<Optional:<OptionalAttr:apiuser>>)
180
181 Edit comment on the pull request or commit,
182 specified by the `comment_id` and version. Initially version should be 0
183
184 :param apiuser: This is filled automatically from the |authtoken|.
185 :type apiuser: AuthUser
186 :param comment_id: Specify the comment_id for editing
187 :type comment_id: int
188 :param version: version of the comment that will be created, starts from 0
189 :type version: int
190 :param message: The text content of the comment.
191 :type message: str
192 :param userid: Comment on the pull request as this user
193 :type userid: Optional(str or int)
194
195 Example output:
196
197 .. code-block:: bash
198
199 id : <id_given_in_input>
200 result : {
201 "comment": "<comment data>",
202 "version": "<Integer>",
203 },
204 error : null
205
206
176 207 fork_repo
177 208 ---------
178 209
179 210 .. py:function:: fork_repo(apiuser, repoid, fork_name, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:None>, copy_permissions=<Optional:False>)
180 211
181 212 Creates a fork of the specified |repo|.
182 213
183 214 * If the fork_name contains "/", fork will be created inside
184 215 a repository group or nested repository groups
185 216
186 217 For example "foo/bar/fork-repo" will create fork called "fork-repo"
187 218 inside group "foo/bar". You have to have permissions to access and
188 219 write to the last repository group ("bar" in this example)
189 220
190 221 This command can only be run using an |authtoken| with minimum
191 222 read permissions of the forked repo, create fork permissions for an user.
192 223
193 224 :param apiuser: This is filled automatically from the |authtoken|.
194 225 :type apiuser: AuthUser
195 226 :param repoid: Set repository name or repository ID.
196 227 :type repoid: str or int
197 228 :param fork_name: Set the fork name, including it's repository group membership.
198 229 :type fork_name: str
199 230 :param owner: Set the fork owner.
200 231 :type owner: str
201 232 :param description: Set the fork description.
202 233 :type description: str
203 234 :param copy_permissions: Copy permissions from parent |repo|. The
204 235 default is False.
205 236 :type copy_permissions: bool
206 237 :param private: Make the fork private. The default is False.
207 238 :type private: bool
208 239 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
209 240
210 241 Example output:
211 242
212 243 .. code-block:: bash
213 244
214 245 id : <id_for_response>
215 246 api_key : "<api_key>"
216 247 args: {
217 248 "repoid" : "<reponame or repo_id>",
218 249 "fork_name": "<forkname>",
219 250 "owner": "<username or user_id = Optional(=apiuser)>",
220 251 "description": "<description>",
221 252 "copy_permissions": "<bool>",
222 253 "private": "<bool>",
223 254 "landing_rev": "<landing_rev>"
224 255 }
225 256
226 257 Example error output:
227 258
228 259 .. code-block:: bash
229 260
230 261 id : <id_given_in_input>
231 262 result: {
232 263 "msg": "Created fork of `<reponame>` as `<forkname>`",
233 264 "success": true,
234 265 "task": "<celery task id or None if done sync>"
235 266 }
236 267 error: null
237 268
238 269
270 get_comment
271 -----------
272
273 .. py:function:: get_comment(apiuser, comment_id)
274
275 Get single comment from repository or pull_request
276
277 :param apiuser: This is filled automatically from the |authtoken|.
278 :type apiuser: AuthUser
279 :param comment_id: comment id found in the URL of comment
280 :type comment_id: str or int
281
282 Example error output:
283
284 .. code-block:: bash
285
286 {
287 "id" : <id_given_in_input>,
288 "result" : {
289 "comment_author": <USER_DETAILS>,
290 "comment_created_on": "2017-02-01T14:38:16.309",
291 "comment_f_path": "file.txt",
292 "comment_id": 282,
293 "comment_lineno": "n1",
294 "comment_resolved_by": null,
295 "comment_status": [],
296 "comment_text": "This file needs a header",
297 "comment_type": "todo",
298 "comment_last_version: 0
299 },
300 "error" : null
301 }
302
303
239 304 get_repo
240 305 --------
241 306
242 307 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
243 308
244 309 Gets an existing repository by its name or repository_id.
245 310
246 311 The members section so the output returns users groups or users
247 312 associated with that repository.
248 313
249 314 This command can only be run using an |authtoken| with admin rights,
250 315 or users with at least read rights to the |repo|.
251 316
252 317 :param apiuser: This is filled automatically from the |authtoken|.
253 318 :type apiuser: AuthUser
254 319 :param repoid: The repository name or repository id.
255 320 :type repoid: str or int
256 321 :param cache: use the cached value for last changeset
257 322 :type: cache: Optional(bool)
258 323
259 324 Example output:
260 325
261 326 .. code-block:: bash
262 327
263 328 {
264 329 "error": null,
265 330 "id": <repo_id>,
266 331 "result": {
267 332 "clone_uri": null,
268 333 "created_on": "timestamp",
269 334 "description": "repo description",
270 335 "enable_downloads": false,
271 336 "enable_locking": false,
272 337 "enable_statistics": false,
273 338 "followers": [
274 339 {
275 340 "active": true,
276 341 "admin": false,
277 342 "api_key": "****************************************",
278 343 "api_keys": [
279 344 "****************************************"
280 345 ],
281 346 "email": "user@example.com",
282 347 "emails": [
283 348 "user@example.com"
284 349 ],
285 350 "extern_name": "rhodecode",
286 351 "extern_type": "rhodecode",
287 352 "firstname": "username",
288 353 "ip_addresses": [],
289 354 "language": null,
290 355 "last_login": "2015-09-16T17:16:35.854",
291 356 "lastname": "surname",
292 357 "user_id": <user_id>,
293 358 "username": "name"
294 359 }
295 360 ],
296 361 "fork_of": "parent-repo",
297 362 "landing_rev": [
298 363 "rev",
299 364 "tip"
300 365 ],
301 366 "last_changeset": {
302 367 "author": "User <user@example.com>",
303 368 "branch": "default",
304 369 "date": "timestamp",
305 370 "message": "last commit message",
306 371 "parents": [
307 372 {
308 373 "raw_id": "commit-id"
309 374 }
310 375 ],
311 376 "raw_id": "commit-id",
312 377 "revision": <revision number>,
313 378 "short_id": "short id"
314 379 },
315 380 "lock_reason": null,
316 381 "locked_by": null,
317 382 "locked_date": null,
318 383 "owner": "owner-name",
319 384 "permissions": [
320 385 {
321 386 "name": "super-admin-name",
322 387 "origin": "super-admin",
323 388 "permission": "repository.admin",
324 389 "type": "user"
325 390 },
326 391 {
327 392 "name": "owner-name",
328 393 "origin": "owner",
329 394 "permission": "repository.admin",
330 395 "type": "user"
331 396 },
332 397 {
333 398 "name": "user-group-name",
334 399 "origin": "permission",
335 400 "permission": "repository.write",
336 401 "type": "user_group"
337 402 }
338 403 ],
339 404 "private": true,
340 405 "repo_id": 676,
341 406 "repo_name": "user-group/repo-name",
342 407 "repo_type": "hg"
343 408 }
344 409 }
345 410
346 411
347 412 get_repo_changeset
348 413 ------------------
349 414
350 415 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
351 416
352 417 Returns information about a changeset.
353 418
354 419 Additionally parameters define the amount of details returned by
355 420 this function.
356 421
357 422 This command can only be run using an |authtoken| with admin rights,
358 423 or users with at least read rights to the |repo|.
359 424
360 425 :param apiuser: This is filled automatically from the |authtoken|.
361 426 :type apiuser: AuthUser
362 427 :param repoid: The repository name or repository id
363 428 :type repoid: str or int
364 429 :param revision: revision for which listing should be done
365 430 :type revision: str
366 431 :param details: details can be 'basic|extended|full' full gives diff
367 432 info details like the diff itself, and number of changed files etc.
368 433 :type details: Optional(str)
369 434
370 435
371 436 get_repo_changesets
372 437 -------------------
373 438
374 439 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
375 440
376 441 Returns a set of commits limited by the number starting
377 442 from the `start_rev` option.
378 443
379 444 Additional parameters define the amount of details returned by this
380 445 function.
381 446
382 447 This command can only be run using an |authtoken| with admin rights,
383 448 or users with at least read rights to |repos|.
384 449
385 450 :param apiuser: This is filled automatically from the |authtoken|.
386 451 :type apiuser: AuthUser
387 452 :param repoid: The repository name or repository ID.
388 453 :type repoid: str or int
389 454 :param start_rev: The starting revision from where to get changesets.
390 455 :type start_rev: str
391 456 :param limit: Limit the number of commits to this amount
392 457 :type limit: str or int
393 458 :param details: Set the level of detail returned. Valid option are:
394 459 ``basic``, ``extended`` and ``full``.
395 460 :type details: Optional(str)
396 461
397 462 .. note::
398 463
399 464 Setting the parameter `details` to the value ``full`` is extensive
400 465 and returns details like the diff itself, and the number
401 466 of changed files.
402 467
403 468
404 469 get_repo_comments
405 470 -----------------
406 471
407 472 .. py:function:: get_repo_comments(apiuser, repoid, commit_id=<Optional:None>, comment_type=<Optional:None>, userid=<Optional:None>)
408 473
409 474 Get all comments for a repository
410 475
411 476 :param apiuser: This is filled automatically from the |authtoken|.
412 477 :type apiuser: AuthUser
413 478 :param repoid: Set the repository name or repository ID.
414 479 :type repoid: str or int
415 480 :param commit_id: Optionally filter the comments by the commit_id
416 481 :type commit_id: Optional(str), default: None
417 482 :param comment_type: Optionally filter the comments by the comment_type
418 483 one of: 'note', 'todo'
419 484 :type comment_type: Optional(str), default: None
420 485 :param userid: Optionally filter the comments by the author of comment
421 486 :type userid: Optional(str or int), Default: None
422 487
423 488 Example error output:
424 489
425 490 .. code-block:: bash
426 491
427 492 {
428 493 "id" : <id_given_in_input>,
429 494 "result" : [
430 495 {
431 496 "comment_author": <USER_DETAILS>,
432 497 "comment_created_on": "2017-02-01T14:38:16.309",
433 498 "comment_f_path": "file.txt",
434 499 "comment_id": 282,
435 500 "comment_lineno": "n1",
436 501 "comment_resolved_by": null,
437 502 "comment_status": [],
438 503 "comment_text": "This file needs a header",
439 "comment_type": "todo"
504 "comment_type": "todo",
505 "comment_last_version: 0
440 506 }
441 507 ],
442 508 "error" : null
443 509 }
444 510
445 511
446 512 get_repo_file
447 513 -------------
448 514
449 515 .. py:function:: get_repo_file(apiuser, repoid, commit_id, file_path, max_file_bytes=<Optional:None>, details=<Optional:'basic'>, cache=<Optional:True>)
450 516
451 517 Returns a single file from repository at given revision.
452 518
453 519 This command can only be run using an |authtoken| with admin rights,
454 520 or users with at least read rights to |repos|.
455 521
456 522 :param apiuser: This is filled automatically from the |authtoken|.
457 523 :type apiuser: AuthUser
458 524 :param repoid: The repository name or repository ID.
459 525 :type repoid: str or int
460 526 :param commit_id: The revision for which listing should be done.
461 527 :type commit_id: str
462 528 :param file_path: The path from which to start displaying.
463 529 :type file_path: str
464 530 :param details: Returns different set of information about nodes.
465 531 The valid options are ``minimal`` ``basic`` and ``full``.
466 532 :type details: Optional(str)
467 533 :param max_file_bytes: Only return file content under this file size bytes
468 534 :type max_file_bytes: Optional(int)
469 535 :param cache: Use internal caches for fetching files. If disabled fetching
470 536 files is slower but more memory efficient
471 537 :type cache: Optional(bool)
472 538
473 539 Example output:
474 540
475 541 .. code-block:: bash
476 542
477 543 id : <id_given_in_input>
478 544 result: {
479 545 "binary": false,
480 546 "extension": "py",
481 547 "lines": 35,
482 548 "content": "....",
483 549 "md5": "76318336366b0f17ee249e11b0c99c41",
484 550 "mimetype": "text/x-python",
485 551 "name": "python.py",
486 552 "size": 817,
487 553 "type": "file",
488 554 }
489 555 error: null
490 556
491 557
492 558 get_repo_fts_tree
493 559 -----------------
494 560
495 561 .. py:function:: get_repo_fts_tree(apiuser, repoid, commit_id, root_path)
496 562
497 563 Returns a list of tree nodes for path at given revision. This api is built
498 564 strictly for usage in full text search building, and shouldn't be consumed
499 565
500 566 This command can only be run using an |authtoken| with admin rights,
501 567 or users with at least read rights to |repos|.
502 568
503 569
504 570 get_repo_nodes
505 571 --------------
506 572
507 573 .. py:function:: get_repo_nodes(apiuser, repoid, revision, root_path, ret_type=<Optional:'all'>, details=<Optional:'basic'>, max_file_bytes=<Optional:None>)
508 574
509 575 Returns a list of nodes and children in a flat list for a given
510 576 path at given revision.
511 577
512 578 It's possible to specify ret_type to show only `files` or `dirs`.
513 579
514 580 This command can only be run using an |authtoken| with admin rights,
515 581 or users with at least read rights to |repos|.
516 582
517 583 :param apiuser: This is filled automatically from the |authtoken|.
518 584 :type apiuser: AuthUser
519 585 :param repoid: The repository name or repository ID.
520 586 :type repoid: str or int
521 587 :param revision: The revision for which listing should be done.
522 588 :type revision: str
523 589 :param root_path: The path from which to start displaying.
524 590 :type root_path: str
525 591 :param ret_type: Set the return type. Valid options are
526 592 ``all`` (default), ``files`` and ``dirs``.
527 593 :type ret_type: Optional(str)
528 594 :param details: Returns extended information about nodes, such as
529 595 md5, binary, and or content.
530 596 The valid options are ``basic`` and ``full``.
531 597 :type details: Optional(str)
532 598 :param max_file_bytes: Only return file content under this file size bytes
533 599 :type details: Optional(int)
534 600
535 601 Example output:
536 602
537 603 .. code-block:: bash
538 604
539 605 id : <id_given_in_input>
540 606 result: [
541 607 {
542 608 "binary": false,
543 609 "content": "File line",
544 610 "extension": "md",
545 611 "lines": 2,
546 612 "md5": "059fa5d29b19c0657e384749480f6422",
547 613 "mimetype": "text/x-minidsrc",
548 614 "name": "file.md",
549 615 "size": 580,
550 616 "type": "file"
551 617 },
552 618 ...
553 619 ]
554 620 error: null
555 621
556 622
557 623 get_repo_refs
558 624 -------------
559 625
560 626 .. py:function:: get_repo_refs(apiuser, repoid)
561 627
562 628 Returns a dictionary of current references. It returns
563 629 bookmarks, branches, closed_branches, and tags for given repository
564 630
565 631 It's possible to specify ret_type to show only `files` or `dirs`.
566 632
567 633 This command can only be run using an |authtoken| with admin rights,
568 634 or users with at least read rights to |repos|.
569 635
570 636 :param apiuser: This is filled automatically from the |authtoken|.
571 637 :type apiuser: AuthUser
572 638 :param repoid: The repository name or repository ID.
573 639 :type repoid: str or int
574 640
575 641 Example output:
576 642
577 643 .. code-block:: bash
578 644
579 645 id : <id_given_in_input>
580 646 "result": {
581 647 "bookmarks": {
582 648 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
583 649 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
584 650 },
585 651 "branches": {
586 652 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
587 653 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
588 654 },
589 655 "branches_closed": {},
590 656 "tags": {
591 657 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
592 658 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
593 659 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
594 660 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
595 661 }
596 662 }
597 663 error: null
598 664
599 665
600 666 get_repo_settings
601 667 -----------------
602 668
603 669 .. py:function:: get_repo_settings(apiuser, repoid, key=<Optional:None>)
604 670
605 671 Returns all settings for a repository. If key is given it only returns the
606 672 setting identified by the key or null.
607 673
608 674 :param apiuser: This is filled automatically from the |authtoken|.
609 675 :type apiuser: AuthUser
610 676 :param repoid: The repository name or repository id.
611 677 :type repoid: str or int
612 678 :param key: Key of the setting to return.
613 679 :type: key: Optional(str)
614 680
615 681 Example output:
616 682
617 683 .. code-block:: bash
618 684
619 685 {
620 686 "error": null,
621 687 "id": 237,
622 688 "result": {
623 689 "extensions_largefiles": true,
624 690 "extensions_evolve": true,
625 691 "hooks_changegroup_push_logger": true,
626 692 "hooks_changegroup_repo_size": false,
627 693 "hooks_outgoing_pull_logger": true,
628 694 "phases_publish": "True",
629 695 "rhodecode_hg_use_rebase_for_merging": true,
630 696 "rhodecode_pr_merge_enabled": true,
631 697 "rhodecode_use_outdated_comments": true
632 698 }
633 699 }
634 700
635 701
636 702 get_repos
637 703 ---------
638 704
639 705 .. py:function:: get_repos(apiuser, root=<Optional:None>, traverse=<Optional:True>)
640 706
641 707 Lists all existing repositories.
642 708
643 709 This command can only be run using an |authtoken| with admin rights,
644 710 or users with at least read rights to |repos|.
645 711
646 712 :param apiuser: This is filled automatically from the |authtoken|.
647 713 :type apiuser: AuthUser
648 714 :param root: specify root repository group to fetch repositories.
649 715 filters the returned repositories to be members of given root group.
650 716 :type root: Optional(None)
651 717 :param traverse: traverse given root into subrepositories. With this flag
652 718 set to False, it will only return top-level repositories from `root`.
653 719 if root is empty it will return just top-level repositories.
654 720 :type traverse: Optional(True)
655 721
656 722
657 723 Example output:
658 724
659 725 .. code-block:: bash
660 726
661 727 id : <id_given_in_input>
662 728 result: [
663 729 {
664 730 "repo_id" : "<repo_id>",
665 731 "repo_name" : "<reponame>"
666 732 "repo_type" : "<repo_type>",
667 733 "clone_uri" : "<clone_uri>",
668 734 "private": : "<bool>",
669 735 "created_on" : "<datetimecreated>",
670 736 "description" : "<description>",
671 737 "landing_rev": "<landing_rev>",
672 738 "owner": "<repo_owner>",
673 739 "fork_of": "<name_of_fork_parent>",
674 740 "enable_downloads": "<bool>",
675 741 "enable_locking": "<bool>",
676 742 "enable_statistics": "<bool>",
677 743 },
678 744 ...
679 745 ]
680 746 error: null
681 747
682 748
683 749 grant_user_group_permission
684 750 ---------------------------
685 751
686 752 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
687 753
688 754 Grant permission for a user group on the specified repository,
689 755 or update existing permissions.
690 756
691 757 This command can only be run using an |authtoken| with admin
692 758 permissions on the |repo|.
693 759
694 760 :param apiuser: This is filled automatically from the |authtoken|.
695 761 :type apiuser: AuthUser
696 762 :param repoid: Set the repository name or repository ID.
697 763 :type repoid: str or int
698 764 :param usergroupid: Specify the ID of the user group.
699 765 :type usergroupid: str or int
700 766 :param perm: Set the user group permissions using the following
701 767 format: (repository.(none|read|write|admin))
702 768 :type perm: str
703 769
704 770 Example output:
705 771
706 772 .. code-block:: bash
707 773
708 774 id : <id_given_in_input>
709 775 result : {
710 776 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
711 777 "success": true
712 778
713 779 }
714 780 error : null
715 781
716 782 Example error output:
717 783
718 784 .. code-block:: bash
719 785
720 786 id : <id_given_in_input>
721 787 result : null
722 788 error : {
723 789 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
724 790 }
725 791
726 792
727 793 grant_user_permission
728 794 ---------------------
729 795
730 796 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
731 797
732 798 Grant permissions for the specified user on the given repository,
733 799 or update existing permissions if found.
734 800
735 801 This command can only be run using an |authtoken| with admin
736 802 permissions on the |repo|.
737 803
738 804 :param apiuser: This is filled automatically from the |authtoken|.
739 805 :type apiuser: AuthUser
740 806 :param repoid: Set the repository name or repository ID.
741 807 :type repoid: str or int
742 808 :param userid: Set the user name.
743 809 :type userid: str
744 810 :param perm: Set the user permissions, using the following format
745 811 ``(repository.(none|read|write|admin))``
746 812 :type perm: str
747 813
748 814 Example output:
749 815
750 816 .. code-block:: bash
751 817
752 818 id : <id_given_in_input>
753 819 result: {
754 820 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
755 821 "success": true
756 822 }
757 823 error: null
758 824
759 825
760 826 invalidate_cache
761 827 ----------------
762 828
763 829 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
764 830
765 831 Invalidates the cache for the specified repository.
766 832
767 833 This command can only be run using an |authtoken| with admin rights to
768 834 the specified repository.
769 835
770 836 This command takes the following options:
771 837
772 838 :param apiuser: This is filled automatically from |authtoken|.
773 839 :type apiuser: AuthUser
774 840 :param repoid: Sets the repository name or repository ID.
775 841 :type repoid: str or int
776 842 :param delete_keys: This deletes the invalidated keys instead of
777 843 just flagging them.
778 844 :type delete_keys: Optional(``True`` | ``False``)
779 845
780 846 Example output:
781 847
782 848 .. code-block:: bash
783 849
784 850 id : <id_given_in_input>
785 851 result : {
786 852 'msg': Cache for repository `<repository name>` was invalidated,
787 853 'repository': <repository name>
788 854 }
789 855 error : null
790 856
791 857 Example error output:
792 858
793 859 .. code-block:: bash
794 860
795 861 id : <id_given_in_input>
796 862 result : null
797 863 error : {
798 864 'Error occurred during cache invalidation action'
799 865 }
800 866
801 867
802 868 lock
803 869 ----
804 870
805 871 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
806 872
807 873 Sets the lock state of the specified |repo| by the given user.
808 874 From more information, see :ref:`repo-locking`.
809 875
810 876 * If the ``userid`` option is not set, the repository is locked to the
811 877 user who called the method.
812 878 * If the ``locked`` parameter is not set, the current lock state of the
813 879 repository is displayed.
814 880
815 881 This command can only be run using an |authtoken| with admin rights to
816 882 the specified repository.
817 883
818 884 This command takes the following options:
819 885
820 886 :param apiuser: This is filled automatically from the |authtoken|.
821 887 :type apiuser: AuthUser
822 888 :param repoid: Sets the repository name or repository ID.
823 889 :type repoid: str or int
824 890 :param locked: Sets the lock state.
825 891 :type locked: Optional(``True`` | ``False``)
826 892 :param userid: Set the repository lock to this user.
827 893 :type userid: Optional(str or int)
828 894
829 895 Example error output:
830 896
831 897 .. code-block:: bash
832 898
833 899 id : <id_given_in_input>
834 900 result : {
835 901 'repo': '<reponame>',
836 902 'locked': <bool: lock state>,
837 903 'locked_since': <int: lock timestamp>,
838 904 'locked_by': <username of person who made the lock>,
839 905 'lock_reason': <str: reason for locking>,
840 906 'lock_state_changed': <bool: True if lock state has been changed in this request>,
841 907 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
842 908 or
843 909 'msg': 'Repo `<repository name>` not locked.'
844 910 or
845 911 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
846 912 }
847 913 error : null
848 914
849 915 Example error output:
850 916
851 917 .. code-block:: bash
852 918
853 919 id : <id_given_in_input>
854 920 result : null
855 921 error : {
856 922 'Error occurred locking repository `<reponame>`'
857 923 }
858 924
859 925
860 926 maintenance
861 927 -----------
862 928
863 929 .. py:function:: maintenance(apiuser, repoid)
864 930
865 931 Triggers a maintenance on the given repository.
866 932
867 933 This command can only be run using an |authtoken| with admin
868 934 rights to the specified repository. For more information,
869 935 see :ref:`config-token-ref`.
870 936
871 937 This command takes the following options:
872 938
873 939 :param apiuser: This is filled automatically from the |authtoken|.
874 940 :type apiuser: AuthUser
875 941 :param repoid: The repository name or repository ID.
876 942 :type repoid: str or int
877 943
878 944 Example output:
879 945
880 946 .. code-block:: bash
881 947
882 948 id : <id_given_in_input>
883 949 result : {
884 950 "msg": "executed maintenance command",
885 951 "executed_actions": [
886 952 <action_message>, <action_message2>...
887 953 ],
888 954 "repository": "<repository name>"
889 955 }
890 956 error : null
891 957
892 958 Example error output:
893 959
894 960 .. code-block:: bash
895 961
896 962 id : <id_given_in_input>
897 963 result : null
898 964 error : {
899 965 "Unable to execute maintenance on `<reponame>`"
900 966 }
901 967
902 968
903 969 pull
904 970 ----
905 971
906 972 .. py:function:: pull(apiuser, repoid, remote_uri=<Optional:None>)
907 973
908 974 Triggers a pull on the given repository from a remote location. You
909 975 can use this to keep remote repositories up-to-date.
910 976
911 977 This command can only be run using an |authtoken| with admin
912 978 rights to the specified repository. For more information,
913 979 see :ref:`config-token-ref`.
914 980
915 981 This command takes the following options:
916 982
917 983 :param apiuser: This is filled automatically from the |authtoken|.
918 984 :type apiuser: AuthUser
919 985 :param repoid: The repository name or repository ID.
920 986 :type repoid: str or int
921 987 :param remote_uri: Optional remote URI to pass in for pull
922 988 :type remote_uri: str
923 989
924 990 Example output:
925 991
926 992 .. code-block:: bash
927 993
928 994 id : <id_given_in_input>
929 995 result : {
930 996 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
931 997 "repository": "<repository name>"
932 998 }
933 999 error : null
934 1000
935 1001 Example error output:
936 1002
937 1003 .. code-block:: bash
938 1004
939 1005 id : <id_given_in_input>
940 1006 result : null
941 1007 error : {
942 1008 "Unable to push changes from `<remote_url>`"
943 1009 }
944 1010
945 1011
946 1012 remove_field_from_repo
947 1013 ----------------------
948 1014
949 1015 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
950 1016
951 1017 Removes an extra field from a repository.
952 1018
953 1019 This command can only be run using an |authtoken| with at least
954 1020 write permissions to the |repo|.
955 1021
956 1022 :param apiuser: This is filled automatically from the |authtoken|.
957 1023 :type apiuser: AuthUser
958 1024 :param repoid: Set the repository name or repository ID.
959 1025 :type repoid: str or int
960 1026 :param key: Set the unique field key for this repository.
961 1027 :type key: str
962 1028
963 1029
964 1030 revoke_user_group_permission
965 1031 ----------------------------
966 1032
967 1033 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
968 1034
969 1035 Revoke the permissions of a user group on a given repository.
970 1036
971 1037 This command can only be run using an |authtoken| with admin
972 1038 permissions on the |repo|.
973 1039
974 1040 :param apiuser: This is filled automatically from the |authtoken|.
975 1041 :type apiuser: AuthUser
976 1042 :param repoid: Set the repository name or repository ID.
977 1043 :type repoid: str or int
978 1044 :param usergroupid: Specify the user group ID.
979 1045 :type usergroupid: str or int
980 1046
981 1047 Example output:
982 1048
983 1049 .. code-block:: bash
984 1050
985 1051 id : <id_given_in_input>
986 1052 result: {
987 1053 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
988 1054 "success": true
989 1055 }
990 1056 error: null
991 1057
992 1058
993 1059 revoke_user_permission
994 1060 ----------------------
995 1061
996 1062 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
997 1063
998 1064 Revoke permission for a user on the specified repository.
999 1065
1000 1066 This command can only be run using an |authtoken| with admin
1001 1067 permissions on the |repo|.
1002 1068
1003 1069 :param apiuser: This is filled automatically from the |authtoken|.
1004 1070 :type apiuser: AuthUser
1005 1071 :param repoid: Set the repository name or repository ID.
1006 1072 :type repoid: str or int
1007 1073 :param userid: Set the user name of revoked user.
1008 1074 :type userid: str or int
1009 1075
1010 1076 Example error output:
1011 1077
1012 1078 .. code-block:: bash
1013 1079
1014 1080 id : <id_given_in_input>
1015 1081 result: {
1016 1082 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1017 1083 "success": true
1018 1084 }
1019 1085 error: null
1020 1086
1021 1087
1022 1088 set_repo_settings
1023 1089 -----------------
1024 1090
1025 1091 .. py:function:: set_repo_settings(apiuser, repoid, settings)
1026 1092
1027 1093 Update repository settings. Returns true on success.
1028 1094
1029 1095 :param apiuser: This is filled automatically from the |authtoken|.
1030 1096 :type apiuser: AuthUser
1031 1097 :param repoid: The repository name or repository id.
1032 1098 :type repoid: str or int
1033 1099 :param settings: The new settings for the repository.
1034 1100 :type: settings: dict
1035 1101
1036 1102 Example output:
1037 1103
1038 1104 .. code-block:: bash
1039 1105
1040 1106 {
1041 1107 "error": null,
1042 1108 "id": 237,
1043 1109 "result": true
1044 1110 }
1045 1111
1046 1112
1047 1113 strip
1048 1114 -----
1049 1115
1050 1116 .. py:function:: strip(apiuser, repoid, revision, branch)
1051 1117
1052 1118 Strips the given revision from the specified repository.
1053 1119
1054 1120 * This will remove the revision and all of its decendants.
1055 1121
1056 1122 This command can only be run using an |authtoken| with admin rights to
1057 1123 the specified repository.
1058 1124
1059 1125 This command takes the following options:
1060 1126
1061 1127 :param apiuser: This is filled automatically from the |authtoken|.
1062 1128 :type apiuser: AuthUser
1063 1129 :param repoid: The repository name or repository ID.
1064 1130 :type repoid: str or int
1065 1131 :param revision: The revision you wish to strip.
1066 1132 :type revision: str
1067 1133 :param branch: The branch from which to strip the revision.
1068 1134 :type branch: str
1069 1135
1070 1136 Example output:
1071 1137
1072 1138 .. code-block:: bash
1073 1139
1074 1140 id : <id_given_in_input>
1075 1141 result : {
1076 1142 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1077 1143 "repository": "<repository name>"
1078 1144 }
1079 1145 error : null
1080 1146
1081 1147 Example error output:
1082 1148
1083 1149 .. code-block:: bash
1084 1150
1085 1151 id : <id_given_in_input>
1086 1152 result : null
1087 1153 error : {
1088 1154 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1089 1155 }
1090 1156
1091 1157
1092 1158 update_repo
1093 1159 -----------
1094 1160
1095 1161 .. py:function:: update_repo(apiuser, repoid, repo_name=<Optional:None>, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, push_uri=<Optional:None>, landing_rev=<Optional:None>, fork_of=<Optional:None>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, fields=<Optional:''>)
1096 1162
1097 1163 Updates a repository with the given information.
1098 1164
1099 1165 This command can only be run using an |authtoken| with at least
1100 1166 admin permissions to the |repo|.
1101 1167
1102 1168 * If the repository name contains "/", repository will be updated
1103 1169 accordingly with a repository group or nested repository groups
1104 1170
1105 1171 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
1106 1172 called "repo-test" and place it inside group "foo/bar".
1107 1173 You have to have permissions to access and write to the last repository
1108 1174 group ("bar" in this example)
1109 1175
1110 1176 :param apiuser: This is filled automatically from the |authtoken|.
1111 1177 :type apiuser: AuthUser
1112 1178 :param repoid: repository name or repository ID.
1113 1179 :type repoid: str or int
1114 1180 :param repo_name: Update the |repo| name, including the
1115 1181 repository group it's in.
1116 1182 :type repo_name: str
1117 1183 :param owner: Set the |repo| owner.
1118 1184 :type owner: str
1119 1185 :param fork_of: Set the |repo| as fork of another |repo|.
1120 1186 :type fork_of: str
1121 1187 :param description: Update the |repo| description.
1122 1188 :type description: str
1123 1189 :param private: Set the |repo| as private. (True | False)
1124 1190 :type private: bool
1125 1191 :param clone_uri: Update the |repo| clone URI.
1126 1192 :type clone_uri: str
1127 1193 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1128 1194 :type landing_rev: str
1129 1195 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1130 1196 :type enable_statistics: bool
1131 1197 :param enable_locking: Enable |repo| locking.
1132 1198 :type enable_locking: bool
1133 1199 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1134 1200 :type enable_downloads: bool
1135 1201 :param fields: Add extra fields to the |repo|. Use the following
1136 1202 example format: ``field_key=field_val,field_key2=fieldval2``.
1137 1203 Escape ', ' with \,
1138 1204 :type fields: str
1139 1205
1140 1206
@@ -1,30 +1,45 b''
1 1 .. _set-up-mail:
2 2
3 3 Set up Email
4 4 ------------
5 5
6 6 To setup email with your |RCE| instance, open the default
7 7 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
8 8 file and uncomment and configure the email section. If it is not there,
9 9 use the below example to insert it.
10 10
11 11 Once configured you can check the settings for your |RCE| instance on the
12 12 :menuselection:`Admin --> Settings --> Email` page.
13 13
14 Please be aware that both section should be changed the `[DEFAULT]` for main applications
15 email config, and `[server:main]` for exception tracking email
16
14 17 .. code-block:: ini
15 18
16 ################################################################################
17 ## Uncomment and replace with the email address which should receive ##
18 ## any error reports after an application crash ##
19 ## Additionally these settings will be used by the RhodeCode mailing system ##
20 ################################################################################
21 #email_to = admin@localhost
19 [DEFAULT]
20 ; ########################################################################
21 ; EMAIL CONFIGURATION
22 ; These settings will be used by the RhodeCode mailing system
23 ; ########################################################################
24
25 ; prefix all emails subjects with given prefix, helps filtering out emails
26 #email_prefix = [RhodeCode]
27
28 ; email FROM address all mails will be sent
22 29 #app_email_from = rhodecode-noreply@localhost
23 #email_prefix = [RhodeCode]
24 30
25 31 #smtp_server = mail.server.com
26 32 #smtp_username =
27 33 #smtp_password =
28 34 #smtp_port =
29 35 #smtp_use_tls = false
30 36 #smtp_use_ssl = true
37
38 [server:main]
39 ; Send email with exception details when it happens
40 #exception_tracker.send_email = true
41
42 ; Comma separated list of recipients for exception emails,
43 ; e.g admin@rhodecode.com,devops@rhodecode.com
44 ; Can be left empty, then emails will be sent to ALL super-admins
45 #exception_tracker.send_email_recipients =
@@ -1,144 +1,145 b''
1 1 .. _rhodecode-release-notes-ref:
2 2
3 3 Release Notes
4 4 =============
5 5
6 6 |RCE| 4.x Versions
7 7 ------------------
8 8
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.20.0.rst
12 13 release-notes-4.19.3.rst
13 14 release-notes-4.19.2.rst
14 15 release-notes-4.19.1.rst
15 16 release-notes-4.19.0.rst
16 17 release-notes-4.18.3.rst
17 18 release-notes-4.18.2.rst
18 19 release-notes-4.18.1.rst
19 20 release-notes-4.18.0.rst
20 21 release-notes-4.17.4.rst
21 22 release-notes-4.17.3.rst
22 23 release-notes-4.17.2.rst
23 24 release-notes-4.17.1.rst
24 25 release-notes-4.17.0.rst
25 26 release-notes-4.16.2.rst
26 27 release-notes-4.16.1.rst
27 28 release-notes-4.16.0.rst
28 29 release-notes-4.15.2.rst
29 30 release-notes-4.15.1.rst
30 31 release-notes-4.15.0.rst
31 32 release-notes-4.14.1.rst
32 33 release-notes-4.14.0.rst
33 34 release-notes-4.13.3.rst
34 35 release-notes-4.13.2.rst
35 36 release-notes-4.13.1.rst
36 37 release-notes-4.13.0.rst
37 38 release-notes-4.12.4.rst
38 39 release-notes-4.12.3.rst
39 40 release-notes-4.12.2.rst
40 41 release-notes-4.12.1.rst
41 42 release-notes-4.12.0.rst
42 43 release-notes-4.11.6.rst
43 44 release-notes-4.11.5.rst
44 45 release-notes-4.11.4.rst
45 46 release-notes-4.11.3.rst
46 47 release-notes-4.11.2.rst
47 48 release-notes-4.11.1.rst
48 49 release-notes-4.11.0.rst
49 50 release-notes-4.10.6.rst
50 51 release-notes-4.10.5.rst
51 52 release-notes-4.10.4.rst
52 53 release-notes-4.10.3.rst
53 54 release-notes-4.10.2.rst
54 55 release-notes-4.10.1.rst
55 56 release-notes-4.10.0.rst
56 57 release-notes-4.9.1.rst
57 58 release-notes-4.9.0.rst
58 59 release-notes-4.8.0.rst
59 60 release-notes-4.7.2.rst
60 61 release-notes-4.7.1.rst
61 62 release-notes-4.7.0.rst
62 63 release-notes-4.6.1.rst
63 64 release-notes-4.6.0.rst
64 65 release-notes-4.5.2.rst
65 66 release-notes-4.5.1.rst
66 67 release-notes-4.5.0.rst
67 68 release-notes-4.4.2.rst
68 69 release-notes-4.4.1.rst
69 70 release-notes-4.4.0.rst
70 71 release-notes-4.3.1.rst
71 72 release-notes-4.3.0.rst
72 73 release-notes-4.2.1.rst
73 74 release-notes-4.2.0.rst
74 75 release-notes-4.1.2.rst
75 76 release-notes-4.1.1.rst
76 77 release-notes-4.1.0.rst
77 78 release-notes-4.0.1.rst
78 79 release-notes-4.0.0.rst
79 80
80 81 |RCE| 3.x Versions
81 82 ------------------
82 83
83 84 .. toctree::
84 85 :maxdepth: 1
85 86
86 87 release-notes-3.8.4.rst
87 88 release-notes-3.8.3.rst
88 89 release-notes-3.8.2.rst
89 90 release-notes-3.8.1.rst
90 91 release-notes-3.8.0.rst
91 92 release-notes-3.7.1.rst
92 93 release-notes-3.7.0.rst
93 94 release-notes-3.6.1.rst
94 95 release-notes-3.6.0.rst
95 96 release-notes-3.5.2.rst
96 97 release-notes-3.5.1.rst
97 98 release-notes-3.5.0.rst
98 99 release-notes-3.4.1.rst
99 100 release-notes-3.4.0.rst
100 101 release-notes-3.3.4.rst
101 102 release-notes-3.3.3.rst
102 103 release-notes-3.3.2.rst
103 104 release-notes-3.3.1.rst
104 105 release-notes-3.3.0.rst
105 106 release-notes-3.2.3.rst
106 107 release-notes-3.2.2.rst
107 108 release-notes-3.2.1.rst
108 109 release-notes-3.2.0.rst
109 110 release-notes-3.1.1.rst
110 111 release-notes-3.1.0.rst
111 112 release-notes-3.0.2.rst
112 113 release-notes-3.0.1.rst
113 114 release-notes-3.0.0.rst
114 115
115 116 |RCE| 2.x Versions
116 117 ------------------
117 118
118 119 .. toctree::
119 120 :maxdepth: 1
120 121
121 122 release-notes-2.2.8.rst
122 123 release-notes-2.2.7.rst
123 124 release-notes-2.2.6.rst
124 125 release-notes-2.2.5.rst
125 126 release-notes-2.2.4.rst
126 127 release-notes-2.2.3.rst
127 128 release-notes-2.2.2.rst
128 129 release-notes-2.2.1.rst
129 130 release-notes-2.2.0.rst
130 131 release-notes-2.1.0.rst
131 132 release-notes-2.0.2.rst
132 133 release-notes-2.0.1.rst
133 134 release-notes-2.0.0.rst
134 135
135 136 |RCE| 1.x Versions
136 137 ------------------
137 138
138 139 .. toctree::
139 140 :maxdepth: 1
140 141
141 142 release-notes-1.7.2.rst
142 143 release-notes-1.7.1.rst
143 144 release-notes-1.7.0.rst
144 145 release-notes-1.6.0.rst
@@ -1,184 +1,187 b''
1 1 {
2 2 "dirs": {
3 3 "css": {
4 4 "src": "rhodecode/public/css",
5 5 "dest": "rhodecode/public/css"
6 6 },
7 7 "js": {
8 8 "src": "rhodecode/public/js/src",
9 9 "src_rc": "rhodecode/public/js/rhodecode",
10 10 "dest": "rhodecode/public/js",
11 11 "node_modules": "node_modules"
12 12 }
13 13 },
14 14 "copy": {
15 15 "main": {
16 16 "files": [
17 17 {
18 18 "expand": true,
19 19 "cwd": "node_modules/@webcomponents",
20 20 "src": "webcomponentsjs/*.*",
21 21 "dest": "<%= dirs.js.dest %>/vendors"
22 22 },
23 23 {
24 24 "src": "<%= dirs.css.src %>/style-polymer.css",
25 25 "dest": "<%= dirs.js.dest %>/src/components/style-polymer.css"
26 26 }
27 27 ]
28 28 }
29 29 },
30 30 "concat": {
31 31 "dist": {
32 32 "src": [
33 33 "<%= dirs.js.node_modules %>/jquery/dist/jquery.min.js",
34 34 "<%= dirs.js.node_modules %>/mousetrap/mousetrap.min.js",
35 35 "<%= dirs.js.node_modules %>/moment/min/moment.min.js",
36 36 "<%= dirs.js.node_modules %>/clipboard/dist/clipboard.min.js",
37 37 "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js",
38 38 "<%= dirs.js.node_modules %>/dropzone/dist/min/dropzone.min.js",
39 39 "<%= dirs.js.node_modules %>/sweetalert2/dist/sweetalert2.min.js",
40 40 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/sticky-sidebar.min.js",
41 41 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/jquery.sticky-sidebar.min.js",
42 42 "<%= dirs.js.node_modules %>/waypoints/lib/noframework.waypoints.min.js",
43 43 "<%= dirs.js.node_modules %>/waypoints/lib/jquery.waypoints.min.js",
44 44 "<%= dirs.js.node_modules %>/appenlight-client/appenlight-client.min.js",
45 45 "<%= dirs.js.src %>/logging.js",
46 46 "<%= dirs.js.src %>/bootstrap.js",
47 47 "<%= dirs.js.src %>/i18n_utils.js",
48 48 "<%= dirs.js.src %>/deform.js",
49 49 "<%= dirs.js.src %>/ejs.js",
50 50 "<%= dirs.js.src %>/ejs_templates/utils.js",
51 51 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
52 52 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
53 53 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
54 "<%= dirs.js.src %>/plugins/within_viewport.js",
54 55 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
55 56 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
56 57 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
58 "<%= dirs.js.src %>/plugins/jquery.scrollstop.js",
59 "<%= dirs.js.src %>/plugins/jquery.within-viewport.js",
57 60 "<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
58 61 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
59 62 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
60 63 "<%= dirs.js.src %>/select2/select2.js",
61 64 "<%= dirs.js.src %>/codemirror/codemirror.js",
62 65 "<%= dirs.js.src %>/codemirror/codemirror_loadmode.js",
63 66 "<%= dirs.js.src %>/codemirror/codemirror_hint.js",
64 67 "<%= dirs.js.src %>/codemirror/codemirror_overlay.js",
65 68 "<%= dirs.js.src %>/codemirror/codemirror_placeholder.js",
66 69 "<%= dirs.js.src %>/codemirror/codemirror_simplemode.js",
67 70 "<%= dirs.js.dest %>/mode/meta.js",
68 71 "<%= dirs.js.dest %>/mode/meta_ext.js",
69 72 "<%= dirs.js.src_rc %>/i18n/select2/translations.js",
70 73 "<%= dirs.js.src %>/rhodecode/utils/array.js",
71 74 "<%= dirs.js.src %>/rhodecode/utils/string.js",
72 75 "<%= dirs.js.src %>/rhodecode/utils/pyroutes.js",
73 76 "<%= dirs.js.src %>/rhodecode/utils/ajax.js",
74 77 "<%= dirs.js.src %>/rhodecode/utils/autocomplete.js",
75 78 "<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js",
76 79 "<%= dirs.js.src %>/rhodecode/utils/ie.js",
77 80 "<%= dirs.js.src %>/rhodecode/utils/os.js",
78 81 "<%= dirs.js.src %>/rhodecode/utils/topics.js",
79 82 "<%= dirs.js.src %>/rhodecode/init.js",
80 83 "<%= dirs.js.src %>/rhodecode/changelog.js",
81 84 "<%= dirs.js.src %>/rhodecode/codemirror.js",
82 85 "<%= dirs.js.src %>/rhodecode/comments.js",
83 86 "<%= dirs.js.src %>/rhodecode/constants.js",
84 87 "<%= dirs.js.src %>/rhodecode/files.js",
85 88 "<%= dirs.js.src %>/rhodecode/followers.js",
86 89 "<%= dirs.js.src %>/rhodecode/menus.js",
87 90 "<%= dirs.js.src %>/rhodecode/notifications.js",
88 91 "<%= dirs.js.src %>/rhodecode/permissions.js",
89 92 "<%= dirs.js.src %>/rhodecode/pjax.js",
90 93 "<%= dirs.js.src %>/rhodecode/pullrequests.js",
91 94 "<%= dirs.js.src %>/rhodecode/settings.js",
92 95 "<%= dirs.js.src %>/rhodecode/select2_widgets.js",
93 96 "<%= dirs.js.src %>/rhodecode/tooltips.js",
94 97 "<%= dirs.js.src %>/rhodecode/users.js",
95 98 "<%= dirs.js.src %>/rhodecode/appenlight.js",
96 99 "<%= dirs.js.src %>/rhodecode.js",
97 100 "<%= dirs.js.dest %>/rhodecode-components.js"
98 101 ],
99 102 "dest": "<%= dirs.js.dest %>/scripts.js",
100 103 "nonull": true
101 104 }
102 105 },
103 106 "uglify": {
104 107 "dist": {
105 108 "src": "<%= dirs.js.dest %>/scripts.js",
106 109 "dest": "<%= dirs.js.dest %>/scripts.min.js"
107 110 }
108 111 },
109 112 "less": {
110 113 "development": {
111 114 "options": {
112 115 "compress": false,
113 116 "yuicompress": false,
114 117 "optimization": 0
115 118 },
116 119 "files": {
117 120 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
118 121 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
119 122 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
120 123 }
121 124 },
122 125 "production": {
123 126 "options": {
124 127 "compress": true,
125 128 "yuicompress": true,
126 129 "optimization": 2
127 130 },
128 131 "files": {
129 132 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
130 133 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
131 134 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
132 135 }
133 136 },
134 137 "components": {
135 138 "files": [
136 139 {
137 140 "cwd": "<%= dirs.js.src %>/components/",
138 141 "dest": "<%= dirs.js.src %>/components/",
139 142 "src": [
140 143 "**/*.less"
141 144 ],
142 145 "expand": true,
143 146 "ext": ".css"
144 147 }
145 148 ]
146 149 }
147 150 },
148 151 "watch": {
149 152 "less": {
150 153 "files": [
151 154 "<%= dirs.css.src %>/**/*.less",
152 155 "<%= dirs.js.src %>/components/**/*.less"
153 156 ],
154 157 "tasks": [
155 158 "less:development",
156 159 "less:components",
157 160 "concat:polymercss",
158 161 "webpack",
159 162 "concat:dist"
160 163 ]
161 164 },
162 165 "js": {
163 166 "files": [
164 167 "!<%= dirs.js.src %>/components/root-styles.gen.html",
165 168 "<%= dirs.js.src %>/**/*.js",
166 169 "<%= dirs.js.src %>/components/**/*.html"
167 170 ],
168 171 "tasks": [
169 172 "less:components",
170 173 "concat:polymercss",
171 174 "webpack",
172 175 "concat:dist"
173 176 ]
174 177 }
175 178 },
176 179 "jshint": {
177 180 "rhodecode": {
178 181 "src": "<%= dirs.js.src %>/rhodecode/**/*.js",
179 182 "options": {
180 183 "jshintrc": ".jshintrc"
181 184 }
182 185 }
183 186 }
184 187 }
@@ -1,2444 +1,2444 b''
1 1 # Generated by pip2nix 0.8.0.dev1
2 2 # See https://github.com/johbo/pip2nix
3 3
4 4 { pkgs, fetchurl, fetchgit, fetchhg }:
5 5
6 6 self: super: {
7 7 "alembic" = super.buildPythonPackage {
8 8 name = "alembic-1.4.2";
9 9 doCheck = false;
10 10 propagatedBuildInputs = [
11 11 self."sqlalchemy"
12 12 self."mako"
13 13 self."python-editor"
14 14 self."python-dateutil"
15 15 ];
16 16 src = fetchurl {
17 17 url = "https://files.pythonhosted.org/packages/60/1e/cabc75a189de0fbb2841d0975243e59bde8b7822bacbb95008ac6fe9ad47/alembic-1.4.2.tar.gz";
18 18 sha256 = "1gsdrzx9h7wfva200qvvsc9sn4w79mk2vs0bbnzjhxi1jw2b0nh3";
19 19 };
20 20 meta = {
21 21 license = [ pkgs.lib.licenses.mit ];
22 22 };
23 23 };
24 24 "amqp" = super.buildPythonPackage {
25 25 name = "amqp-2.5.2";
26 26 doCheck = false;
27 27 propagatedBuildInputs = [
28 28 self."vine"
29 29 ];
30 30 src = fetchurl {
31 31 url = "https://files.pythonhosted.org/packages/92/1d/433541994a5a69f4ad2fff39746ddbb0bdedb0ea0d85673eb0db68a7edd9/amqp-2.5.2.tar.gz";
32 32 sha256 = "13dhhfxjrqcjybnq4zahg92mydhpg2l76nxcmq7d560687wsxwbp";
33 33 };
34 34 meta = {
35 35 license = [ pkgs.lib.licenses.bsdOriginal ];
36 36 };
37 37 };
38 38 "appenlight-client" = super.buildPythonPackage {
39 39 name = "appenlight-client-0.6.26";
40 40 doCheck = false;
41 41 propagatedBuildInputs = [
42 42 self."webob"
43 43 self."requests"
44 44 self."six"
45 45 ];
46 46 src = fetchurl {
47 47 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
48 48 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
49 49 };
50 50 meta = {
51 51 license = [ pkgs.lib.licenses.bsdOriginal ];
52 52 };
53 53 };
54 54 "asn1crypto" = super.buildPythonPackage {
55 55 name = "asn1crypto-0.24.0";
56 56 doCheck = false;
57 57 src = fetchurl {
58 58 url = "https://files.pythonhosted.org/packages/fc/f1/8db7daa71f414ddabfa056c4ef792e1461ff655c2ae2928a2b675bfed6b4/asn1crypto-0.24.0.tar.gz";
59 59 sha256 = "0jaf8rf9dx1lf23xfv2cdd5h52f1qr3w8k63985bc35g3d220p4x";
60 60 };
61 61 meta = {
62 62 license = [ pkgs.lib.licenses.mit ];
63 63 };
64 64 };
65 65 "atomicwrites" = super.buildPythonPackage {
66 66 name = "atomicwrites-1.3.0";
67 67 doCheck = false;
68 68 src = fetchurl {
69 69 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
70 70 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
71 71 };
72 72 meta = {
73 73 license = [ pkgs.lib.licenses.mit ];
74 74 };
75 75 };
76 76 "attrs" = super.buildPythonPackage {
77 77 name = "attrs-19.3.0";
78 78 doCheck = false;
79 79 src = fetchurl {
80 80 url = "https://files.pythonhosted.org/packages/98/c3/2c227e66b5e896e15ccdae2e00bbc69aa46e9a8ce8869cc5fa96310bf612/attrs-19.3.0.tar.gz";
81 81 sha256 = "0wky4h28n7xnr6xv69p9z6kv8bzn50d10c3drmd9ds8gawbcxdzp";
82 82 };
83 83 meta = {
84 84 license = [ pkgs.lib.licenses.mit ];
85 85 };
86 86 };
87 87 "babel" = super.buildPythonPackage {
88 88 name = "babel-1.3";
89 89 doCheck = false;
90 90 propagatedBuildInputs = [
91 91 self."pytz"
92 92 ];
93 93 src = fetchurl {
94 94 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
95 95 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
96 96 };
97 97 meta = {
98 98 license = [ pkgs.lib.licenses.bsdOriginal ];
99 99 };
100 100 };
101 101 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
102 102 name = "backports.shutil-get-terminal-size-1.0.0";
103 103 doCheck = false;
104 104 src = fetchurl {
105 105 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
106 106 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
107 107 };
108 108 meta = {
109 109 license = [ pkgs.lib.licenses.mit ];
110 110 };
111 111 };
112 112 "beaker" = super.buildPythonPackage {
113 113 name = "beaker-1.9.1";
114 114 doCheck = false;
115 115 propagatedBuildInputs = [
116 116 self."funcsigs"
117 117 ];
118 118 src = fetchurl {
119 119 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
120 120 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
121 121 };
122 122 meta = {
123 123 license = [ pkgs.lib.licenses.bsdOriginal ];
124 124 };
125 125 };
126 126 "beautifulsoup4" = super.buildPythonPackage {
127 127 name = "beautifulsoup4-4.6.3";
128 128 doCheck = false;
129 129 src = fetchurl {
130 130 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
131 131 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
132 132 };
133 133 meta = {
134 134 license = [ pkgs.lib.licenses.mit ];
135 135 };
136 136 };
137 137 "billiard" = super.buildPythonPackage {
138 138 name = "billiard-3.6.1.0";
139 139 doCheck = false;
140 140 src = fetchurl {
141 141 url = "https://files.pythonhosted.org/packages/68/1d/2aea8fbb0b1e1260a8a2e77352de2983d36d7ac01207cf14c2b9c6cc860e/billiard-3.6.1.0.tar.gz";
142 142 sha256 = "09hzy3aqi7visy4vmf4xiish61n0rq5nd3iwjydydps8yrs9r05q";
143 143 };
144 144 meta = {
145 145 license = [ pkgs.lib.licenses.bsdOriginal ];
146 146 };
147 147 };
148 148 "bleach" = super.buildPythonPackage {
149 149 name = "bleach-3.1.3";
150 150 doCheck = false;
151 151 propagatedBuildInputs = [
152 152 self."six"
153 153 self."webencodings"
154 154 ];
155 155 src = fetchurl {
156 156 url = "https://files.pythonhosted.org/packages/de/09/5267f8577a92487ed43bc694476c4629c6eca2e3c93fcf690a26bfe39e1d/bleach-3.1.3.tar.gz";
157 157 sha256 = "0al437aw4p2xp83az5hhlrp913nsf0cg6kg4qj3fjhv4wakxipzq";
158 158 };
159 159 meta = {
160 160 license = [ pkgs.lib.licenses.asl20 ];
161 161 };
162 162 };
163 163 "bumpversion" = super.buildPythonPackage {
164 164 name = "bumpversion-0.5.3";
165 165 doCheck = false;
166 166 src = fetchurl {
167 167 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
168 168 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
169 169 };
170 170 meta = {
171 171 license = [ pkgs.lib.licenses.mit ];
172 172 };
173 173 };
174 174 "cachetools" = super.buildPythonPackage {
175 175 name = "cachetools-3.1.1";
176 176 doCheck = false;
177 177 src = fetchurl {
178 178 url = "https://files.pythonhosted.org/packages/ae/37/7fd45996b19200e0cb2027a0b6bef4636951c4ea111bfad36c71287247f6/cachetools-3.1.1.tar.gz";
179 179 sha256 = "16m69l6n6y1r1y7cklm92rr7v69ldig2n3lbl3j323w5jz7d78lf";
180 180 };
181 181 meta = {
182 182 license = [ pkgs.lib.licenses.mit ];
183 183 };
184 184 };
185 185 "celery" = super.buildPythonPackage {
186 186 name = "celery-4.3.0";
187 187 doCheck = false;
188 188 propagatedBuildInputs = [
189 189 self."pytz"
190 190 self."billiard"
191 191 self."kombu"
192 192 self."vine"
193 193 ];
194 194 src = fetchurl {
195 195 url = "https://files.pythonhosted.org/packages/a2/4b/d020836f751617e907e84753a41c92231cd4b673ff991b8ee9da52361323/celery-4.3.0.tar.gz";
196 196 sha256 = "1y8y0gbgkwimpxqnxq2rm5qz2vy01fvjiybnpm00y5rzd2m34iac";
197 197 };
198 198 meta = {
199 199 license = [ pkgs.lib.licenses.bsdOriginal ];
200 200 };
201 201 };
202 202 "certifi" = super.buildPythonPackage {
203 203 name = "certifi-2020.4.5.1";
204 204 doCheck = false;
205 205 src = fetchurl {
206 206 url = "https://files.pythonhosted.org/packages/b8/e2/a3a86a67c3fc8249ed305fc7b7d290ebe5e4d46ad45573884761ef4dea7b/certifi-2020.4.5.1.tar.gz";
207 207 sha256 = "06b5gfs7wmmipln8f3z928d2mmx2j4b3x7pnqmj6cvmyfh8v7z2i";
208 208 };
209 209 meta = {
210 210 license = [ pkgs.lib.licenses.mpl20 { fullName = "Mozilla Public License 2.0 (MPL 2.0)"; } ];
211 211 };
212 212 };
213 213 "cffi" = super.buildPythonPackage {
214 214 name = "cffi-1.12.3";
215 215 doCheck = false;
216 216 propagatedBuildInputs = [
217 217 self."pycparser"
218 218 ];
219 219 src = fetchurl {
220 220 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
221 221 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
222 222 };
223 223 meta = {
224 224 license = [ pkgs.lib.licenses.mit ];
225 225 };
226 226 };
227 227 "chameleon" = super.buildPythonPackage {
228 228 name = "chameleon-2.24";
229 229 doCheck = false;
230 230 src = fetchurl {
231 231 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
232 232 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
233 233 };
234 234 meta = {
235 235 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
236 236 };
237 237 };
238 238 "channelstream" = super.buildPythonPackage {
239 239 name = "channelstream-0.5.2";
240 240 doCheck = false;
241 241 propagatedBuildInputs = [
242 242 self."gevent"
243 243 self."ws4py"
244 244 self."pyramid"
245 245 self."pyramid-jinja2"
246 246 self."itsdangerous"
247 247 self."requests"
248 248 self."six"
249 249 ];
250 250 src = fetchurl {
251 251 url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
252 252 sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
253 253 };
254 254 meta = {
255 255 license = [ pkgs.lib.licenses.bsdOriginal ];
256 256 };
257 257 };
258 258 "chardet" = super.buildPythonPackage {
259 259 name = "chardet-3.0.4";
260 260 doCheck = false;
261 261 src = fetchurl {
262 262 url = "https://files.pythonhosted.org/packages/fc/bb/a5768c230f9ddb03acc9ef3f0d4a3cf93462473795d18e9535498c8f929d/chardet-3.0.4.tar.gz";
263 263 sha256 = "1bpalpia6r5x1kknbk11p1fzph56fmmnp405ds8icksd3knr5aw4";
264 264 };
265 265 meta = {
266 266 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
267 267 };
268 268 };
269 269 "click" = super.buildPythonPackage {
270 270 name = "click-7.0";
271 271 doCheck = false;
272 272 src = fetchurl {
273 273 url = "https://files.pythonhosted.org/packages/f8/5c/f60e9d8a1e77005f664b76ff8aeaee5bc05d0a91798afd7f53fc998dbc47/Click-7.0.tar.gz";
274 274 sha256 = "1mzjixd4vjbjvzb6vylki9w1556a9qmdh35kzmq6cign46av952v";
275 275 };
276 276 meta = {
277 277 license = [ pkgs.lib.licenses.bsdOriginal ];
278 278 };
279 279 };
280 280 "colander" = super.buildPythonPackage {
281 281 name = "colander-1.7.0";
282 282 doCheck = false;
283 283 propagatedBuildInputs = [
284 284 self."translationstring"
285 285 self."iso8601"
286 286 self."enum34"
287 287 ];
288 288 src = fetchurl {
289 289 url = "https://files.pythonhosted.org/packages/db/e4/74ab06f54211917b41865cafc987ce511e35503de48da9bfe9358a1bdc3e/colander-1.7.0.tar.gz";
290 290 sha256 = "1wl1bqab307lbbcjx81i28s3yl6dlm4rf15fxawkjb6j48x1cn6p";
291 291 };
292 292 meta = {
293 293 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
294 294 };
295 295 };
296 296 "configobj" = super.buildPythonPackage {
297 297 name = "configobj-5.0.6";
298 298 doCheck = false;
299 299 propagatedBuildInputs = [
300 300 self."six"
301 301 ];
302 302 src = fetchurl {
303 303 url = "https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626";
304 304 sha256 = "0kqfrdfr14mw8yd8qwq14dv2xghpkjmd3yjsy8dfcbvpcc17xnxp";
305 305 };
306 306 meta = {
307 307 license = [ pkgs.lib.licenses.bsdOriginal ];
308 308 };
309 309 };
310 310 "configparser" = super.buildPythonPackage {
311 311 name = "configparser-4.0.2";
312 312 doCheck = false;
313 313 src = fetchurl {
314 314 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
315 315 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
316 316 };
317 317 meta = {
318 318 license = [ pkgs.lib.licenses.mit ];
319 319 };
320 320 };
321 321 "contextlib2" = super.buildPythonPackage {
322 322 name = "contextlib2-0.6.0.post1";
323 323 doCheck = false;
324 324 src = fetchurl {
325 325 url = "https://files.pythonhosted.org/packages/02/54/669207eb72e3d8ae8b38aa1f0703ee87a0e9f88f30d3c0a47bebdb6de242/contextlib2-0.6.0.post1.tar.gz";
326 326 sha256 = "0bhnr2ac7wy5l85ji909gyljyk85n92w8pdvslmrvc8qih4r1x01";
327 327 };
328 328 meta = {
329 329 license = [ pkgs.lib.licenses.psfl ];
330 330 };
331 331 };
332 332 "cov-core" = super.buildPythonPackage {
333 333 name = "cov-core-1.15.0";
334 334 doCheck = false;
335 335 propagatedBuildInputs = [
336 336 self."coverage"
337 337 ];
338 338 src = fetchurl {
339 339 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
340 340 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
341 341 };
342 342 meta = {
343 343 license = [ pkgs.lib.licenses.mit ];
344 344 };
345 345 };
346 346 "coverage" = super.buildPythonPackage {
347 347 name = "coverage-4.5.4";
348 348 doCheck = false;
349 349 src = fetchurl {
350 350 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
351 351 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
352 352 };
353 353 meta = {
354 354 license = [ pkgs.lib.licenses.asl20 ];
355 355 };
356 356 };
357 357 "cryptography" = super.buildPythonPackage {
358 358 name = "cryptography-2.6.1";
359 359 doCheck = false;
360 360 propagatedBuildInputs = [
361 361 self."asn1crypto"
362 362 self."six"
363 363 self."cffi"
364 364 self."enum34"
365 365 self."ipaddress"
366 366 ];
367 367 src = fetchurl {
368 368 url = "https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz";
369 369 sha256 = "19iwz5avym5zl6jrrrkym1rdaa9h61j20ph4cswsqgv8xg5j3j16";
370 370 };
371 371 meta = {
372 372 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
373 373 };
374 374 };
375 375 "cssselect" = super.buildPythonPackage {
376 376 name = "cssselect-1.0.3";
377 377 doCheck = false;
378 378 src = fetchurl {
379 379 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
380 380 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
381 381 };
382 382 meta = {
383 383 license = [ pkgs.lib.licenses.bsdOriginal ];
384 384 };
385 385 };
386 386 "cssutils" = super.buildPythonPackage {
387 387 name = "cssutils-1.0.2";
388 388 doCheck = false;
389 389 src = fetchurl {
390 390 url = "https://files.pythonhosted.org/packages/5c/0b/c5f29d29c037e97043770b5e7c740b6252993e4b57f029b3cd03c78ddfec/cssutils-1.0.2.tar.gz";
391 391 sha256 = "1bxchrbqzapwijap0yhlxdil1w9bmwvgx77aizlkhc2mcxjg1z52";
392 392 };
393 393 meta = {
394 394 license = [ { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL 2.1 or later, see also http://cthedot.de/cssutils/"; } ];
395 395 };
396 396 };
397 397 "decorator" = super.buildPythonPackage {
398 398 name = "decorator-4.1.2";
399 399 doCheck = false;
400 400 src = fetchurl {
401 401 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
402 402 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
403 403 };
404 404 meta = {
405 405 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
406 406 };
407 407 };
408 408 "deform" = super.buildPythonPackage {
409 409 name = "deform-2.0.8";
410 410 doCheck = false;
411 411 propagatedBuildInputs = [
412 412 self."chameleon"
413 413 self."colander"
414 414 self."iso8601"
415 415 self."peppercorn"
416 416 self."translationstring"
417 417 self."zope.deprecation"
418 418 ];
419 419 src = fetchurl {
420 420 url = "https://files.pythonhosted.org/packages/21/d0/45fdf891a82722c02fc2da319cf2d1ae6b5abf9e470ad3762135a895a868/deform-2.0.8.tar.gz";
421 421 sha256 = "0wbjv98sib96649aqaygzxnrkclyy50qij2rha6fn1i4c86bfdl9";
422 422 };
423 423 meta = {
424 424 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
425 425 };
426 426 };
427 427 "defusedxml" = super.buildPythonPackage {
428 428 name = "defusedxml-0.6.0";
429 429 doCheck = false;
430 430 src = fetchurl {
431 431 url = "https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz";
432 432 sha256 = "1xbp8fivl3wlbyg2jrvs4lalaqv1xp9a9f29p75wdx2s2d6h717n";
433 433 };
434 434 meta = {
435 435 license = [ pkgs.lib.licenses.psfl ];
436 436 };
437 437 };
438 438 "dm.xmlsec.binding" = super.buildPythonPackage {
439 439 name = "dm.xmlsec.binding-1.3.7";
440 440 doCheck = false;
441 441 propagatedBuildInputs = [
442 442 self."setuptools"
443 443 self."lxml"
444 444 ];
445 445 src = fetchurl {
446 446 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
447 447 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
448 448 };
449 449 meta = {
450 450 license = [ pkgs.lib.licenses.bsdOriginal ];
451 451 };
452 452 };
453 453 "docutils" = super.buildPythonPackage {
454 454 name = "docutils-0.16";
455 455 doCheck = false;
456 456 src = fetchurl {
457 457 url = "https://files.pythonhosted.org/packages/2f/e0/3d435b34abd2d62e8206171892f174b180cd37b09d57b924ca5c2ef2219d/docutils-0.16.tar.gz";
458 458 sha256 = "1z3qliszqca9m719q3qhdkh0ghh90g500avzdgi7pl77x5h3mpn2";
459 459 };
460 460 meta = {
461 461 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 ];
462 462 };
463 463 };
464 464 "dogpile.cache" = super.buildPythonPackage {
465 465 name = "dogpile.cache-0.9.0";
466 466 doCheck = false;
467 467 propagatedBuildInputs = [
468 468 self."decorator"
469 469 ];
470 470 src = fetchurl {
471 471 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
472 472 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
473 473 };
474 474 meta = {
475 475 license = [ pkgs.lib.licenses.bsdOriginal ];
476 476 };
477 477 };
478 478 "dogpile.core" = super.buildPythonPackage {
479 479 name = "dogpile.core-0.4.1";
480 480 doCheck = false;
481 481 src = fetchurl {
482 482 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
483 483 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
484 484 };
485 485 meta = {
486 486 license = [ pkgs.lib.licenses.bsdOriginal ];
487 487 };
488 488 };
489 489 "ecdsa" = super.buildPythonPackage {
490 490 name = "ecdsa-0.13.2";
491 491 doCheck = false;
492 492 src = fetchurl {
493 493 url = "https://files.pythonhosted.org/packages/51/76/139bf6e9b7b6684d5891212cdbd9e0739f2bfc03f380a1a6ffa700f392ac/ecdsa-0.13.2.tar.gz";
494 494 sha256 = "116qaq7bh4lcynzi613960jhsnn19v0kmsqwahiwjfj14gx4y0sw";
495 495 };
496 496 meta = {
497 497 license = [ pkgs.lib.licenses.mit ];
498 498 };
499 499 };
500 500 "elasticsearch" = super.buildPythonPackage {
501 501 name = "elasticsearch-6.3.1";
502 502 doCheck = false;
503 503 propagatedBuildInputs = [
504 504 self."urllib3"
505 505 ];
506 506 src = fetchurl {
507 507 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
508 508 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
509 509 };
510 510 meta = {
511 511 license = [ pkgs.lib.licenses.asl20 ];
512 512 };
513 513 };
514 514 "elasticsearch-dsl" = super.buildPythonPackage {
515 515 name = "elasticsearch-dsl-6.3.1";
516 516 doCheck = false;
517 517 propagatedBuildInputs = [
518 518 self."six"
519 519 self."python-dateutil"
520 520 self."elasticsearch"
521 521 self."ipaddress"
522 522 ];
523 523 src = fetchurl {
524 524 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
525 525 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
526 526 };
527 527 meta = {
528 528 license = [ pkgs.lib.licenses.asl20 ];
529 529 };
530 530 };
531 531 "elasticsearch1" = super.buildPythonPackage {
532 532 name = "elasticsearch1-1.10.0";
533 533 doCheck = false;
534 534 propagatedBuildInputs = [
535 535 self."urllib3"
536 536 ];
537 537 src = fetchurl {
538 538 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
539 539 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
540 540 };
541 541 meta = {
542 542 license = [ pkgs.lib.licenses.asl20 ];
543 543 };
544 544 };
545 545 "elasticsearch1-dsl" = super.buildPythonPackage {
546 546 name = "elasticsearch1-dsl-0.0.12";
547 547 doCheck = false;
548 548 propagatedBuildInputs = [
549 549 self."six"
550 550 self."python-dateutil"
551 551 self."elasticsearch1"
552 552 ];
553 553 src = fetchurl {
554 554 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
555 555 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
556 556 };
557 557 meta = {
558 558 license = [ pkgs.lib.licenses.asl20 ];
559 559 };
560 560 };
561 561 "elasticsearch2" = super.buildPythonPackage {
562 562 name = "elasticsearch2-2.5.1";
563 563 doCheck = false;
564 564 propagatedBuildInputs = [
565 565 self."urllib3"
566 566 ];
567 567 src = fetchurl {
568 568 url = "https://files.pythonhosted.org/packages/f6/09/f9b24aa6b1120bea371cd57ef6f57c7694cf16660469456a8be6c2bdbe22/elasticsearch2-2.5.1.tar.gz";
569 569 sha256 = "19k2znpjfyp0hrq73cz7pjyj289040xpsxsm0xhh4jfh6y551g7k";
570 570 };
571 571 meta = {
572 572 license = [ pkgs.lib.licenses.asl20 ];
573 573 };
574 574 };
575 575 "entrypoints" = super.buildPythonPackage {
576 576 name = "entrypoints-0.2.2";
577 577 doCheck = false;
578 578 propagatedBuildInputs = [
579 579 self."configparser"
580 580 ];
581 581 src = fetchurl {
582 582 url = "https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d";
583 583 sha256 = "0qih72n2myclanplqipqxpgpj9d2yhff1pz5d02zq1cfqyd173w5";
584 584 };
585 585 meta = {
586 586 license = [ pkgs.lib.licenses.mit ];
587 587 };
588 588 };
589 589 "enum34" = super.buildPythonPackage {
590 590 name = "enum34-1.1.10";
591 591 doCheck = false;
592 592 src = fetchurl {
593 593 url = "https://files.pythonhosted.org/packages/11/c4/2da1f4952ba476677a42f25cd32ab8aaf0e1c0d0e00b89822b835c7e654c/enum34-1.1.10.tar.gz";
594 594 sha256 = "0j7ji699fwswm4vg6w1v07fkbf8dkzdm6gfh88jvs5nqgr3sgrnc";
595 595 };
596 596 meta = {
597 597 license = [ pkgs.lib.licenses.bsdOriginal ];
598 598 };
599 599 };
600 600 "formencode" = super.buildPythonPackage {
601 601 name = "formencode-1.2.4";
602 602 doCheck = false;
603 603 src = fetchurl {
604 604 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
605 605 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
606 606 };
607 607 meta = {
608 608 license = [ pkgs.lib.licenses.psfl ];
609 609 };
610 610 };
611 611 "funcsigs" = super.buildPythonPackage {
612 612 name = "funcsigs-1.0.2";
613 613 doCheck = false;
614 614 src = fetchurl {
615 615 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
616 616 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
617 617 };
618 618 meta = {
619 619 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
620 620 };
621 621 };
622 622 "functools32" = super.buildPythonPackage {
623 623 name = "functools32-3.2.3.post2";
624 624 doCheck = false;
625 625 src = fetchurl {
626 626 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
627 627 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
628 628 };
629 629 meta = {
630 630 license = [ pkgs.lib.licenses.psfl ];
631 631 };
632 632 };
633 633 "future" = super.buildPythonPackage {
634 634 name = "future-0.14.3";
635 635 doCheck = false;
636 636 src = fetchurl {
637 637 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
638 638 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
639 639 };
640 640 meta = {
641 641 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
642 642 };
643 643 };
644 644 "futures" = super.buildPythonPackage {
645 645 name = "futures-3.0.2";
646 646 doCheck = false;
647 647 src = fetchurl {
648 648 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
649 649 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
650 650 };
651 651 meta = {
652 652 license = [ pkgs.lib.licenses.bsdOriginal ];
653 653 };
654 654 };
655 655 "gevent" = super.buildPythonPackage {
656 656 name = "gevent-1.5.0";
657 657 doCheck = false;
658 658 propagatedBuildInputs = [
659 659 self."greenlet"
660 660 ];
661 661 src = fetchurl {
662 662 url = "https://files.pythonhosted.org/packages/5a/79/2c63d385d017b5dd7d70983a463dfd25befae70c824fedb857df6e72eff2/gevent-1.5.0.tar.gz";
663 663 sha256 = "0aac3d4vhv5n4rsb6cqzq0d1xx9immqz4fmpddw35yxkwdc450dj";
664 664 };
665 665 meta = {
666 666 license = [ pkgs.lib.licenses.mit ];
667 667 };
668 668 };
669 669 "gnureadline" = super.buildPythonPackage {
670 670 name = "gnureadline-6.3.8";
671 671 doCheck = false;
672 672 src = fetchurl {
673 673 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
674 674 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
675 675 };
676 676 meta = {
677 677 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
678 678 };
679 679 };
680 680 "gprof2dot" = super.buildPythonPackage {
681 681 name = "gprof2dot-2017.9.19";
682 682 doCheck = false;
683 683 src = fetchurl {
684 684 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
685 685 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
686 686 };
687 687 meta = {
688 688 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
689 689 };
690 690 };
691 691 "greenlet" = super.buildPythonPackage {
692 692 name = "greenlet-0.4.15";
693 693 doCheck = false;
694 694 src = fetchurl {
695 695 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
696 696 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
697 697 };
698 698 meta = {
699 699 license = [ pkgs.lib.licenses.mit ];
700 700 };
701 701 };
702 702 "gunicorn" = super.buildPythonPackage {
703 703 name = "gunicorn-19.9.0";
704 704 doCheck = false;
705 705 src = fetchurl {
706 706 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
707 707 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
708 708 };
709 709 meta = {
710 710 license = [ pkgs.lib.licenses.mit ];
711 711 };
712 712 };
713 713 "hupper" = super.buildPythonPackage {
714 714 name = "hupper-1.10.2";
715 715 doCheck = false;
716 716 src = fetchurl {
717 717 url = "https://files.pythonhosted.org/packages/41/24/ea90fef04706e54bd1635c05c50dc9cf87cda543c59303a03e7aa7dda0ce/hupper-1.10.2.tar.gz";
718 718 sha256 = "0am0p6g5cz6xmcaf04xq8q6dzdd9qz0phj6gcmpsckf2mcyza61q";
719 719 };
720 720 meta = {
721 721 license = [ pkgs.lib.licenses.mit ];
722 722 };
723 723 };
724 724 "idna" = super.buildPythonPackage {
725 725 name = "idna-2.8";
726 726 doCheck = false;
727 727 src = fetchurl {
728 728 url = "https://files.pythonhosted.org/packages/ad/13/eb56951b6f7950cadb579ca166e448ba77f9d24efc03edd7e55fa57d04b7/idna-2.8.tar.gz";
729 729 sha256 = "01rlkigdxg17sf9yar1jl8n18ls59367wqh59hnawlyg53vb6my3";
730 730 };
731 731 meta = {
732 732 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD-like"; } ];
733 733 };
734 734 };
735 735 "importlib-metadata" = super.buildPythonPackage {
736 736 name = "importlib-metadata-1.6.0";
737 737 doCheck = false;
738 738 propagatedBuildInputs = [
739 739 self."zipp"
740 740 self."pathlib2"
741 741 self."contextlib2"
742 742 self."configparser"
743 743 ];
744 744 src = fetchurl {
745 745 url = "https://files.pythonhosted.org/packages/b4/1b/baab42e3cd64c9d5caac25a9d6c054f8324cdc38975a44d600569f1f7158/importlib_metadata-1.6.0.tar.gz";
746 746 sha256 = "07icyggasn38yv2swdrd8z6i0plazmc9adavsdkbqqj91j53ll9l";
747 747 };
748 748 meta = {
749 749 license = [ pkgs.lib.licenses.asl20 ];
750 750 };
751 751 };
752 752 "infrae.cache" = super.buildPythonPackage {
753 753 name = "infrae.cache-1.0.1";
754 754 doCheck = false;
755 755 propagatedBuildInputs = [
756 756 self."beaker"
757 757 self."repoze.lru"
758 758 ];
759 759 src = fetchurl {
760 760 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
761 761 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
762 762 };
763 763 meta = {
764 764 license = [ pkgs.lib.licenses.zpl21 ];
765 765 };
766 766 };
767 767 "invoke" = super.buildPythonPackage {
768 768 name = "invoke-0.13.0";
769 769 doCheck = false;
770 770 src = fetchurl {
771 771 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
772 772 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
773 773 };
774 774 meta = {
775 775 license = [ pkgs.lib.licenses.bsdOriginal ];
776 776 };
777 777 };
778 778 "ipaddress" = super.buildPythonPackage {
779 779 name = "ipaddress-1.0.23";
780 780 doCheck = false;
781 781 src = fetchurl {
782 782 url = "https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz";
783 783 sha256 = "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p";
784 784 };
785 785 meta = {
786 786 license = [ pkgs.lib.licenses.psfl ];
787 787 };
788 788 };
789 789 "ipdb" = super.buildPythonPackage {
790 790 name = "ipdb-0.13.2";
791 791 doCheck = false;
792 792 propagatedBuildInputs = [
793 793 self."setuptools"
794 794 self."ipython"
795 795 ];
796 796 src = fetchurl {
797 797 url = "https://files.pythonhosted.org/packages/2c/bb/a3e1a441719ebd75c6dac8170d3ddba884b7ee8a5c0f9aefa7297386627a/ipdb-0.13.2.tar.gz";
798 798 sha256 = "0jcd849rx30y3wcgzsqbn06v0yjlzvb9x3076q0yxpycdwm1ryvp";
799 799 };
800 800 meta = {
801 801 license = [ pkgs.lib.licenses.bsdOriginal ];
802 802 };
803 803 };
804 804 "ipython" = super.buildPythonPackage {
805 805 name = "ipython-5.1.0";
806 806 doCheck = false;
807 807 propagatedBuildInputs = [
808 808 self."setuptools"
809 809 self."decorator"
810 810 self."pickleshare"
811 811 self."simplegeneric"
812 812 self."traitlets"
813 813 self."prompt-toolkit"
814 814 self."pygments"
815 815 self."pexpect"
816 816 self."backports.shutil-get-terminal-size"
817 817 self."pathlib2"
818 818 self."pexpect"
819 819 ];
820 820 src = fetchurl {
821 821 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
822 822 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
823 823 };
824 824 meta = {
825 825 license = [ pkgs.lib.licenses.bsdOriginal ];
826 826 };
827 827 };
828 828 "ipython-genutils" = super.buildPythonPackage {
829 829 name = "ipython-genutils-0.2.0";
830 830 doCheck = false;
831 831 src = fetchurl {
832 832 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
833 833 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
834 834 };
835 835 meta = {
836 836 license = [ pkgs.lib.licenses.bsdOriginal ];
837 837 };
838 838 };
839 839 "iso8601" = super.buildPythonPackage {
840 840 name = "iso8601-0.1.12";
841 841 doCheck = false;
842 842 src = fetchurl {
843 843 url = "https://files.pythonhosted.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
844 844 sha256 = "10nyvvnrhw2w3p09v1ica4lgj6f4g9j3kkfx17qmraiq3w7b5i29";
845 845 };
846 846 meta = {
847 847 license = [ pkgs.lib.licenses.mit ];
848 848 };
849 849 };
850 850 "isodate" = super.buildPythonPackage {
851 851 name = "isodate-0.6.0";
852 852 doCheck = false;
853 853 propagatedBuildInputs = [
854 854 self."six"
855 855 ];
856 856 src = fetchurl {
857 857 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
858 858 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
859 859 };
860 860 meta = {
861 861 license = [ pkgs.lib.licenses.bsdOriginal ];
862 862 };
863 863 };
864 864 "itsdangerous" = super.buildPythonPackage {
865 865 name = "itsdangerous-0.24";
866 866 doCheck = false;
867 867 src = fetchurl {
868 868 url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
869 869 sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
870 870 };
871 871 meta = {
872 872 license = [ pkgs.lib.licenses.bsdOriginal ];
873 873 };
874 874 };
875 875 "jinja2" = super.buildPythonPackage {
876 876 name = "jinja2-2.9.6";
877 877 doCheck = false;
878 878 propagatedBuildInputs = [
879 879 self."markupsafe"
880 880 ];
881 881 src = fetchurl {
882 882 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
883 883 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
884 884 };
885 885 meta = {
886 886 license = [ pkgs.lib.licenses.bsdOriginal ];
887 887 };
888 888 };
889 889 "jsonschema" = super.buildPythonPackage {
890 890 name = "jsonschema-2.6.0";
891 891 doCheck = false;
892 892 propagatedBuildInputs = [
893 893 self."functools32"
894 894 ];
895 895 src = fetchurl {
896 896 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
897 897 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
898 898 };
899 899 meta = {
900 900 license = [ pkgs.lib.licenses.mit ];
901 901 };
902 902 };
903 903 "jupyter-client" = super.buildPythonPackage {
904 904 name = "jupyter-client-5.0.0";
905 905 doCheck = false;
906 906 propagatedBuildInputs = [
907 907 self."traitlets"
908 908 self."jupyter-core"
909 909 self."pyzmq"
910 910 self."python-dateutil"
911 911 ];
912 912 src = fetchurl {
913 913 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
914 914 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
915 915 };
916 916 meta = {
917 917 license = [ pkgs.lib.licenses.bsdOriginal ];
918 918 };
919 919 };
920 920 "jupyter-core" = super.buildPythonPackage {
921 921 name = "jupyter-core-4.5.0";
922 922 doCheck = false;
923 923 propagatedBuildInputs = [
924 924 self."traitlets"
925 925 ];
926 926 src = fetchurl {
927 927 url = "https://files.pythonhosted.org/packages/4a/de/ff4ca734656d17ebe0450807b59d728f45277e2e7f4b82bc9aae6cb82961/jupyter_core-4.5.0.tar.gz";
928 928 sha256 = "1xr4pbghwk5hayn5wwnhb7z95380r45p79gf5if5pi1akwg7qvic";
929 929 };
930 930 meta = {
931 931 license = [ pkgs.lib.licenses.bsdOriginal ];
932 932 };
933 933 };
934 934 "kombu" = super.buildPythonPackage {
935 935 name = "kombu-4.6.6";
936 936 doCheck = false;
937 937 propagatedBuildInputs = [
938 938 self."amqp"
939 939 self."importlib-metadata"
940 940 ];
941 941 src = fetchurl {
942 942 url = "https://files.pythonhosted.org/packages/20/e6/bc2d9affba6138a1dc143f77fef253e9e08e238fa7c0688d917c09005e96/kombu-4.6.6.tar.gz";
943 943 sha256 = "11mxpcy8mg1l35bgbhba70v29bydr2hrhdbdlb4lg98m3m5vaq0p";
944 944 };
945 945 meta = {
946 946 license = [ pkgs.lib.licenses.bsdOriginal ];
947 947 };
948 948 };
949 949 "lxml" = super.buildPythonPackage {
950 950 name = "lxml-4.2.5";
951 951 doCheck = false;
952 952 src = fetchurl {
953 953 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
954 954 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
955 955 };
956 956 meta = {
957 957 license = [ pkgs.lib.licenses.bsdOriginal ];
958 958 };
959 959 };
960 960 "mako" = super.buildPythonPackage {
961 961 name = "mako-1.1.0";
962 962 doCheck = false;
963 963 propagatedBuildInputs = [
964 964 self."markupsafe"
965 965 ];
966 966 src = fetchurl {
967 967 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
968 968 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
969 969 };
970 970 meta = {
971 971 license = [ pkgs.lib.licenses.mit ];
972 972 };
973 973 };
974 974 "markdown" = super.buildPythonPackage {
975 975 name = "markdown-2.6.11";
976 976 doCheck = false;
977 977 src = fetchurl {
978 978 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
979 979 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
980 980 };
981 981 meta = {
982 982 license = [ pkgs.lib.licenses.bsdOriginal ];
983 983 };
984 984 };
985 985 "markupsafe" = super.buildPythonPackage {
986 986 name = "markupsafe-1.1.1";
987 987 doCheck = false;
988 988 src = fetchurl {
989 989 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
990 990 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
991 991 };
992 992 meta = {
993 993 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
994 994 };
995 995 };
996 996 "mistune" = super.buildPythonPackage {
997 997 name = "mistune-0.8.4";
998 998 doCheck = false;
999 999 src = fetchurl {
1000 1000 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
1001 1001 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
1002 1002 };
1003 1003 meta = {
1004 1004 license = [ pkgs.lib.licenses.bsdOriginal ];
1005 1005 };
1006 1006 };
1007 1007 "mock" = super.buildPythonPackage {
1008 1008 name = "mock-3.0.5";
1009 1009 doCheck = false;
1010 1010 propagatedBuildInputs = [
1011 1011 self."six"
1012 1012 self."funcsigs"
1013 1013 ];
1014 1014 src = fetchurl {
1015 1015 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
1016 1016 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
1017 1017 };
1018 1018 meta = {
1019 1019 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
1020 1020 };
1021 1021 };
1022 1022 "more-itertools" = super.buildPythonPackage {
1023 1023 name = "more-itertools-5.0.0";
1024 1024 doCheck = false;
1025 1025 propagatedBuildInputs = [
1026 1026 self."six"
1027 1027 ];
1028 1028 src = fetchurl {
1029 1029 url = "https://files.pythonhosted.org/packages/dd/26/30fc0d541d9fdf55faf5ba4b0fd68f81d5bd2447579224820ad525934178/more-itertools-5.0.0.tar.gz";
1030 1030 sha256 = "1r12cm6mcdwdzz7d47a6g4l437xsvapdlgyhqay3i2nrlv03da9q";
1031 1031 };
1032 1032 meta = {
1033 1033 license = [ pkgs.lib.licenses.mit ];
1034 1034 };
1035 1035 };
1036 1036 "msgpack-python" = super.buildPythonPackage {
1037 1037 name = "msgpack-python-0.5.6";
1038 1038 doCheck = false;
1039 1039 src = fetchurl {
1040 1040 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
1041 1041 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
1042 1042 };
1043 1043 meta = {
1044 1044 license = [ pkgs.lib.licenses.asl20 ];
1045 1045 };
1046 1046 };
1047 1047 "mysql-python" = super.buildPythonPackage {
1048 1048 name = "mysql-python-1.2.5";
1049 1049 doCheck = false;
1050 1050 src = fetchurl {
1051 1051 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
1052 1052 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
1053 1053 };
1054 1054 meta = {
1055 1055 license = [ pkgs.lib.licenses.gpl1 ];
1056 1056 };
1057 1057 };
1058 1058 "nbconvert" = super.buildPythonPackage {
1059 1059 name = "nbconvert-5.3.1";
1060 1060 doCheck = false;
1061 1061 propagatedBuildInputs = [
1062 1062 self."mistune"
1063 1063 self."jinja2"
1064 1064 self."pygments"
1065 1065 self."traitlets"
1066 1066 self."jupyter-core"
1067 1067 self."nbformat"
1068 1068 self."entrypoints"
1069 1069 self."bleach"
1070 1070 self."pandocfilters"
1071 1071 self."testpath"
1072 1072 ];
1073 1073 src = fetchurl {
1074 1074 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
1075 1075 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
1076 1076 };
1077 1077 meta = {
1078 1078 license = [ pkgs.lib.licenses.bsdOriginal ];
1079 1079 };
1080 1080 };
1081 1081 "nbformat" = super.buildPythonPackage {
1082 1082 name = "nbformat-4.4.0";
1083 1083 doCheck = false;
1084 1084 propagatedBuildInputs = [
1085 1085 self."ipython-genutils"
1086 1086 self."traitlets"
1087 1087 self."jsonschema"
1088 1088 self."jupyter-core"
1089 1089 ];
1090 1090 src = fetchurl {
1091 1091 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
1092 1092 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
1093 1093 };
1094 1094 meta = {
1095 1095 license = [ pkgs.lib.licenses.bsdOriginal ];
1096 1096 };
1097 1097 };
1098 1098 "packaging" = super.buildPythonPackage {
1099 1099 name = "packaging-20.3";
1100 1100 doCheck = false;
1101 1101 propagatedBuildInputs = [
1102 1102 self."pyparsing"
1103 1103 self."six"
1104 1104 ];
1105 1105 src = fetchurl {
1106 1106 url = "https://files.pythonhosted.org/packages/65/37/83e3f492eb52d771e2820e88105f605335553fe10422cba9d256faeb1702/packaging-20.3.tar.gz";
1107 1107 sha256 = "18xpablq278janh03bai9xd4kz9b0yfp6vflazn725ns9x3jna9w";
1108 1108 };
1109 1109 meta = {
1110 1110 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
1111 1111 };
1112 1112 };
1113 1113 "pandocfilters" = super.buildPythonPackage {
1114 1114 name = "pandocfilters-1.4.2";
1115 1115 doCheck = false;
1116 1116 src = fetchurl {
1117 1117 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1118 1118 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
1119 1119 };
1120 1120 meta = {
1121 1121 license = [ pkgs.lib.licenses.bsdOriginal ];
1122 1122 };
1123 1123 };
1124 1124 "paste" = super.buildPythonPackage {
1125 1125 name = "paste-3.4.0";
1126 1126 doCheck = false;
1127 1127 propagatedBuildInputs = [
1128 1128 self."six"
1129 1129 ];
1130 1130 src = fetchurl {
1131 1131 url = "https://files.pythonhosted.org/packages/79/4a/45821b71dd40000507549afd1491546afad8279c0a87527c88776a794158/Paste-3.4.0.tar.gz";
1132 1132 sha256 = "16sichvhyci1gaarkjs35mai8vphh7b244qm14hj1isw38nx4c03";
1133 1133 };
1134 1134 meta = {
1135 1135 license = [ pkgs.lib.licenses.mit ];
1136 1136 };
1137 1137 };
1138 1138 "pastedeploy" = super.buildPythonPackage {
1139 1139 name = "pastedeploy-2.1.0";
1140 1140 doCheck = false;
1141 1141 src = fetchurl {
1142 1142 url = "https://files.pythonhosted.org/packages/c4/e9/972a1c20318b3ae9edcab11a6cef64308fbae5d0d45ab52c6f8b2b8f35b8/PasteDeploy-2.1.0.tar.gz";
1143 1143 sha256 = "16qsq5y6mryslmbp5pn35x4z8z3ndp5rpgl42h226879nrw9hmg7";
1144 1144 };
1145 1145 meta = {
1146 1146 license = [ pkgs.lib.licenses.mit ];
1147 1147 };
1148 1148 };
1149 1149 "pastescript" = super.buildPythonPackage {
1150 1150 name = "pastescript-3.2.0";
1151 1151 doCheck = false;
1152 1152 propagatedBuildInputs = [
1153 1153 self."paste"
1154 1154 self."pastedeploy"
1155 1155 self."six"
1156 1156 ];
1157 1157 src = fetchurl {
1158 1158 url = "https://files.pythonhosted.org/packages/ff/47/45c6f5a3cb8f5abf786fea98dbb8d02400a55768a9b623afb7df12346c61/PasteScript-3.2.0.tar.gz";
1159 1159 sha256 = "1b3jq7xh383nvrrlblk05m37345bv97xrhx77wshllba3h7mq3wv";
1160 1160 };
1161 1161 meta = {
1162 1162 license = [ pkgs.lib.licenses.mit ];
1163 1163 };
1164 1164 };
1165 1165 "pathlib2" = super.buildPythonPackage {
1166 1166 name = "pathlib2-2.3.5";
1167 1167 doCheck = false;
1168 1168 propagatedBuildInputs = [
1169 1169 self."six"
1170 1170 self."scandir"
1171 1171 ];
1172 1172 src = fetchurl {
1173 1173 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
1174 1174 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
1175 1175 };
1176 1176 meta = {
1177 1177 license = [ pkgs.lib.licenses.mit ];
1178 1178 };
1179 1179 };
1180 1180 "peppercorn" = super.buildPythonPackage {
1181 1181 name = "peppercorn-0.6";
1182 1182 doCheck = false;
1183 1183 src = fetchurl {
1184 1184 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1185 1185 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1186 1186 };
1187 1187 meta = {
1188 1188 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1189 1189 };
1190 1190 };
1191 1191 "pexpect" = super.buildPythonPackage {
1192 1192 name = "pexpect-4.8.0";
1193 1193 doCheck = false;
1194 1194 propagatedBuildInputs = [
1195 1195 self."ptyprocess"
1196 1196 ];
1197 1197 src = fetchurl {
1198 1198 url = "https://files.pythonhosted.org/packages/e5/9b/ff402e0e930e70467a7178abb7c128709a30dfb22d8777c043e501bc1b10/pexpect-4.8.0.tar.gz";
1199 1199 sha256 = "032cg337h8awydgypz6f4wx848lw8dyrj4zy988x0lyib4ws8rgw";
1200 1200 };
1201 1201 meta = {
1202 1202 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1203 1203 };
1204 1204 };
1205 1205 "pickleshare" = super.buildPythonPackage {
1206 1206 name = "pickleshare-0.7.5";
1207 1207 doCheck = false;
1208 1208 propagatedBuildInputs = [
1209 1209 self."pathlib2"
1210 1210 ];
1211 1211 src = fetchurl {
1212 1212 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1213 1213 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1214 1214 };
1215 1215 meta = {
1216 1216 license = [ pkgs.lib.licenses.mit ];
1217 1217 };
1218 1218 };
1219 1219 "plaster" = super.buildPythonPackage {
1220 1220 name = "plaster-1.0";
1221 1221 doCheck = false;
1222 1222 propagatedBuildInputs = [
1223 1223 self."setuptools"
1224 1224 ];
1225 1225 src = fetchurl {
1226 1226 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1227 1227 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1228 1228 };
1229 1229 meta = {
1230 1230 license = [ pkgs.lib.licenses.mit ];
1231 1231 };
1232 1232 };
1233 1233 "plaster-pastedeploy" = super.buildPythonPackage {
1234 1234 name = "plaster-pastedeploy-0.7";
1235 1235 doCheck = false;
1236 1236 propagatedBuildInputs = [
1237 1237 self."pastedeploy"
1238 1238 self."plaster"
1239 1239 ];
1240 1240 src = fetchurl {
1241 1241 url = "https://files.pythonhosted.org/packages/99/69/2d3bc33091249266a1bd3cf24499e40ab31d54dffb4a7d76fe647950b98c/plaster_pastedeploy-0.7.tar.gz";
1242 1242 sha256 = "1zg7gcsvc1kzay1ry5p699rg2qavfsxqwl17mqxzr0gzw6j9679r";
1243 1243 };
1244 1244 meta = {
1245 1245 license = [ pkgs.lib.licenses.mit ];
1246 1246 };
1247 1247 };
1248 1248 "pluggy" = super.buildPythonPackage {
1249 1249 name = "pluggy-0.13.1";
1250 1250 doCheck = false;
1251 1251 propagatedBuildInputs = [
1252 1252 self."importlib-metadata"
1253 1253 ];
1254 1254 src = fetchurl {
1255 1255 url = "https://files.pythonhosted.org/packages/f8/04/7a8542bed4b16a65c2714bf76cf5a0b026157da7f75e87cc88774aa10b14/pluggy-0.13.1.tar.gz";
1256 1256 sha256 = "1c35qyhvy27q9ih9n899f3h4sdnpgq027dbiilly2qb5cvgarchm";
1257 1257 };
1258 1258 meta = {
1259 1259 license = [ pkgs.lib.licenses.mit ];
1260 1260 };
1261 1261 };
1262 1262 "premailer" = super.buildPythonPackage {
1263 1263 name = "premailer-3.6.1";
1264 1264 doCheck = false;
1265 1265 propagatedBuildInputs = [
1266 1266 self."lxml"
1267 1267 self."cssselect"
1268 1268 self."cssutils"
1269 1269 self."requests"
1270 1270 self."cachetools"
1271 1271 ];
1272 1272 src = fetchurl {
1273 1273 url = "https://files.pythonhosted.org/packages/62/da/2f43cdf9d3d79c80c4856a12389a1f257d65fe9ccc44bc6b4383c8a18e33/premailer-3.6.1.tar.gz";
1274 1274 sha256 = "08pshx7a110k4ll20x0xhpvyn3kkipkrbgxjjn7ncdxs54ihdhgw";
1275 1275 };
1276 1276 meta = {
1277 1277 license = [ pkgs.lib.licenses.psfl { fullName = "Python"; } ];
1278 1278 };
1279 1279 };
1280 1280 "prompt-toolkit" = super.buildPythonPackage {
1281 1281 name = "prompt-toolkit-1.0.18";
1282 1282 doCheck = false;
1283 1283 propagatedBuildInputs = [
1284 1284 self."six"
1285 1285 self."wcwidth"
1286 1286 ];
1287 1287 src = fetchurl {
1288 1288 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
1289 1289 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
1290 1290 };
1291 1291 meta = {
1292 1292 license = [ pkgs.lib.licenses.bsdOriginal ];
1293 1293 };
1294 1294 };
1295 1295 "psutil" = super.buildPythonPackage {
1296 1296 name = "psutil-5.7.0";
1297 1297 doCheck = false;
1298 1298 src = fetchurl {
1299 1299 url = "https://files.pythonhosted.org/packages/c4/b8/3512f0e93e0db23a71d82485ba256071ebef99b227351f0f5540f744af41/psutil-5.7.0.tar.gz";
1300 1300 sha256 = "03jykdi3dgf1cdal9bv4fq9zjvzj9l9bs99gi5ar81sdl5nc2pk8";
1301 1301 };
1302 1302 meta = {
1303 1303 license = [ pkgs.lib.licenses.bsdOriginal ];
1304 1304 };
1305 1305 };
1306 1306 "psycopg2" = super.buildPythonPackage {
1307 1307 name = "psycopg2-2.8.4";
1308 1308 doCheck = false;
1309 1309 src = fetchurl {
1310 1310 url = "https://files.pythonhosted.org/packages/84/d7/6a93c99b5ba4d4d22daa3928b983cec66df4536ca50b22ce5dcac65e4e71/psycopg2-2.8.4.tar.gz";
1311 1311 sha256 = "1djvh98pi4hjd8rxbq8qzc63bg8v78k33yg6pl99wak61b6fb67q";
1312 1312 };
1313 1313 meta = {
1314 1314 license = [ pkgs.lib.licenses.zpl21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1315 1315 };
1316 1316 };
1317 1317 "ptyprocess" = super.buildPythonPackage {
1318 1318 name = "ptyprocess-0.6.0";
1319 1319 doCheck = false;
1320 1320 src = fetchurl {
1321 1321 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1322 1322 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1323 1323 };
1324 1324 meta = {
1325 1325 license = [ ];
1326 1326 };
1327 1327 };
1328 1328 "py" = super.buildPythonPackage {
1329 1329 name = "py-1.8.0";
1330 1330 doCheck = false;
1331 1331 src = fetchurl {
1332 1332 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
1333 1333 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
1334 1334 };
1335 1335 meta = {
1336 1336 license = [ pkgs.lib.licenses.mit ];
1337 1337 };
1338 1338 };
1339 1339 "py-bcrypt" = super.buildPythonPackage {
1340 1340 name = "py-bcrypt-0.4";
1341 1341 doCheck = false;
1342 1342 src = fetchurl {
1343 1343 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1344 1344 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1345 1345 };
1346 1346 meta = {
1347 1347 license = [ pkgs.lib.licenses.bsdOriginal ];
1348 1348 };
1349 1349 };
1350 1350 "py-gfm" = super.buildPythonPackage {
1351 1351 name = "py-gfm-0.1.4";
1352 1352 doCheck = false;
1353 1353 propagatedBuildInputs = [
1354 1354 self."setuptools"
1355 1355 self."markdown"
1356 1356 ];
1357 1357 src = fetchurl {
1358 1358 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1359 1359 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1360 1360 };
1361 1361 meta = {
1362 1362 license = [ pkgs.lib.licenses.bsdOriginal ];
1363 1363 };
1364 1364 };
1365 1365 "pyasn1" = super.buildPythonPackage {
1366 1366 name = "pyasn1-0.4.8";
1367 1367 doCheck = false;
1368 1368 src = fetchurl {
1369 1369 url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz";
1370 1370 sha256 = "1fnhbi3rmk47l9851gbik0flfr64vs5j0hbqx24cafjap6gprxxf";
1371 1371 };
1372 1372 meta = {
1373 1373 license = [ pkgs.lib.licenses.bsdOriginal ];
1374 1374 };
1375 1375 };
1376 1376 "pyasn1-modules" = super.buildPythonPackage {
1377 1377 name = "pyasn1-modules-0.2.6";
1378 1378 doCheck = false;
1379 1379 propagatedBuildInputs = [
1380 1380 self."pyasn1"
1381 1381 ];
1382 1382 src = fetchurl {
1383 1383 url = "https://files.pythonhosted.org/packages/f1/a9/a1ef72a0e43feff643cf0130a08123dea76205e7a0dda37e3efb5f054a31/pyasn1-modules-0.2.6.tar.gz";
1384 1384 sha256 = "08hph9j1r018drnrny29l7dl2q0cin78csswrhwrh8jmq61pmha3";
1385 1385 };
1386 1386 meta = {
1387 1387 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
1388 1388 };
1389 1389 };
1390 1390 "pycparser" = super.buildPythonPackage {
1391 1391 name = "pycparser-2.20";
1392 1392 doCheck = false;
1393 1393 src = fetchurl {
1394 1394 url = "https://files.pythonhosted.org/packages/0f/86/e19659527668d70be91d0369aeaa055b4eb396b0f387a4f92293a20035bd/pycparser-2.20.tar.gz";
1395 1395 sha256 = "1w0m3xvlrzq4lkbvd1ngfm8mdw64r1yxy6n7djlw6qj5d0km6ird";
1396 1396 };
1397 1397 meta = {
1398 1398 license = [ pkgs.lib.licenses.bsdOriginal ];
1399 1399 };
1400 1400 };
1401 1401 "pycrypto" = super.buildPythonPackage {
1402 1402 name = "pycrypto-2.6.1";
1403 1403 doCheck = false;
1404 1404 src = fetchurl {
1405 1405 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1406 1406 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1407 1407 };
1408 1408 meta = {
1409 1409 license = [ pkgs.lib.licenses.publicDomain ];
1410 1410 };
1411 1411 };
1412 1412 "pycurl" = super.buildPythonPackage {
1413 1413 name = "pycurl-7.43.0.3";
1414 1414 doCheck = false;
1415 1415 src = fetchurl {
1416 1416 url = "https://files.pythonhosted.org/packages/ac/b3/0f3979633b7890bab6098d84c84467030b807a1e2b31f5d30103af5a71ca/pycurl-7.43.0.3.tar.gz";
1417 1417 sha256 = "13nsvqhvnmnvfk75s8iynqsgszyv06cjp4drd3psi7zpbh63623g";
1418 1418 };
1419 1419 meta = {
1420 1420 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1421 1421 };
1422 1422 };
1423 1423 "pygments" = super.buildPythonPackage {
1424 1424 name = "pygments-2.4.2";
1425 1425 doCheck = false;
1426 1426 src = fetchurl {
1427 1427 url = "https://files.pythonhosted.org/packages/7e/ae/26808275fc76bf2832deb10d3a3ed3107bc4de01b85dcccbe525f2cd6d1e/Pygments-2.4.2.tar.gz";
1428 1428 sha256 = "15v2sqm5g12bqa0c7wikfh9ck2nl97ayizy1hpqhmws5gqalq748";
1429 1429 };
1430 1430 meta = {
1431 1431 license = [ pkgs.lib.licenses.bsdOriginal ];
1432 1432 };
1433 1433 };
1434 1434 "pymysql" = super.buildPythonPackage {
1435 1435 name = "pymysql-0.8.1";
1436 1436 doCheck = false;
1437 1437 src = fetchurl {
1438 1438 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1439 1439 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1440 1440 };
1441 1441 meta = {
1442 1442 license = [ pkgs.lib.licenses.mit ];
1443 1443 };
1444 1444 };
1445 1445 "pyotp" = super.buildPythonPackage {
1446 1446 name = "pyotp-2.3.0";
1447 1447 doCheck = false;
1448 1448 src = fetchurl {
1449 1449 url = "https://files.pythonhosted.org/packages/f7/15/395c4945ea6bc37e8811280bb675615cb4c2b2c1cd70bdc43329da91a386/pyotp-2.3.0.tar.gz";
1450 1450 sha256 = "18d13ikra1iq0xyfqfm72zhgwxi2qi9ps6z1a6zmqp4qrn57wlzw";
1451 1451 };
1452 1452 meta = {
1453 1453 license = [ pkgs.lib.licenses.mit ];
1454 1454 };
1455 1455 };
1456 1456 "pyparsing" = super.buildPythonPackage {
1457 1457 name = "pyparsing-2.4.7";
1458 1458 doCheck = false;
1459 1459 src = fetchurl {
1460 1460 url = "https://files.pythonhosted.org/packages/c1/47/dfc9c342c9842bbe0036c7f763d2d6686bcf5eb1808ba3e170afdb282210/pyparsing-2.4.7.tar.gz";
1461 1461 sha256 = "1hgc8qrbq1ymxbwfbjghv01fm3fbpjwpjwi0bcailxxzhf3yq0y2";
1462 1462 };
1463 1463 meta = {
1464 1464 license = [ pkgs.lib.licenses.mit ];
1465 1465 };
1466 1466 };
1467 1467 "pyramid" = super.buildPythonPackage {
1468 1468 name = "pyramid-1.10.4";
1469 1469 doCheck = false;
1470 1470 propagatedBuildInputs = [
1471 1471 self."hupper"
1472 1472 self."plaster"
1473 1473 self."plaster-pastedeploy"
1474 1474 self."setuptools"
1475 1475 self."translationstring"
1476 1476 self."venusian"
1477 1477 self."webob"
1478 1478 self."zope.deprecation"
1479 1479 self."zope.interface"
1480 1480 self."repoze.lru"
1481 1481 ];
1482 1482 src = fetchurl {
1483 1483 url = "https://files.pythonhosted.org/packages/c2/43/1ae701c9c6bb3a434358e678a5e72c96e8aa55cf4cb1d2fa2041b5dd38b7/pyramid-1.10.4.tar.gz";
1484 1484 sha256 = "0rkxs1ajycg2zh1c94xlmls56mx5m161sn8112skj0amza6cn36q";
1485 1485 };
1486 1486 meta = {
1487 1487 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1488 1488 };
1489 1489 };
1490 1490 "pyramid-debugtoolbar" = super.buildPythonPackage {
1491 1491 name = "pyramid-debugtoolbar-4.6.1";
1492 1492 doCheck = false;
1493 1493 propagatedBuildInputs = [
1494 1494 self."pyramid"
1495 1495 self."pyramid-mako"
1496 1496 self."repoze.lru"
1497 1497 self."pygments"
1498 1498 self."ipaddress"
1499 1499 ];
1500 1500 src = fetchurl {
1501 1501 url = "https://files.pythonhosted.org/packages/99/f6/b8603f82c18275be293921bc3a2184205056ca505747bf64ab8a0c08e124/pyramid_debugtoolbar-4.6.1.tar.gz";
1502 1502 sha256 = "185z7q8n959ga5331iczwra2iljwkidfx4qn6bbd7vm3rm4w6llv";
1503 1503 };
1504 1504 meta = {
1505 1505 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1506 1506 };
1507 1507 };
1508 1508 "pyramid-jinja2" = super.buildPythonPackage {
1509 1509 name = "pyramid-jinja2-2.7";
1510 1510 doCheck = false;
1511 1511 propagatedBuildInputs = [
1512 1512 self."pyramid"
1513 1513 self."zope.deprecation"
1514 1514 self."jinja2"
1515 1515 self."markupsafe"
1516 1516 ];
1517 1517 src = fetchurl {
1518 1518 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1519 1519 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1520 1520 };
1521 1521 meta = {
1522 1522 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1523 1523 };
1524 1524 };
1525 1525 "pyramid-mailer" = super.buildPythonPackage {
1526 1526 name = "pyramid-mailer-0.15.1";
1527 1527 doCheck = false;
1528 1528 propagatedBuildInputs = [
1529 1529 self."pyramid"
1530 1530 self."repoze.sendmail"
1531 1531 self."transaction"
1532 1532 ];
1533 1533 src = fetchurl {
1534 1534 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1535 1535 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1536 1536 };
1537 1537 meta = {
1538 1538 license = [ pkgs.lib.licenses.bsdOriginal ];
1539 1539 };
1540 1540 };
1541 1541 "pyramid-mako" = super.buildPythonPackage {
1542 1542 name = "pyramid-mako-1.1.0";
1543 1543 doCheck = false;
1544 1544 propagatedBuildInputs = [
1545 1545 self."pyramid"
1546 1546 self."mako"
1547 1547 ];
1548 1548 src = fetchurl {
1549 1549 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
1550 1550 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
1551 1551 };
1552 1552 meta = {
1553 1553 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1554 1554 };
1555 1555 };
1556 1556 "pysqlite" = super.buildPythonPackage {
1557 1557 name = "pysqlite-2.8.3";
1558 1558 doCheck = false;
1559 1559 src = fetchurl {
1560 1560 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1561 1561 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1562 1562 };
1563 1563 meta = {
1564 1564 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1565 1565 };
1566 1566 };
1567 1567 "pytest" = super.buildPythonPackage {
1568 1568 name = "pytest-4.6.5";
1569 1569 doCheck = false;
1570 1570 propagatedBuildInputs = [
1571 1571 self."py"
1572 1572 self."six"
1573 1573 self."packaging"
1574 1574 self."attrs"
1575 1575 self."atomicwrites"
1576 1576 self."pluggy"
1577 1577 self."importlib-metadata"
1578 1578 self."wcwidth"
1579 1579 self."funcsigs"
1580 1580 self."pathlib2"
1581 1581 self."more-itertools"
1582 1582 ];
1583 1583 src = fetchurl {
1584 1584 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
1585 1585 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
1586 1586 };
1587 1587 meta = {
1588 1588 license = [ pkgs.lib.licenses.mit ];
1589 1589 };
1590 1590 };
1591 1591 "pytest-cov" = super.buildPythonPackage {
1592 1592 name = "pytest-cov-2.7.1";
1593 1593 doCheck = false;
1594 1594 propagatedBuildInputs = [
1595 1595 self."pytest"
1596 1596 self."coverage"
1597 1597 ];
1598 1598 src = fetchurl {
1599 1599 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
1600 1600 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
1601 1601 };
1602 1602 meta = {
1603 1603 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1604 1604 };
1605 1605 };
1606 1606 "pytest-profiling" = super.buildPythonPackage {
1607 1607 name = "pytest-profiling-1.7.0";
1608 1608 doCheck = false;
1609 1609 propagatedBuildInputs = [
1610 1610 self."six"
1611 1611 self."pytest"
1612 1612 self."gprof2dot"
1613 1613 ];
1614 1614 src = fetchurl {
1615 1615 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
1616 1616 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
1617 1617 };
1618 1618 meta = {
1619 1619 license = [ pkgs.lib.licenses.mit ];
1620 1620 };
1621 1621 };
1622 1622 "pytest-runner" = super.buildPythonPackage {
1623 1623 name = "pytest-runner-5.1";
1624 1624 doCheck = false;
1625 1625 src = fetchurl {
1626 1626 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
1627 1627 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
1628 1628 };
1629 1629 meta = {
1630 1630 license = [ pkgs.lib.licenses.mit ];
1631 1631 };
1632 1632 };
1633 1633 "pytest-sugar" = super.buildPythonPackage {
1634 1634 name = "pytest-sugar-0.9.2";
1635 1635 doCheck = false;
1636 1636 propagatedBuildInputs = [
1637 1637 self."pytest"
1638 1638 self."termcolor"
1639 1639 self."packaging"
1640 1640 ];
1641 1641 src = fetchurl {
1642 1642 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
1643 1643 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
1644 1644 };
1645 1645 meta = {
1646 1646 license = [ pkgs.lib.licenses.bsdOriginal ];
1647 1647 };
1648 1648 };
1649 1649 "pytest-timeout" = super.buildPythonPackage {
1650 1650 name = "pytest-timeout-1.3.3";
1651 1651 doCheck = false;
1652 1652 propagatedBuildInputs = [
1653 1653 self."pytest"
1654 1654 ];
1655 1655 src = fetchurl {
1656 1656 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
1657 1657 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
1658 1658 };
1659 1659 meta = {
1660 1660 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1661 1661 };
1662 1662 };
1663 1663 "python-dateutil" = super.buildPythonPackage {
1664 1664 name = "python-dateutil-2.8.1";
1665 1665 doCheck = false;
1666 1666 propagatedBuildInputs = [
1667 1667 self."six"
1668 1668 ];
1669 1669 src = fetchurl {
1670 1670 url = "https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz";
1671 1671 sha256 = "0g42w7k5007iv9dam6gnja2ry8ydwirh99mgdll35s12pyfzxsvk";
1672 1672 };
1673 1673 meta = {
1674 1674 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1675 1675 };
1676 1676 };
1677 1677 "python-editor" = super.buildPythonPackage {
1678 1678 name = "python-editor-1.0.4";
1679 1679 doCheck = false;
1680 1680 src = fetchurl {
1681 1681 url = "https://files.pythonhosted.org/packages/0a/85/78f4a216d28343a67b7397c99825cff336330893f00601443f7c7b2f2234/python-editor-1.0.4.tar.gz";
1682 1682 sha256 = "0yrjh8w72ivqxi4i7xsg5b1vz15x8fg51xra7c3bgfyxqnyadzai";
1683 1683 };
1684 1684 meta = {
1685 1685 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1686 1686 };
1687 1687 };
1688 1688 "python-ldap" = super.buildPythonPackage {
1689 1689 name = "python-ldap-3.2.0";
1690 1690 doCheck = false;
1691 1691 propagatedBuildInputs = [
1692 1692 self."pyasn1"
1693 1693 self."pyasn1-modules"
1694 1694 ];
1695 1695 src = fetchurl {
1696 1696 url = "https://files.pythonhosted.org/packages/ea/93/596f875e003c770447f4b99267820a0c769dd2dc3ae3ed19afe460fcbad0/python-ldap-3.2.0.tar.gz";
1697 1697 sha256 = "13nvrhp85yr0jyxixcjj012iw8l9wynxxlykm9j3alss6waln73x";
1698 1698 };
1699 1699 meta = {
1700 1700 license = [ pkgs.lib.licenses.psfl ];
1701 1701 };
1702 1702 };
1703 1703 "python-memcached" = super.buildPythonPackage {
1704 1704 name = "python-memcached-1.59";
1705 1705 doCheck = false;
1706 1706 propagatedBuildInputs = [
1707 1707 self."six"
1708 1708 ];
1709 1709 src = fetchurl {
1710 1710 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1711 1711 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1712 1712 };
1713 1713 meta = {
1714 1714 license = [ pkgs.lib.licenses.psfl ];
1715 1715 };
1716 1716 };
1717 1717 "python-pam" = super.buildPythonPackage {
1718 1718 name = "python-pam-1.8.4";
1719 1719 doCheck = false;
1720 1720 src = fetchurl {
1721 1721 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1722 1722 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1723 1723 };
1724 1724 meta = {
1725 1725 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1726 1726 };
1727 1727 };
1728 1728 "python-saml" = super.buildPythonPackage {
1729 1729 name = "python-saml-2.4.2";
1730 1730 doCheck = false;
1731 1731 propagatedBuildInputs = [
1732 1732 self."dm.xmlsec.binding"
1733 1733 self."isodate"
1734 1734 self."defusedxml"
1735 1735 ];
1736 1736 src = fetchurl {
1737 1737 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1738 1738 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1739 1739 };
1740 1740 meta = {
1741 1741 license = [ pkgs.lib.licenses.mit ];
1742 1742 };
1743 1743 };
1744 1744 "pytz" = super.buildPythonPackage {
1745 1745 name = "pytz-2019.3";
1746 1746 doCheck = false;
1747 1747 src = fetchurl {
1748 1748 url = "https://files.pythonhosted.org/packages/82/c3/534ddba230bd4fbbd3b7a3d35f3341d014cca213f369a9940925e7e5f691/pytz-2019.3.tar.gz";
1749 1749 sha256 = "1ghrk1wg45d3nymj7bf4zj03n3bh64xmczhk4pfi577hdkdhcb5h";
1750 1750 };
1751 1751 meta = {
1752 1752 license = [ pkgs.lib.licenses.mit ];
1753 1753 };
1754 1754 };
1755 1755 "pyzmq" = super.buildPythonPackage {
1756 1756 name = "pyzmq-14.6.0";
1757 1757 doCheck = false;
1758 1758 src = fetchurl {
1759 1759 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1760 1760 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1761 1761 };
1762 1762 meta = {
1763 1763 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1764 1764 };
1765 1765 };
1766 1766 "redis" = super.buildPythonPackage {
1767 1767 name = "redis-3.4.1";
1768 1768 doCheck = false;
1769 1769 src = fetchurl {
1770 1770 url = "https://files.pythonhosted.org/packages/ef/2e/2c0f59891db7db087a7eeaa79bc7c7f2c039e71a2b5b0a41391e9d462926/redis-3.4.1.tar.gz";
1771 1771 sha256 = "07yaj0j9fs7xdkg5bg926fa990khyigjbp31si8ai20vj8sv7kqd";
1772 1772 };
1773 1773 meta = {
1774 1774 license = [ pkgs.lib.licenses.mit ];
1775 1775 };
1776 1776 };
1777 1777 "repoze.lru" = super.buildPythonPackage {
1778 1778 name = "repoze.lru-0.7";
1779 1779 doCheck = false;
1780 1780 src = fetchurl {
1781 1781 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1782 1782 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1783 1783 };
1784 1784 meta = {
1785 1785 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1786 1786 };
1787 1787 };
1788 1788 "repoze.sendmail" = super.buildPythonPackage {
1789 1789 name = "repoze.sendmail-4.4.1";
1790 1790 doCheck = false;
1791 1791 propagatedBuildInputs = [
1792 1792 self."setuptools"
1793 1793 self."zope.interface"
1794 1794 self."transaction"
1795 1795 ];
1796 1796 src = fetchurl {
1797 1797 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1798 1798 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1799 1799 };
1800 1800 meta = {
1801 1801 license = [ pkgs.lib.licenses.zpl21 ];
1802 1802 };
1803 1803 };
1804 1804 "requests" = super.buildPythonPackage {
1805 1805 name = "requests-2.22.0";
1806 1806 doCheck = false;
1807 1807 propagatedBuildInputs = [
1808 1808 self."chardet"
1809 1809 self."idna"
1810 1810 self."urllib3"
1811 1811 self."certifi"
1812 1812 ];
1813 1813 src = fetchurl {
1814 1814 url = "https://files.pythonhosted.org/packages/01/62/ddcf76d1d19885e8579acb1b1df26a852b03472c0e46d2b959a714c90608/requests-2.22.0.tar.gz";
1815 1815 sha256 = "1d5ybh11jr5sm7xp6mz8fyc7vrp4syifds91m7sj60xalal0gq0i";
1816 1816 };
1817 1817 meta = {
1818 1818 license = [ pkgs.lib.licenses.asl20 ];
1819 1819 };
1820 1820 };
1821 1821 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1822 name = "rhodecode-enterprise-ce-4.19.3";
1822 name = "rhodecode-enterprise-ce-4.20.0";
1823 1823 buildInputs = [
1824 1824 self."pytest"
1825 1825 self."py"
1826 1826 self."pytest-cov"
1827 1827 self."pytest-sugar"
1828 1828 self."pytest-runner"
1829 1829 self."pytest-profiling"
1830 1830 self."pytest-timeout"
1831 1831 self."gprof2dot"
1832 1832 self."mock"
1833 1833 self."cov-core"
1834 1834 self."coverage"
1835 1835 self."webtest"
1836 1836 self."beautifulsoup4"
1837 1837 self."configobj"
1838 1838 ];
1839 1839 doCheck = true;
1840 1840 propagatedBuildInputs = [
1841 1841 self."amqp"
1842 1842 self."babel"
1843 1843 self."beaker"
1844 1844 self."bleach"
1845 1845 self."celery"
1846 1846 self."channelstream"
1847 1847 self."click"
1848 1848 self."colander"
1849 1849 self."configobj"
1850 1850 self."cssselect"
1851 1851 self."cryptography"
1852 1852 self."decorator"
1853 1853 self."deform"
1854 1854 self."docutils"
1855 1855 self."dogpile.cache"
1856 1856 self."dogpile.core"
1857 1857 self."formencode"
1858 1858 self."future"
1859 1859 self."futures"
1860 1860 self."infrae.cache"
1861 1861 self."iso8601"
1862 1862 self."itsdangerous"
1863 1863 self."kombu"
1864 1864 self."lxml"
1865 1865 self."mako"
1866 1866 self."markdown"
1867 1867 self."markupsafe"
1868 1868 self."msgpack-python"
1869 1869 self."pyotp"
1870 1870 self."packaging"
1871 1871 self."pathlib2"
1872 1872 self."paste"
1873 1873 self."pastedeploy"
1874 1874 self."pastescript"
1875 1875 self."peppercorn"
1876 1876 self."premailer"
1877 1877 self."psutil"
1878 1878 self."py-bcrypt"
1879 1879 self."pycurl"
1880 1880 self."pycrypto"
1881 1881 self."pygments"
1882 1882 self."pyparsing"
1883 1883 self."pyramid-debugtoolbar"
1884 1884 self."pyramid-mako"
1885 1885 self."pyramid"
1886 1886 self."pyramid-mailer"
1887 1887 self."python-dateutil"
1888 1888 self."python-ldap"
1889 1889 self."python-memcached"
1890 1890 self."python-pam"
1891 1891 self."python-saml"
1892 1892 self."pytz"
1893 1893 self."tzlocal"
1894 1894 self."pyzmq"
1895 1895 self."py-gfm"
1896 1896 self."redis"
1897 1897 self."repoze.lru"
1898 1898 self."requests"
1899 1899 self."routes"
1900 1900 self."simplejson"
1901 1901 self."six"
1902 1902 self."sqlalchemy"
1903 1903 self."sshpubkeys"
1904 1904 self."subprocess32"
1905 1905 self."supervisor"
1906 1906 self."translationstring"
1907 1907 self."urllib3"
1908 1908 self."urlobject"
1909 1909 self."venusian"
1910 1910 self."weberror"
1911 1911 self."webhelpers2"
1912 1912 self."webob"
1913 1913 self."whoosh"
1914 1914 self."wsgiref"
1915 1915 self."zope.cachedescriptors"
1916 1916 self."zope.deprecation"
1917 1917 self."zope.event"
1918 1918 self."zope.interface"
1919 1919 self."mysql-python"
1920 1920 self."pymysql"
1921 1921 self."pysqlite"
1922 1922 self."psycopg2"
1923 1923 self."nbconvert"
1924 1924 self."nbformat"
1925 1925 self."jupyter-client"
1926 1926 self."jupyter-core"
1927 1927 self."alembic"
1928 1928 self."invoke"
1929 1929 self."bumpversion"
1930 1930 self."gevent"
1931 1931 self."greenlet"
1932 1932 self."gunicorn"
1933 1933 self."waitress"
1934 1934 self."ipdb"
1935 1935 self."ipython"
1936 1936 self."rhodecode-tools"
1937 1937 self."appenlight-client"
1938 1938 self."pytest"
1939 1939 self."py"
1940 1940 self."pytest-cov"
1941 1941 self."pytest-sugar"
1942 1942 self."pytest-runner"
1943 1943 self."pytest-profiling"
1944 1944 self."pytest-timeout"
1945 1945 self."gprof2dot"
1946 1946 self."mock"
1947 1947 self."cov-core"
1948 1948 self."coverage"
1949 1949 self."webtest"
1950 1950 self."beautifulsoup4"
1951 1951 ];
1952 1952 src = ./.;
1953 1953 meta = {
1954 1954 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1955 1955 };
1956 1956 };
1957 1957 "rhodecode-tools" = super.buildPythonPackage {
1958 1958 name = "rhodecode-tools-1.4.0";
1959 1959 doCheck = false;
1960 1960 propagatedBuildInputs = [
1961 1961 self."click"
1962 1962 self."future"
1963 1963 self."six"
1964 1964 self."mako"
1965 1965 self."markupsafe"
1966 1966 self."requests"
1967 1967 self."urllib3"
1968 1968 self."whoosh"
1969 1969 self."elasticsearch"
1970 1970 self."elasticsearch-dsl"
1971 1971 self."elasticsearch2"
1972 1972 self."elasticsearch1-dsl"
1973 1973 ];
1974 1974 src = fetchurl {
1975 1975 url = "https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-ed54e749-2ef5-4bc7-ae7f-7900e3c2aa15.tar.gz?sha256=76f024bad3a1e55fdb3d64f13f5b77ff21a12fee699918de2110fe21effd5a3a";
1976 1976 sha256 = "0fjszppj3zhh47g1i6b9xqps28gzfxdkzwb47pdmzrd1sfx29w3n";
1977 1977 };
1978 1978 meta = {
1979 1979 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
1980 1980 };
1981 1981 };
1982 1982 "routes" = super.buildPythonPackage {
1983 1983 name = "routes-2.4.1";
1984 1984 doCheck = false;
1985 1985 propagatedBuildInputs = [
1986 1986 self."six"
1987 1987 self."repoze.lru"
1988 1988 ];
1989 1989 src = fetchurl {
1990 1990 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
1991 1991 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
1992 1992 };
1993 1993 meta = {
1994 1994 license = [ pkgs.lib.licenses.mit ];
1995 1995 };
1996 1996 };
1997 1997 "scandir" = super.buildPythonPackage {
1998 1998 name = "scandir-1.10.0";
1999 1999 doCheck = false;
2000 2000 src = fetchurl {
2001 2001 url = "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz";
2002 2002 sha256 = "1bkqwmf056pkchf05ywbnf659wqlp6lljcdb0y88wr9f0vv32ijd";
2003 2003 };
2004 2004 meta = {
2005 2005 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
2006 2006 };
2007 2007 };
2008 2008 "setproctitle" = super.buildPythonPackage {
2009 2009 name = "setproctitle-1.1.10";
2010 2010 doCheck = false;
2011 2011 src = fetchurl {
2012 2012 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
2013 2013 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
2014 2014 };
2015 2015 meta = {
2016 2016 license = [ pkgs.lib.licenses.bsdOriginal ];
2017 2017 };
2018 2018 };
2019 2019 "setuptools" = super.buildPythonPackage {
2020 2020 name = "setuptools-44.1.0";
2021 2021 doCheck = false;
2022 2022 src = fetchurl {
2023 2023 url = "https://files.pythonhosted.org/packages/ed/7b/bbf89ca71e722b7f9464ebffe4b5ee20a9e5c9a555a56e2d3914bb9119a6/setuptools-44.1.0.zip";
2024 2024 sha256 = "1jja896zvd1ppccnjbhkgagxbwchgq6vfamp6qn1hvywq6q9cjkr";
2025 2025 };
2026 2026 meta = {
2027 2027 license = [ pkgs.lib.licenses.mit ];
2028 2028 };
2029 2029 };
2030 2030 "simplegeneric" = super.buildPythonPackage {
2031 2031 name = "simplegeneric-0.8.1";
2032 2032 doCheck = false;
2033 2033 src = fetchurl {
2034 2034 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
2035 2035 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
2036 2036 };
2037 2037 meta = {
2038 2038 license = [ pkgs.lib.licenses.zpl21 ];
2039 2039 };
2040 2040 };
2041 2041 "simplejson" = super.buildPythonPackage {
2042 2042 name = "simplejson-3.16.0";
2043 2043 doCheck = false;
2044 2044 src = fetchurl {
2045 2045 url = "https://files.pythonhosted.org/packages/e3/24/c35fb1c1c315fc0fffe61ea00d3f88e85469004713dab488dee4f35b0aff/simplejson-3.16.0.tar.gz";
2046 2046 sha256 = "19cws1syk8jzq2pw43878dv6fjkb0ifvjpx0i9aajix6kc9jkwxi";
2047 2047 };
2048 2048 meta = {
2049 2049 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
2050 2050 };
2051 2051 };
2052 2052 "six" = super.buildPythonPackage {
2053 2053 name = "six-1.11.0";
2054 2054 doCheck = false;
2055 2055 src = fetchurl {
2056 2056 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
2057 2057 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
2058 2058 };
2059 2059 meta = {
2060 2060 license = [ pkgs.lib.licenses.mit ];
2061 2061 };
2062 2062 };
2063 2063 "sqlalchemy" = super.buildPythonPackage {
2064 2064 name = "sqlalchemy-1.3.15";
2065 2065 doCheck = false;
2066 2066 src = fetchurl {
2067 2067 url = "https://files.pythonhosted.org/packages/8c/30/4134e726dd5ed13728ff814fa91fc01c447ad8700504653fe99d91fdd34b/SQLAlchemy-1.3.15.tar.gz";
2068 2068 sha256 = "0iglkvymfp35zm5pxy5kzqvcv96kkas0chqdx7xpla86sspa9k64";
2069 2069 };
2070 2070 meta = {
2071 2071 license = [ pkgs.lib.licenses.mit ];
2072 2072 };
2073 2073 };
2074 2074 "sshpubkeys" = super.buildPythonPackage {
2075 2075 name = "sshpubkeys-3.1.0";
2076 2076 doCheck = false;
2077 2077 propagatedBuildInputs = [
2078 2078 self."cryptography"
2079 2079 self."ecdsa"
2080 2080 ];
2081 2081 src = fetchurl {
2082 2082 url = "https://files.pythonhosted.org/packages/00/23/f7508a12007c96861c3da811992f14283d79c819d71a217b3e12d5196649/sshpubkeys-3.1.0.tar.gz";
2083 2083 sha256 = "105g2li04nm1hb15a2y6hm9m9k7fbrkd5l3gy12w3kgcmsf3k25k";
2084 2084 };
2085 2085 meta = {
2086 2086 license = [ pkgs.lib.licenses.bsdOriginal ];
2087 2087 };
2088 2088 };
2089 2089 "subprocess32" = super.buildPythonPackage {
2090 2090 name = "subprocess32-3.5.4";
2091 2091 doCheck = false;
2092 2092 src = fetchurl {
2093 2093 url = "https://files.pythonhosted.org/packages/32/c8/564be4d12629b912ea431f1a50eb8b3b9d00f1a0b1ceff17f266be190007/subprocess32-3.5.4.tar.gz";
2094 2094 sha256 = "17f7mvwx2271s1wrl0qac3wjqqnrqag866zs3qc8v5wp0k43fagb";
2095 2095 };
2096 2096 meta = {
2097 2097 license = [ pkgs.lib.licenses.psfl ];
2098 2098 };
2099 2099 };
2100 2100 "supervisor" = super.buildPythonPackage {
2101 2101 name = "supervisor-4.1.0";
2102 2102 doCheck = false;
2103 2103 src = fetchurl {
2104 2104 url = "https://files.pythonhosted.org/packages/de/87/ee1ad8fa533a4b5f2c7623f4a2b585d3c1947af7bed8e65bc7772274320e/supervisor-4.1.0.tar.gz";
2105 2105 sha256 = "10q36sa1jqljyyyl7cif52akpygl5kmlqq9x91hmx53f8zh6zj1d";
2106 2106 };
2107 2107 meta = {
2108 2108 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2109 2109 };
2110 2110 };
2111 2111 "tempita" = super.buildPythonPackage {
2112 2112 name = "tempita-0.5.2";
2113 2113 doCheck = false;
2114 2114 src = fetchurl {
2115 2115 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
2116 2116 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
2117 2117 };
2118 2118 meta = {
2119 2119 license = [ pkgs.lib.licenses.mit ];
2120 2120 };
2121 2121 };
2122 2122 "termcolor" = super.buildPythonPackage {
2123 2123 name = "termcolor-1.1.0";
2124 2124 doCheck = false;
2125 2125 src = fetchurl {
2126 2126 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
2127 2127 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
2128 2128 };
2129 2129 meta = {
2130 2130 license = [ pkgs.lib.licenses.mit ];
2131 2131 };
2132 2132 };
2133 2133 "testpath" = super.buildPythonPackage {
2134 2134 name = "testpath-0.4.4";
2135 2135 doCheck = false;
2136 2136 src = fetchurl {
2137 2137 url = "https://files.pythonhosted.org/packages/2c/b3/5d57205e896d8998d77ad12aa42ebce75cd97d8b9a97d00ba078c4c9ffeb/testpath-0.4.4.tar.gz";
2138 2138 sha256 = "0zpcmq22dz79ipvvsfnw1ykpjcaj6xyzy7ws77s5b5ql3hka7q30";
2139 2139 };
2140 2140 meta = {
2141 2141 license = [ ];
2142 2142 };
2143 2143 };
2144 2144 "traitlets" = super.buildPythonPackage {
2145 2145 name = "traitlets-4.3.3";
2146 2146 doCheck = false;
2147 2147 propagatedBuildInputs = [
2148 2148 self."ipython-genutils"
2149 2149 self."six"
2150 2150 self."decorator"
2151 2151 self."enum34"
2152 2152 ];
2153 2153 src = fetchurl {
2154 2154 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
2155 2155 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
2156 2156 };
2157 2157 meta = {
2158 2158 license = [ pkgs.lib.licenses.bsdOriginal ];
2159 2159 };
2160 2160 };
2161 2161 "transaction" = super.buildPythonPackage {
2162 2162 name = "transaction-2.4.0";
2163 2163 doCheck = false;
2164 2164 propagatedBuildInputs = [
2165 2165 self."zope.interface"
2166 2166 ];
2167 2167 src = fetchurl {
2168 2168 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2169 2169 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2170 2170 };
2171 2171 meta = {
2172 2172 license = [ pkgs.lib.licenses.zpl21 ];
2173 2173 };
2174 2174 };
2175 2175 "translationstring" = super.buildPythonPackage {
2176 2176 name = "translationstring-1.3";
2177 2177 doCheck = false;
2178 2178 src = fetchurl {
2179 2179 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2180 2180 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2181 2181 };
2182 2182 meta = {
2183 2183 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2184 2184 };
2185 2185 };
2186 2186 "tzlocal" = super.buildPythonPackage {
2187 2187 name = "tzlocal-1.5.1";
2188 2188 doCheck = false;
2189 2189 propagatedBuildInputs = [
2190 2190 self."pytz"
2191 2191 ];
2192 2192 src = fetchurl {
2193 2193 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2194 2194 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2195 2195 };
2196 2196 meta = {
2197 2197 license = [ pkgs.lib.licenses.mit ];
2198 2198 };
2199 2199 };
2200 2200 "urllib3" = super.buildPythonPackage {
2201 2201 name = "urllib3-1.25.2";
2202 2202 doCheck = false;
2203 2203 src = fetchurl {
2204 2204 url = "https://files.pythonhosted.org/packages/9a/8b/ea6d2beb2da6e331e9857d0a60b79ed4f72dcbc4e2c7f2d2521b0480fda2/urllib3-1.25.2.tar.gz";
2205 2205 sha256 = "1nq2k4pss1ihsjh02r41sqpjpm5rfqkjfysyq7g7n2i1p7c66c55";
2206 2206 };
2207 2207 meta = {
2208 2208 license = [ pkgs.lib.licenses.mit ];
2209 2209 };
2210 2210 };
2211 2211 "urlobject" = super.buildPythonPackage {
2212 2212 name = "urlobject-2.4.3";
2213 2213 doCheck = false;
2214 2214 src = fetchurl {
2215 2215 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2216 2216 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2217 2217 };
2218 2218 meta = {
2219 2219 license = [ pkgs.lib.licenses.publicDomain ];
2220 2220 };
2221 2221 };
2222 2222 "venusian" = super.buildPythonPackage {
2223 2223 name = "venusian-1.2.0";
2224 2224 doCheck = false;
2225 2225 src = fetchurl {
2226 2226 url = "https://files.pythonhosted.org/packages/7e/6f/40a9d43ac77cb51cb62be5b5662d170f43f8037bdc4eab56336c4ca92bb7/venusian-1.2.0.tar.gz";
2227 2227 sha256 = "0ghyx66g8ikx9nx1mnwqvdcqm11i1vlq0hnvwl50s48bp22q5v34";
2228 2228 };
2229 2229 meta = {
2230 2230 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2231 2231 };
2232 2232 };
2233 2233 "vine" = super.buildPythonPackage {
2234 2234 name = "vine-1.3.0";
2235 2235 doCheck = false;
2236 2236 src = fetchurl {
2237 2237 url = "https://files.pythonhosted.org/packages/1c/e1/79fb8046e607dd6c2ad05c9b8ebac9d0bd31d086a08f02699e96fc5b3046/vine-1.3.0.tar.gz";
2238 2238 sha256 = "11ydsbhl1vabndc2r979dv61s6j2b0giq6dgvryifvq1m7bycghk";
2239 2239 };
2240 2240 meta = {
2241 2241 license = [ pkgs.lib.licenses.bsdOriginal ];
2242 2242 };
2243 2243 };
2244 2244 "waitress" = super.buildPythonPackage {
2245 2245 name = "waitress-1.3.1";
2246 2246 doCheck = false;
2247 2247 src = fetchurl {
2248 2248 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
2249 2249 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
2250 2250 };
2251 2251 meta = {
2252 2252 license = [ pkgs.lib.licenses.zpl21 ];
2253 2253 };
2254 2254 };
2255 2255 "wcwidth" = super.buildPythonPackage {
2256 2256 name = "wcwidth-0.1.9";
2257 2257 doCheck = false;
2258 2258 src = fetchurl {
2259 2259 url = "https://files.pythonhosted.org/packages/25/9d/0acbed6e4a4be4fc99148f275488580968f44ddb5e69b8ceb53fc9df55a0/wcwidth-0.1.9.tar.gz";
2260 2260 sha256 = "1wf5ycjx8s066rdvr0fgz4xds9a8zhs91c4jzxvvymm1c8l8cwzf";
2261 2261 };
2262 2262 meta = {
2263 2263 license = [ pkgs.lib.licenses.mit ];
2264 2264 };
2265 2265 };
2266 2266 "webencodings" = super.buildPythonPackage {
2267 2267 name = "webencodings-0.5.1";
2268 2268 doCheck = false;
2269 2269 src = fetchurl {
2270 2270 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2271 2271 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2272 2272 };
2273 2273 meta = {
2274 2274 license = [ pkgs.lib.licenses.bsdOriginal ];
2275 2275 };
2276 2276 };
2277 2277 "weberror" = super.buildPythonPackage {
2278 2278 name = "weberror-0.13.1";
2279 2279 doCheck = false;
2280 2280 propagatedBuildInputs = [
2281 2281 self."webob"
2282 2282 self."tempita"
2283 2283 self."pygments"
2284 2284 self."paste"
2285 2285 ];
2286 2286 src = fetchurl {
2287 2287 url = "https://files.pythonhosted.org/packages/07/0a/09ca5eb0fab5c0d17b380026babe81c96ecebb13f2b06c3203432dd7be72/WebError-0.13.1.tar.gz";
2288 2288 sha256 = "0r4qvnf2r92gfnpa1kwygh4j2x6j3axg2i4an6hyxwg2gpaqp7y1";
2289 2289 };
2290 2290 meta = {
2291 2291 license = [ pkgs.lib.licenses.mit ];
2292 2292 };
2293 2293 };
2294 2294 "webhelpers2" = super.buildPythonPackage {
2295 2295 name = "webhelpers2-2.0";
2296 2296 doCheck = false;
2297 2297 propagatedBuildInputs = [
2298 2298 self."markupsafe"
2299 2299 self."six"
2300 2300 ];
2301 2301 src = fetchurl {
2302 2302 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2303 2303 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2304 2304 };
2305 2305 meta = {
2306 2306 license = [ pkgs.lib.licenses.mit ];
2307 2307 };
2308 2308 };
2309 2309 "webob" = super.buildPythonPackage {
2310 2310 name = "webob-1.8.5";
2311 2311 doCheck = false;
2312 2312 src = fetchurl {
2313 2313 url = "https://files.pythonhosted.org/packages/9d/1a/0c89c070ee2829c934cb6c7082287c822e28236a4fcf90063e6be7c35532/WebOb-1.8.5.tar.gz";
2314 2314 sha256 = "11khpzaxc88q31v25ic330gsf56fwmbdc9b30br8mvp0fmwspah5";
2315 2315 };
2316 2316 meta = {
2317 2317 license = [ pkgs.lib.licenses.mit ];
2318 2318 };
2319 2319 };
2320 2320 "webtest" = super.buildPythonPackage {
2321 2321 name = "webtest-2.0.34";
2322 2322 doCheck = false;
2323 2323 propagatedBuildInputs = [
2324 2324 self."six"
2325 2325 self."webob"
2326 2326 self."waitress"
2327 2327 self."beautifulsoup4"
2328 2328 ];
2329 2329 src = fetchurl {
2330 2330 url = "https://files.pythonhosted.org/packages/2c/74/a0e63feee438735d628631e2b70d82280276a930637ac535479e5fad9427/WebTest-2.0.34.tar.gz";
2331 2331 sha256 = "0x1y2c8z4fmpsny4hbp6ka37si2g10r5r2jwxhvv5mx7g3blq4bi";
2332 2332 };
2333 2333 meta = {
2334 2334 license = [ pkgs.lib.licenses.mit ];
2335 2335 };
2336 2336 };
2337 2337 "whoosh" = super.buildPythonPackage {
2338 2338 name = "whoosh-2.7.4";
2339 2339 doCheck = false;
2340 2340 src = fetchurl {
2341 2341 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2342 2342 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2343 2343 };
2344 2344 meta = {
2345 2345 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2346 2346 };
2347 2347 };
2348 2348 "ws4py" = super.buildPythonPackage {
2349 2349 name = "ws4py-0.5.1";
2350 2350 doCheck = false;
2351 2351 src = fetchurl {
2352 2352 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2353 2353 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2354 2354 };
2355 2355 meta = {
2356 2356 license = [ pkgs.lib.licenses.bsdOriginal ];
2357 2357 };
2358 2358 };
2359 2359 "wsgiref" = super.buildPythonPackage {
2360 2360 name = "wsgiref-0.1.2";
2361 2361 doCheck = false;
2362 2362 src = fetchurl {
2363 2363 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2364 2364 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2365 2365 };
2366 2366 meta = {
2367 2367 license = [ { fullName = "PSF or ZPL"; } ];
2368 2368 };
2369 2369 };
2370 2370 "zipp" = super.buildPythonPackage {
2371 2371 name = "zipp-1.2.0";
2372 2372 doCheck = false;
2373 2373 propagatedBuildInputs = [
2374 2374 self."contextlib2"
2375 2375 ];
2376 2376 src = fetchurl {
2377 2377 url = "https://files.pythonhosted.org/packages/78/08/d52f0ea643bc1068d6dc98b412f4966a9b63255d20911a23ac3220c033c4/zipp-1.2.0.tar.gz";
2378 2378 sha256 = "1c91lnv1bxjimh8as27hz7bghsjkkbxn1d37xq7in9c82iai0167";
2379 2379 };
2380 2380 meta = {
2381 2381 license = [ pkgs.lib.licenses.mit ];
2382 2382 };
2383 2383 };
2384 2384 "zope.cachedescriptors" = super.buildPythonPackage {
2385 2385 name = "zope.cachedescriptors-4.3.1";
2386 2386 doCheck = false;
2387 2387 propagatedBuildInputs = [
2388 2388 self."setuptools"
2389 2389 ];
2390 2390 src = fetchurl {
2391 2391 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2392 2392 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2393 2393 };
2394 2394 meta = {
2395 2395 license = [ pkgs.lib.licenses.zpl21 ];
2396 2396 };
2397 2397 };
2398 2398 "zope.deprecation" = super.buildPythonPackage {
2399 2399 name = "zope.deprecation-4.4.0";
2400 2400 doCheck = false;
2401 2401 propagatedBuildInputs = [
2402 2402 self."setuptools"
2403 2403 ];
2404 2404 src = fetchurl {
2405 2405 url = "https://files.pythonhosted.org/packages/34/da/46e92d32d545dd067b9436279d84c339e8b16de2ca393d7b892bc1e1e9fd/zope.deprecation-4.4.0.tar.gz";
2406 2406 sha256 = "1pz2cv7gv9y1r3m0bdv7ks1alagmrn5msm5spwdzkb2by0w36i8d";
2407 2407 };
2408 2408 meta = {
2409 2409 license = [ pkgs.lib.licenses.zpl21 ];
2410 2410 };
2411 2411 };
2412 2412 "zope.event" = super.buildPythonPackage {
2413 2413 name = "zope.event-4.4";
2414 2414 doCheck = false;
2415 2415 propagatedBuildInputs = [
2416 2416 self."setuptools"
2417 2417 ];
2418 2418 src = fetchurl {
2419 2419 url = "https://files.pythonhosted.org/packages/4c/b2/51c0369adcf5be2334280eed230192ab3b03f81f8efda9ddea6f65cc7b32/zope.event-4.4.tar.gz";
2420 2420 sha256 = "1ksbc726av9xacml6jhcfyn828hlhb9xlddpx6fcvnlvmpmpvhk9";
2421 2421 };
2422 2422 meta = {
2423 2423 license = [ pkgs.lib.licenses.zpl21 ];
2424 2424 };
2425 2425 };
2426 2426 "zope.interface" = super.buildPythonPackage {
2427 2427 name = "zope.interface-4.6.0";
2428 2428 doCheck = false;
2429 2429 propagatedBuildInputs = [
2430 2430 self."setuptools"
2431 2431 ];
2432 2432 src = fetchurl {
2433 2433 url = "https://files.pythonhosted.org/packages/4e/d0/c9d16bd5b38de44a20c6dc5d5ed80a49626fafcb3db9f9efdc2a19026db6/zope.interface-4.6.0.tar.gz";
2434 2434 sha256 = "1rgh2x3rcl9r0v0499kf78xy86rnmanajf4ywmqb943wpk50sg8v";
2435 2435 };
2436 2436 meta = {
2437 2437 license = [ pkgs.lib.licenses.zpl21 ];
2438 2438 };
2439 2439 };
2440 2440
2441 2441 ### Test requirements
2442 2442
2443 2443
2444 2444 }
@@ -1,19 +1,21 b''
1 1 [pytest]
2 2 testpaths = rhodecode
3 3 norecursedirs = rhodecode/public rhodecode/templates tests/scripts
4 4 cache_dir = /tmp/.pytest_cache
5 5
6 6 pyramid_config = rhodecode/tests/rhodecode.ini
7 7 vcsserver_protocol = http
8 8 vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
9 9
10 10 addopts =
11 11 --pdbcls=IPython.terminal.debugger:TerminalPdb
12 12 --strict-markers
13 --capture=no
14 --show-capture=no
13 15
14 16 markers =
15 17 vcs_operations: Mark tests depending on a running RhodeCode instance.
16 18 xfail_backends: Mark tests as xfail for given backends.
17 19 skip_backends: Mark tests as skipped for given backends.
18 20 backends: Mark backends
19 21 dbs: database markers for running tests for given DB
@@ -1,1 +1,1 b''
1 4.19.3 No newline at end of file
1 4.20.0 No newline at end of file
@@ -1,60 +1,60 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 from collections import OrderedDict
23 23
24 24 import sys
25 25 import platform
26 26
27 27 VERSION = tuple(open(os.path.join(
28 28 os.path.dirname(__file__), 'VERSION')).read().split('.'))
29 29
30 30 BACKENDS = OrderedDict()
31 31
32 32 BACKENDS['hg'] = 'Mercurial repository'
33 33 BACKENDS['git'] = 'Git repository'
34 34 BACKENDS['svn'] = 'Subversion repository'
35 35
36 36
37 37 CELERY_ENABLED = False
38 38 CELERY_EAGER = False
39 39
40 40 # link to config for pyramid
41 41 CONFIG = {}
42 42
43 43 # Populated with the settings dictionary from application init in
44 44 # rhodecode.conf.environment.load_pyramid_environment
45 45 PYRAMID_SETTINGS = {}
46 46
47 47 # Linked module for extensions
48 48 EXTENSIONS = {}
49 49
50 50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
51 __dbversion__ = 107 # defines current db version for migrations
51 __dbversion__ = 108 # defines current db version for migrations
52 52 __platform__ = platform.system()
53 53 __license__ = 'AGPLv3, and Commercial License'
54 54 __author__ = 'RhodeCode GmbH'
55 55 __url__ = 'https://code.rhodecode.com'
56 56
57 57 is_windows = __platform__ in ['Windows']
58 58 is_unix = not is_windows
59 59 is_test = False
60 60 disable_error_handler = False
@@ -1,133 +1,134 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.utils import Optional, OAttr
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApi(object):
30 30 maxDiff = None
31 31
32 32 def test_Optional_object(self):
33 33
34 34 option1 = Optional(None)
35 35 assert '<Optional:%s>' % (None,) == repr(option1)
36 36 assert option1() is None
37 37
38 38 assert 1 == Optional.extract(Optional(1))
39 39 assert 'example' == Optional.extract('example')
40 40
41 41 def test_Optional_OAttr(self):
42 42 option1 = Optional(OAttr('apiuser'))
43 43 assert 'apiuser' == Optional.extract(option1)
44 44
45 45 def test_OAttr_object(self):
46 46 oattr1 = OAttr('apiuser')
47 47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 48 assert oattr1() == oattr1
49 49
50 50 def test_api_wrong_key(self):
51 51 id_, params = build_data('trololo', 'get_user')
52 52 response = api_call(self.app, params)
53 53
54 54 expected = 'Invalid API KEY'
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 def test_api_missing_non_optional_param(self):
58 58 id_, params = build_data(self.apikey, 'get_repo')
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_missing_non_optional_param_args_null(self):
65 65 id_, params = build_data(self.apikey, 'get_repo')
66 66 params = params.replace('"args": {}', '"args": null')
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 def test_api_missing_non_optional_param_args_bad(self):
73 73 id_, params = build_data(self.apikey, 'get_repo')
74 74 params = params.replace('"args": {}', '"args": 1')
75 75 response = api_call(self.app, params)
76 76
77 77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 78 assert_error(id_, expected, given=response.body)
79 79
80 80 def test_api_non_existing_method(self, request):
81 81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 82 response = api_call(self.app, params)
83 83 expected = 'No such method: not_existing. Similar methods: none'
84 84 assert_error(id_, expected, given=response.body)
85 85
86 86 def test_api_non_existing_method_have_similar(self, request):
87 87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 88 response = api_call(self.app, params)
89 89 expected = 'No such method: comment. ' \
90 90 'Similar methods: changeset_comment, comment_pull_request, ' \
91 'get_pull_request_comments, comment_commit, get_repo_comments'
91 'get_pull_request_comments, comment_commit, edit_comment, ' \
92 'get_comment, get_repo_comments'
92 93 assert_error(id_, expected, given=response.body)
93 94
94 95 def test_api_disabled_user(self, request):
95 96
96 97 def set_active(active):
97 98 from rhodecode.model.db import Session, User
98 99 user = User.get_by_auth_token(self.apikey)
99 100 user.active = active
100 101 Session().add(user)
101 102 Session().commit()
102 103
103 104 request.addfinalizer(lambda: set_active(True))
104 105
105 106 set_active(False)
106 107 id_, params = build_data(self.apikey, 'test', args='xx')
107 108 response = api_call(self.app, params)
108 109 expected = 'Request from this user not allowed'
109 110 assert_error(id_, expected, given=response.body)
110 111
111 112 def test_api_args_is_null(self):
112 113 __, params = build_data(self.apikey, 'get_users', )
113 114 params = params.replace('"args": {}', '"args": null')
114 115 response = api_call(self.app, params)
115 116 assert response.status == '200 OK'
116 117
117 118 def test_api_args_is_bad(self):
118 119 __, params = build_data(self.apikey, 'get_users', )
119 120 params = params.replace('"args": {}', '"args": 1')
120 121 response = api_call(self.app, params)
121 122 assert response.status == '200 OK'
122 123
123 124 def test_api_args_different_args(self):
124 125 import string
125 126 expected = {
126 127 'ascii_letters': string.ascii_letters,
127 128 'ws': string.whitespace,
128 129 'printables': string.printable
129 130 }
130 131 id_, params = build_data(self.apikey, 'test', args=expected)
131 132 response = api_call(self.app, params)
132 133 assert response.status == '200 OK'
133 134 assert_ok(id_, expected, response.body)
@@ -1,246 +1,390 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.comment import CommentsModel
24 from rhodecode.model.db import UserLog, User
24 from rhodecode.model.db import UserLog, User, ChangesetComment
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestCommentPullRequest(object):
33 33 finalizers = []
34 34
35 35 def teardown_method(self, method):
36 36 if self.finalizers:
37 37 for finalizer in self.finalizers:
38 38 finalizer()
39 39 self.finalizers = []
40 40
41 41 @pytest.mark.backends("git", "hg")
42 42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 43 pull_request = pr_util.create_pull_request()
44 44 pull_request_id = pull_request.pull_request_id
45 45 author = pull_request.user_id
46 46 repo = pull_request.target_repo.repo_id
47 47 id_, params = build_data(
48 48 self.apikey, 'comment_pull_request',
49 49 repoid=pull_request.target_repo.repo_name,
50 50 pullrequestid=pull_request.pull_request_id,
51 51 message='test message')
52 52 response = api_call(self.app, params)
53 53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54 54
55 55 comments = CommentsModel().get_comments(
56 56 pull_request.target_repo.repo_id, pull_request=pull_request)
57 57
58 58 expected = {
59 59 'pull_request_id': pull_request.pull_request_id,
60 60 'comment_id': comments[-1].comment_id,
61 61 'status': {'given': None, 'was_changed': None}
62 62 }
63 63 assert_ok(id_, expected, response.body)
64 64
65 65 journal = UserLog.query()\
66 66 .filter(UserLog.user_id == author)\
67 67 .filter(UserLog.repository_id == repo) \
68 68 .order_by(UserLog.user_log_id.asc()) \
69 69 .all()
70 70 assert journal[-1].action == 'repo.pull_request.comment.create'
71 71
72 72 @pytest.mark.backends("git", "hg")
73 73 def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util):
74 74 pull_request = pr_util.create_pull_request()
75 75
76 76 user1 = user_util.create_user()
77 77 user1_id = user1.user_id
78 78 user2 = user_util.create_user()
79 79 user2_id = user2.user_id
80 80
81 81 id_, params = build_data(
82 82 self.apikey, 'comment_pull_request',
83 83 repoid=pull_request.target_repo.repo_name,
84 84 pullrequestid=pull_request.pull_request_id,
85 85 message='test message',
86 86 extra_recipients=[user1.user_id, user2.username]
87 87 )
88 88 response = api_call(self.app, params)
89 89 pull_request = PullRequestModel().get(pull_request.pull_request_id)
90 90
91 91 comments = CommentsModel().get_comments(
92 92 pull_request.target_repo.repo_id, pull_request=pull_request)
93 93
94 94 expected = {
95 95 'pull_request_id': pull_request.pull_request_id,
96 96 'comment_id': comments[-1].comment_id,
97 97 'status': {'given': None, 'was_changed': None}
98 98 }
99 99 assert_ok(id_, expected, response.body)
100 100 # check user1/user2 inbox for notification
101 101 user1 = User.get(user1_id)
102 102 assert 1 == len(user1.notifications)
103 103 assert 'test message' in user1.notifications[0].notification.body
104 104
105 105 user2 = User.get(user2_id)
106 106 assert 1 == len(user2.notifications)
107 107 assert 'test message' in user2.notifications[0].notification.body
108 108
109 109 @pytest.mark.backends("git", "hg")
110 110 def test_api_comment_pull_request_change_status(
111 111 self, pr_util, no_notifications):
112 112 pull_request = pr_util.create_pull_request()
113 113 pull_request_id = pull_request.pull_request_id
114 114 id_, params = build_data(
115 115 self.apikey, 'comment_pull_request',
116 116 repoid=pull_request.target_repo.repo_name,
117 117 pullrequestid=pull_request.pull_request_id,
118 118 status='rejected')
119 119 response = api_call(self.app, params)
120 120 pull_request = PullRequestModel().get(pull_request_id)
121 121
122 122 comments = CommentsModel().get_comments(
123 123 pull_request.target_repo.repo_id, pull_request=pull_request)
124 124 expected = {
125 125 'pull_request_id': pull_request.pull_request_id,
126 126 'comment_id': comments[-1].comment_id,
127 127 'status': {'given': 'rejected', 'was_changed': True}
128 128 }
129 129 assert_ok(id_, expected, response.body)
130 130
131 131 @pytest.mark.backends("git", "hg")
132 132 def test_api_comment_pull_request_change_status_with_specific_commit_id(
133 133 self, pr_util, no_notifications):
134 134 pull_request = pr_util.create_pull_request()
135 135 pull_request_id = pull_request.pull_request_id
136 136 latest_commit_id = 'test_commit'
137 137 # inject additional revision, to fail test the status change on
138 138 # non-latest commit
139 139 pull_request.revisions = pull_request.revisions + ['test_commit']
140 140
141 141 id_, params = build_data(
142 142 self.apikey, 'comment_pull_request',
143 143 repoid=pull_request.target_repo.repo_name,
144 144 pullrequestid=pull_request.pull_request_id,
145 145 status='approved', commit_id=latest_commit_id)
146 146 response = api_call(self.app, params)
147 147 pull_request = PullRequestModel().get(pull_request_id)
148 148
149 149 expected = {
150 150 'pull_request_id': pull_request.pull_request_id,
151 151 'comment_id': None,
152 152 'status': {'given': 'approved', 'was_changed': False}
153 153 }
154 154 assert_ok(id_, expected, response.body)
155 155
156 156 @pytest.mark.backends("git", "hg")
157 157 def test_api_comment_pull_request_change_status_with_specific_commit_id(
158 158 self, pr_util, no_notifications):
159 159 pull_request = pr_util.create_pull_request()
160 160 pull_request_id = pull_request.pull_request_id
161 161 latest_commit_id = pull_request.revisions[0]
162 162
163 163 id_, params = build_data(
164 164 self.apikey, 'comment_pull_request',
165 165 repoid=pull_request.target_repo.repo_name,
166 166 pullrequestid=pull_request.pull_request_id,
167 167 status='approved', commit_id=latest_commit_id)
168 168 response = api_call(self.app, params)
169 169 pull_request = PullRequestModel().get(pull_request_id)
170 170
171 171 comments = CommentsModel().get_comments(
172 172 pull_request.target_repo.repo_id, pull_request=pull_request)
173 173 expected = {
174 174 'pull_request_id': pull_request.pull_request_id,
175 175 'comment_id': comments[-1].comment_id,
176 176 'status': {'given': 'approved', 'was_changed': True}
177 177 }
178 178 assert_ok(id_, expected, response.body)
179 179
180 180 @pytest.mark.backends("git", "hg")
181 181 def test_api_comment_pull_request_missing_params_error(self, pr_util):
182 182 pull_request = pr_util.create_pull_request()
183 183 pull_request_id = pull_request.pull_request_id
184 184 pull_request_repo = pull_request.target_repo.repo_name
185 185 id_, params = build_data(
186 186 self.apikey, 'comment_pull_request',
187 187 repoid=pull_request_repo,
188 188 pullrequestid=pull_request_id)
189 189 response = api_call(self.app, params)
190 190
191 191 expected = 'Both message and status parameters are missing. At least one is required.'
192 192 assert_error(id_, expected, given=response.body)
193 193
194 194 @pytest.mark.backends("git", "hg")
195 195 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
196 196 pull_request = pr_util.create_pull_request()
197 197 pull_request_id = pull_request.pull_request_id
198 198 pull_request_repo = pull_request.target_repo.repo_name
199 199 id_, params = build_data(
200 200 self.apikey, 'comment_pull_request',
201 201 repoid=pull_request_repo,
202 202 pullrequestid=pull_request_id,
203 203 status='42')
204 204 response = api_call(self.app, params)
205 205
206 206 expected = 'Unknown comment status: `42`'
207 207 assert_error(id_, expected, given=response.body)
208 208
209 209 @pytest.mark.backends("git", "hg")
210 210 def test_api_comment_pull_request_repo_error(self, pr_util):
211 211 pull_request = pr_util.create_pull_request()
212 212 id_, params = build_data(
213 213 self.apikey, 'comment_pull_request',
214 214 repoid=666, pullrequestid=pull_request.pull_request_id)
215 215 response = api_call(self.app, params)
216 216
217 217 expected = 'repository `666` does not exist'
218 218 assert_error(id_, expected, given=response.body)
219 219
220 220 @pytest.mark.backends("git", "hg")
221 def test_api_comment_pull_request_non_admin_with_userid_error(
222 self, pr_util):
221 def test_api_comment_pull_request_non_admin_with_userid_error(self, pr_util):
222 pull_request = pr_util.create_pull_request()
223 id_, params = build_data(
224 self.apikey_regular, 'comment_pull_request',
225 repoid=pull_request.target_repo.repo_name,
226 pullrequestid=pull_request.pull_request_id,
227 userid=TEST_USER_ADMIN_LOGIN)
228 response = api_call(self.app, params)
229
230 expected = 'userid is not the same as your user'
231 assert_error(id_, expected, given=response.body)
232
233 @pytest.mark.backends("git", "hg")
234 def test_api_comment_pull_request_non_admin_with_userid_error(self, pr_util):
223 235 pull_request = pr_util.create_pull_request()
224 236 id_, params = build_data(
225 237 self.apikey_regular, 'comment_pull_request',
226 238 repoid=pull_request.target_repo.repo_name,
227 239 pullrequestid=pull_request.pull_request_id,
228 240 userid=TEST_USER_ADMIN_LOGIN)
229 241 response = api_call(self.app, params)
230 242
231 243 expected = 'userid is not the same as your user'
232 244 assert_error(id_, expected, given=response.body)
233 245
234 246 @pytest.mark.backends("git", "hg")
235 247 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
236 248 pull_request = pr_util.create_pull_request()
237 249 id_, params = build_data(
238 250 self.apikey_regular, 'comment_pull_request',
239 251 repoid=pull_request.target_repo.repo_name,
240 252 status='approved',
241 253 pullrequestid=pull_request.pull_request_id,
242 254 commit_id='XXX')
243 255 response = api_call(self.app, params)
244 256
245 257 expected = 'Invalid commit_id `XXX` for this pull request.'
246 258 assert_error(id_, expected, given=response.body)
259
260 @pytest.mark.backends("git", "hg")
261 def test_api_edit_comment(self, pr_util):
262 pull_request = pr_util.create_pull_request()
263
264 id_, params = build_data(
265 self.apikey,
266 'comment_pull_request',
267 repoid=pull_request.target_repo.repo_name,
268 pullrequestid=pull_request.pull_request_id,
269 message='test message',
270 )
271 response = api_call(self.app, params)
272 json_response = response.json
273 comment_id = json_response['result']['comment_id']
274
275 message_after_edit = 'just message'
276 id_, params = build_data(
277 self.apikey,
278 'edit_comment',
279 comment_id=comment_id,
280 message=message_after_edit,
281 version=0,
282 )
283 response = api_call(self.app, params)
284 json_response = response.json
285 assert json_response['result']['version'] == 1
286
287 text_form_db = ChangesetComment.get(comment_id).text
288 assert message_after_edit == text_form_db
289
290 @pytest.mark.backends("git", "hg")
291 def test_api_edit_comment_wrong_version(self, pr_util):
292 pull_request = pr_util.create_pull_request()
293
294 id_, params = build_data(
295 self.apikey, 'comment_pull_request',
296 repoid=pull_request.target_repo.repo_name,
297 pullrequestid=pull_request.pull_request_id,
298 message='test message')
299 response = api_call(self.app, params)
300 json_response = response.json
301 comment_id = json_response['result']['comment_id']
302
303 message_after_edit = 'just message'
304 id_, params = build_data(
305 self.apikey_regular,
306 'edit_comment',
307 comment_id=comment_id,
308 message=message_after_edit,
309 version=1,
310 )
311 response = api_call(self.app, params)
312 expected = 'comment ({}) version ({}) mismatch'.format(comment_id, 1)
313 assert_error(id_, expected, given=response.body)
314
315 @pytest.mark.backends("git", "hg")
316 def test_api_edit_comment_wrong_version(self, pr_util):
317 pull_request = pr_util.create_pull_request()
318
319 id_, params = build_data(
320 self.apikey, 'comment_pull_request',
321 repoid=pull_request.target_repo.repo_name,
322 pullrequestid=pull_request.pull_request_id,
323 message='test message')
324 response = api_call(self.app, params)
325 json_response = response.json
326 comment_id = json_response['result']['comment_id']
327
328 id_, params = build_data(
329 self.apikey,
330 'edit_comment',
331 comment_id=comment_id,
332 message='',
333 version=0,
334 )
335 response = api_call(self.app, params)
336 expected = "comment ({}) can't be changed with empty string".format(comment_id, 1)
337 assert_error(id_, expected, given=response.body)
338
339 @pytest.mark.backends("git", "hg")
340 def test_api_edit_comment_wrong_user_set_by_non_admin(self, pr_util):
341 pull_request = pr_util.create_pull_request()
342 pull_request_id = pull_request.pull_request_id
343 id_, params = build_data(
344 self.apikey,
345 'comment_pull_request',
346 repoid=pull_request.target_repo.repo_name,
347 pullrequestid=pull_request_id,
348 message='test message'
349 )
350 response = api_call(self.app, params)
351 json_response = response.json
352 comment_id = json_response['result']['comment_id']
353
354 id_, params = build_data(
355 self.apikey_regular,
356 'edit_comment',
357 comment_id=comment_id,
358 message='just message',
359 version=0,
360 userid=TEST_USER_ADMIN_LOGIN
361 )
362 response = api_call(self.app, params)
363 expected = 'userid is not the same as your user'
364 assert_error(id_, expected, given=response.body)
365
366 @pytest.mark.backends("git", "hg")
367 def test_api_edit_comment_wrong_user_with_permissions_to_edit_comment(self, pr_util):
368 pull_request = pr_util.create_pull_request()
369 pull_request_id = pull_request.pull_request_id
370 id_, params = build_data(
371 self.apikey,
372 'comment_pull_request',
373 repoid=pull_request.target_repo.repo_name,
374 pullrequestid=pull_request_id,
375 message='test message'
376 )
377 response = api_call(self.app, params)
378 json_response = response.json
379 comment_id = json_response['result']['comment_id']
380
381 id_, params = build_data(
382 self.apikey_regular,
383 'edit_comment',
384 comment_id=comment_id,
385 message='just message',
386 version=0,
387 )
388 response = api_call(self.app, params)
389 expected = "you don't have access to edit this comment"
390 assert_error(id_, expected, given=response.body)
@@ -1,289 +1,289 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo_group import RepoGroupModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, crash)
30 30 from rhodecode.tests.fixture import Fixture
31 31
32 32
33 33 fixture = Fixture()
34 34
35 35
36 36 @pytest.mark.usefixtures("testuser_api", "app")
37 37 class TestCreateRepoGroup(object):
38 38 def test_api_create_repo_group(self):
39 39 repo_group_name = 'api-repo-group'
40 40
41 41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
42 42 assert repo_group is None
43 43
44 44 id_, params = build_data(
45 45 self.apikey, 'create_repo_group',
46 46 group_name=repo_group_name,
47 47 owner=TEST_USER_ADMIN_LOGIN,)
48 48 response = api_call(self.app, params)
49 49
50 50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
51 51 assert repo_group is not None
52 52 ret = {
53 53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
54 54 'repo_group': repo_group.get_api_data()
55 55 }
56 56 expected = ret
57 57 try:
58 58 assert_ok(id_, expected, given=response.body)
59 59 finally:
60 60 fixture.destroy_repo_group(repo_group_name)
61 61
62 62 def test_api_create_repo_group_in_another_group(self):
63 63 repo_group_name = 'api-repo-group'
64 64
65 65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
66 66 assert repo_group is None
67 67 # create the parent
68 68 fixture.create_repo_group(repo_group_name)
69 69
70 70 full_repo_group_name = repo_group_name+'/'+repo_group_name
71 71 id_, params = build_data(
72 72 self.apikey, 'create_repo_group',
73 73 group_name=full_repo_group_name,
74 74 owner=TEST_USER_ADMIN_LOGIN,
75 75 copy_permissions=True)
76 76 response = api_call(self.app, params)
77 77
78 78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
79 79 assert repo_group is not None
80 80 ret = {
81 81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
82 82 'repo_group': repo_group.get_api_data()
83 83 }
84 84 expected = ret
85 85 try:
86 86 assert_ok(id_, expected, given=response.body)
87 87 finally:
88 88 fixture.destroy_repo_group(full_repo_group_name)
89 89 fixture.destroy_repo_group(repo_group_name)
90 90
91 91 def test_api_create_repo_group_in_another_group_not_existing(self):
92 92 repo_group_name = 'api-repo-group-no'
93 93
94 94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
95 95 assert repo_group is None
96 96
97 97 full_repo_group_name = repo_group_name+'/'+repo_group_name
98 98 id_, params = build_data(
99 99 self.apikey, 'create_repo_group',
100 100 group_name=full_repo_group_name,
101 101 owner=TEST_USER_ADMIN_LOGIN,
102 102 copy_permissions=True)
103 103 response = api_call(self.app, params)
104 104 expected = {
105 105 'repo_group':
106 106 'Parent repository group `{}` does not exist'.format(
107 107 repo_group_name)}
108 108 assert_error(id_, expected, given=response.body)
109 109
110 110 def test_api_create_repo_group_that_exists(self):
111 111 repo_group_name = 'api-repo-group'
112 112
113 113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
114 114 assert repo_group is None
115 115
116 116 fixture.create_repo_group(repo_group_name)
117 117 id_, params = build_data(
118 118 self.apikey, 'create_repo_group',
119 119 group_name=repo_group_name,
120 120 owner=TEST_USER_ADMIN_LOGIN,)
121 121 response = api_call(self.app, params)
122 122 expected = {
123 123 'unique_repo_group_name':
124 124 'Repository group with name `{}` already exists'.format(
125 125 repo_group_name)}
126 126 try:
127 127 assert_error(id_, expected, given=response.body)
128 128 finally:
129 129 fixture.destroy_repo_group(repo_group_name)
130 130
131 131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
132 132 self, user_util):
133 133 regular_user = user_util.create_user()
134 134 regular_user_api_key = regular_user.api_key
135 135
136 136 repo_group_name = 'api-repo-group-by-regular-user'
137 137
138 138 usr = UserModel().get_by_username(regular_user.username)
139 139 usr.inherit_default_permissions = False
140 140 Session().add(usr)
141 141
142 142 UserModel().grant_perm(
143 143 regular_user.username, 'hg.repogroup.create.true')
144 144 Session().commit()
145 145
146 146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
147 147 assert repo_group is None
148 148
149 149 id_, params = build_data(
150 150 regular_user_api_key, 'create_repo_group',
151 151 group_name=repo_group_name)
152 152 response = api_call(self.app, params)
153 153
154 154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
155 155 assert repo_group is not None
156 156 expected = {
157 157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
158 158 'repo_group': repo_group.get_api_data()
159 159 }
160 160 try:
161 161 assert_ok(id_, expected, given=response.body)
162 162 finally:
163 163 fixture.destroy_repo_group(repo_group_name)
164 164
165 165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
166 166 self, user_util):
167 167
168 168 repo_group_name = 'api-repo-group-parent'
169 169
170 170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
171 171 assert repo_group is None
172 172 # create the parent
173 173 fixture.create_repo_group(repo_group_name)
174 174
175 175 # user perms
176 176 regular_user = user_util.create_user()
177 177 regular_user_api_key = regular_user.api_key
178 178
179 179 usr = UserModel().get_by_username(regular_user.username)
180 180 usr.inherit_default_permissions = False
181 181 Session().add(usr)
182 182
183 183 RepoGroupModel().grant_user_permission(
184 184 repo_group_name, regular_user.username, 'group.admin')
185 185 Session().commit()
186 186
187 187 full_repo_group_name = repo_group_name + '/' + repo_group_name
188 188 id_, params = build_data(
189 189 regular_user_api_key, 'create_repo_group',
190 190 group_name=full_repo_group_name)
191 191 response = api_call(self.app, params)
192 192
193 193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
194 194 assert repo_group is not None
195 195 expected = {
196 196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
197 197 'repo_group': repo_group.get_api_data()
198 198 }
199 199 try:
200 200 assert_ok(id_, expected, given=response.body)
201 201 finally:
202 202 fixture.destroy_repo_group(full_repo_group_name)
203 203 fixture.destroy_repo_group(repo_group_name)
204 204
205 205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
206 206 repo_group_name = 'api-repo-group'
207 207
208 208 id_, params = build_data(
209 209 self.apikey_regular, 'create_repo_group',
210 210 group_name=repo_group_name)
211 211 response = api_call(self.app, params)
212 212
213 213 expected = {
214 214 'repo_group':
215 215 u'You do not have the permission to store '
216 216 u'repository groups in the root location.'}
217 217 assert_error(id_, expected, given=response.body)
218 218
219 219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
220 220 repo_group_name = 'api-repo-group-regular-user'
221 221
222 222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
223 223 assert repo_group is None
224 224 # create the parent
225 225 fixture.create_repo_group(repo_group_name)
226 226
227 227 full_repo_group_name = repo_group_name+'/'+repo_group_name
228 228
229 229 id_, params = build_data(
230 230 self.apikey_regular, 'create_repo_group',
231 231 group_name=full_repo_group_name)
232 232 response = api_call(self.app, params)
233 233
234 234 expected = {
235 235 'repo_group':
236 'Parent repository group `{}` does not exist'.format(
237 repo_group_name)}
236 u"You do not have the permissions to store "
237 u"repository groups inside repository group `{}`".format(repo_group_name)}
238 238 try:
239 239 assert_error(id_, expected, given=response.body)
240 240 finally:
241 241 fixture.destroy_repo_group(repo_group_name)
242 242
243 243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
244 244 self):
245 245 repo_group_name = 'api-repo-group'
246 246
247 247 id_, params = build_data(
248 248 self.apikey_regular, 'create_repo_group',
249 249 group_name=repo_group_name,
250 250 owner=TEST_USER_ADMIN_LOGIN,)
251 251 response = api_call(self.app, params)
252 252
253 253 expected = "Only RhodeCode super-admin can specify `owner` param"
254 254 assert_error(id_, expected, given=response.body)
255 255
256 256 @mock.patch.object(RepoGroupModel, 'create', crash)
257 257 def test_api_create_repo_group_exception_occurred(self):
258 258 repo_group_name = 'api-repo-group'
259 259
260 260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
261 261 assert repo_group is None
262 262
263 263 id_, params = build_data(
264 264 self.apikey, 'create_repo_group',
265 265 group_name=repo_group_name,
266 266 owner=TEST_USER_ADMIN_LOGIN,)
267 267 response = api_call(self.app, params)
268 268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
269 269 assert_error(id_, expected, given=response.body)
270 270
271 271 def test_create_group_with_extra_slashes_in_name(self, user_util):
272 272 existing_repo_group = user_util.create_repo_group()
273 273 dirty_group_name = '//{}//group2//'.format(
274 274 existing_repo_group.group_name)
275 275 cleaned_group_name = '{}/group2'.format(
276 276 existing_repo_group.group_name)
277 277
278 278 id_, params = build_data(
279 279 self.apikey, 'create_repo_group',
280 280 group_name=dirty_group_name,
281 281 owner=TEST_USER_ADMIN_LOGIN,)
282 282 response = api_call(self.app, params)
283 283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
284 284 expected = {
285 285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
286 286 'repo_group': repo_group.get_api_data()
287 287 }
288 288 assert_ok(id_, expected, given=response.body)
289 289 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,61 +1,63 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetMethod(object):
29 29 def test_get_methods_no_matches(self):
30 30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 31 response = api_call(self.app, params)
32 32
33 33 expected = []
34 34 assert_ok(id_, expected, given=response.body)
35 35
36 36 def test_get_methods(self):
37 37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 38 response = api_call(self.app, params)
39 39
40 expected = ['changeset_comment', 'comment_pull_request',
41 'get_pull_request_comments', 'comment_commit', 'get_repo_comments']
40 expected = [
41 'changeset_comment', 'comment_pull_request', 'get_pull_request_comments',
42 'comment_commit', 'edit_comment', 'get_comment', 'get_repo_comments'
43 ]
42 44 assert_ok(id_, expected, given=response.body)
43 45
44 46 def test_get_methods_on_single_match(self):
45 47 id_, params = build_data(self.apikey, 'get_method',
46 48 pattern='*comment_commit*')
47 49 response = api_call(self.app, params)
48 50
49 51 expected = ['comment_commit',
50 52 {'apiuser': '<RequiredType>',
51 53 'comment_type': "<Optional:u'note'>",
52 54 'commit_id': '<RequiredType>',
53 55 'extra_recipients': '<Optional:[]>',
54 56 'message': '<RequiredType>',
55 57 'repoid': '<RequiredType>',
56 58 'request': '<RequiredType>',
57 59 'resolves_comment_id': '<Optional:None>',
58 60 'status': '<Optional:None>',
59 61 'userid': '<Optional:<OptionalAttr:apiuser>>',
60 62 'send_email': '<Optional:True>'}]
61 63 assert_ok(id_, expected, given=response.body)
@@ -1,86 +1,87 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23 import urlobject
24 24
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import safe_unicode
29 29
30 30 pytestmark = pytest.mark.backends("git", "hg")
31 31
32 32
33 33 @pytest.mark.usefixtures("testuser_api", "app")
34 34 class TestGetPullRequestComments(object):
35 35
36 36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
37 37 from rhodecode.model.pull_request import PullRequestModel
38 38
39 39 pull_request = pr_util.create_pull_request(mergeable=True)
40 40 id_, params = build_data(
41 41 self.apikey, 'get_pull_request_comments',
42 42 pullrequestid=pull_request.pull_request_id)
43 43
44 44 response = api_call(self.app, params)
45 45
46 46 assert response.status == '200 OK'
47 47 resp_date = response.json['result'][0]['comment_created_on']
48 48 resp_comment_id = response.json['result'][0]['comment_id']
49 49
50 50 expected = [
51 51 {'comment_author': {'active': True,
52 52 'full_name_or_username': 'RhodeCode Admin',
53 53 'username': 'test_admin'},
54 54 'comment_created_on': resp_date,
55 55 'comment_f_path': None,
56 56 'comment_id': resp_comment_id,
57 57 'comment_lineno': None,
58 58 'comment_status': {'status': 'under_review',
59 59 'status_lbl': 'Under Review'},
60 60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
61 61 'comment_type': 'note',
62 62 'comment_resolved_by': None,
63 63 'pull_request_version': None,
64 'comment_last_version': 0,
64 65 'comment_commit_id': None,
65 66 'comment_pull_request_id': pull_request.pull_request_id
66 67 }
67 68 ]
68 69 assert_ok(id_, expected, response.body)
69 70
70 71 def test_api_get_pull_request_comments_repo_error(self, pr_util):
71 72 pull_request = pr_util.create_pull_request()
72 73 id_, params = build_data(
73 74 self.apikey, 'get_pull_request_comments',
74 75 repoid=666, pullrequestid=pull_request.pull_request_id)
75 76 response = api_call(self.app, params)
76 77
77 78 expected = 'repository `666` does not exist'
78 79 assert_error(id_, expected, given=response.body)
79 80
80 81 def test_api_get_pull_request_comments_pull_request_error(self):
81 82 id_, params = build_data(
82 83 self.apikey, 'get_pull_request_comments', pullrequestid=666)
83 84 response = api_call(self.app, params)
84 85
85 86 expected = 'pull request `666` does not exist'
86 87 assert_error(id_, expected, given=response.body)
@@ -1,110 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23
24 24 from rhodecode.model.db import User, ChangesetComment
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.model.comment import CommentsModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_call_ok)
29 29
30 30
31 31 @pytest.fixture()
32 32 def make_repo_comments_factory(request):
33 33
34 34 class Make(object):
35 35
36 36 def make_comments(self, repo):
37 37 user = User.get_first_super_admin()
38 38 commit = repo.scm_instance()[0]
39 39
40 40 commit_id = commit.raw_id
41 41 file_0 = commit.affected_files[0]
42 42 comments = []
43 43
44 44 # general
45 CommentsModel().create(
45 comment = CommentsModel().create(
46 46 text='General Comment', repo=repo, user=user, commit_id=commit_id,
47 47 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
48 comments.append(comment)
48 49
49 50 # inline
50 CommentsModel().create(
51 comment = CommentsModel().create(
51 52 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
52 53 f_path=file_0, line_no='n1',
53 54 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
55 comments.append(comment)
54 56
55 57 # todo
56 CommentsModel().create(
58 comment = CommentsModel().create(
57 59 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
58 60 f_path=file_0, line_no='n1',
59 61 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
62 comments.append(comment)
60 63
61 @request.addfinalizer
62 def cleanup():
63 for comment in comments:
64 Session().delete(comment)
64 return comments
65
65 66 return Make()
66 67
67 68
68 69 @pytest.mark.usefixtures("testuser_api", "app")
69 70 class TestGetRepo(object):
70 71
71 72 @pytest.mark.parametrize('filters, expected_count', [
72 73 ({}, 3),
73 74 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
74 75 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
75 76 ({'commit_id': 'FILLED DYNAMIC'}, 3),
76 77 ])
77 78 def test_api_get_repo_comments(self, backend, user_util,
78 79 make_repo_comments_factory, filters, expected_count):
79 80 commits = [{'message': 'A'}, {'message': 'B'}]
80 81 repo = backend.create_repo(commits=commits)
81 82 make_repo_comments_factory.make_comments(repo)
82 83
83 84 api_call_params = {'repoid': repo.repo_name,}
84 85 api_call_params.update(filters)
85 86
86 87 if 'commit_id' in api_call_params:
87 88 commit = repo.scm_instance()[0]
88 89 commit_id = commit.raw_id
89 90 api_call_params['commit_id'] = commit_id
90 91
91 92 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
92 93 response = api_call(self.app, params)
93 94 result = assert_call_ok(id_, given=response.body)
94 95
95 96 assert len(result) == expected_count
96 97
97 98 def test_api_get_repo_comments_wrong_comment_type(
98 99 self, make_repo_comments_factory, backend_hg):
99 100 commits = [{'message': 'A'}, {'message': 'B'}]
100 101 repo = backend_hg.create_repo(commits=commits)
101 102 make_repo_comments_factory.make_comments(repo)
102 103
103 104 api_call_params = {'repoid': repo.repo_name}
104 105 api_call_params.update({'comment_type': 'bogus'})
105 106
106 107 expected = 'comment_type must be one of `{}` got {}'.format(
107 108 ChangesetComment.COMMENT_TYPES, 'bogus')
108 109 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
109 110 response = api_call(self.app, params)
110 111 assert_error(id_, expected, given=response.body)
112
113 def test_api_get_comment(self, make_repo_comments_factory, backend_hg):
114 commits = [{'message': 'A'}, {'message': 'B'}]
115 repo = backend_hg.create_repo(commits=commits)
116
117 comments = make_repo_comments_factory.make_comments(repo)
118 comment_ids = [x.comment_id for x in comments]
119 Session().commit()
120
121 for comment_id in comment_ids:
122 id_, params = build_data(self.apikey, 'get_comment',
123 **{'comment_id': comment_id})
124 response = api_call(self.app, params)
125 result = assert_call_ok(id_, given=response.body)
126 assert result['comment_id'] == comment_id
127
128 def test_api_get_comment_no_access(self, make_repo_comments_factory, backend_hg, user_util):
129 commits = [{'message': 'A'}, {'message': 'B'}]
130 repo = backend_hg.create_repo(commits=commits)
131 comments = make_repo_comments_factory.make_comments(repo)
132 comment_id = comments[0].comment_id
133
134 test_user = user_util.create_user()
135 user_util.grant_user_permission_to_repo(repo, test_user, 'repository.none')
136
137 id_, params = build_data(test_user.api_key, 'get_comment',
138 **{'comment_id': comment_id})
139 response = api_call(self.app, params)
140 assert_error(id_,
141 expected='comment `{}` does not exist'.format(comment_id),
142 given=response.body)
@@ -1,1018 +1,1018 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 from rhodecode import events
25 24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 25 from rhodecode.api.utils import (
27 26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 28 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
30 29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 30 from rhodecode.lib.base import vcs_operation_context
32 31 from rhodecode.lib.utils2 import str2bool
33 32 from rhodecode.model.changeset_status import ChangesetStatusModel
34 33 from rhodecode.model.comment import CommentsModel
35 34 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
36 35 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 36 from rhodecode.model.settings import SettingsModel
38 37 from rhodecode.model.validation_schema import Invalid
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 ReviewerListSchema)
38 from rhodecode.model.validation_schema.schemas.reviewer_schema import ReviewerListSchema
41 39
42 40 log = logging.getLogger(__name__)
43 41
44 42
45 43 @jsonrpc_method()
46 44 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
47 45 merge_state=Optional(False)):
48 46 """
49 47 Get a pull request based on the given ID.
50 48
51 49 :param apiuser: This is filled automatically from the |authtoken|.
52 50 :type apiuser: AuthUser
53 51 :param repoid: Optional, repository name or repository ID from where
54 52 the pull request was opened.
55 53 :type repoid: str or int
56 54 :param pullrequestid: ID of the requested pull request.
57 55 :type pullrequestid: int
58 56 :param merge_state: Optional calculate merge state for each repository.
59 57 This could result in longer time to fetch the data
60 58 :type merge_state: bool
61 59
62 60 Example output:
63 61
64 62 .. code-block:: bash
65 63
66 64 "id": <id_given_in_input>,
67 65 "result":
68 66 {
69 67 "pull_request_id": "<pull_request_id>",
70 68 "url": "<url>",
71 69 "title": "<title>",
72 70 "description": "<description>",
73 71 "status" : "<status>",
74 72 "created_on": "<date_time_created>",
75 73 "updated_on": "<date_time_updated>",
76 74 "versions": "<number_or_versions_of_pr>",
77 75 "commit_ids": [
78 76 ...
79 77 "<commit_id>",
80 78 "<commit_id>",
81 79 ...
82 80 ],
83 81 "review_status": "<review_status>",
84 82 "mergeable": {
85 83 "status": "<bool>",
86 84 "message": "<message>",
87 85 },
88 86 "source": {
89 87 "clone_url": "<clone_url>",
90 88 "repository": "<repository_name>",
91 89 "reference":
92 90 {
93 91 "name": "<name>",
94 92 "type": "<type>",
95 93 "commit_id": "<commit_id>",
96 94 }
97 95 },
98 96 "target": {
99 97 "clone_url": "<clone_url>",
100 98 "repository": "<repository_name>",
101 99 "reference":
102 100 {
103 101 "name": "<name>",
104 102 "type": "<type>",
105 103 "commit_id": "<commit_id>",
106 104 }
107 105 },
108 106 "merge": {
109 107 "clone_url": "<clone_url>",
110 108 "reference":
111 109 {
112 110 "name": "<name>",
113 111 "type": "<type>",
114 112 "commit_id": "<commit_id>",
115 113 }
116 114 },
117 115 "author": <user_obj>,
118 116 "reviewers": [
119 117 ...
120 118 {
121 119 "user": "<user_obj>",
122 120 "review_status": "<review_status>",
123 121 }
124 122 ...
125 123 ]
126 124 },
127 125 "error": null
128 126 """
129 127
130 128 pull_request = get_pull_request_or_error(pullrequestid)
131 129 if Optional.extract(repoid):
132 130 repo = get_repo_or_error(repoid)
133 131 else:
134 132 repo = pull_request.target_repo
135 133
136 134 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
137 135 raise JSONRPCError('repository `%s` or pull request `%s` '
138 136 'does not exist' % (repoid, pullrequestid))
139 137
140 138 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
141 139 # otherwise we can lock the repo on calculation of merge state while update/merge
142 140 # is happening.
143 141 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
144 142 merge_state = Optional.extract(merge_state, binary=True) and pr_created
145 143 data = pull_request.get_api_data(with_merge_state=merge_state)
146 144 return data
147 145
148 146
149 147 @jsonrpc_method()
150 148 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
151 149 merge_state=Optional(False)):
152 150 """
153 151 Get all pull requests from the repository specified in `repoid`.
154 152
155 153 :param apiuser: This is filled automatically from the |authtoken|.
156 154 :type apiuser: AuthUser
157 155 :param repoid: Optional repository name or repository ID.
158 156 :type repoid: str or int
159 157 :param status: Only return pull requests with the specified status.
160 158 Valid options are.
161 159 * ``new`` (default)
162 160 * ``open``
163 161 * ``closed``
164 162 :type status: str
165 163 :param merge_state: Optional calculate merge state for each repository.
166 164 This could result in longer time to fetch the data
167 165 :type merge_state: bool
168 166
169 167 Example output:
170 168
171 169 .. code-block:: bash
172 170
173 171 "id": <id_given_in_input>,
174 172 "result":
175 173 [
176 174 ...
177 175 {
178 176 "pull_request_id": "<pull_request_id>",
179 177 "url": "<url>",
180 178 "title" : "<title>",
181 179 "description": "<description>",
182 180 "status": "<status>",
183 181 "created_on": "<date_time_created>",
184 182 "updated_on": "<date_time_updated>",
185 183 "commit_ids": [
186 184 ...
187 185 "<commit_id>",
188 186 "<commit_id>",
189 187 ...
190 188 ],
191 189 "review_status": "<review_status>",
192 190 "mergeable": {
193 191 "status": "<bool>",
194 192 "message: "<message>",
195 193 },
196 194 "source": {
197 195 "clone_url": "<clone_url>",
198 196 "reference":
199 197 {
200 198 "name": "<name>",
201 199 "type": "<type>",
202 200 "commit_id": "<commit_id>",
203 201 }
204 202 },
205 203 "target": {
206 204 "clone_url": "<clone_url>",
207 205 "reference":
208 206 {
209 207 "name": "<name>",
210 208 "type": "<type>",
211 209 "commit_id": "<commit_id>",
212 210 }
213 211 },
214 212 "merge": {
215 213 "clone_url": "<clone_url>",
216 214 "reference":
217 215 {
218 216 "name": "<name>",
219 217 "type": "<type>",
220 218 "commit_id": "<commit_id>",
221 219 }
222 220 },
223 221 "author": <user_obj>,
224 222 "reviewers": [
225 223 ...
226 224 {
227 225 "user": "<user_obj>",
228 226 "review_status": "<review_status>",
229 227 }
230 228 ...
231 229 ]
232 230 }
233 231 ...
234 232 ],
235 233 "error": null
236 234
237 235 """
238 236 repo = get_repo_or_error(repoid)
239 237 if not has_superadmin_permission(apiuser):
240 238 _perms = (
241 239 'repository.admin', 'repository.write', 'repository.read',)
242 240 validate_repo_permissions(apiuser, repoid, repo, _perms)
243 241
244 242 status = Optional.extract(status)
245 243 merge_state = Optional.extract(merge_state, binary=True)
246 244 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
247 245 order_by='id', order_dir='desc')
248 246 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
249 247 return data
250 248
251 249
252 250 @jsonrpc_method()
253 251 def merge_pull_request(
254 252 request, apiuser, pullrequestid, repoid=Optional(None),
255 253 userid=Optional(OAttr('apiuser'))):
256 254 """
257 255 Merge the pull request specified by `pullrequestid` into its target
258 256 repository.
259 257
260 258 :param apiuser: This is filled automatically from the |authtoken|.
261 259 :type apiuser: AuthUser
262 260 :param repoid: Optional, repository name or repository ID of the
263 261 target repository to which the |pr| is to be merged.
264 262 :type repoid: str or int
265 263 :param pullrequestid: ID of the pull request which shall be merged.
266 264 :type pullrequestid: int
267 265 :param userid: Merge the pull request as this user.
268 266 :type userid: Optional(str or int)
269 267
270 268 Example output:
271 269
272 270 .. code-block:: bash
273 271
274 272 "id": <id_given_in_input>,
275 273 "result": {
276 274 "executed": "<bool>",
277 275 "failure_reason": "<int>",
278 276 "merge_status_message": "<str>",
279 277 "merge_commit_id": "<merge_commit_id>",
280 278 "possible": "<bool>",
281 279 "merge_ref": {
282 280 "commit_id": "<commit_id>",
283 281 "type": "<type>",
284 282 "name": "<name>"
285 283 }
286 284 },
287 285 "error": null
288 286 """
289 287 pull_request = get_pull_request_or_error(pullrequestid)
290 288 if Optional.extract(repoid):
291 289 repo = get_repo_or_error(repoid)
292 290 else:
293 291 repo = pull_request.target_repo
294 292 auth_user = apiuser
293
295 294 if not isinstance(userid, Optional):
296 if (has_superadmin_permission(apiuser) or
297 HasRepoPermissionAnyApi('repository.admin')(
298 user=apiuser, repo_name=repo.repo_name)):
295 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
296 user=apiuser, repo_name=repo.repo_name)
297 if has_superadmin_permission(apiuser) or is_repo_admin:
299 298 apiuser = get_user_or_error(userid)
300 299 auth_user = apiuser.AuthUser()
301 300 else:
302 301 raise JSONRPCError('userid is not the same as your user')
303 302
304 303 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
305 304 raise JSONRPCError(
306 305 'Operation forbidden because pull request is in state {}, '
307 306 'only state {} is allowed.'.format(
308 307 pull_request.pull_request_state, PullRequest.STATE_CREATED))
309 308
310 309 with pull_request.set_state(PullRequest.STATE_UPDATING):
311 310 check = MergeCheck.validate(pull_request, auth_user=auth_user,
312 311 translator=request.translate)
313 312 merge_possible = not check.failed
314 313
315 314 if not merge_possible:
316 315 error_messages = []
317 316 for err_type, error_msg in check.errors:
318 317 error_msg = request.translate(error_msg)
319 318 error_messages.append(error_msg)
320 319
321 320 reasons = ','.join(error_messages)
322 321 raise JSONRPCError(
323 322 'merge not possible for following reasons: {}'.format(reasons))
324 323
325 324 target_repo = pull_request.target_repo
326 325 extras = vcs_operation_context(
327 326 request.environ, repo_name=target_repo.repo_name,
328 327 username=auth_user.username, action='push',
329 328 scm=target_repo.repo_type)
330 329 with pull_request.set_state(PullRequest.STATE_UPDATING):
331 330 merge_response = PullRequestModel().merge_repo(
332 331 pull_request, apiuser, extras=extras)
333 332 if merge_response.executed:
334 333 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
335 334
336 335 Session().commit()
337 336
338 337 # In previous versions the merge response directly contained the merge
339 338 # commit id. It is now contained in the merge reference object. To be
340 339 # backwards compatible we have to extract it again.
341 340 merge_response = merge_response.asdict()
342 341 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
343 342
344 343 return merge_response
345 344
346 345
347 346 @jsonrpc_method()
348 347 def get_pull_request_comments(
349 348 request, apiuser, pullrequestid, repoid=Optional(None)):
350 349 """
351 350 Get all comments of pull request specified with the `pullrequestid`
352 351
353 352 :param apiuser: This is filled automatically from the |authtoken|.
354 353 :type apiuser: AuthUser
355 354 :param repoid: Optional repository name or repository ID.
356 355 :type repoid: str or int
357 356 :param pullrequestid: The pull request ID.
358 357 :type pullrequestid: int
359 358
360 359 Example output:
361 360
362 361 .. code-block:: bash
363 362
364 363 id : <id_given_in_input>
365 364 result : [
366 365 {
367 366 "comment_author": {
368 367 "active": true,
369 368 "full_name_or_username": "Tom Gore",
370 369 "username": "admin"
371 370 },
372 371 "comment_created_on": "2017-01-02T18:43:45.533",
373 372 "comment_f_path": null,
374 373 "comment_id": 25,
375 374 "comment_lineno": null,
376 375 "comment_status": {
377 376 "status": "under_review",
378 377 "status_lbl": "Under Review"
379 378 },
380 379 "comment_text": "Example text",
381 380 "comment_type": null,
381 "comment_last_version: 0,
382 382 "pull_request_version": null,
383 383 "comment_commit_id": None,
384 384 "comment_pull_request_id": <pull_request_id>
385 385 }
386 386 ],
387 387 error : null
388 388 """
389 389
390 390 pull_request = get_pull_request_or_error(pullrequestid)
391 391 if Optional.extract(repoid):
392 392 repo = get_repo_or_error(repoid)
393 393 else:
394 394 repo = pull_request.target_repo
395 395
396 396 if not PullRequestModel().check_user_read(
397 397 pull_request, apiuser, api=True):
398 398 raise JSONRPCError('repository `%s` or pull request `%s` '
399 399 'does not exist' % (repoid, pullrequestid))
400 400
401 401 (pull_request_latest,
402 402 pull_request_at_ver,
403 403 pull_request_display_obj,
404 404 at_version) = PullRequestModel().get_pr_version(
405 405 pull_request.pull_request_id, version=None)
406 406
407 407 versions = pull_request_display_obj.versions()
408 408 ver_map = {
409 409 ver.pull_request_version_id: cnt
410 410 for cnt, ver in enumerate(versions, 1)
411 411 }
412 412
413 413 # GENERAL COMMENTS with versions #
414 414 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
415 415 q = q.order_by(ChangesetComment.comment_id.asc())
416 416 general_comments = q.all()
417 417
418 418 # INLINE COMMENTS with versions #
419 419 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
420 420 q = q.order_by(ChangesetComment.comment_id.asc())
421 421 inline_comments = q.all()
422 422
423 423 data = []
424 424 for comment in inline_comments + general_comments:
425 425 full_data = comment.get_api_data()
426 426 pr_version_id = None
427 427 if comment.pull_request_version_id:
428 428 pr_version_id = 'v{}'.format(
429 429 ver_map[comment.pull_request_version_id])
430 430
431 431 # sanitize some entries
432 432
433 433 full_data['pull_request_version'] = pr_version_id
434 434 full_data['comment_author'] = {
435 435 'username': full_data['comment_author'].username,
436 436 'full_name_or_username': full_data['comment_author'].full_name_or_username,
437 437 'active': full_data['comment_author'].active,
438 438 }
439 439
440 440 if full_data['comment_status']:
441 441 full_data['comment_status'] = {
442 442 'status': full_data['comment_status'][0].status,
443 443 'status_lbl': full_data['comment_status'][0].status_lbl,
444 444 }
445 445 else:
446 446 full_data['comment_status'] = {}
447 447
448 448 data.append(full_data)
449 449 return data
450 450
451 451
452 452 @jsonrpc_method()
453 453 def comment_pull_request(
454 454 request, apiuser, pullrequestid, repoid=Optional(None),
455 455 message=Optional(None), commit_id=Optional(None), status=Optional(None),
456 456 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
457 457 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
458 458 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
459 459 """
460 460 Comment on the pull request specified with the `pullrequestid`,
461 461 in the |repo| specified by the `repoid`, and optionally change the
462 462 review status.
463 463
464 464 :param apiuser: This is filled automatically from the |authtoken|.
465 465 :type apiuser: AuthUser
466 466 :param repoid: Optional repository name or repository ID.
467 467 :type repoid: str or int
468 468 :param pullrequestid: The pull request ID.
469 469 :type pullrequestid: int
470 470 :param commit_id: Specify the commit_id for which to set a comment. If
471 471 given commit_id is different than latest in the PR status
472 472 change won't be performed.
473 473 :type commit_id: str
474 474 :param message: The text content of the comment.
475 475 :type message: str
476 476 :param status: (**Optional**) Set the approval status of the pull
477 477 request. One of: 'not_reviewed', 'approved', 'rejected',
478 478 'under_review'
479 479 :type status: str
480 480 :param comment_type: Comment type, one of: 'note', 'todo'
481 481 :type comment_type: Optional(str), default: 'note'
482 482 :param resolves_comment_id: id of comment which this one will resolve
483 483 :type resolves_comment_id: Optional(int)
484 484 :param extra_recipients: list of user ids or usernames to add
485 485 notifications for this comment. Acts like a CC for notification
486 486 :type extra_recipients: Optional(list)
487 487 :param userid: Comment on the pull request as this user
488 488 :type userid: Optional(str or int)
489 489 :param send_email: Define if this comment should also send email notification
490 490 :type send_email: Optional(bool)
491 491
492 492 Example output:
493 493
494 494 .. code-block:: bash
495 495
496 496 id : <id_given_in_input>
497 497 result : {
498 498 "pull_request_id": "<Integer>",
499 499 "comment_id": "<Integer>",
500 500 "status": {"given": <given_status>,
501 501 "was_changed": <bool status_was_actually_changed> },
502 502 },
503 503 error : null
504 504 """
505 505 pull_request = get_pull_request_or_error(pullrequestid)
506 506 if Optional.extract(repoid):
507 507 repo = get_repo_or_error(repoid)
508 508 else:
509 509 repo = pull_request.target_repo
510 510
511 511 auth_user = apiuser
512 512 if not isinstance(userid, Optional):
513 if (has_superadmin_permission(apiuser) or
514 HasRepoPermissionAnyApi('repository.admin')(
515 user=apiuser, repo_name=repo.repo_name)):
513 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
514 user=apiuser, repo_name=repo.repo_name)
515 if has_superadmin_permission(apiuser) or is_repo_admin:
516 516 apiuser = get_user_or_error(userid)
517 517 auth_user = apiuser.AuthUser()
518 518 else:
519 519 raise JSONRPCError('userid is not the same as your user')
520 520
521 521 if pull_request.is_closed():
522 522 raise JSONRPCError(
523 523 'pull request `%s` comment failed, pull request is closed' % (
524 524 pullrequestid,))
525 525
526 526 if not PullRequestModel().check_user_read(
527 527 pull_request, apiuser, api=True):
528 528 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
529 529 message = Optional.extract(message)
530 530 status = Optional.extract(status)
531 531 commit_id = Optional.extract(commit_id)
532 532 comment_type = Optional.extract(comment_type)
533 533 resolves_comment_id = Optional.extract(resolves_comment_id)
534 534 extra_recipients = Optional.extract(extra_recipients)
535 535 send_email = Optional.extract(send_email, binary=True)
536 536
537 537 if not message and not status:
538 538 raise JSONRPCError(
539 539 'Both message and status parameters are missing. '
540 540 'At least one is required.')
541 541
542 542 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
543 543 status is not None):
544 544 raise JSONRPCError('Unknown comment status: `%s`' % status)
545 545
546 546 if commit_id and commit_id not in pull_request.revisions:
547 547 raise JSONRPCError(
548 548 'Invalid commit_id `%s` for this pull request.' % commit_id)
549 549
550 550 allowed_to_change_status = PullRequestModel().check_user_change_status(
551 551 pull_request, apiuser)
552 552
553 553 # if commit_id is passed re-validated if user is allowed to change status
554 554 # based on latest commit_id from the PR
555 555 if commit_id:
556 556 commit_idx = pull_request.revisions.index(commit_id)
557 557 if commit_idx != 0:
558 558 allowed_to_change_status = False
559 559
560 560 if resolves_comment_id:
561 561 comment = ChangesetComment.get(resolves_comment_id)
562 562 if not comment:
563 563 raise JSONRPCError(
564 564 'Invalid resolves_comment_id `%s` for this pull request.'
565 565 % resolves_comment_id)
566 566 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
567 567 raise JSONRPCError(
568 568 'Comment `%s` is wrong type for setting status to resolved.'
569 569 % resolves_comment_id)
570 570
571 571 text = message
572 572 status_label = ChangesetStatus.get_status_lbl(status)
573 573 if status and allowed_to_change_status:
574 574 st_message = ('Status change %(transition_icon)s %(status)s'
575 575 % {'transition_icon': '>', 'status': status_label})
576 576 text = message or st_message
577 577
578 578 rc_config = SettingsModel().get_all_settings()
579 579 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
580 580
581 581 status_change = status and allowed_to_change_status
582 582 comment = CommentsModel().create(
583 583 text=text,
584 584 repo=pull_request.target_repo.repo_id,
585 585 user=apiuser.user_id,
586 586 pull_request=pull_request.pull_request_id,
587 587 f_path=None,
588 588 line_no=None,
589 589 status_change=(status_label if status_change else None),
590 590 status_change_type=(status if status_change else None),
591 591 closing_pr=False,
592 592 renderer=renderer,
593 593 comment_type=comment_type,
594 594 resolves_comment_id=resolves_comment_id,
595 595 auth_user=auth_user,
596 596 extra_recipients=extra_recipients,
597 597 send_email=send_email
598 598 )
599 599
600 600 if allowed_to_change_status and status:
601 601 old_calculated_status = pull_request.calculated_review_status()
602 602 ChangesetStatusModel().set_status(
603 603 pull_request.target_repo.repo_id,
604 604 status,
605 605 apiuser.user_id,
606 606 comment,
607 607 pull_request=pull_request.pull_request_id
608 608 )
609 609 Session().flush()
610 610
611 611 Session().commit()
612 612
613 613 PullRequestModel().trigger_pull_request_hook(
614 614 pull_request, apiuser, 'comment',
615 615 data={'comment': comment})
616 616
617 617 if allowed_to_change_status and status:
618 618 # we now calculate the status of pull request, and based on that
619 619 # calculation we set the commits status
620 620 calculated_status = pull_request.calculated_review_status()
621 621 if old_calculated_status != calculated_status:
622 622 PullRequestModel().trigger_pull_request_hook(
623 623 pull_request, apiuser, 'review_status_change',
624 624 data={'status': calculated_status})
625 625
626 626 data = {
627 627 'pull_request_id': pull_request.pull_request_id,
628 628 'comment_id': comment.comment_id if comment else None,
629 629 'status': {'given': status, 'was_changed': status_change},
630 630 }
631 631 return data
632 632
633 633
634 634 @jsonrpc_method()
635 635 def create_pull_request(
636 636 request, apiuser, source_repo, target_repo, source_ref, target_ref,
637 637 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
638 638 description_renderer=Optional(''), reviewers=Optional(None)):
639 639 """
640 640 Creates a new pull request.
641 641
642 642 Accepts refs in the following formats:
643 643
644 644 * branch:<branch_name>:<sha>
645 645 * branch:<branch_name>
646 646 * bookmark:<bookmark_name>:<sha> (Mercurial only)
647 647 * bookmark:<bookmark_name> (Mercurial only)
648 648
649 649 :param apiuser: This is filled automatically from the |authtoken|.
650 650 :type apiuser: AuthUser
651 651 :param source_repo: Set the source repository name.
652 652 :type source_repo: str
653 653 :param target_repo: Set the target repository name.
654 654 :type target_repo: str
655 655 :param source_ref: Set the source ref name.
656 656 :type source_ref: str
657 657 :param target_ref: Set the target ref name.
658 658 :type target_ref: str
659 659 :param owner: user_id or username
660 660 :type owner: Optional(str)
661 661 :param title: Optionally Set the pull request title, it's generated otherwise
662 662 :type title: str
663 663 :param description: Set the pull request description.
664 664 :type description: Optional(str)
665 665 :type description_renderer: Optional(str)
666 666 :param description_renderer: Set pull request renderer for the description.
667 667 It should be 'rst', 'markdown' or 'plain'. If not give default
668 668 system renderer will be used
669 669 :param reviewers: Set the new pull request reviewers list.
670 670 Reviewer defined by review rules will be added automatically to the
671 671 defined list.
672 672 :type reviewers: Optional(list)
673 673 Accepts username strings or objects of the format:
674 674
675 675 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
676 676 """
677 677
678 678 source_db_repo = get_repo_or_error(source_repo)
679 679 target_db_repo = get_repo_or_error(target_repo)
680 680 if not has_superadmin_permission(apiuser):
681 681 _perms = ('repository.admin', 'repository.write', 'repository.read',)
682 682 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
683 683
684 684 owner = validate_set_owner_permissions(apiuser, owner)
685 685
686 686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
687 687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
688 688
689 689 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
690 690 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
691 691
692 692 reviewer_objects = Optional.extract(reviewers) or []
693 693
694 694 # serialize and validate passed in given reviewers
695 695 if reviewer_objects:
696 696 schema = ReviewerListSchema()
697 697 try:
698 698 reviewer_objects = schema.deserialize(reviewer_objects)
699 699 except Invalid as err:
700 700 raise JSONRPCValidationError(colander_exc=err)
701 701
702 702 # validate users
703 703 for reviewer_object in reviewer_objects:
704 704 user = get_user_or_error(reviewer_object['username'])
705 705 reviewer_object['user_id'] = user.user_id
706 706
707 707 get_default_reviewers_data, validate_default_reviewers = \
708 708 PullRequestModel().get_reviewer_functions()
709 709
710 710 # recalculate reviewers logic, to make sure we can validate this
711 711 default_reviewers_data = get_default_reviewers_data(
712 712 owner, source_db_repo,
713 713 source_commit, target_db_repo, target_commit)
714 714
715 715 # now MERGE our given with the calculated
716 716 reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects
717 717
718 718 try:
719 719 reviewers = validate_default_reviewers(
720 720 reviewer_objects, default_reviewers_data)
721 721 except ValueError as e:
722 722 raise JSONRPCError('Reviewers Validation: {}'.format(e))
723 723
724 724 title = Optional.extract(title)
725 725 if not title:
726 726 title_source_ref = source_ref.split(':', 2)[1]
727 727 title = PullRequestModel().generate_pullrequest_title(
728 728 source=source_repo,
729 729 source_ref=title_source_ref,
730 730 target=target_repo
731 731 )
732 732
733 733 diff_info = default_reviewers_data['diff_info']
734 734 common_ancestor_id = diff_info['ancestor']
735 735 commits = diff_info['commits']
736 736
737 737 if not common_ancestor_id:
738 738 raise JSONRPCError('no common ancestor found')
739 739
740 740 if not commits:
741 741 raise JSONRPCError('no commits found')
742 742
743 743 # NOTE(marcink): reversed is consistent with how we open it in the WEB interface
744 744 revisions = [commit.raw_id for commit in reversed(commits)]
745 745
746 746 # recalculate target ref based on ancestor
747 747 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
748 748 full_target_ref = ':'.join((target_ref_type, target_ref_name, common_ancestor_id))
749 749
750 750 # fetch renderer, if set fallback to plain in case of PR
751 751 rc_config = SettingsModel().get_all_settings()
752 752 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
753 753 description = Optional.extract(description)
754 754 description_renderer = Optional.extract(description_renderer) or default_system_renderer
755 755
756 756 pull_request = PullRequestModel().create(
757 757 created_by=owner.user_id,
758 758 source_repo=source_repo,
759 759 source_ref=full_source_ref,
760 760 target_repo=target_repo,
761 761 target_ref=full_target_ref,
762 762 common_ancestor_id=common_ancestor_id,
763 763 revisions=revisions,
764 764 reviewers=reviewers,
765 765 title=title,
766 766 description=description,
767 767 description_renderer=description_renderer,
768 768 reviewer_data=default_reviewers_data,
769 769 auth_user=apiuser
770 770 )
771 771
772 772 Session().commit()
773 773 data = {
774 774 'msg': 'Created new pull request `{}`'.format(title),
775 775 'pull_request_id': pull_request.pull_request_id,
776 776 }
777 777 return data
778 778
779 779
780 780 @jsonrpc_method()
781 781 def update_pull_request(
782 782 request, apiuser, pullrequestid, repoid=Optional(None),
783 783 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
784 784 reviewers=Optional(None), update_commits=Optional(None)):
785 785 """
786 786 Updates a pull request.
787 787
788 788 :param apiuser: This is filled automatically from the |authtoken|.
789 789 :type apiuser: AuthUser
790 790 :param repoid: Optional repository name or repository ID.
791 791 :type repoid: str or int
792 792 :param pullrequestid: The pull request ID.
793 793 :type pullrequestid: int
794 794 :param title: Set the pull request title.
795 795 :type title: str
796 796 :param description: Update pull request description.
797 797 :type description: Optional(str)
798 798 :type description_renderer: Optional(str)
799 799 :param description_renderer: Update pull request renderer for the description.
800 800 It should be 'rst', 'markdown' or 'plain'
801 801 :param reviewers: Update pull request reviewers list with new value.
802 802 :type reviewers: Optional(list)
803 803 Accepts username strings or objects of the format:
804 804
805 805 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
806 806
807 807 :param update_commits: Trigger update of commits for this pull request
808 808 :type: update_commits: Optional(bool)
809 809
810 810 Example output:
811 811
812 812 .. code-block:: bash
813 813
814 814 id : <id_given_in_input>
815 815 result : {
816 816 "msg": "Updated pull request `63`",
817 817 "pull_request": <pull_request_object>,
818 818 "updated_reviewers": {
819 819 "added": [
820 820 "username"
821 821 ],
822 822 "removed": []
823 823 },
824 824 "updated_commits": {
825 825 "added": [
826 826 "<sha1_hash>"
827 827 ],
828 828 "common": [
829 829 "<sha1_hash>",
830 830 "<sha1_hash>",
831 831 ],
832 832 "removed": []
833 833 }
834 834 }
835 835 error : null
836 836 """
837 837
838 838 pull_request = get_pull_request_or_error(pullrequestid)
839 839 if Optional.extract(repoid):
840 840 repo = get_repo_or_error(repoid)
841 841 else:
842 842 repo = pull_request.target_repo
843 843
844 844 if not PullRequestModel().check_user_update(
845 845 pull_request, apiuser, api=True):
846 846 raise JSONRPCError(
847 847 'pull request `%s` update failed, no permission to update.' % (
848 848 pullrequestid,))
849 849 if pull_request.is_closed():
850 850 raise JSONRPCError(
851 851 'pull request `%s` update failed, pull request is closed' % (
852 852 pullrequestid,))
853 853
854 854 reviewer_objects = Optional.extract(reviewers) or []
855 855
856 856 if reviewer_objects:
857 857 schema = ReviewerListSchema()
858 858 try:
859 859 reviewer_objects = schema.deserialize(reviewer_objects)
860 860 except Invalid as err:
861 861 raise JSONRPCValidationError(colander_exc=err)
862 862
863 863 # validate users
864 864 for reviewer_object in reviewer_objects:
865 865 user = get_user_or_error(reviewer_object['username'])
866 866 reviewer_object['user_id'] = user.user_id
867 867
868 868 get_default_reviewers_data, get_validated_reviewers = \
869 869 PullRequestModel().get_reviewer_functions()
870 870
871 871 # re-use stored rules
872 872 reviewer_rules = pull_request.reviewer_data
873 873 try:
874 874 reviewers = get_validated_reviewers(
875 875 reviewer_objects, reviewer_rules)
876 876 except ValueError as e:
877 877 raise JSONRPCError('Reviewers Validation: {}'.format(e))
878 878 else:
879 879 reviewers = []
880 880
881 881 title = Optional.extract(title)
882 882 description = Optional.extract(description)
883 883 description_renderer = Optional.extract(description_renderer)
884 884
885 885 if title or description:
886 886 PullRequestModel().edit(
887 887 pull_request,
888 888 title or pull_request.title,
889 889 description or pull_request.description,
890 890 description_renderer or pull_request.description_renderer,
891 891 apiuser)
892 892 Session().commit()
893 893
894 894 commit_changes = {"added": [], "common": [], "removed": []}
895 895 if str2bool(Optional.extract(update_commits)):
896 896
897 897 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
898 898 raise JSONRPCError(
899 899 'Operation forbidden because pull request is in state {}, '
900 900 'only state {} is allowed.'.format(
901 901 pull_request.pull_request_state, PullRequest.STATE_CREATED))
902 902
903 903 with pull_request.set_state(PullRequest.STATE_UPDATING):
904 904 if PullRequestModel().has_valid_update_type(pull_request):
905 905 db_user = apiuser.get_instance()
906 906 update_response = PullRequestModel().update_commits(
907 907 pull_request, db_user)
908 908 commit_changes = update_response.changes or commit_changes
909 909 Session().commit()
910 910
911 911 reviewers_changes = {"added": [], "removed": []}
912 912 if reviewers:
913 913 old_calculated_status = pull_request.calculated_review_status()
914 914 added_reviewers, removed_reviewers = \
915 915 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
916 916
917 917 reviewers_changes['added'] = sorted(
918 918 [get_user_or_error(n).username for n in added_reviewers])
919 919 reviewers_changes['removed'] = sorted(
920 920 [get_user_or_error(n).username for n in removed_reviewers])
921 921 Session().commit()
922 922
923 923 # trigger status changed if change in reviewers changes the status
924 924 calculated_status = pull_request.calculated_review_status()
925 925 if old_calculated_status != calculated_status:
926 926 PullRequestModel().trigger_pull_request_hook(
927 927 pull_request, apiuser, 'review_status_change',
928 928 data={'status': calculated_status})
929 929
930 930 data = {
931 931 'msg': 'Updated pull request `{}`'.format(
932 932 pull_request.pull_request_id),
933 933 'pull_request': pull_request.get_api_data(),
934 934 'updated_commits': commit_changes,
935 935 'updated_reviewers': reviewers_changes
936 936 }
937 937
938 938 return data
939 939
940 940
941 941 @jsonrpc_method()
942 942 def close_pull_request(
943 943 request, apiuser, pullrequestid, repoid=Optional(None),
944 944 userid=Optional(OAttr('apiuser')), message=Optional('')):
945 945 """
946 946 Close the pull request specified by `pullrequestid`.
947 947
948 948 :param apiuser: This is filled automatically from the |authtoken|.
949 949 :type apiuser: AuthUser
950 950 :param repoid: Repository name or repository ID to which the pull
951 951 request belongs.
952 952 :type repoid: str or int
953 953 :param pullrequestid: ID of the pull request to be closed.
954 954 :type pullrequestid: int
955 955 :param userid: Close the pull request as this user.
956 956 :type userid: Optional(str or int)
957 957 :param message: Optional message to close the Pull Request with. If not
958 958 specified it will be generated automatically.
959 959 :type message: Optional(str)
960 960
961 961 Example output:
962 962
963 963 .. code-block:: bash
964 964
965 965 "id": <id_given_in_input>,
966 966 "result": {
967 967 "pull_request_id": "<int>",
968 968 "close_status": "<str:status_lbl>,
969 969 "closed": "<bool>"
970 970 },
971 971 "error": null
972 972
973 973 """
974 974 _ = request.translate
975 975
976 976 pull_request = get_pull_request_or_error(pullrequestid)
977 977 if Optional.extract(repoid):
978 978 repo = get_repo_or_error(repoid)
979 979 else:
980 980 repo = pull_request.target_repo
981 981
982 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
983 user=apiuser, repo_name=repo.repo_name)
982 984 if not isinstance(userid, Optional):
983 if (has_superadmin_permission(apiuser) or
984 HasRepoPermissionAnyApi('repository.admin')(
985 user=apiuser, repo_name=repo.repo_name)):
985 if has_superadmin_permission(apiuser) or is_repo_admin:
986 986 apiuser = get_user_or_error(userid)
987 987 else:
988 988 raise JSONRPCError('userid is not the same as your user')
989 989
990 990 if pull_request.is_closed():
991 991 raise JSONRPCError(
992 992 'pull request `%s` is already closed' % (pullrequestid,))
993 993
994 994 # only owner or admin or person with write permissions
995 995 allowed_to_close = PullRequestModel().check_user_update(
996 996 pull_request, apiuser, api=True)
997 997
998 998 if not allowed_to_close:
999 999 raise JSONRPCError(
1000 1000 'pull request `%s` close failed, no permission to close.' % (
1001 1001 pullrequestid,))
1002 1002
1003 1003 # message we're using to close the PR, else it's automatically generated
1004 1004 message = Optional.extract(message)
1005 1005
1006 1006 # finally close the PR, with proper message comment
1007 1007 comment, status = PullRequestModel().close_pull_request_with_comment(
1008 1008 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1009 1009 status_lbl = ChangesetStatus.get_status_lbl(status)
1010 1010
1011 1011 Session().commit()
1012 1012
1013 1013 data = {
1014 1014 'pull_request_id': pull_request.pull_request_id,
1015 1015 'close_status': status_lbl,
1016 1016 'closed': True,
1017 1017 }
1018 1018 return data
@@ -1,2349 +1,2506 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import time
23 23
24 24 import rhodecode
25 25 from rhodecode.api import (
26 26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 27 from rhodecode.api.utils import (
28 28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 31 validate_set_owner_permissions)
32 32 from rhodecode.lib import audit_logger, rc_cache
33 33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import (
35 HasPermissionAnyApi, HasUserGroupPermissionAnyApi,
36 HasRepoPermissionAnyApi)
35 37 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int, safe_unicode
38 from rhodecode.lib.utils2 import (
39 str2bool, time_to_datetime, safe_str, safe_int, safe_unicode)
37 40 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 from rhodecode.lib.exceptions import (
42 StatusChangeOnClosedPullRequestError, CommentVersionMismatch)
39 43 from rhodecode.lib.vcs import RepositoryError
40 44 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
41 45 from rhodecode.model.changeset_status import ChangesetStatusModel
42 46 from rhodecode.model.comment import CommentsModel
43 47 from rhodecode.model.db import (
44 48 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 49 ChangesetComment)
46 50 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.pull_request import PullRequestModel
47 52 from rhodecode.model.repo import RepoModel
48 53 from rhodecode.model.scm import ScmModel, RepoList
49 54 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
50 55 from rhodecode.model import validation_schema
51 56 from rhodecode.model.validation_schema.schemas import repo_schema
52 57
53 58 log = logging.getLogger(__name__)
54 59
55 60
56 61 @jsonrpc_method()
57 62 def get_repo(request, apiuser, repoid, cache=Optional(True)):
58 63 """
59 64 Gets an existing repository by its name or repository_id.
60 65
61 66 The members section so the output returns users groups or users
62 67 associated with that repository.
63 68
64 69 This command can only be run using an |authtoken| with admin rights,
65 70 or users with at least read rights to the |repo|.
66 71
67 72 :param apiuser: This is filled automatically from the |authtoken|.
68 73 :type apiuser: AuthUser
69 74 :param repoid: The repository name or repository id.
70 75 :type repoid: str or int
71 76 :param cache: use the cached value for last changeset
72 77 :type: cache: Optional(bool)
73 78
74 79 Example output:
75 80
76 81 .. code-block:: bash
77 82
78 83 {
79 84 "error": null,
80 85 "id": <repo_id>,
81 86 "result": {
82 87 "clone_uri": null,
83 88 "created_on": "timestamp",
84 89 "description": "repo description",
85 90 "enable_downloads": false,
86 91 "enable_locking": false,
87 92 "enable_statistics": false,
88 93 "followers": [
89 94 {
90 95 "active": true,
91 96 "admin": false,
92 97 "api_key": "****************************************",
93 98 "api_keys": [
94 99 "****************************************"
95 100 ],
96 101 "email": "user@example.com",
97 102 "emails": [
98 103 "user@example.com"
99 104 ],
100 105 "extern_name": "rhodecode",
101 106 "extern_type": "rhodecode",
102 107 "firstname": "username",
103 108 "ip_addresses": [],
104 109 "language": null,
105 110 "last_login": "2015-09-16T17:16:35.854",
106 111 "lastname": "surname",
107 112 "user_id": <user_id>,
108 113 "username": "name"
109 114 }
110 115 ],
111 116 "fork_of": "parent-repo",
112 117 "landing_rev": [
113 118 "rev",
114 119 "tip"
115 120 ],
116 121 "last_changeset": {
117 122 "author": "User <user@example.com>",
118 123 "branch": "default",
119 124 "date": "timestamp",
120 125 "message": "last commit message",
121 126 "parents": [
122 127 {
123 128 "raw_id": "commit-id"
124 129 }
125 130 ],
126 131 "raw_id": "commit-id",
127 132 "revision": <revision number>,
128 133 "short_id": "short id"
129 134 },
130 135 "lock_reason": null,
131 136 "locked_by": null,
132 137 "locked_date": null,
133 138 "owner": "owner-name",
134 139 "permissions": [
135 140 {
136 141 "name": "super-admin-name",
137 142 "origin": "super-admin",
138 143 "permission": "repository.admin",
139 144 "type": "user"
140 145 },
141 146 {
142 147 "name": "owner-name",
143 148 "origin": "owner",
144 149 "permission": "repository.admin",
145 150 "type": "user"
146 151 },
147 152 {
148 153 "name": "user-group-name",
149 154 "origin": "permission",
150 155 "permission": "repository.write",
151 156 "type": "user_group"
152 157 }
153 158 ],
154 159 "private": true,
155 160 "repo_id": 676,
156 161 "repo_name": "user-group/repo-name",
157 162 "repo_type": "hg"
158 163 }
159 164 }
160 165 """
161 166
162 167 repo = get_repo_or_error(repoid)
163 168 cache = Optional.extract(cache)
164 169
165 170 include_secrets = False
166 171 if has_superadmin_permission(apiuser):
167 172 include_secrets = True
168 173 else:
169 174 # check if we have at least read permission for this repo !
170 175 _perms = (
171 176 'repository.admin', 'repository.write', 'repository.read',)
172 177 validate_repo_permissions(apiuser, repoid, repo, _perms)
173 178
174 179 permissions = []
175 180 for _user in repo.permissions():
176 181 user_data = {
177 182 'name': _user.username,
178 183 'permission': _user.permission,
179 184 'origin': get_origin(_user),
180 185 'type': "user",
181 186 }
182 187 permissions.append(user_data)
183 188
184 189 for _user_group in repo.permission_user_groups():
185 190 user_group_data = {
186 191 'name': _user_group.users_group_name,
187 192 'permission': _user_group.permission,
188 193 'origin': get_origin(_user_group),
189 194 'type': "user_group",
190 195 }
191 196 permissions.append(user_group_data)
192 197
193 198 following_users = [
194 199 user.user.get_api_data(include_secrets=include_secrets)
195 200 for user in repo.followers]
196 201
197 202 if not cache:
198 203 repo.update_commit_cache()
199 204 data = repo.get_api_data(include_secrets=include_secrets)
200 205 data['permissions'] = permissions
201 206 data['followers'] = following_users
202 207 return data
203 208
204 209
205 210 @jsonrpc_method()
206 211 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
207 212 """
208 213 Lists all existing repositories.
209 214
210 215 This command can only be run using an |authtoken| with admin rights,
211 216 or users with at least read rights to |repos|.
212 217
213 218 :param apiuser: This is filled automatically from the |authtoken|.
214 219 :type apiuser: AuthUser
215 220 :param root: specify root repository group to fetch repositories.
216 221 filters the returned repositories to be members of given root group.
217 222 :type root: Optional(None)
218 223 :param traverse: traverse given root into subrepositories. With this flag
219 224 set to False, it will only return top-level repositories from `root`.
220 225 if root is empty it will return just top-level repositories.
221 226 :type traverse: Optional(True)
222 227
223 228
224 229 Example output:
225 230
226 231 .. code-block:: bash
227 232
228 233 id : <id_given_in_input>
229 234 result: [
230 235 {
231 236 "repo_id" : "<repo_id>",
232 237 "repo_name" : "<reponame>"
233 238 "repo_type" : "<repo_type>",
234 239 "clone_uri" : "<clone_uri>",
235 240 "private": : "<bool>",
236 241 "created_on" : "<datetimecreated>",
237 242 "description" : "<description>",
238 243 "landing_rev": "<landing_rev>",
239 244 "owner": "<repo_owner>",
240 245 "fork_of": "<name_of_fork_parent>",
241 246 "enable_downloads": "<bool>",
242 247 "enable_locking": "<bool>",
243 248 "enable_statistics": "<bool>",
244 249 },
245 250 ...
246 251 ]
247 252 error: null
248 253 """
249 254
250 255 include_secrets = has_superadmin_permission(apiuser)
251 256 _perms = ('repository.read', 'repository.write', 'repository.admin',)
252 257 extras = {'user': apiuser}
253 258
254 259 root = Optional.extract(root)
255 260 traverse = Optional.extract(traverse, binary=True)
256 261
257 262 if root:
258 263 # verify parent existance, if it's empty return an error
259 264 parent = RepoGroup.get_by_group_name(root)
260 265 if not parent:
261 266 raise JSONRPCError(
262 267 'Root repository group `{}` does not exist'.format(root))
263 268
264 269 if traverse:
265 270 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
266 271 else:
267 272 repos = RepoModel().get_repos_for_root(root=parent)
268 273 else:
269 274 if traverse:
270 275 repos = RepoModel().get_all()
271 276 else:
272 277 # return just top-level
273 278 repos = RepoModel().get_repos_for_root(root=None)
274 279
275 280 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
276 281 return [repo.get_api_data(include_secrets=include_secrets)
277 282 for repo in repo_list]
278 283
279 284
280 285 @jsonrpc_method()
281 286 def get_repo_changeset(request, apiuser, repoid, revision,
282 287 details=Optional('basic')):
283 288 """
284 289 Returns information about a changeset.
285 290
286 291 Additionally parameters define the amount of details returned by
287 292 this function.
288 293
289 294 This command can only be run using an |authtoken| with admin rights,
290 295 or users with at least read rights to the |repo|.
291 296
292 297 :param apiuser: This is filled automatically from the |authtoken|.
293 298 :type apiuser: AuthUser
294 299 :param repoid: The repository name or repository id
295 300 :type repoid: str or int
296 301 :param revision: revision for which listing should be done
297 302 :type revision: str
298 303 :param details: details can be 'basic|extended|full' full gives diff
299 304 info details like the diff itself, and number of changed files etc.
300 305 :type details: Optional(str)
301 306
302 307 """
303 308 repo = get_repo_or_error(repoid)
304 309 if not has_superadmin_permission(apiuser):
305 310 _perms = (
306 311 'repository.admin', 'repository.write', 'repository.read',)
307 312 validate_repo_permissions(apiuser, repoid, repo, _perms)
308 313
309 314 changes_details = Optional.extract(details)
310 315 _changes_details_types = ['basic', 'extended', 'full']
311 316 if changes_details not in _changes_details_types:
312 317 raise JSONRPCError(
313 318 'ret_type must be one of %s' % (
314 319 ','.join(_changes_details_types)))
315 320
316 321 pre_load = ['author', 'branch', 'date', 'message', 'parents',
317 322 'status', '_commit', '_file_paths']
318 323
319 324 try:
320 325 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
321 326 except TypeError as e:
322 327 raise JSONRPCError(safe_str(e))
323 328 _cs_json = cs.__json__()
324 329 _cs_json['diff'] = build_commit_data(cs, changes_details)
325 330 if changes_details == 'full':
326 331 _cs_json['refs'] = cs._get_refs()
327 332 return _cs_json
328 333
329 334
330 335 @jsonrpc_method()
331 336 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
332 337 details=Optional('basic')):
333 338 """
334 339 Returns a set of commits limited by the number starting
335 340 from the `start_rev` option.
336 341
337 342 Additional parameters define the amount of details returned by this
338 343 function.
339 344
340 345 This command can only be run using an |authtoken| with admin rights,
341 346 or users with at least read rights to |repos|.
342 347
343 348 :param apiuser: This is filled automatically from the |authtoken|.
344 349 :type apiuser: AuthUser
345 350 :param repoid: The repository name or repository ID.
346 351 :type repoid: str or int
347 352 :param start_rev: The starting revision from where to get changesets.
348 353 :type start_rev: str
349 354 :param limit: Limit the number of commits to this amount
350 355 :type limit: str or int
351 356 :param details: Set the level of detail returned. Valid option are:
352 357 ``basic``, ``extended`` and ``full``.
353 358 :type details: Optional(str)
354 359
355 360 .. note::
356 361
357 362 Setting the parameter `details` to the value ``full`` is extensive
358 363 and returns details like the diff itself, and the number
359 364 of changed files.
360 365
361 366 """
362 367 repo = get_repo_or_error(repoid)
363 368 if not has_superadmin_permission(apiuser):
364 369 _perms = (
365 370 'repository.admin', 'repository.write', 'repository.read',)
366 371 validate_repo_permissions(apiuser, repoid, repo, _perms)
367 372
368 373 changes_details = Optional.extract(details)
369 374 _changes_details_types = ['basic', 'extended', 'full']
370 375 if changes_details not in _changes_details_types:
371 376 raise JSONRPCError(
372 377 'ret_type must be one of %s' % (
373 378 ','.join(_changes_details_types)))
374 379
375 380 limit = int(limit)
376 381 pre_load = ['author', 'branch', 'date', 'message', 'parents',
377 382 'status', '_commit', '_file_paths']
378 383
379 384 vcs_repo = repo.scm_instance()
380 385 # SVN needs a special case to distinguish its index and commit id
381 386 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
382 387 start_rev = vcs_repo.commit_ids[0]
383 388
384 389 try:
385 390 commits = vcs_repo.get_commits(
386 391 start_id=start_rev, pre_load=pre_load, translate_tags=False)
387 392 except TypeError as e:
388 393 raise JSONRPCError(safe_str(e))
389 394 except Exception:
390 395 log.exception('Fetching of commits failed')
391 396 raise JSONRPCError('Error occurred during commit fetching')
392 397
393 398 ret = []
394 399 for cnt, commit in enumerate(commits):
395 400 if cnt >= limit != -1:
396 401 break
397 402 _cs_json = commit.__json__()
398 403 _cs_json['diff'] = build_commit_data(commit, changes_details)
399 404 if changes_details == 'full':
400 405 _cs_json['refs'] = {
401 406 'branches': [commit.branch],
402 407 'bookmarks': getattr(commit, 'bookmarks', []),
403 408 'tags': commit.tags
404 409 }
405 410 ret.append(_cs_json)
406 411 return ret
407 412
408 413
409 414 @jsonrpc_method()
410 415 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
411 416 ret_type=Optional('all'), details=Optional('basic'),
412 417 max_file_bytes=Optional(None)):
413 418 """
414 419 Returns a list of nodes and children in a flat list for a given
415 420 path at given revision.
416 421
417 422 It's possible to specify ret_type to show only `files` or `dirs`.
418 423
419 424 This command can only be run using an |authtoken| with admin rights,
420 425 or users with at least read rights to |repos|.
421 426
422 427 :param apiuser: This is filled automatically from the |authtoken|.
423 428 :type apiuser: AuthUser
424 429 :param repoid: The repository name or repository ID.
425 430 :type repoid: str or int
426 431 :param revision: The revision for which listing should be done.
427 432 :type revision: str
428 433 :param root_path: The path from which to start displaying.
429 434 :type root_path: str
430 435 :param ret_type: Set the return type. Valid options are
431 436 ``all`` (default), ``files`` and ``dirs``.
432 437 :type ret_type: Optional(str)
433 438 :param details: Returns extended information about nodes, such as
434 439 md5, binary, and or content.
435 440 The valid options are ``basic`` and ``full``.
436 441 :type details: Optional(str)
437 442 :param max_file_bytes: Only return file content under this file size bytes
438 443 :type details: Optional(int)
439 444
440 445 Example output:
441 446
442 447 .. code-block:: bash
443 448
444 449 id : <id_given_in_input>
445 450 result: [
446 451 {
447 452 "binary": false,
448 453 "content": "File line",
449 454 "extension": "md",
450 455 "lines": 2,
451 456 "md5": "059fa5d29b19c0657e384749480f6422",
452 457 "mimetype": "text/x-minidsrc",
453 458 "name": "file.md",
454 459 "size": 580,
455 460 "type": "file"
456 461 },
457 462 ...
458 463 ]
459 464 error: null
460 465 """
461 466
462 467 repo = get_repo_or_error(repoid)
463 468 if not has_superadmin_permission(apiuser):
464 469 _perms = ('repository.admin', 'repository.write', 'repository.read',)
465 470 validate_repo_permissions(apiuser, repoid, repo, _perms)
466 471
467 472 ret_type = Optional.extract(ret_type)
468 473 details = Optional.extract(details)
469 474 _extended_types = ['basic', 'full']
470 475 if details not in _extended_types:
471 476 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
472 477 extended_info = False
473 478 content = False
474 479 if details == 'basic':
475 480 extended_info = True
476 481
477 482 if details == 'full':
478 483 extended_info = content = True
479 484
480 485 _map = {}
481 486 try:
482 487 # check if repo is not empty by any chance, skip quicker if it is.
483 488 _scm = repo.scm_instance()
484 489 if _scm.is_empty():
485 490 return []
486 491
487 492 _d, _f = ScmModel().get_nodes(
488 493 repo, revision, root_path, flat=False,
489 494 extended_info=extended_info, content=content,
490 495 max_file_bytes=max_file_bytes)
491 496 _map = {
492 497 'all': _d + _f,
493 498 'files': _f,
494 499 'dirs': _d,
495 500 }
496 501 return _map[ret_type]
497 502 except KeyError:
498 503 raise JSONRPCError(
499 504 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
500 505 except Exception:
501 506 log.exception("Exception occurred while trying to get repo nodes")
502 507 raise JSONRPCError(
503 508 'failed to get repo: `%s` nodes' % repo.repo_name
504 509 )
505 510
506 511
507 512 @jsonrpc_method()
508 513 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
509 514 max_file_bytes=Optional(None), details=Optional('basic'),
510 515 cache=Optional(True)):
511 516 """
512 517 Returns a single file from repository at given revision.
513 518
514 519 This command can only be run using an |authtoken| with admin rights,
515 520 or users with at least read rights to |repos|.
516 521
517 522 :param apiuser: This is filled automatically from the |authtoken|.
518 523 :type apiuser: AuthUser
519 524 :param repoid: The repository name or repository ID.
520 525 :type repoid: str or int
521 526 :param commit_id: The revision for which listing should be done.
522 527 :type commit_id: str
523 528 :param file_path: The path from which to start displaying.
524 529 :type file_path: str
525 530 :param details: Returns different set of information about nodes.
526 531 The valid options are ``minimal`` ``basic`` and ``full``.
527 532 :type details: Optional(str)
528 533 :param max_file_bytes: Only return file content under this file size bytes
529 534 :type max_file_bytes: Optional(int)
530 535 :param cache: Use internal caches for fetching files. If disabled fetching
531 536 files is slower but more memory efficient
532 537 :type cache: Optional(bool)
533 538
534 539 Example output:
535 540
536 541 .. code-block:: bash
537 542
538 543 id : <id_given_in_input>
539 544 result: {
540 545 "binary": false,
541 546 "extension": "py",
542 547 "lines": 35,
543 548 "content": "....",
544 549 "md5": "76318336366b0f17ee249e11b0c99c41",
545 550 "mimetype": "text/x-python",
546 551 "name": "python.py",
547 552 "size": 817,
548 553 "type": "file",
549 554 }
550 555 error: null
551 556 """
552 557
553 558 repo = get_repo_or_error(repoid)
554 559 if not has_superadmin_permission(apiuser):
555 560 _perms = ('repository.admin', 'repository.write', 'repository.read',)
556 561 validate_repo_permissions(apiuser, repoid, repo, _perms)
557 562
558 563 cache = Optional.extract(cache, binary=True)
559 564 details = Optional.extract(details)
560 565 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
561 566 if details not in _extended_types:
562 567 raise JSONRPCError(
563 568 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
564 569 extended_info = False
565 570 content = False
566 571
567 572 if details == 'minimal':
568 573 extended_info = False
569 574
570 575 elif details == 'basic':
571 576 extended_info = True
572 577
573 578 elif details == 'full':
574 579 extended_info = content = True
575 580
576 581 file_path = safe_unicode(file_path)
577 582 try:
578 583 # check if repo is not empty by any chance, skip quicker if it is.
579 584 _scm = repo.scm_instance()
580 585 if _scm.is_empty():
581 586 return None
582 587
583 588 node = ScmModel().get_node(
584 589 repo, commit_id, file_path, extended_info=extended_info,
585 590 content=content, max_file_bytes=max_file_bytes, cache=cache)
586 591 except NodeDoesNotExistError:
587 592 raise JSONRPCError(u'There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
588 593 repo.repo_name, file_path, commit_id))
589 594 except Exception:
590 595 log.exception(u"Exception occurred while trying to get repo %s file",
591 596 repo.repo_name)
592 597 raise JSONRPCError(u'failed to get repo: `{}` file at path {}'.format(
593 598 repo.repo_name, file_path))
594 599
595 600 return node
596 601
597 602
598 603 @jsonrpc_method()
599 604 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
600 605 """
601 606 Returns a list of tree nodes for path at given revision. This api is built
602 607 strictly for usage in full text search building, and shouldn't be consumed
603 608
604 609 This command can only be run using an |authtoken| with admin rights,
605 610 or users with at least read rights to |repos|.
606 611
607 612 """
608 613
609 614 repo = get_repo_or_error(repoid)
610 615 if not has_superadmin_permission(apiuser):
611 616 _perms = ('repository.admin', 'repository.write', 'repository.read',)
612 617 validate_repo_permissions(apiuser, repoid, repo, _perms)
613 618
614 619 repo_id = repo.repo_id
615 620 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
616 621 cache_on = cache_seconds > 0
617 622
618 623 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
619 624 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
620 625
621 626 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
622 627 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
623 628
624 629 try:
625 630 # check if repo is not empty by any chance, skip quicker if it is.
626 631 _scm = repo.scm_instance()
627 632 if _scm.is_empty():
628 633 return []
629 634 except RepositoryError:
630 635 log.exception("Exception occurred while trying to get repo nodes")
631 636 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
632 637
633 638 try:
634 639 # we need to resolve commit_id to a FULL sha for cache to work correctly.
635 640 # sending 'master' is a pointer that needs to be translated to current commit.
636 641 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
637 642 log.debug(
638 643 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
639 644 'with caching: %s[TTL: %ss]' % (
640 645 repo_id, commit_id, cache_on, cache_seconds or 0))
641 646
642 647 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
643 648 return tree_files
644 649
645 650 except Exception:
646 651 log.exception("Exception occurred while trying to get repo nodes")
647 652 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
648 653
649 654
650 655 @jsonrpc_method()
651 656 def get_repo_refs(request, apiuser, repoid):
652 657 """
653 658 Returns a dictionary of current references. It returns
654 659 bookmarks, branches, closed_branches, and tags for given repository
655 660
656 661 It's possible to specify ret_type to show only `files` or `dirs`.
657 662
658 663 This command can only be run using an |authtoken| with admin rights,
659 664 or users with at least read rights to |repos|.
660 665
661 666 :param apiuser: This is filled automatically from the |authtoken|.
662 667 :type apiuser: AuthUser
663 668 :param repoid: The repository name or repository ID.
664 669 :type repoid: str or int
665 670
666 671 Example output:
667 672
668 673 .. code-block:: bash
669 674
670 675 id : <id_given_in_input>
671 676 "result": {
672 677 "bookmarks": {
673 678 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
674 679 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
675 680 },
676 681 "branches": {
677 682 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
678 683 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
679 684 },
680 685 "branches_closed": {},
681 686 "tags": {
682 687 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
683 688 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
684 689 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
685 690 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
686 691 }
687 692 }
688 693 error: null
689 694 """
690 695
691 696 repo = get_repo_or_error(repoid)
692 697 if not has_superadmin_permission(apiuser):
693 698 _perms = ('repository.admin', 'repository.write', 'repository.read',)
694 699 validate_repo_permissions(apiuser, repoid, repo, _perms)
695 700
696 701 try:
697 702 # check if repo is not empty by any chance, skip quicker if it is.
698 703 vcs_instance = repo.scm_instance()
699 704 refs = vcs_instance.refs()
700 705 return refs
701 706 except Exception:
702 707 log.exception("Exception occurred while trying to get repo refs")
703 708 raise JSONRPCError(
704 709 'failed to get repo: `%s` references' % repo.repo_name
705 710 )
706 711
707 712
708 713 @jsonrpc_method()
709 714 def create_repo(
710 715 request, apiuser, repo_name, repo_type,
711 716 owner=Optional(OAttr('apiuser')),
712 717 description=Optional(''),
713 718 private=Optional(False),
714 719 clone_uri=Optional(None),
715 720 push_uri=Optional(None),
716 721 landing_rev=Optional(None),
717 722 enable_statistics=Optional(False),
718 723 enable_locking=Optional(False),
719 724 enable_downloads=Optional(False),
720 725 copy_permissions=Optional(False)):
721 726 """
722 727 Creates a repository.
723 728
724 729 * If the repository name contains "/", repository will be created inside
725 730 a repository group or nested repository groups
726 731
727 732 For example "foo/bar/repo1" will create |repo| called "repo1" inside
728 733 group "foo/bar". You have to have permissions to access and write to
729 734 the last repository group ("bar" in this example)
730 735
731 736 This command can only be run using an |authtoken| with at least
732 737 permissions to create repositories, or write permissions to
733 738 parent repository groups.
734 739
735 740 :param apiuser: This is filled automatically from the |authtoken|.
736 741 :type apiuser: AuthUser
737 742 :param repo_name: Set the repository name.
738 743 :type repo_name: str
739 744 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
740 745 :type repo_type: str
741 746 :param owner: user_id or username
742 747 :type owner: Optional(str)
743 748 :param description: Set the repository description.
744 749 :type description: Optional(str)
745 750 :param private: set repository as private
746 751 :type private: bool
747 752 :param clone_uri: set clone_uri
748 753 :type clone_uri: str
749 754 :param push_uri: set push_uri
750 755 :type push_uri: str
751 756 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
752 757 :type landing_rev: str
753 758 :param enable_locking:
754 759 :type enable_locking: bool
755 760 :param enable_downloads:
756 761 :type enable_downloads: bool
757 762 :param enable_statistics:
758 763 :type enable_statistics: bool
759 764 :param copy_permissions: Copy permission from group in which the
760 765 repository is being created.
761 766 :type copy_permissions: bool
762 767
763 768
764 769 Example output:
765 770
766 771 .. code-block:: bash
767 772
768 773 id : <id_given_in_input>
769 774 result: {
770 775 "msg": "Created new repository `<reponame>`",
771 776 "success": true,
772 777 "task": "<celery task id or None if done sync>"
773 778 }
774 779 error: null
775 780
776 781
777 782 Example error output:
778 783
779 784 .. code-block:: bash
780 785
781 786 id : <id_given_in_input>
782 787 result : null
783 788 error : {
784 789 'failed to create repository `<repo_name>`'
785 790 }
786 791
787 792 """
788 793
789 794 owner = validate_set_owner_permissions(apiuser, owner)
790 795
791 796 description = Optional.extract(description)
792 797 copy_permissions = Optional.extract(copy_permissions)
793 798 clone_uri = Optional.extract(clone_uri)
794 799 push_uri = Optional.extract(push_uri)
795 800
796 801 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
797 802 if isinstance(private, Optional):
798 803 private = defs.get('repo_private') or Optional.extract(private)
799 804 if isinstance(repo_type, Optional):
800 805 repo_type = defs.get('repo_type')
801 806 if isinstance(enable_statistics, Optional):
802 807 enable_statistics = defs.get('repo_enable_statistics')
803 808 if isinstance(enable_locking, Optional):
804 809 enable_locking = defs.get('repo_enable_locking')
805 810 if isinstance(enable_downloads, Optional):
806 811 enable_downloads = defs.get('repo_enable_downloads')
807 812
808 813 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
809 814 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
810 815 ref_choices = list(set(ref_choices + [landing_ref]))
811 816
812 817 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
813 818
814 819 schema = repo_schema.RepoSchema().bind(
815 820 repo_type_options=rhodecode.BACKENDS.keys(),
816 821 repo_ref_options=ref_choices,
817 822 repo_type=repo_type,
818 823 # user caller
819 824 user=apiuser)
820 825
821 826 try:
822 827 schema_data = schema.deserialize(dict(
823 828 repo_name=repo_name,
824 829 repo_type=repo_type,
825 830 repo_owner=owner.username,
826 831 repo_description=description,
827 832 repo_landing_commit_ref=landing_commit_ref,
828 833 repo_clone_uri=clone_uri,
829 834 repo_push_uri=push_uri,
830 835 repo_private=private,
831 836 repo_copy_permissions=copy_permissions,
832 837 repo_enable_statistics=enable_statistics,
833 838 repo_enable_downloads=enable_downloads,
834 839 repo_enable_locking=enable_locking))
835 840 except validation_schema.Invalid as err:
836 841 raise JSONRPCValidationError(colander_exc=err)
837 842
838 843 try:
839 844 data = {
840 845 'owner': owner,
841 846 'repo_name': schema_data['repo_group']['repo_name_without_group'],
842 847 'repo_name_full': schema_data['repo_name'],
843 848 'repo_group': schema_data['repo_group']['repo_group_id'],
844 849 'repo_type': schema_data['repo_type'],
845 850 'repo_description': schema_data['repo_description'],
846 851 'repo_private': schema_data['repo_private'],
847 852 'clone_uri': schema_data['repo_clone_uri'],
848 853 'push_uri': schema_data['repo_push_uri'],
849 854 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
850 855 'enable_statistics': schema_data['repo_enable_statistics'],
851 856 'enable_locking': schema_data['repo_enable_locking'],
852 857 'enable_downloads': schema_data['repo_enable_downloads'],
853 858 'repo_copy_permissions': schema_data['repo_copy_permissions'],
854 859 }
855 860
856 861 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
857 862 task_id = get_task_id(task)
858 863 # no commit, it's done in RepoModel, or async via celery
859 864 return {
860 865 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
861 866 'success': True, # cannot return the repo data here since fork
862 867 # can be done async
863 868 'task': task_id
864 869 }
865 870 except Exception:
866 871 log.exception(
867 872 u"Exception while trying to create the repository %s",
868 873 schema_data['repo_name'])
869 874 raise JSONRPCError(
870 875 'failed to create repository `%s`' % (schema_data['repo_name'],))
871 876
872 877
873 878 @jsonrpc_method()
874 879 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
875 880 description=Optional('')):
876 881 """
877 882 Adds an extra field to a repository.
878 883
879 884 This command can only be run using an |authtoken| with at least
880 885 write permissions to the |repo|.
881 886
882 887 :param apiuser: This is filled automatically from the |authtoken|.
883 888 :type apiuser: AuthUser
884 889 :param repoid: Set the repository name or repository id.
885 890 :type repoid: str or int
886 891 :param key: Create a unique field key for this repository.
887 892 :type key: str
888 893 :param label:
889 894 :type label: Optional(str)
890 895 :param description:
891 896 :type description: Optional(str)
892 897 """
893 898 repo = get_repo_or_error(repoid)
894 899 if not has_superadmin_permission(apiuser):
895 900 _perms = ('repository.admin',)
896 901 validate_repo_permissions(apiuser, repoid, repo, _perms)
897 902
898 903 label = Optional.extract(label) or key
899 904 description = Optional.extract(description)
900 905
901 906 field = RepositoryField.get_by_key_name(key, repo)
902 907 if field:
903 908 raise JSONRPCError('Field with key '
904 909 '`%s` exists for repo `%s`' % (key, repoid))
905 910
906 911 try:
907 912 RepoModel().add_repo_field(repo, key, field_label=label,
908 913 field_desc=description)
909 914 Session().commit()
910 915 return {
911 916 'msg': "Added new repository field `%s`" % (key,),
912 917 'success': True,
913 918 }
914 919 except Exception:
915 920 log.exception("Exception occurred while trying to add field to repo")
916 921 raise JSONRPCError(
917 922 'failed to create new field for repository `%s`' % (repoid,))
918 923
919 924
920 925 @jsonrpc_method()
921 926 def remove_field_from_repo(request, apiuser, repoid, key):
922 927 """
923 928 Removes an extra field from a repository.
924 929
925 930 This command can only be run using an |authtoken| with at least
926 931 write permissions to the |repo|.
927 932
928 933 :param apiuser: This is filled automatically from the |authtoken|.
929 934 :type apiuser: AuthUser
930 935 :param repoid: Set the repository name or repository ID.
931 936 :type repoid: str or int
932 937 :param key: Set the unique field key for this repository.
933 938 :type key: str
934 939 """
935 940
936 941 repo = get_repo_or_error(repoid)
937 942 if not has_superadmin_permission(apiuser):
938 943 _perms = ('repository.admin',)
939 944 validate_repo_permissions(apiuser, repoid, repo, _perms)
940 945
941 946 field = RepositoryField.get_by_key_name(key, repo)
942 947 if not field:
943 948 raise JSONRPCError('Field with key `%s` does not '
944 949 'exists for repo `%s`' % (key, repoid))
945 950
946 951 try:
947 952 RepoModel().delete_repo_field(repo, field_key=key)
948 953 Session().commit()
949 954 return {
950 955 'msg': "Deleted repository field `%s`" % (key,),
951 956 'success': True,
952 957 }
953 958 except Exception:
954 959 log.exception(
955 960 "Exception occurred while trying to delete field from repo")
956 961 raise JSONRPCError(
957 962 'failed to delete field for repository `%s`' % (repoid,))
958 963
959 964
960 965 @jsonrpc_method()
961 966 def update_repo(
962 967 request, apiuser, repoid, repo_name=Optional(None),
963 968 owner=Optional(OAttr('apiuser')), description=Optional(''),
964 969 private=Optional(False),
965 970 clone_uri=Optional(None), push_uri=Optional(None),
966 971 landing_rev=Optional(None), fork_of=Optional(None),
967 972 enable_statistics=Optional(False),
968 973 enable_locking=Optional(False),
969 974 enable_downloads=Optional(False), fields=Optional('')):
970 975 """
971 976 Updates a repository with the given information.
972 977
973 978 This command can only be run using an |authtoken| with at least
974 979 admin permissions to the |repo|.
975 980
976 981 * If the repository name contains "/", repository will be updated
977 982 accordingly with a repository group or nested repository groups
978 983
979 984 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
980 985 called "repo-test" and place it inside group "foo/bar".
981 986 You have to have permissions to access and write to the last repository
982 987 group ("bar" in this example)
983 988
984 989 :param apiuser: This is filled automatically from the |authtoken|.
985 990 :type apiuser: AuthUser
986 991 :param repoid: repository name or repository ID.
987 992 :type repoid: str or int
988 993 :param repo_name: Update the |repo| name, including the
989 994 repository group it's in.
990 995 :type repo_name: str
991 996 :param owner: Set the |repo| owner.
992 997 :type owner: str
993 998 :param fork_of: Set the |repo| as fork of another |repo|.
994 999 :type fork_of: str
995 1000 :param description: Update the |repo| description.
996 1001 :type description: str
997 1002 :param private: Set the |repo| as private. (True | False)
998 1003 :type private: bool
999 1004 :param clone_uri: Update the |repo| clone URI.
1000 1005 :type clone_uri: str
1001 1006 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1002 1007 :type landing_rev: str
1003 1008 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1004 1009 :type enable_statistics: bool
1005 1010 :param enable_locking: Enable |repo| locking.
1006 1011 :type enable_locking: bool
1007 1012 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1008 1013 :type enable_downloads: bool
1009 1014 :param fields: Add extra fields to the |repo|. Use the following
1010 1015 example format: ``field_key=field_val,field_key2=fieldval2``.
1011 1016 Escape ', ' with \,
1012 1017 :type fields: str
1013 1018 """
1014 1019
1015 1020 repo = get_repo_or_error(repoid)
1016 1021
1017 1022 include_secrets = False
1018 1023 if not has_superadmin_permission(apiuser):
1019 1024 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1020 1025 else:
1021 1026 include_secrets = True
1022 1027
1023 1028 updates = dict(
1024 1029 repo_name=repo_name
1025 1030 if not isinstance(repo_name, Optional) else repo.repo_name,
1026 1031
1027 1032 fork_id=fork_of
1028 1033 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1029 1034
1030 1035 user=owner
1031 1036 if not isinstance(owner, Optional) else repo.user.username,
1032 1037
1033 1038 repo_description=description
1034 1039 if not isinstance(description, Optional) else repo.description,
1035 1040
1036 1041 repo_private=private
1037 1042 if not isinstance(private, Optional) else repo.private,
1038 1043
1039 1044 clone_uri=clone_uri
1040 1045 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1041 1046
1042 1047 push_uri=push_uri
1043 1048 if not isinstance(push_uri, Optional) else repo.push_uri,
1044 1049
1045 1050 repo_landing_rev=landing_rev
1046 1051 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1047 1052
1048 1053 repo_enable_statistics=enable_statistics
1049 1054 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1050 1055
1051 1056 repo_enable_locking=enable_locking
1052 1057 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1053 1058
1054 1059 repo_enable_downloads=enable_downloads
1055 1060 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1056 1061
1057 1062 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1058 1063 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1059 1064 request.translate, repo=repo)
1060 1065 ref_choices = list(set(ref_choices + [landing_ref]))
1061 1066
1062 1067 old_values = repo.get_api_data()
1063 1068 repo_type = repo.repo_type
1064 1069 schema = repo_schema.RepoSchema().bind(
1065 1070 repo_type_options=rhodecode.BACKENDS.keys(),
1066 1071 repo_ref_options=ref_choices,
1067 1072 repo_type=repo_type,
1068 1073 # user caller
1069 1074 user=apiuser,
1070 1075 old_values=old_values)
1071 1076 try:
1072 1077 schema_data = schema.deserialize(dict(
1073 1078 # we save old value, users cannot change type
1074 1079 repo_type=repo_type,
1075 1080
1076 1081 repo_name=updates['repo_name'],
1077 1082 repo_owner=updates['user'],
1078 1083 repo_description=updates['repo_description'],
1079 1084 repo_clone_uri=updates['clone_uri'],
1080 1085 repo_push_uri=updates['push_uri'],
1081 1086 repo_fork_of=updates['fork_id'],
1082 1087 repo_private=updates['repo_private'],
1083 1088 repo_landing_commit_ref=updates['repo_landing_rev'],
1084 1089 repo_enable_statistics=updates['repo_enable_statistics'],
1085 1090 repo_enable_downloads=updates['repo_enable_downloads'],
1086 1091 repo_enable_locking=updates['repo_enable_locking']))
1087 1092 except validation_schema.Invalid as err:
1088 1093 raise JSONRPCValidationError(colander_exc=err)
1089 1094
1090 1095 # save validated data back into the updates dict
1091 1096 validated_updates = dict(
1092 1097 repo_name=schema_data['repo_group']['repo_name_without_group'],
1093 1098 repo_group=schema_data['repo_group']['repo_group_id'],
1094 1099
1095 1100 user=schema_data['repo_owner'],
1096 1101 repo_description=schema_data['repo_description'],
1097 1102 repo_private=schema_data['repo_private'],
1098 1103 clone_uri=schema_data['repo_clone_uri'],
1099 1104 push_uri=schema_data['repo_push_uri'],
1100 1105 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1101 1106 repo_enable_statistics=schema_data['repo_enable_statistics'],
1102 1107 repo_enable_locking=schema_data['repo_enable_locking'],
1103 1108 repo_enable_downloads=schema_data['repo_enable_downloads'],
1104 1109 )
1105 1110
1106 1111 if schema_data['repo_fork_of']:
1107 1112 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1108 1113 validated_updates['fork_id'] = fork_repo.repo_id
1109 1114
1110 1115 # extra fields
1111 1116 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1112 1117 if fields:
1113 1118 validated_updates.update(fields)
1114 1119
1115 1120 try:
1116 1121 RepoModel().update(repo, **validated_updates)
1117 1122 audit_logger.store_api(
1118 1123 'repo.edit', action_data={'old_data': old_values},
1119 1124 user=apiuser, repo=repo)
1120 1125 Session().commit()
1121 1126 return {
1122 1127 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1123 1128 'repository': repo.get_api_data(include_secrets=include_secrets)
1124 1129 }
1125 1130 except Exception:
1126 1131 log.exception(
1127 1132 u"Exception while trying to update the repository %s",
1128 1133 repoid)
1129 1134 raise JSONRPCError('failed to update repo `%s`' % repoid)
1130 1135
1131 1136
1132 1137 @jsonrpc_method()
1133 1138 def fork_repo(request, apiuser, repoid, fork_name,
1134 1139 owner=Optional(OAttr('apiuser')),
1135 1140 description=Optional(''),
1136 1141 private=Optional(False),
1137 1142 clone_uri=Optional(None),
1138 1143 landing_rev=Optional(None),
1139 1144 copy_permissions=Optional(False)):
1140 1145 """
1141 1146 Creates a fork of the specified |repo|.
1142 1147
1143 1148 * If the fork_name contains "/", fork will be created inside
1144 1149 a repository group or nested repository groups
1145 1150
1146 1151 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1147 1152 inside group "foo/bar". You have to have permissions to access and
1148 1153 write to the last repository group ("bar" in this example)
1149 1154
1150 1155 This command can only be run using an |authtoken| with minimum
1151 1156 read permissions of the forked repo, create fork permissions for an user.
1152 1157
1153 1158 :param apiuser: This is filled automatically from the |authtoken|.
1154 1159 :type apiuser: AuthUser
1155 1160 :param repoid: Set repository name or repository ID.
1156 1161 :type repoid: str or int
1157 1162 :param fork_name: Set the fork name, including it's repository group membership.
1158 1163 :type fork_name: str
1159 1164 :param owner: Set the fork owner.
1160 1165 :type owner: str
1161 1166 :param description: Set the fork description.
1162 1167 :type description: str
1163 1168 :param copy_permissions: Copy permissions from parent |repo|. The
1164 1169 default is False.
1165 1170 :type copy_permissions: bool
1166 1171 :param private: Make the fork private. The default is False.
1167 1172 :type private: bool
1168 1173 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1169 1174
1170 1175 Example output:
1171 1176
1172 1177 .. code-block:: bash
1173 1178
1174 1179 id : <id_for_response>
1175 1180 api_key : "<api_key>"
1176 1181 args: {
1177 1182 "repoid" : "<reponame or repo_id>",
1178 1183 "fork_name": "<forkname>",
1179 1184 "owner": "<username or user_id = Optional(=apiuser)>",
1180 1185 "description": "<description>",
1181 1186 "copy_permissions": "<bool>",
1182 1187 "private": "<bool>",
1183 1188 "landing_rev": "<landing_rev>"
1184 1189 }
1185 1190
1186 1191 Example error output:
1187 1192
1188 1193 .. code-block:: bash
1189 1194
1190 1195 id : <id_given_in_input>
1191 1196 result: {
1192 1197 "msg": "Created fork of `<reponame>` as `<forkname>`",
1193 1198 "success": true,
1194 1199 "task": "<celery task id or None if done sync>"
1195 1200 }
1196 1201 error: null
1197 1202
1198 1203 """
1199 1204
1200 1205 repo = get_repo_or_error(repoid)
1201 1206 repo_name = repo.repo_name
1202 1207
1203 1208 if not has_superadmin_permission(apiuser):
1204 1209 # check if we have at least read permission for
1205 1210 # this repo that we fork !
1206 1211 _perms = (
1207 1212 'repository.admin', 'repository.write', 'repository.read')
1208 1213 validate_repo_permissions(apiuser, repoid, repo, _perms)
1209 1214
1210 1215 # check if the regular user has at least fork permissions as well
1211 1216 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1212 1217 raise JSONRPCForbidden()
1213 1218
1214 1219 # check if user can set owner parameter
1215 1220 owner = validate_set_owner_permissions(apiuser, owner)
1216 1221
1217 1222 description = Optional.extract(description)
1218 1223 copy_permissions = Optional.extract(copy_permissions)
1219 1224 clone_uri = Optional.extract(clone_uri)
1220 1225
1221 1226 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1222 1227 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1223 1228 ref_choices = list(set(ref_choices + [landing_ref]))
1224 1229 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1225 1230
1226 1231 private = Optional.extract(private)
1227 1232
1228 1233 schema = repo_schema.RepoSchema().bind(
1229 1234 repo_type_options=rhodecode.BACKENDS.keys(),
1230 1235 repo_ref_options=ref_choices,
1231 1236 repo_type=repo.repo_type,
1232 1237 # user caller
1233 1238 user=apiuser)
1234 1239
1235 1240 try:
1236 1241 schema_data = schema.deserialize(dict(
1237 1242 repo_name=fork_name,
1238 1243 repo_type=repo.repo_type,
1239 1244 repo_owner=owner.username,
1240 1245 repo_description=description,
1241 1246 repo_landing_commit_ref=landing_commit_ref,
1242 1247 repo_clone_uri=clone_uri,
1243 1248 repo_private=private,
1244 1249 repo_copy_permissions=copy_permissions))
1245 1250 except validation_schema.Invalid as err:
1246 1251 raise JSONRPCValidationError(colander_exc=err)
1247 1252
1248 1253 try:
1249 1254 data = {
1250 1255 'fork_parent_id': repo.repo_id,
1251 1256
1252 1257 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1253 1258 'repo_name_full': schema_data['repo_name'],
1254 1259 'repo_group': schema_data['repo_group']['repo_group_id'],
1255 1260 'repo_type': schema_data['repo_type'],
1256 1261 'description': schema_data['repo_description'],
1257 1262 'private': schema_data['repo_private'],
1258 1263 'copy_permissions': schema_data['repo_copy_permissions'],
1259 1264 'landing_rev': schema_data['repo_landing_commit_ref'],
1260 1265 }
1261 1266
1262 1267 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1263 1268 # no commit, it's done in RepoModel, or async via celery
1264 1269 task_id = get_task_id(task)
1265 1270
1266 1271 return {
1267 1272 'msg': 'Created fork of `%s` as `%s`' % (
1268 1273 repo.repo_name, schema_data['repo_name']),
1269 1274 'success': True, # cannot return the repo data here since fork
1270 1275 # can be done async
1271 1276 'task': task_id
1272 1277 }
1273 1278 except Exception:
1274 1279 log.exception(
1275 1280 u"Exception while trying to create fork %s",
1276 1281 schema_data['repo_name'])
1277 1282 raise JSONRPCError(
1278 1283 'failed to fork repository `%s` as `%s`' % (
1279 1284 repo_name, schema_data['repo_name']))
1280 1285
1281 1286
1282 1287 @jsonrpc_method()
1283 1288 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1284 1289 """
1285 1290 Deletes a repository.
1286 1291
1287 1292 * When the `forks` parameter is set it's possible to detach or delete
1288 1293 forks of deleted repository.
1289 1294
1290 1295 This command can only be run using an |authtoken| with admin
1291 1296 permissions on the |repo|.
1292 1297
1293 1298 :param apiuser: This is filled automatically from the |authtoken|.
1294 1299 :type apiuser: AuthUser
1295 1300 :param repoid: Set the repository name or repository ID.
1296 1301 :type repoid: str or int
1297 1302 :param forks: Set to `detach` or `delete` forks from the |repo|.
1298 1303 :type forks: Optional(str)
1299 1304
1300 1305 Example error output:
1301 1306
1302 1307 .. code-block:: bash
1303 1308
1304 1309 id : <id_given_in_input>
1305 1310 result: {
1306 1311 "msg": "Deleted repository `<reponame>`",
1307 1312 "success": true
1308 1313 }
1309 1314 error: null
1310 1315 """
1311 1316
1312 1317 repo = get_repo_or_error(repoid)
1313 1318 repo_name = repo.repo_name
1314 1319 if not has_superadmin_permission(apiuser):
1315 1320 _perms = ('repository.admin',)
1316 1321 validate_repo_permissions(apiuser, repoid, repo, _perms)
1317 1322
1318 1323 try:
1319 1324 handle_forks = Optional.extract(forks)
1320 1325 _forks_msg = ''
1321 1326 _forks = [f for f in repo.forks]
1322 1327 if handle_forks == 'detach':
1323 1328 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1324 1329 elif handle_forks == 'delete':
1325 1330 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1326 1331 elif _forks:
1327 1332 raise JSONRPCError(
1328 1333 'Cannot delete `%s` it still contains attached forks' %
1329 1334 (repo.repo_name,)
1330 1335 )
1331 1336 old_data = repo.get_api_data()
1332 1337 RepoModel().delete(repo, forks=forks)
1333 1338
1334 1339 repo = audit_logger.RepoWrap(repo_id=None,
1335 1340 repo_name=repo.repo_name)
1336 1341
1337 1342 audit_logger.store_api(
1338 1343 'repo.delete', action_data={'old_data': old_data},
1339 1344 user=apiuser, repo=repo)
1340 1345
1341 1346 ScmModel().mark_for_invalidation(repo_name, delete=True)
1342 1347 Session().commit()
1343 1348 return {
1344 1349 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1345 1350 'success': True
1346 1351 }
1347 1352 except Exception:
1348 1353 log.exception("Exception occurred while trying to delete repo")
1349 1354 raise JSONRPCError(
1350 1355 'failed to delete repository `%s`' % (repo_name,)
1351 1356 )
1352 1357
1353 1358
1354 1359 #TODO: marcink, change name ?
1355 1360 @jsonrpc_method()
1356 1361 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1357 1362 """
1358 1363 Invalidates the cache for the specified repository.
1359 1364
1360 1365 This command can only be run using an |authtoken| with admin rights to
1361 1366 the specified repository.
1362 1367
1363 1368 This command takes the following options:
1364 1369
1365 1370 :param apiuser: This is filled automatically from |authtoken|.
1366 1371 :type apiuser: AuthUser
1367 1372 :param repoid: Sets the repository name or repository ID.
1368 1373 :type repoid: str or int
1369 1374 :param delete_keys: This deletes the invalidated keys instead of
1370 1375 just flagging them.
1371 1376 :type delete_keys: Optional(``True`` | ``False``)
1372 1377
1373 1378 Example output:
1374 1379
1375 1380 .. code-block:: bash
1376 1381
1377 1382 id : <id_given_in_input>
1378 1383 result : {
1379 1384 'msg': Cache for repository `<repository name>` was invalidated,
1380 1385 'repository': <repository name>
1381 1386 }
1382 1387 error : null
1383 1388
1384 1389 Example error output:
1385 1390
1386 1391 .. code-block:: bash
1387 1392
1388 1393 id : <id_given_in_input>
1389 1394 result : null
1390 1395 error : {
1391 1396 'Error occurred during cache invalidation action'
1392 1397 }
1393 1398
1394 1399 """
1395 1400
1396 1401 repo = get_repo_or_error(repoid)
1397 1402 if not has_superadmin_permission(apiuser):
1398 1403 _perms = ('repository.admin', 'repository.write',)
1399 1404 validate_repo_permissions(apiuser, repoid, repo, _perms)
1400 1405
1401 1406 delete = Optional.extract(delete_keys)
1402 1407 try:
1403 1408 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1404 1409 return {
1405 1410 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1406 1411 'repository': repo.repo_name
1407 1412 }
1408 1413 except Exception:
1409 1414 log.exception(
1410 1415 "Exception occurred while trying to invalidate repo cache")
1411 1416 raise JSONRPCError(
1412 1417 'Error occurred during cache invalidation action'
1413 1418 )
1414 1419
1415 1420
1416 1421 #TODO: marcink, change name ?
1417 1422 @jsonrpc_method()
1418 1423 def lock(request, apiuser, repoid, locked=Optional(None),
1419 1424 userid=Optional(OAttr('apiuser'))):
1420 1425 """
1421 1426 Sets the lock state of the specified |repo| by the given user.
1422 1427 From more information, see :ref:`repo-locking`.
1423 1428
1424 1429 * If the ``userid`` option is not set, the repository is locked to the
1425 1430 user who called the method.
1426 1431 * If the ``locked`` parameter is not set, the current lock state of the
1427 1432 repository is displayed.
1428 1433
1429 1434 This command can only be run using an |authtoken| with admin rights to
1430 1435 the specified repository.
1431 1436
1432 1437 This command takes the following options:
1433 1438
1434 1439 :param apiuser: This is filled automatically from the |authtoken|.
1435 1440 :type apiuser: AuthUser
1436 1441 :param repoid: Sets the repository name or repository ID.
1437 1442 :type repoid: str or int
1438 1443 :param locked: Sets the lock state.
1439 1444 :type locked: Optional(``True`` | ``False``)
1440 1445 :param userid: Set the repository lock to this user.
1441 1446 :type userid: Optional(str or int)
1442 1447
1443 1448 Example error output:
1444 1449
1445 1450 .. code-block:: bash
1446 1451
1447 1452 id : <id_given_in_input>
1448 1453 result : {
1449 1454 'repo': '<reponame>',
1450 1455 'locked': <bool: lock state>,
1451 1456 'locked_since': <int: lock timestamp>,
1452 1457 'locked_by': <username of person who made the lock>,
1453 1458 'lock_reason': <str: reason for locking>,
1454 1459 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1455 1460 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1456 1461 or
1457 1462 'msg': 'Repo `<repository name>` not locked.'
1458 1463 or
1459 1464 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1460 1465 }
1461 1466 error : null
1462 1467
1463 1468 Example error output:
1464 1469
1465 1470 .. code-block:: bash
1466 1471
1467 1472 id : <id_given_in_input>
1468 1473 result : null
1469 1474 error : {
1470 1475 'Error occurred locking repository `<reponame>`'
1471 1476 }
1472 1477 """
1473 1478
1474 1479 repo = get_repo_or_error(repoid)
1475 1480 if not has_superadmin_permission(apiuser):
1476 1481 # check if we have at least write permission for this repo !
1477 1482 _perms = ('repository.admin', 'repository.write',)
1478 1483 validate_repo_permissions(apiuser, repoid, repo, _perms)
1479 1484
1480 1485 # make sure normal user does not pass someone else userid,
1481 1486 # he is not allowed to do that
1482 1487 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1483 1488 raise JSONRPCError('userid is not the same as your user')
1484 1489
1485 1490 if isinstance(userid, Optional):
1486 1491 userid = apiuser.user_id
1487 1492
1488 1493 user = get_user_or_error(userid)
1489 1494
1490 1495 if isinstance(locked, Optional):
1491 1496 lockobj = repo.locked
1492 1497
1493 1498 if lockobj[0] is None:
1494 1499 _d = {
1495 1500 'repo': repo.repo_name,
1496 1501 'locked': False,
1497 1502 'locked_since': None,
1498 1503 'locked_by': None,
1499 1504 'lock_reason': None,
1500 1505 'lock_state_changed': False,
1501 1506 'msg': 'Repo `%s` not locked.' % repo.repo_name
1502 1507 }
1503 1508 return _d
1504 1509 else:
1505 1510 _user_id, _time, _reason = lockobj
1506 1511 lock_user = get_user_or_error(userid)
1507 1512 _d = {
1508 1513 'repo': repo.repo_name,
1509 1514 'locked': True,
1510 1515 'locked_since': _time,
1511 1516 'locked_by': lock_user.username,
1512 1517 'lock_reason': _reason,
1513 1518 'lock_state_changed': False,
1514 1519 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1515 1520 % (repo.repo_name, lock_user.username,
1516 1521 json.dumps(time_to_datetime(_time))))
1517 1522 }
1518 1523 return _d
1519 1524
1520 1525 # force locked state through a flag
1521 1526 else:
1522 1527 locked = str2bool(locked)
1523 1528 lock_reason = Repository.LOCK_API
1524 1529 try:
1525 1530 if locked:
1526 1531 lock_time = time.time()
1527 1532 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1528 1533 else:
1529 1534 lock_time = None
1530 1535 Repository.unlock(repo)
1531 1536 _d = {
1532 1537 'repo': repo.repo_name,
1533 1538 'locked': locked,
1534 1539 'locked_since': lock_time,
1535 1540 'locked_by': user.username,
1536 1541 'lock_reason': lock_reason,
1537 1542 'lock_state_changed': True,
1538 1543 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1539 1544 % (user.username, repo.repo_name, locked))
1540 1545 }
1541 1546 return _d
1542 1547 except Exception:
1543 1548 log.exception(
1544 1549 "Exception occurred while trying to lock repository")
1545 1550 raise JSONRPCError(
1546 1551 'Error occurred locking repository `%s`' % repo.repo_name
1547 1552 )
1548 1553
1549 1554
1550 1555 @jsonrpc_method()
1551 1556 def comment_commit(
1552 1557 request, apiuser, repoid, commit_id, message, status=Optional(None),
1553 1558 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1554 1559 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1555 1560 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1556 1561 """
1557 1562 Set a commit comment, and optionally change the status of the commit.
1558 1563
1559 1564 :param apiuser: This is filled automatically from the |authtoken|.
1560 1565 :type apiuser: AuthUser
1561 1566 :param repoid: Set the repository name or repository ID.
1562 1567 :type repoid: str or int
1563 1568 :param commit_id: Specify the commit_id for which to set a comment.
1564 1569 :type commit_id: str
1565 1570 :param message: The comment text.
1566 1571 :type message: str
1567 1572 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1568 1573 'approved', 'rejected', 'under_review'
1569 1574 :type status: str
1570 1575 :param comment_type: Comment type, one of: 'note', 'todo'
1571 1576 :type comment_type: Optional(str), default: 'note'
1572 1577 :param resolves_comment_id: id of comment which this one will resolve
1573 1578 :type resolves_comment_id: Optional(int)
1574 1579 :param extra_recipients: list of user ids or usernames to add
1575 1580 notifications for this comment. Acts like a CC for notification
1576 1581 :type extra_recipients: Optional(list)
1577 1582 :param userid: Set the user name of the comment creator.
1578 1583 :type userid: Optional(str or int)
1579 1584 :param send_email: Define if this comment should also send email notification
1580 1585 :type send_email: Optional(bool)
1581 1586
1582 1587 Example error output:
1583 1588
1584 1589 .. code-block:: bash
1585 1590
1586 1591 {
1587 1592 "id" : <id_given_in_input>,
1588 1593 "result" : {
1589 1594 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1590 1595 "status_change": null or <status>,
1591 1596 "success": true
1592 1597 },
1593 1598 "error" : null
1594 1599 }
1595 1600
1596 1601 """
1597 1602 repo = get_repo_or_error(repoid)
1598 1603 if not has_superadmin_permission(apiuser):
1599 1604 _perms = ('repository.read', 'repository.write', 'repository.admin')
1600 1605 validate_repo_permissions(apiuser, repoid, repo, _perms)
1601 1606
1602 1607 try:
1603 1608 commit = repo.scm_instance().get_commit(commit_id=commit_id)
1604 1609 commit_id = commit.raw_id
1605 1610 except Exception as e:
1606 1611 log.exception('Failed to fetch commit')
1607 1612 raise JSONRPCError(safe_str(e))
1608 1613
1609 1614 if isinstance(userid, Optional):
1610 1615 userid = apiuser.user_id
1611 1616
1612 1617 user = get_user_or_error(userid)
1613 1618 status = Optional.extract(status)
1614 1619 comment_type = Optional.extract(comment_type)
1615 1620 resolves_comment_id = Optional.extract(resolves_comment_id)
1616 1621 extra_recipients = Optional.extract(extra_recipients)
1617 1622 send_email = Optional.extract(send_email, binary=True)
1618 1623
1619 1624 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1620 1625 if status and status not in allowed_statuses:
1621 1626 raise JSONRPCError('Bad status, must be on '
1622 1627 'of %s got %s' % (allowed_statuses, status,))
1623 1628
1624 1629 if resolves_comment_id:
1625 1630 comment = ChangesetComment.get(resolves_comment_id)
1626 1631 if not comment:
1627 1632 raise JSONRPCError(
1628 1633 'Invalid resolves_comment_id `%s` for this commit.'
1629 1634 % resolves_comment_id)
1630 1635 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1631 1636 raise JSONRPCError(
1632 1637 'Comment `%s` is wrong type for setting status to resolved.'
1633 1638 % resolves_comment_id)
1634 1639
1635 1640 try:
1636 1641 rc_config = SettingsModel().get_all_settings()
1637 1642 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1638 1643 status_change_label = ChangesetStatus.get_status_lbl(status)
1639 1644 comment = CommentsModel().create(
1640 1645 message, repo, user, commit_id=commit_id,
1641 1646 status_change=status_change_label,
1642 1647 status_change_type=status,
1643 1648 renderer=renderer,
1644 1649 comment_type=comment_type,
1645 1650 resolves_comment_id=resolves_comment_id,
1646 1651 auth_user=apiuser,
1647 1652 extra_recipients=extra_recipients,
1648 1653 send_email=send_email
1649 1654 )
1650 1655 if status:
1651 1656 # also do a status change
1652 1657 try:
1653 1658 ChangesetStatusModel().set_status(
1654 1659 repo, status, user, comment, revision=commit_id,
1655 1660 dont_allow_on_closed_pull_request=True
1656 1661 )
1657 1662 except StatusChangeOnClosedPullRequestError:
1658 1663 log.exception(
1659 1664 "Exception occurred while trying to change repo commit status")
1660 1665 msg = ('Changing status on a commit associated with '
1661 1666 'a closed pull request is not allowed')
1662 1667 raise JSONRPCError(msg)
1663 1668
1664 1669 CommentsModel().trigger_commit_comment_hook(
1665 1670 repo, apiuser, 'create',
1666 1671 data={'comment': comment, 'commit': commit})
1667 1672
1668 1673 Session().commit()
1669 1674 return {
1670 1675 'msg': (
1671 1676 'Commented on commit `%s` for repository `%s`' % (
1672 1677 comment.revision, repo.repo_name)),
1673 1678 'status_change': status,
1674 1679 'success': True,
1675 1680 }
1676 1681 except JSONRPCError:
1677 1682 # catch any inside errors, and re-raise them to prevent from
1678 1683 # below global catch to silence them
1679 1684 raise
1680 1685 except Exception:
1681 1686 log.exception("Exception occurred while trying to comment on commit")
1682 1687 raise JSONRPCError(
1683 1688 'failed to set comment on repository `%s`' % (repo.repo_name,)
1684 1689 )
1685 1690
1686 1691
1687 1692 @jsonrpc_method()
1688 1693 def get_repo_comments(request, apiuser, repoid,
1689 1694 commit_id=Optional(None), comment_type=Optional(None),
1690 1695 userid=Optional(None)):
1691 1696 """
1692 1697 Get all comments for a repository
1693 1698
1694 1699 :param apiuser: This is filled automatically from the |authtoken|.
1695 1700 :type apiuser: AuthUser
1696 1701 :param repoid: Set the repository name or repository ID.
1697 1702 :type repoid: str or int
1698 1703 :param commit_id: Optionally filter the comments by the commit_id
1699 1704 :type commit_id: Optional(str), default: None
1700 1705 :param comment_type: Optionally filter the comments by the comment_type
1701 1706 one of: 'note', 'todo'
1702 1707 :type comment_type: Optional(str), default: None
1703 1708 :param userid: Optionally filter the comments by the author of comment
1704 1709 :type userid: Optional(str or int), Default: None
1705 1710
1706 1711 Example error output:
1707 1712
1708 1713 .. code-block:: bash
1709 1714
1710 1715 {
1711 1716 "id" : <id_given_in_input>,
1712 1717 "result" : [
1713 1718 {
1714 1719 "comment_author": <USER_DETAILS>,
1715 1720 "comment_created_on": "2017-02-01T14:38:16.309",
1716 1721 "comment_f_path": "file.txt",
1717 1722 "comment_id": 282,
1718 1723 "comment_lineno": "n1",
1719 1724 "comment_resolved_by": null,
1720 1725 "comment_status": [],
1721 1726 "comment_text": "This file needs a header",
1722 "comment_type": "todo"
1727 "comment_type": "todo",
1728 "comment_last_version: 0
1723 1729 }
1724 1730 ],
1725 1731 "error" : null
1726 1732 }
1727 1733
1728 1734 """
1729 1735 repo = get_repo_or_error(repoid)
1730 1736 if not has_superadmin_permission(apiuser):
1731 1737 _perms = ('repository.read', 'repository.write', 'repository.admin')
1732 1738 validate_repo_permissions(apiuser, repoid, repo, _perms)
1733 1739
1734 1740 commit_id = Optional.extract(commit_id)
1735 1741
1736 1742 userid = Optional.extract(userid)
1737 1743 if userid:
1738 1744 user = get_user_or_error(userid)
1739 1745 else:
1740 1746 user = None
1741 1747
1742 1748 comment_type = Optional.extract(comment_type)
1743 1749 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1744 1750 raise JSONRPCError(
1745 1751 'comment_type must be one of `{}` got {}'.format(
1746 1752 ChangesetComment.COMMENT_TYPES, comment_type)
1747 1753 )
1748 1754
1749 1755 comments = CommentsModel().get_repository_comments(
1750 1756 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1751 1757 return comments
1752 1758
1753 1759
1754 1760 @jsonrpc_method()
1761 def get_comment(request, apiuser, comment_id):
1762 """
1763 Get single comment from repository or pull_request
1764
1765 :param apiuser: This is filled automatically from the |authtoken|.
1766 :type apiuser: AuthUser
1767 :param comment_id: comment id found in the URL of comment
1768 :type comment_id: str or int
1769
1770 Example error output:
1771
1772 .. code-block:: bash
1773
1774 {
1775 "id" : <id_given_in_input>,
1776 "result" : {
1777 "comment_author": <USER_DETAILS>,
1778 "comment_created_on": "2017-02-01T14:38:16.309",
1779 "comment_f_path": "file.txt",
1780 "comment_id": 282,
1781 "comment_lineno": "n1",
1782 "comment_resolved_by": null,
1783 "comment_status": [],
1784 "comment_text": "This file needs a header",
1785 "comment_type": "todo",
1786 "comment_last_version: 0
1787 },
1788 "error" : null
1789 }
1790
1791 """
1792
1793 comment = ChangesetComment.get(comment_id)
1794 if not comment:
1795 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1796
1797 perms = ('repository.read', 'repository.write', 'repository.admin')
1798 has_comment_perm = HasRepoPermissionAnyApi(*perms)\
1799 (user=apiuser, repo_name=comment.repo.repo_name)
1800
1801 if not has_comment_perm:
1802 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1803
1804 return comment
1805
1806
1807 @jsonrpc_method()
1808 def edit_comment(request, apiuser, message, comment_id, version,
1809 userid=Optional(OAttr('apiuser'))):
1810 """
1811 Edit comment on the pull request or commit,
1812 specified by the `comment_id` and version. Initially version should be 0
1813
1814 :param apiuser: This is filled automatically from the |authtoken|.
1815 :type apiuser: AuthUser
1816 :param comment_id: Specify the comment_id for editing
1817 :type comment_id: int
1818 :param version: version of the comment that will be created, starts from 0
1819 :type version: int
1820 :param message: The text content of the comment.
1821 :type message: str
1822 :param userid: Comment on the pull request as this user
1823 :type userid: Optional(str or int)
1824
1825 Example output:
1826
1827 .. code-block:: bash
1828
1829 id : <id_given_in_input>
1830 result : {
1831 "comment": "<comment data>",
1832 "version": "<Integer>",
1833 },
1834 error : null
1835 """
1836
1837 auth_user = apiuser
1838 comment = ChangesetComment.get(comment_id)
1839 if not comment:
1840 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1841
1842 is_super_admin = has_superadmin_permission(apiuser)
1843 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1844 (user=apiuser, repo_name=comment.repo.repo_name)
1845
1846 if not isinstance(userid, Optional):
1847 if is_super_admin or is_repo_admin:
1848 apiuser = get_user_or_error(userid)
1849 auth_user = apiuser.AuthUser()
1850 else:
1851 raise JSONRPCError('userid is not the same as your user')
1852
1853 comment_author = comment.author.user_id == auth_user.user_id
1854 if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1855 raise JSONRPCError("you don't have access to edit this comment")
1856
1857 try:
1858 comment_history = CommentsModel().edit(
1859 comment_id=comment_id,
1860 text=message,
1861 auth_user=auth_user,
1862 version=version,
1863 )
1864 Session().commit()
1865 except CommentVersionMismatch:
1866 raise JSONRPCError(
1867 'comment ({}) version ({}) mismatch'.format(comment_id, version)
1868 )
1869 if not comment_history and not message:
1870 raise JSONRPCError(
1871 "comment ({}) can't be changed with empty string".format(comment_id)
1872 )
1873
1874 if comment.pull_request:
1875 pull_request = comment.pull_request
1876 PullRequestModel().trigger_pull_request_hook(
1877 pull_request, apiuser, 'comment_edit',
1878 data={'comment': comment})
1879 else:
1880 db_repo = comment.repo
1881 commit_id = comment.revision
1882 commit = db_repo.get_commit(commit_id)
1883 CommentsModel().trigger_commit_comment_hook(
1884 db_repo, apiuser, 'edit',
1885 data={'comment': comment, 'commit': commit})
1886
1887 data = {
1888 'comment': comment,
1889 'version': comment_history.version if comment_history else None,
1890 }
1891 return data
1892
1893
1894 # TODO(marcink): write this with all required logic for deleting a comments in PR or commits
1895 # @jsonrpc_method()
1896 # def delete_comment(request, apiuser, comment_id):
1897 # auth_user = apiuser
1898 #
1899 # comment = ChangesetComment.get(comment_id)
1900 # if not comment:
1901 # raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1902 #
1903 # is_super_admin = has_superadmin_permission(apiuser)
1904 # is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1905 # (user=apiuser, repo_name=comment.repo.repo_name)
1906 #
1907 # comment_author = comment.author.user_id == auth_user.user_id
1908 # if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1909 # raise JSONRPCError("you don't have access to edit this comment")
1910
1911 @jsonrpc_method()
1755 1912 def grant_user_permission(request, apiuser, repoid, userid, perm):
1756 1913 """
1757 1914 Grant permissions for the specified user on the given repository,
1758 1915 or update existing permissions if found.
1759 1916
1760 1917 This command can only be run using an |authtoken| with admin
1761 1918 permissions on the |repo|.
1762 1919
1763 1920 :param apiuser: This is filled automatically from the |authtoken|.
1764 1921 :type apiuser: AuthUser
1765 1922 :param repoid: Set the repository name or repository ID.
1766 1923 :type repoid: str or int
1767 1924 :param userid: Set the user name.
1768 1925 :type userid: str
1769 1926 :param perm: Set the user permissions, using the following format
1770 1927 ``(repository.(none|read|write|admin))``
1771 1928 :type perm: str
1772 1929
1773 1930 Example output:
1774 1931
1775 1932 .. code-block:: bash
1776 1933
1777 1934 id : <id_given_in_input>
1778 1935 result: {
1779 1936 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1780 1937 "success": true
1781 1938 }
1782 1939 error: null
1783 1940 """
1784 1941
1785 1942 repo = get_repo_or_error(repoid)
1786 1943 user = get_user_or_error(userid)
1787 1944 perm = get_perm_or_error(perm)
1788 1945 if not has_superadmin_permission(apiuser):
1789 1946 _perms = ('repository.admin',)
1790 1947 validate_repo_permissions(apiuser, repoid, repo, _perms)
1791 1948
1792 1949 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1793 1950 try:
1794 1951 changes = RepoModel().update_permissions(
1795 1952 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1796 1953
1797 1954 action_data = {
1798 1955 'added': changes['added'],
1799 1956 'updated': changes['updated'],
1800 1957 'deleted': changes['deleted'],
1801 1958 }
1802 1959 audit_logger.store_api(
1803 1960 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1804 1961 Session().commit()
1805 1962 PermissionModel().flush_user_permission_caches(changes)
1806 1963
1807 1964 return {
1808 1965 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1809 1966 perm.permission_name, user.username, repo.repo_name
1810 1967 ),
1811 1968 'success': True
1812 1969 }
1813 1970 except Exception:
1814 1971 log.exception("Exception occurred while trying edit permissions for repo")
1815 1972 raise JSONRPCError(
1816 1973 'failed to edit permission for user: `%s` in repo: `%s`' % (
1817 1974 userid, repoid
1818 1975 )
1819 1976 )
1820 1977
1821 1978
1822 1979 @jsonrpc_method()
1823 1980 def revoke_user_permission(request, apiuser, repoid, userid):
1824 1981 """
1825 1982 Revoke permission for a user on the specified repository.
1826 1983
1827 1984 This command can only be run using an |authtoken| with admin
1828 1985 permissions on the |repo|.
1829 1986
1830 1987 :param apiuser: This is filled automatically from the |authtoken|.
1831 1988 :type apiuser: AuthUser
1832 1989 :param repoid: Set the repository name or repository ID.
1833 1990 :type repoid: str or int
1834 1991 :param userid: Set the user name of revoked user.
1835 1992 :type userid: str or int
1836 1993
1837 1994 Example error output:
1838 1995
1839 1996 .. code-block:: bash
1840 1997
1841 1998 id : <id_given_in_input>
1842 1999 result: {
1843 2000 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1844 2001 "success": true
1845 2002 }
1846 2003 error: null
1847 2004 """
1848 2005
1849 2006 repo = get_repo_or_error(repoid)
1850 2007 user = get_user_or_error(userid)
1851 2008 if not has_superadmin_permission(apiuser):
1852 2009 _perms = ('repository.admin',)
1853 2010 validate_repo_permissions(apiuser, repoid, repo, _perms)
1854 2011
1855 2012 perm_deletions = [[user.user_id, None, "user"]]
1856 2013 try:
1857 2014 changes = RepoModel().update_permissions(
1858 2015 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1859 2016
1860 2017 action_data = {
1861 2018 'added': changes['added'],
1862 2019 'updated': changes['updated'],
1863 2020 'deleted': changes['deleted'],
1864 2021 }
1865 2022 audit_logger.store_api(
1866 2023 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1867 2024 Session().commit()
1868 2025 PermissionModel().flush_user_permission_caches(changes)
1869 2026
1870 2027 return {
1871 2028 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1872 2029 user.username, repo.repo_name
1873 2030 ),
1874 2031 'success': True
1875 2032 }
1876 2033 except Exception:
1877 2034 log.exception("Exception occurred while trying revoke permissions to repo")
1878 2035 raise JSONRPCError(
1879 2036 'failed to edit permission for user: `%s` in repo: `%s`' % (
1880 2037 userid, repoid
1881 2038 )
1882 2039 )
1883 2040
1884 2041
1885 2042 @jsonrpc_method()
1886 2043 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1887 2044 """
1888 2045 Grant permission for a user group on the specified repository,
1889 2046 or update existing permissions.
1890 2047
1891 2048 This command can only be run using an |authtoken| with admin
1892 2049 permissions on the |repo|.
1893 2050
1894 2051 :param apiuser: This is filled automatically from the |authtoken|.
1895 2052 :type apiuser: AuthUser
1896 2053 :param repoid: Set the repository name or repository ID.
1897 2054 :type repoid: str or int
1898 2055 :param usergroupid: Specify the ID of the user group.
1899 2056 :type usergroupid: str or int
1900 2057 :param perm: Set the user group permissions using the following
1901 2058 format: (repository.(none|read|write|admin))
1902 2059 :type perm: str
1903 2060
1904 2061 Example output:
1905 2062
1906 2063 .. code-block:: bash
1907 2064
1908 2065 id : <id_given_in_input>
1909 2066 result : {
1910 2067 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1911 2068 "success": true
1912 2069
1913 2070 }
1914 2071 error : null
1915 2072
1916 2073 Example error output:
1917 2074
1918 2075 .. code-block:: bash
1919 2076
1920 2077 id : <id_given_in_input>
1921 2078 result : null
1922 2079 error : {
1923 2080 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1924 2081 }
1925 2082
1926 2083 """
1927 2084
1928 2085 repo = get_repo_or_error(repoid)
1929 2086 perm = get_perm_or_error(perm)
1930 2087 if not has_superadmin_permission(apiuser):
1931 2088 _perms = ('repository.admin',)
1932 2089 validate_repo_permissions(apiuser, repoid, repo, _perms)
1933 2090
1934 2091 user_group = get_user_group_or_error(usergroupid)
1935 2092 if not has_superadmin_permission(apiuser):
1936 2093 # check if we have at least read permission for this user group !
1937 2094 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1938 2095 if not HasUserGroupPermissionAnyApi(*_perms)(
1939 2096 user=apiuser, user_group_name=user_group.users_group_name):
1940 2097 raise JSONRPCError(
1941 2098 'user group `%s` does not exist' % (usergroupid,))
1942 2099
1943 2100 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1944 2101 try:
1945 2102 changes = RepoModel().update_permissions(
1946 2103 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1947 2104 action_data = {
1948 2105 'added': changes['added'],
1949 2106 'updated': changes['updated'],
1950 2107 'deleted': changes['deleted'],
1951 2108 }
1952 2109 audit_logger.store_api(
1953 2110 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1954 2111 Session().commit()
1955 2112 PermissionModel().flush_user_permission_caches(changes)
1956 2113
1957 2114 return {
1958 2115 'msg': 'Granted perm: `%s` for user group: `%s` in '
1959 2116 'repo: `%s`' % (
1960 2117 perm.permission_name, user_group.users_group_name,
1961 2118 repo.repo_name
1962 2119 ),
1963 2120 'success': True
1964 2121 }
1965 2122 except Exception:
1966 2123 log.exception(
1967 2124 "Exception occurred while trying change permission on repo")
1968 2125 raise JSONRPCError(
1969 2126 'failed to edit permission for user group: `%s` in '
1970 2127 'repo: `%s`' % (
1971 2128 usergroupid, repo.repo_name
1972 2129 )
1973 2130 )
1974 2131
1975 2132
1976 2133 @jsonrpc_method()
1977 2134 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1978 2135 """
1979 2136 Revoke the permissions of a user group on a given repository.
1980 2137
1981 2138 This command can only be run using an |authtoken| with admin
1982 2139 permissions on the |repo|.
1983 2140
1984 2141 :param apiuser: This is filled automatically from the |authtoken|.
1985 2142 :type apiuser: AuthUser
1986 2143 :param repoid: Set the repository name or repository ID.
1987 2144 :type repoid: str or int
1988 2145 :param usergroupid: Specify the user group ID.
1989 2146 :type usergroupid: str or int
1990 2147
1991 2148 Example output:
1992 2149
1993 2150 .. code-block:: bash
1994 2151
1995 2152 id : <id_given_in_input>
1996 2153 result: {
1997 2154 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1998 2155 "success": true
1999 2156 }
2000 2157 error: null
2001 2158 """
2002 2159
2003 2160 repo = get_repo_or_error(repoid)
2004 2161 if not has_superadmin_permission(apiuser):
2005 2162 _perms = ('repository.admin',)
2006 2163 validate_repo_permissions(apiuser, repoid, repo, _perms)
2007 2164
2008 2165 user_group = get_user_group_or_error(usergroupid)
2009 2166 if not has_superadmin_permission(apiuser):
2010 2167 # check if we have at least read permission for this user group !
2011 2168 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2012 2169 if not HasUserGroupPermissionAnyApi(*_perms)(
2013 2170 user=apiuser, user_group_name=user_group.users_group_name):
2014 2171 raise JSONRPCError(
2015 2172 'user group `%s` does not exist' % (usergroupid,))
2016 2173
2017 2174 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2018 2175 try:
2019 2176 changes = RepoModel().update_permissions(
2020 2177 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2021 2178 action_data = {
2022 2179 'added': changes['added'],
2023 2180 'updated': changes['updated'],
2024 2181 'deleted': changes['deleted'],
2025 2182 }
2026 2183 audit_logger.store_api(
2027 2184 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2028 2185 Session().commit()
2029 2186 PermissionModel().flush_user_permission_caches(changes)
2030 2187
2031 2188 return {
2032 2189 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2033 2190 user_group.users_group_name, repo.repo_name
2034 2191 ),
2035 2192 'success': True
2036 2193 }
2037 2194 except Exception:
2038 2195 log.exception("Exception occurred while trying revoke "
2039 2196 "user group permission on repo")
2040 2197 raise JSONRPCError(
2041 2198 'failed to edit permission for user group: `%s` in '
2042 2199 'repo: `%s`' % (
2043 2200 user_group.users_group_name, repo.repo_name
2044 2201 )
2045 2202 )
2046 2203
2047 2204
2048 2205 @jsonrpc_method()
2049 2206 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2050 2207 """
2051 2208 Triggers a pull on the given repository from a remote location. You
2052 2209 can use this to keep remote repositories up-to-date.
2053 2210
2054 2211 This command can only be run using an |authtoken| with admin
2055 2212 rights to the specified repository. For more information,
2056 2213 see :ref:`config-token-ref`.
2057 2214
2058 2215 This command takes the following options:
2059 2216
2060 2217 :param apiuser: This is filled automatically from the |authtoken|.
2061 2218 :type apiuser: AuthUser
2062 2219 :param repoid: The repository name or repository ID.
2063 2220 :type repoid: str or int
2064 2221 :param remote_uri: Optional remote URI to pass in for pull
2065 2222 :type remote_uri: str
2066 2223
2067 2224 Example output:
2068 2225
2069 2226 .. code-block:: bash
2070 2227
2071 2228 id : <id_given_in_input>
2072 2229 result : {
2073 2230 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2074 2231 "repository": "<repository name>"
2075 2232 }
2076 2233 error : null
2077 2234
2078 2235 Example error output:
2079 2236
2080 2237 .. code-block:: bash
2081 2238
2082 2239 id : <id_given_in_input>
2083 2240 result : null
2084 2241 error : {
2085 2242 "Unable to push changes from `<remote_url>`"
2086 2243 }
2087 2244
2088 2245 """
2089 2246
2090 2247 repo = get_repo_or_error(repoid)
2091 2248 remote_uri = Optional.extract(remote_uri)
2092 2249 remote_uri_display = remote_uri or repo.clone_uri_hidden
2093 2250 if not has_superadmin_permission(apiuser):
2094 2251 _perms = ('repository.admin',)
2095 2252 validate_repo_permissions(apiuser, repoid, repo, _perms)
2096 2253
2097 2254 try:
2098 2255 ScmModel().pull_changes(
2099 2256 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2100 2257 return {
2101 2258 'msg': 'Pulled from url `%s` on repo `%s`' % (
2102 2259 remote_uri_display, repo.repo_name),
2103 2260 'repository': repo.repo_name
2104 2261 }
2105 2262 except Exception:
2106 2263 log.exception("Exception occurred while trying to "
2107 2264 "pull changes from remote location")
2108 2265 raise JSONRPCError(
2109 2266 'Unable to pull changes from `%s`' % remote_uri_display
2110 2267 )
2111 2268
2112 2269
2113 2270 @jsonrpc_method()
2114 2271 def strip(request, apiuser, repoid, revision, branch):
2115 2272 """
2116 2273 Strips the given revision from the specified repository.
2117 2274
2118 2275 * This will remove the revision and all of its decendants.
2119 2276
2120 2277 This command can only be run using an |authtoken| with admin rights to
2121 2278 the specified repository.
2122 2279
2123 2280 This command takes the following options:
2124 2281
2125 2282 :param apiuser: This is filled automatically from the |authtoken|.
2126 2283 :type apiuser: AuthUser
2127 2284 :param repoid: The repository name or repository ID.
2128 2285 :type repoid: str or int
2129 2286 :param revision: The revision you wish to strip.
2130 2287 :type revision: str
2131 2288 :param branch: The branch from which to strip the revision.
2132 2289 :type branch: str
2133 2290
2134 2291 Example output:
2135 2292
2136 2293 .. code-block:: bash
2137 2294
2138 2295 id : <id_given_in_input>
2139 2296 result : {
2140 2297 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2141 2298 "repository": "<repository name>"
2142 2299 }
2143 2300 error : null
2144 2301
2145 2302 Example error output:
2146 2303
2147 2304 .. code-block:: bash
2148 2305
2149 2306 id : <id_given_in_input>
2150 2307 result : null
2151 2308 error : {
2152 2309 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2153 2310 }
2154 2311
2155 2312 """
2156 2313
2157 2314 repo = get_repo_or_error(repoid)
2158 2315 if not has_superadmin_permission(apiuser):
2159 2316 _perms = ('repository.admin',)
2160 2317 validate_repo_permissions(apiuser, repoid, repo, _perms)
2161 2318
2162 2319 try:
2163 2320 ScmModel().strip(repo, revision, branch)
2164 2321 audit_logger.store_api(
2165 2322 'repo.commit.strip', action_data={'commit_id': revision},
2166 2323 repo=repo,
2167 2324 user=apiuser, commit=True)
2168 2325
2169 2326 return {
2170 2327 'msg': 'Stripped commit %s from repo `%s`' % (
2171 2328 revision, repo.repo_name),
2172 2329 'repository': repo.repo_name
2173 2330 }
2174 2331 except Exception:
2175 2332 log.exception("Exception while trying to strip")
2176 2333 raise JSONRPCError(
2177 2334 'Unable to strip commit %s from repo `%s`' % (
2178 2335 revision, repo.repo_name)
2179 2336 )
2180 2337
2181 2338
2182 2339 @jsonrpc_method()
2183 2340 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2184 2341 """
2185 2342 Returns all settings for a repository. If key is given it only returns the
2186 2343 setting identified by the key or null.
2187 2344
2188 2345 :param apiuser: This is filled automatically from the |authtoken|.
2189 2346 :type apiuser: AuthUser
2190 2347 :param repoid: The repository name or repository id.
2191 2348 :type repoid: str or int
2192 2349 :param key: Key of the setting to return.
2193 2350 :type: key: Optional(str)
2194 2351
2195 2352 Example output:
2196 2353
2197 2354 .. code-block:: bash
2198 2355
2199 2356 {
2200 2357 "error": null,
2201 2358 "id": 237,
2202 2359 "result": {
2203 2360 "extensions_largefiles": true,
2204 2361 "extensions_evolve": true,
2205 2362 "hooks_changegroup_push_logger": true,
2206 2363 "hooks_changegroup_repo_size": false,
2207 2364 "hooks_outgoing_pull_logger": true,
2208 2365 "phases_publish": "True",
2209 2366 "rhodecode_hg_use_rebase_for_merging": true,
2210 2367 "rhodecode_pr_merge_enabled": true,
2211 2368 "rhodecode_use_outdated_comments": true
2212 2369 }
2213 2370 }
2214 2371 """
2215 2372
2216 2373 # Restrict access to this api method to admins only.
2217 2374 if not has_superadmin_permission(apiuser):
2218 2375 raise JSONRPCForbidden()
2219 2376
2220 2377 try:
2221 2378 repo = get_repo_or_error(repoid)
2222 2379 settings_model = VcsSettingsModel(repo=repo)
2223 2380 settings = settings_model.get_global_settings()
2224 2381 settings.update(settings_model.get_repo_settings())
2225 2382
2226 2383 # If only a single setting is requested fetch it from all settings.
2227 2384 key = Optional.extract(key)
2228 2385 if key is not None:
2229 2386 settings = settings.get(key, None)
2230 2387 except Exception:
2231 2388 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2232 2389 log.exception(msg)
2233 2390 raise JSONRPCError(msg)
2234 2391
2235 2392 return settings
2236 2393
2237 2394
2238 2395 @jsonrpc_method()
2239 2396 def set_repo_settings(request, apiuser, repoid, settings):
2240 2397 """
2241 2398 Update repository settings. Returns true on success.
2242 2399
2243 2400 :param apiuser: This is filled automatically from the |authtoken|.
2244 2401 :type apiuser: AuthUser
2245 2402 :param repoid: The repository name or repository id.
2246 2403 :type repoid: str or int
2247 2404 :param settings: The new settings for the repository.
2248 2405 :type: settings: dict
2249 2406
2250 2407 Example output:
2251 2408
2252 2409 .. code-block:: bash
2253 2410
2254 2411 {
2255 2412 "error": null,
2256 2413 "id": 237,
2257 2414 "result": true
2258 2415 }
2259 2416 """
2260 2417 # Restrict access to this api method to admins only.
2261 2418 if not has_superadmin_permission(apiuser):
2262 2419 raise JSONRPCForbidden()
2263 2420
2264 2421 if type(settings) is not dict:
2265 2422 raise JSONRPCError('Settings have to be a JSON Object.')
2266 2423
2267 2424 try:
2268 2425 settings_model = VcsSettingsModel(repo=repoid)
2269 2426
2270 2427 # Merge global, repo and incoming settings.
2271 2428 new_settings = settings_model.get_global_settings()
2272 2429 new_settings.update(settings_model.get_repo_settings())
2273 2430 new_settings.update(settings)
2274 2431
2275 2432 # Update the settings.
2276 2433 inherit_global_settings = new_settings.get(
2277 2434 'inherit_global_settings', False)
2278 2435 settings_model.create_or_update_repo_settings(
2279 2436 new_settings, inherit_global_settings=inherit_global_settings)
2280 2437 Session().commit()
2281 2438 except Exception:
2282 2439 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2283 2440 log.exception(msg)
2284 2441 raise JSONRPCError(msg)
2285 2442
2286 2443 # Indicate success.
2287 2444 return True
2288 2445
2289 2446
2290 2447 @jsonrpc_method()
2291 2448 def maintenance(request, apiuser, repoid):
2292 2449 """
2293 2450 Triggers a maintenance on the given repository.
2294 2451
2295 2452 This command can only be run using an |authtoken| with admin
2296 2453 rights to the specified repository. For more information,
2297 2454 see :ref:`config-token-ref`.
2298 2455
2299 2456 This command takes the following options:
2300 2457
2301 2458 :param apiuser: This is filled automatically from the |authtoken|.
2302 2459 :type apiuser: AuthUser
2303 2460 :param repoid: The repository name or repository ID.
2304 2461 :type repoid: str or int
2305 2462
2306 2463 Example output:
2307 2464
2308 2465 .. code-block:: bash
2309 2466
2310 2467 id : <id_given_in_input>
2311 2468 result : {
2312 2469 "msg": "executed maintenance command",
2313 2470 "executed_actions": [
2314 2471 <action_message>, <action_message2>...
2315 2472 ],
2316 2473 "repository": "<repository name>"
2317 2474 }
2318 2475 error : null
2319 2476
2320 2477 Example error output:
2321 2478
2322 2479 .. code-block:: bash
2323 2480
2324 2481 id : <id_given_in_input>
2325 2482 result : null
2326 2483 error : {
2327 2484 "Unable to execute maintenance on `<reponame>`"
2328 2485 }
2329 2486
2330 2487 """
2331 2488
2332 2489 repo = get_repo_or_error(repoid)
2333 2490 if not has_superadmin_permission(apiuser):
2334 2491 _perms = ('repository.admin',)
2335 2492 validate_repo_permissions(apiuser, repoid, repo, _perms)
2336 2493
2337 2494 try:
2338 2495 maintenance = repo_maintenance.RepoMaintenance()
2339 2496 executed_actions = maintenance.execute(repo)
2340 2497
2341 2498 return {
2342 2499 'msg': 'executed maintenance command',
2343 2500 'executed_actions': executed_actions,
2344 2501 'repository': repo.repo_name
2345 2502 }
2346 2503 except Exception:
2347 2504 log.exception("Exception occurred while trying to run maintenance")
2348 2505 raise JSONRPCError(
2349 2506 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,360 +1,374 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import datetime
21 21 import logging
22 22 import time
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26
27 27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 28 from pyramid.view import view_config
29 29 from pyramid.renderers import render
30 30 from pyramid.response import Response
31 31
32 32 from rhodecode import events
33 33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 34
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, CSRFRequired, NotAnonymous,
37 37 HasPermissionAny, HasRepoGroupPermissionAny)
38 38 from rhodecode.lib import helpers as h, audit_logger
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 40 from rhodecode.model.forms import RepoGroupForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo_group import RepoGroupModel
43 43 from rhodecode.model.scm import RepoGroupList
44 44 from rhodecode.model.db import (
45 45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 return c
56 56
57 57 def _load_form_data(self, c):
58 58 allow_empty_group = False
59 59
60 60 if self._can_create_repo_group():
61 61 # we're global admin, we're ok and we can create TOP level groups
62 62 allow_empty_group = True
63 63
64 64 # override the choices for this form, we need to filter choices
65 65 # and display only those we have ADMIN right
66 66 groups_with_admin_rights = RepoGroupList(
67 67 RepoGroup.query().all(),
68 68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 69 c.repo_groups = RepoGroup.groups_choices(
70 70 groups=groups_with_admin_rights,
71 71 show_empty_group=allow_empty_group)
72 c.personal_repo_group = self._rhodecode_user.personal_repo_group
72 73
73 74 def _can_create_repo_group(self, parent_group_id=None):
74 75 is_admin = HasPermissionAny('hg.admin')('group create controller')
75 76 create_repo_group = HasPermissionAny(
76 77 'hg.repogroup.create.true')('group create controller')
77 78 if is_admin or (create_repo_group and not parent_group_id):
78 79 # we're global admin, or we have global repo group create
79 80 # permission
80 81 # we're ok and we can create TOP level groups
81 82 return True
82 83 elif parent_group_id:
83 84 # we check the permission if we can write to parent group
84 85 group = RepoGroup.get(parent_group_id)
85 86 group_name = group.group_name if group else None
86 87 if HasRepoGroupPermissionAny('group.admin')(
87 88 group_name, 'check if user is an admin of group'):
88 89 # we're an admin of passed in group, we're ok.
89 90 return True
90 91 else:
91 92 return False
92 93 return False
93 94
94 95 # permission check in data loading of
95 96 # `repo_group_list_data` via RepoGroupList
96 97 @LoginRequired()
97 98 @NotAnonymous()
98 99 @view_config(
99 100 route_name='repo_groups', request_method='GET',
100 101 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
101 102 def repo_group_list(self):
102 103 c = self.load_default_context()
103 104 return self._get_template_context(c)
104 105
105 106 # permission check inside
106 107 @LoginRequired()
107 108 @NotAnonymous()
108 109 @view_config(
109 110 route_name='repo_groups_data', request_method='GET',
110 111 renderer='json_ext', xhr=True)
111 112 def repo_group_list_data(self):
112 113 self.load_default_context()
113 114 column_map = {
114 115 'name': 'group_name_hash',
115 116 'desc': 'group_description',
116 117 'last_change': 'updated_on',
117 118 'top_level_repos': 'repos_total',
118 119 'owner': 'user_username',
119 120 }
120 121 draw, start, limit = self._extract_chunk(self.request)
121 122 search_q, order_by, order_dir = self._extract_ordering(
122 123 self.request, column_map=column_map)
123 124
124 125 _render = self.request.get_partial_renderer(
125 126 'rhodecode:templates/data_table/_dt_elements.mako')
126 127 c = _render.get_call_context()
127 128
128 129 def quick_menu(repo_group_name):
129 130 return _render('quick_repo_group_menu', repo_group_name)
130 131
131 132 def repo_group_lnk(repo_group_name):
132 133 return _render('repo_group_name', repo_group_name)
133 134
134 135 def last_change(last_change):
135 136 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
136 137 ts = time.time()
137 138 utc_offset = (datetime.datetime.fromtimestamp(ts)
138 139 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
139 140 last_change = last_change + datetime.timedelta(seconds=utc_offset)
140 141 return _render("last_change", last_change)
141 142
142 143 def desc(desc, personal):
143 144 return _render(
144 145 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
145 146
146 147 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
147 148 return _render(
148 149 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
149 150
150 151 def user_profile(username):
151 152 return _render('user_profile', username)
152 153
153 154 _perms = ['group.admin']
154 155 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
155 156
156 157 repo_groups_data_total_count = RepoGroup.query()\
157 158 .filter(or_(
158 159 # generate multiple IN to fix limitation problems
159 160 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 161 )) \
161 162 .count()
162 163
163 164 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 165 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 166 .count()
166 167
167 168 repo_count = count(Repository.repo_id)
168 169 base_q = Session.query(
169 170 RepoGroup.group_name,
170 171 RepoGroup.group_name_hash,
171 172 RepoGroup.group_description,
172 173 RepoGroup.group_id,
173 174 RepoGroup.personal,
174 175 RepoGroup.updated_on,
175 176 User,
176 177 repo_count.label('repos_count')
177 178 ) \
178 179 .filter(or_(
179 180 # generate multiple IN to fix limitation problems
180 181 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 182 )) \
182 183 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
183 184 .join(User, User.user_id == RepoGroup.user_id) \
184 185 .group_by(RepoGroup, User)
185 186
186 187 if search_q:
187 188 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 189 base_q = base_q.filter(or_(
189 190 RepoGroup.group_name.ilike(like_expression),
190 191 ))
191 192
192 193 repo_groups_data_total_filtered_count = base_q.count()
193 194 # the inactive isn't really used, but we still make it same as other data grids
194 195 # which use inactive (users,user groups)
195 196 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196 197
197 198 sort_defined = False
198 199 if order_by == 'group_name':
199 200 sort_col = func.lower(RepoGroup.group_name)
200 201 sort_defined = True
201 202 elif order_by == 'repos_total':
202 203 sort_col = repo_count
203 204 sort_defined = True
204 205 elif order_by == 'user_username':
205 206 sort_col = User.username
206 207 else:
207 208 sort_col = getattr(RepoGroup, order_by, None)
208 209
209 210 if sort_defined or sort_col:
210 211 if order_dir == 'asc':
211 212 sort_col = sort_col.asc()
212 213 else:
213 214 sort_col = sort_col.desc()
214 215
215 216 base_q = base_q.order_by(sort_col)
216 217 base_q = base_q.offset(start).limit(limit)
217 218
218 219 # authenticated access to user groups
219 220 auth_repo_group_list = base_q.all()
220 221
221 222 repo_groups_data = []
222 223 for repo_gr in auth_repo_group_list:
223 224 row = {
224 225 "menu": quick_menu(repo_gr.group_name),
225 226 "name": repo_group_lnk(repo_gr.group_name),
226 227
227 228 "last_change": last_change(repo_gr.updated_on),
228 229
229 230 "last_changeset": "",
230 231 "last_changeset_raw": "",
231 232
232 233 "desc": desc(repo_gr.group_description, repo_gr.personal),
233 234 "owner": user_profile(repo_gr.User.username),
234 235 "top_level_repos": repo_gr.repos_count,
235 236 "action": repo_group_actions(
236 237 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
237 238
238 239 }
239 240
240 241 repo_groups_data.append(row)
241 242
242 243 data = ({
243 244 'draw': draw,
244 245 'data': repo_groups_data,
245 246 'recordsTotal': repo_groups_data_total_count,
246 247 'recordsTotalInactive': repo_groups_data_total_inactive_count,
247 248 'recordsFiltered': repo_groups_data_total_filtered_count,
248 249 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
249 250 })
250 251
251 252 return data
252 253
253 254 @LoginRequired()
254 255 @NotAnonymous()
255 256 # perm checks inside
256 257 @view_config(
257 258 route_name='repo_group_new', request_method='GET',
258 259 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
259 260 def repo_group_new(self):
260 261 c = self.load_default_context()
261 262
262 263 # perm check for admin, create_group perm or admin of parent_group
263 264 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 _gr = RepoGroup.get(parent_group_id)
264 266 if not self._can_create_repo_group(parent_group_id):
265 267 raise HTTPForbidden()
266 268
267 269 self._load_form_data(c)
268 270
269 271 defaults = {} # Future proof for default of repo group
272
273 parent_group_choice = '-1'
274 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
275 parent_group_choice = self._rhodecode_user.personal_repo_group
276
277 if parent_group_id and _gr:
278 if parent_group_id in [x[0] for x in c.repo_groups]:
279 parent_group_choice = safe_unicode(parent_group_id)
280
281 defaults.update({'group_parent_id': parent_group_choice})
282
270 283 data = render(
271 284 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
272 285 self._get_template_context(c), self.request)
286
273 287 html = formencode.htmlfill.render(
274 288 data,
275 289 defaults=defaults,
276 290 encoding="UTF-8",
277 291 force_defaults=False
278 292 )
279 293 return Response(html)
280 294
281 295 @LoginRequired()
282 296 @NotAnonymous()
283 297 @CSRFRequired()
284 298 # perm checks inside
285 299 @view_config(
286 300 route_name='repo_group_create', request_method='POST',
287 301 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
288 302 def repo_group_create(self):
289 303 c = self.load_default_context()
290 304 _ = self.request.translate
291 305
292 306 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
293 307 can_create = self._can_create_repo_group(parent_group_id)
294 308
295 309 self._load_form_data(c)
296 310 # permissions for can create group based on parent_id are checked
297 311 # here in the Form
298 312 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
299 313 repo_group_form = RepoGroupForm(
300 314 self.request.translate, available_groups=available_groups,
301 315 can_create_in_root=can_create)()
302 316
303 317 repo_group_name = self.request.POST.get('group_name')
304 318 try:
305 319 owner = self._rhodecode_user
306 320 form_result = repo_group_form.to_python(dict(self.request.POST))
307 321 copy_permissions = form_result.get('group_copy_permissions')
308 322 repo_group = RepoGroupModel().create(
309 323 group_name=form_result['group_name_full'],
310 324 group_description=form_result['group_description'],
311 325 owner=owner.user_id,
312 326 copy_permissions=form_result['group_copy_permissions']
313 327 )
314 328 Session().flush()
315 329
316 330 repo_group_data = repo_group.get_api_data()
317 331 audit_logger.store_web(
318 332 'repo_group.create', action_data={'data': repo_group_data},
319 333 user=self._rhodecode_user)
320 334
321 335 Session().commit()
322 336
323 337 _new_group_name = form_result['group_name_full']
324 338
325 339 repo_group_url = h.link_to(
326 340 _new_group_name,
327 341 h.route_path('repo_group_home', repo_group_name=_new_group_name))
328 342 h.flash(h.literal(_('Created repository group %s')
329 343 % repo_group_url), category='success')
330 344
331 345 except formencode.Invalid as errors:
332 346 data = render(
333 347 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
334 348 self._get_template_context(c), self.request)
335 349 html = formencode.htmlfill.render(
336 350 data,
337 351 defaults=errors.value,
338 352 errors=errors.error_dict or {},
339 353 prefix_error=False,
340 354 encoding="UTF-8",
341 355 force_defaults=False
342 356 )
343 357 return Response(html)
344 358 except Exception:
345 359 log.exception("Exception during creation of repository group")
346 360 h.flash(_('Error occurred during creation of repository group %s')
347 361 % repo_group_name, category='error')
348 362 raise HTTPFound(h.route_path('home'))
349 363
350 364 affected_user_ids = [self._rhodecode_user.user_id]
351 365 if copy_permissions:
352 366 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
353 367 copy_perms = [perm['user_id'] for perm in user_group_perms]
354 368 # also include those newly created by copy
355 369 affected_user_ids.extend(copy_perms)
356 370 PermissionModel().trigger_permission_flush(affected_user_ids)
357 371
358 372 raise HTTPFound(
359 373 h.route_path('repo_group_home',
360 374 repo_group_name=form_result['group_name_full']))
@@ -1,266 +1,266 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import formencode
23 23 import formencode.htmlfill
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.celerylib.utils import get_task_id
33 33
34 34 from rhodecode.lib.auth import (
35 35 LoginRequired, CSRFRequired, NotAnonymous,
36 36 HasPermissionAny, HasRepoGroupPermissionAny)
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 40 from rhodecode.model.forms import RepoForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 44 from rhodecode.model.settings import SettingsModel
45 45 from rhodecode.model.db import (
46 46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class AdminReposView(BaseAppView, DataGridAppView):
52 52
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55 55
56 56 return c
57 57
58 58 def _load_form_data(self, c):
59 59 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 60 perm_set=['group.write', 'group.admin'])
61 61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
63 63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
64 64
65 65 @LoginRequired()
66 66 @NotAnonymous()
67 67 # perms check inside
68 68 @view_config(
69 69 route_name='repos', request_method='GET',
70 70 renderer='rhodecode:templates/admin/repos/repos.mako')
71 71 def repository_list(self):
72 72 c = self.load_default_context()
73 73 return self._get_template_context(c)
74 74
75 75 @LoginRequired()
76 76 @NotAnonymous()
77 77 # perms check inside
78 78 @view_config(
79 79 route_name='repos_data', request_method='GET',
80 80 renderer='json_ext', xhr=True)
81 81 def repository_list_data(self):
82 82 self.load_default_context()
83 83 column_map = {
84 84 'name': 'repo_name',
85 85 'desc': 'description',
86 86 'last_change': 'updated_on',
87 87 'owner': 'user_username',
88 88 }
89 89 draw, start, limit = self._extract_chunk(self.request)
90 90 search_q, order_by, order_dir = self._extract_ordering(
91 91 self.request, column_map=column_map)
92 92
93 93 _perms = ['repository.admin']
94 94 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
95 95
96 96 repos_data_total_count = Repository.query() \
97 97 .filter(or_(
98 98 # generate multiple IN to fix limitation problems
99 99 *in_filter_generator(Repository.repo_id, allowed_ids))
100 100 ) \
101 101 .count()
102 102
103 103 base_q = Session.query(
104 104 Repository.repo_id,
105 105 Repository.repo_name,
106 106 Repository.description,
107 107 Repository.repo_type,
108 108 Repository.repo_state,
109 109 Repository.private,
110 110 Repository.archived,
111 111 Repository.fork,
112 112 Repository.updated_on,
113 113 Repository._changeset_cache,
114 114 User,
115 115 ) \
116 116 .filter(or_(
117 117 # generate multiple IN to fix limitation problems
118 118 *in_filter_generator(Repository.repo_id, allowed_ids))
119 119 ) \
120 120 .join(User, User.user_id == Repository.user_id) \
121 121 .group_by(Repository, User)
122 122
123 123 if search_q:
124 124 like_expression = u'%{}%'.format(safe_unicode(search_q))
125 125 base_q = base_q.filter(or_(
126 126 Repository.repo_name.ilike(like_expression),
127 127 ))
128 128
129 129 repos_data_total_filtered_count = base_q.count()
130 130
131 131 sort_defined = False
132 132 if order_by == 'repo_name':
133 133 sort_col = func.lower(Repository.repo_name)
134 134 sort_defined = True
135 135 elif order_by == 'user_username':
136 136 sort_col = User.username
137 137 else:
138 138 sort_col = getattr(Repository, order_by, None)
139 139
140 140 if sort_defined or sort_col:
141 141 if order_dir == 'asc':
142 142 sort_col = sort_col.asc()
143 143 else:
144 144 sort_col = sort_col.desc()
145 145
146 146 base_q = base_q.order_by(sort_col)
147 147 base_q = base_q.offset(start).limit(limit)
148 148
149 149 repos_list = base_q.all()
150 150
151 151 repos_data = RepoModel().get_repos_as_dict(
152 152 repo_list=repos_list, admin=True, super_user_actions=True)
153 153
154 154 data = ({
155 155 'draw': draw,
156 156 'data': repos_data,
157 157 'recordsTotal': repos_data_total_count,
158 158 'recordsFiltered': repos_data_total_filtered_count,
159 159 })
160 160 return data
161 161
162 162 @LoginRequired()
163 163 @NotAnonymous()
164 164 # perms check inside
165 165 @view_config(
166 166 route_name='repo_new', request_method='GET',
167 167 renderer='rhodecode:templates/admin/repos/repo_add.mako')
168 168 def repository_new(self):
169 169 c = self.load_default_context()
170 170
171 171 new_repo = self.request.GET.get('repo', '')
172 parent_group = safe_int(self.request.GET.get('parent_group'))
173 _gr = RepoGroup.get(parent_group)
172 parent_group_id = safe_int(self.request.GET.get('parent_group'))
173 _gr = RepoGroup.get(parent_group_id)
174 174
175 175 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
176 176 # you're not super admin nor have global create permissions,
177 177 # but maybe you have at least write permission to a parent group ?
178 178
179 179 gr_name = _gr.group_name if _gr else None
180 180 # create repositories with write permission on group is set to true
181 181 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
182 182 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
183 183 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
184 184 if not (group_admin or (group_write and create_on_write)):
185 185 raise HTTPForbidden()
186 186
187 187 self._load_form_data(c)
188 188 c.new_repo = repo_name_slug(new_repo)
189 189
190 190 # apply the defaults from defaults page
191 191 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
192 192 # set checkbox to autochecked
193 193 defaults['repo_copy_permissions'] = True
194 194
195 195 parent_group_choice = '-1'
196 196 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
197 197 parent_group_choice = self._rhodecode_user.personal_repo_group
198 198
199 if parent_group and _gr:
200 if parent_group in [x[0] for x in c.repo_groups]:
201 parent_group_choice = safe_unicode(parent_group)
199 if parent_group_id and _gr:
200 if parent_group_id in [x[0] for x in c.repo_groups]:
201 parent_group_choice = safe_unicode(parent_group_id)
202 202
203 203 defaults.update({'repo_group': parent_group_choice})
204 204
205 205 data = render('rhodecode:templates/admin/repos/repo_add.mako',
206 206 self._get_template_context(c), self.request)
207 207 html = formencode.htmlfill.render(
208 208 data,
209 209 defaults=defaults,
210 210 encoding="UTF-8",
211 211 force_defaults=False
212 212 )
213 213 return Response(html)
214 214
215 215 @LoginRequired()
216 216 @NotAnonymous()
217 217 @CSRFRequired()
218 218 # perms check inside
219 219 @view_config(
220 220 route_name='repo_create', request_method='POST',
221 221 renderer='rhodecode:templates/admin/repos/repos.mako')
222 222 def repository_create(self):
223 223 c = self.load_default_context()
224 224
225 225 form_result = {}
226 226 self._load_form_data(c)
227 227
228 228 try:
229 229 # CanWriteToGroup validators checks permissions of this POST
230 230 form = RepoForm(
231 231 self.request.translate, repo_groups=c.repo_groups_choices)()
232 232 form_result = form.to_python(dict(self.request.POST))
233 233 copy_permissions = form_result.get('repo_copy_permissions')
234 234 # create is done sometimes async on celery, db transaction
235 235 # management is handled there.
236 236 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
237 237 task_id = get_task_id(task)
238 238 except formencode.Invalid as errors:
239 239 data = render('rhodecode:templates/admin/repos/repo_add.mako',
240 240 self._get_template_context(c), self.request)
241 241 html = formencode.htmlfill.render(
242 242 data,
243 243 defaults=errors.value,
244 244 errors=errors.error_dict or {},
245 245 prefix_error=False,
246 246 encoding="UTF-8",
247 247 force_defaults=False
248 248 )
249 249 return Response(html)
250 250
251 251 except Exception as e:
252 252 msg = self._log_creation_exception(e, form_result.get('repo_name'))
253 253 h.flash(msg, category='error')
254 254 raise HTTPFound(h.route_path('home'))
255 255
256 256 repo_name = form_result.get('repo_name_full')
257 257
258 258 affected_user_ids = [self._rhodecode_user.user_id]
259 259 if copy_permissions:
260 260 # permission flush is done in repo creating
261 261 pass
262 262 PermissionModel().trigger_permission_flush(affected_user_ids)
263 263
264 264 raise HTTPFound(
265 265 h.route_path('repo_creating', repo_name=repo_name,
266 266 _query=dict(task_id=task_id)))
@@ -1,782 +1,783 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import collections
24 24
25 25 import datetime
26 26 import formencode
27 27 import formencode.htmlfill
28 28
29 29 import rhodecode
30 30 from pyramid.view import view_config
31 31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 from rhodecode.apps._base import BaseAppView
36 36 from rhodecode.apps._base.navigation import navigation_list
37 37 from rhodecode.apps.svn_support.config_keys import generate_config
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 41 from rhodecode.lib.celerylib import tasks, run_task
42 42 from rhodecode.lib.utils import repo2db_mapper
43 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 44 from rhodecode.lib.index import searcher_from_config
45 45
46 46 from rhodecode.model.db import RhodeCodeUi, Repository
47 47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 49 LabsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.permission import PermissionModel
50 51 from rhodecode.model.repo_group import RepoGroupModel
51 52
52 53 from rhodecode.model.scm import ScmModel
53 54 from rhodecode.model.notification import EmailNotificationModel
54 55 from rhodecode.model.meta import Session
55 56 from rhodecode.model.settings import (
56 57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 58 SettingsModel)
58 59
59 60
60 61 log = logging.getLogger(__name__)
61 62
62 63
63 64 class AdminSettingsView(BaseAppView):
64 65
65 66 def load_default_context(self):
66 67 c = self._get_local_tmpl_context()
67 68 c.labs_active = str2bool(
68 69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 70 c.navlist = navigation_list(self.request)
70 71
71 72 return c
72 73
73 74 @classmethod
74 75 def _get_ui_settings(cls):
75 76 ret = RhodeCodeUi.query().all()
76 77
77 78 if not ret:
78 79 raise Exception('Could not get application ui settings !')
79 80 settings = {}
80 81 for each in ret:
81 82 k = each.ui_key
82 83 v = each.ui_value
83 84 if k == '/':
84 85 k = 'root_path'
85 86
86 87 if k in ['push_ssl', 'publish', 'enabled']:
87 88 v = str2bool(v)
88 89
89 90 if k.find('.') != -1:
90 91 k = k.replace('.', '_')
91 92
92 93 if each.ui_section in ['hooks', 'extensions']:
93 94 v = each.ui_active
94 95
95 96 settings[each.ui_section + '_' + k] = v
96 97 return settings
97 98
98 99 @classmethod
99 100 def _form_defaults(cls):
100 101 defaults = SettingsModel().get_all_settings()
101 102 defaults.update(cls._get_ui_settings())
102 103
103 104 defaults.update({
104 105 'new_svn_branch': '',
105 106 'new_svn_tag': '',
106 107 })
107 108 return defaults
108 109
109 110 @LoginRequired()
110 111 @HasPermissionAllDecorator('hg.admin')
111 112 @view_config(
112 113 route_name='admin_settings_vcs', request_method='GET',
113 114 renderer='rhodecode:templates/admin/settings/settings.mako')
114 115 def settings_vcs(self):
115 116 c = self.load_default_context()
116 117 c.active = 'vcs'
117 118 model = VcsSettingsModel()
118 119 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 120 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120 121
121 122 settings = self.request.registry.settings
122 123 c.svn_proxy_generate_config = settings[generate_config]
123 124
124 125 defaults = self._form_defaults()
125 126
126 127 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127 128
128 129 data = render('rhodecode:templates/admin/settings/settings.mako',
129 130 self._get_template_context(c), self.request)
130 131 html = formencode.htmlfill.render(
131 132 data,
132 133 defaults=defaults,
133 134 encoding="UTF-8",
134 135 force_defaults=False
135 136 )
136 137 return Response(html)
137 138
138 139 @LoginRequired()
139 140 @HasPermissionAllDecorator('hg.admin')
140 141 @CSRFRequired()
141 142 @view_config(
142 143 route_name='admin_settings_vcs_update', request_method='POST',
143 144 renderer='rhodecode:templates/admin/settings/settings.mako')
144 145 def settings_vcs_update(self):
145 146 _ = self.request.translate
146 147 c = self.load_default_context()
147 148 c.active = 'vcs'
148 149
149 150 model = VcsSettingsModel()
150 151 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 152 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152 153
153 154 settings = self.request.registry.settings
154 155 c.svn_proxy_generate_config = settings[generate_config]
155 156
156 157 application_form = ApplicationUiSettingsForm(self.request.translate)()
157 158
158 159 try:
159 160 form_result = application_form.to_python(dict(self.request.POST))
160 161 except formencode.Invalid as errors:
161 162 h.flash(
162 163 _("Some form inputs contain invalid data."),
163 164 category='error')
164 165 data = render('rhodecode:templates/admin/settings/settings.mako',
165 166 self._get_template_context(c), self.request)
166 167 html = formencode.htmlfill.render(
167 168 data,
168 169 defaults=errors.value,
169 170 errors=errors.error_dict or {},
170 171 prefix_error=False,
171 172 encoding="UTF-8",
172 173 force_defaults=False
173 174 )
174 175 return Response(html)
175 176
176 177 try:
177 178 if c.visual.allow_repo_location_change:
178 179 model.update_global_path_setting(form_result['paths_root_path'])
179 180
180 181 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 182 model.update_global_hook_settings(form_result)
182 183
183 184 model.create_or_update_global_svn_settings(form_result)
184 185 model.create_or_update_global_hg_settings(form_result)
185 186 model.create_or_update_global_git_settings(form_result)
186 187 model.create_or_update_global_pr_settings(form_result)
187 188 except Exception:
188 189 log.exception("Exception while updating settings")
189 190 h.flash(_('Error occurred during updating '
190 191 'application settings'), category='error')
191 192 else:
192 193 Session().commit()
193 194 h.flash(_('Updated VCS settings'), category='success')
194 195 raise HTTPFound(h.route_path('admin_settings_vcs'))
195 196
196 197 data = render('rhodecode:templates/admin/settings/settings.mako',
197 198 self._get_template_context(c), self.request)
198 199 html = formencode.htmlfill.render(
199 200 data,
200 201 defaults=self._form_defaults(),
201 202 encoding="UTF-8",
202 203 force_defaults=False
203 204 )
204 205 return Response(html)
205 206
206 207 @LoginRequired()
207 208 @HasPermissionAllDecorator('hg.admin')
208 209 @CSRFRequired()
209 210 @view_config(
210 211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 212 renderer='json_ext', xhr=True)
212 213 def settings_vcs_delete_svn_pattern(self):
213 214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 215 model = VcsSettingsModel()
215 216 try:
216 217 model.delete_global_svn_pattern(delete_pattern_id)
217 218 except SettingNotFound:
218 219 log.exception(
219 220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 221 raise HTTPNotFound()
221 222
222 223 Session().commit()
223 224 return True
224 225
225 226 @LoginRequired()
226 227 @HasPermissionAllDecorator('hg.admin')
227 228 @view_config(
228 229 route_name='admin_settings_mapping', request_method='GET',
229 230 renderer='rhodecode:templates/admin/settings/settings.mako')
230 231 def settings_mapping(self):
231 232 c = self.load_default_context()
232 233 c.active = 'mapping'
233 234
234 235 data = render('rhodecode:templates/admin/settings/settings.mako',
235 236 self._get_template_context(c), self.request)
236 237 html = formencode.htmlfill.render(
237 238 data,
238 239 defaults=self._form_defaults(),
239 240 encoding="UTF-8",
240 241 force_defaults=False
241 242 )
242 243 return Response(html)
243 244
244 245 @LoginRequired()
245 246 @HasPermissionAllDecorator('hg.admin')
246 247 @CSRFRequired()
247 248 @view_config(
248 249 route_name='admin_settings_mapping_update', request_method='POST',
249 250 renderer='rhodecode:templates/admin/settings/settings.mako')
250 251 def settings_mapping_update(self):
251 252 _ = self.request.translate
252 253 c = self.load_default_context()
253 254 c.active = 'mapping'
254 255 rm_obsolete = self.request.POST.get('destroy', False)
255 256 invalidate_cache = self.request.POST.get('invalidate', False)
256 log.debug(
257 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
257 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
258 258
259 259 if invalidate_cache:
260 260 log.debug('invalidating all repositories cache')
261 261 for repo in Repository.get_all():
262 262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263 263
264 264 filesystem_repos = ScmModel().repo_scan()
265 265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 PermissionModel().trigger_permission_flush()
267
266 268 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 269 h.flash(_('Repositories successfully '
268 270 'rescanned added: %s ; removed: %s') %
269 271 (_repr(added), _repr(removed)),
270 272 category='success')
271 273 raise HTTPFound(h.route_path('admin_settings_mapping'))
272 274
273 275 @LoginRequired()
274 276 @HasPermissionAllDecorator('hg.admin')
275 277 @view_config(
276 278 route_name='admin_settings', request_method='GET',
277 279 renderer='rhodecode:templates/admin/settings/settings.mako')
278 280 @view_config(
279 281 route_name='admin_settings_global', request_method='GET',
280 282 renderer='rhodecode:templates/admin/settings/settings.mako')
281 283 def settings_global(self):
282 284 c = self.load_default_context()
283 285 c.active = 'global'
284 286 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 287 .get_personal_group_name_pattern()
286 288
287 289 data = render('rhodecode:templates/admin/settings/settings.mako',
288 290 self._get_template_context(c), self.request)
289 291 html = formencode.htmlfill.render(
290 292 data,
291 293 defaults=self._form_defaults(),
292 294 encoding="UTF-8",
293 295 force_defaults=False
294 296 )
295 297 return Response(html)
296 298
297 299 @LoginRequired()
298 300 @HasPermissionAllDecorator('hg.admin')
299 301 @CSRFRequired()
300 302 @view_config(
301 303 route_name='admin_settings_update', request_method='POST',
302 304 renderer='rhodecode:templates/admin/settings/settings.mako')
303 305 @view_config(
304 306 route_name='admin_settings_global_update', request_method='POST',
305 307 renderer='rhodecode:templates/admin/settings/settings.mako')
306 308 def settings_global_update(self):
307 309 _ = self.request.translate
308 310 c = self.load_default_context()
309 311 c.active = 'global'
310 312 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 313 .get_personal_group_name_pattern()
312 314 application_form = ApplicationSettingsForm(self.request.translate)()
313 315 try:
314 316 form_result = application_form.to_python(dict(self.request.POST))
315 317 except formencode.Invalid as errors:
316 318 h.flash(
317 319 _("Some form inputs contain invalid data."),
318 320 category='error')
319 321 data = render('rhodecode:templates/admin/settings/settings.mako',
320 322 self._get_template_context(c), self.request)
321 323 html = formencode.htmlfill.render(
322 324 data,
323 325 defaults=errors.value,
324 326 errors=errors.error_dict or {},
325 327 prefix_error=False,
326 328 encoding="UTF-8",
327 329 force_defaults=False
328 330 )
329 331 return Response(html)
330 332
331 333 settings = [
332 334 ('title', 'rhodecode_title', 'unicode'),
333 335 ('realm', 'rhodecode_realm', 'unicode'),
334 336 ('pre_code', 'rhodecode_pre_code', 'unicode'),
335 337 ('post_code', 'rhodecode_post_code', 'unicode'),
336 338 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
337 339 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
338 340 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
339 341 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
340 342 ]
341 343 try:
342 344 for setting, form_key, type_ in settings:
343 345 sett = SettingsModel().create_or_update_setting(
344 346 setting, form_result[form_key], type_)
345 347 Session().add(sett)
346 348
347 349 Session().commit()
348 350 SettingsModel().invalidate_settings_cache()
349 351 h.flash(_('Updated application settings'), category='success')
350 352 except Exception:
351 353 log.exception("Exception while updating application settings")
352 354 h.flash(
353 355 _('Error occurred during updating application settings'),
354 356 category='error')
355 357
356 358 raise HTTPFound(h.route_path('admin_settings_global'))
357 359
358 360 @LoginRequired()
359 361 @HasPermissionAllDecorator('hg.admin')
360 362 @view_config(
361 363 route_name='admin_settings_visual', request_method='GET',
362 364 renderer='rhodecode:templates/admin/settings/settings.mako')
363 365 def settings_visual(self):
364 366 c = self.load_default_context()
365 367 c.active = 'visual'
366 368
367 369 data = render('rhodecode:templates/admin/settings/settings.mako',
368 370 self._get_template_context(c), self.request)
369 371 html = formencode.htmlfill.render(
370 372 data,
371 373 defaults=self._form_defaults(),
372 374 encoding="UTF-8",
373 375 force_defaults=False
374 376 )
375 377 return Response(html)
376 378
377 379 @LoginRequired()
378 380 @HasPermissionAllDecorator('hg.admin')
379 381 @CSRFRequired()
380 382 @view_config(
381 383 route_name='admin_settings_visual_update', request_method='POST',
382 384 renderer='rhodecode:templates/admin/settings/settings.mako')
383 385 def settings_visual_update(self):
384 386 _ = self.request.translate
385 387 c = self.load_default_context()
386 388 c.active = 'visual'
387 389 application_form = ApplicationVisualisationForm(self.request.translate)()
388 390 try:
389 391 form_result = application_form.to_python(dict(self.request.POST))
390 392 except formencode.Invalid as errors:
391 393 h.flash(
392 394 _("Some form inputs contain invalid data."),
393 395 category='error')
394 396 data = render('rhodecode:templates/admin/settings/settings.mako',
395 397 self._get_template_context(c), self.request)
396 398 html = formencode.htmlfill.render(
397 399 data,
398 400 defaults=errors.value,
399 401 errors=errors.error_dict or {},
400 402 prefix_error=False,
401 403 encoding="UTF-8",
402 404 force_defaults=False
403 405 )
404 406 return Response(html)
405 407
406 408 try:
407 409 settings = [
408 410 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
409 411 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
410 412 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
411 413 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
412 414 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
413 415 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
414 416 ('show_version', 'rhodecode_show_version', 'bool'),
415 417 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
416 418 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
417 419 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
418 420 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
419 421 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
420 422 ('support_url', 'rhodecode_support_url', 'unicode'),
421 423 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
422 424 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
423 425 ]
424 426 for setting, form_key, type_ in settings:
425 427 sett = SettingsModel().create_or_update_setting(
426 428 setting, form_result[form_key], type_)
427 429 Session().add(sett)
428 430
429 431 Session().commit()
430 432 SettingsModel().invalidate_settings_cache()
431 433 h.flash(_('Updated visualisation settings'), category='success')
432 434 except Exception:
433 435 log.exception("Exception updating visualization settings")
434 436 h.flash(_('Error occurred during updating '
435 437 'visualisation settings'),
436 438 category='error')
437 439
438 440 raise HTTPFound(h.route_path('admin_settings_visual'))
439 441
440 442 @LoginRequired()
441 443 @HasPermissionAllDecorator('hg.admin')
442 444 @view_config(
443 445 route_name='admin_settings_issuetracker', request_method='GET',
444 446 renderer='rhodecode:templates/admin/settings/settings.mako')
445 447 def settings_issuetracker(self):
446 448 c = self.load_default_context()
447 449 c.active = 'issuetracker'
448 450 defaults = c.rc_config
449 451
450 452 entry_key = 'rhodecode_issuetracker_pat_'
451 453
452 454 c.issuetracker_entries = {}
453 455 for k, v in defaults.items():
454 456 if k.startswith(entry_key):
455 457 uid = k[len(entry_key):]
456 458 c.issuetracker_entries[uid] = None
457 459
458 460 for uid in c.issuetracker_entries:
459 461 c.issuetracker_entries[uid] = AttributeDict({
460 462 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
461 463 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
462 464 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
463 465 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
464 466 })
465 467
466 468 return self._get_template_context(c)
467 469
468 470 @LoginRequired()
469 471 @HasPermissionAllDecorator('hg.admin')
470 472 @CSRFRequired()
471 473 @view_config(
472 474 route_name='admin_settings_issuetracker_test', request_method='POST',
473 475 renderer='string', xhr=True)
474 476 def settings_issuetracker_test(self):
475 477 return h.urlify_commit_message(
476 478 self.request.POST.get('test_text', ''),
477 479 'repo_group/test_repo1')
478 480
479 481 @LoginRequired()
480 482 @HasPermissionAllDecorator('hg.admin')
481 483 @CSRFRequired()
482 484 @view_config(
483 485 route_name='admin_settings_issuetracker_update', request_method='POST',
484 486 renderer='rhodecode:templates/admin/settings/settings.mako')
485 487 def settings_issuetracker_update(self):
486 488 _ = self.request.translate
487 489 self.load_default_context()
488 490 settings_model = IssueTrackerSettingsModel()
489 491
490 492 try:
491 493 form = IssueTrackerPatternsForm(self.request.translate)()
492 494 data = form.to_python(self.request.POST)
493 495 except formencode.Invalid as errors:
494 496 log.exception('Failed to add new pattern')
495 497 error = errors
496 498 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
497 499 category='error')
498 500 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
499 501
500 502 if data:
501 503 for uid in data.get('delete_patterns', []):
502 504 settings_model.delete_entries(uid)
503 505
504 506 for pattern in data.get('patterns', []):
505 507 for setting, value, type_ in pattern:
506 508 sett = settings_model.create_or_update_setting(
507 509 setting, value, type_)
508 510 Session().add(sett)
509 511
510 512 Session().commit()
511 513
512 514 SettingsModel().invalidate_settings_cache()
513 515 h.flash(_('Updated issue tracker entries'), category='success')
514 516 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
515 517
516 518 @LoginRequired()
517 519 @HasPermissionAllDecorator('hg.admin')
518 520 @CSRFRequired()
519 521 @view_config(
520 522 route_name='admin_settings_issuetracker_delete', request_method='POST',
521 523 renderer='json_ext', xhr=True)
522 524 def settings_issuetracker_delete(self):
523 525 _ = self.request.translate
524 526 self.load_default_context()
525 527 uid = self.request.POST.get('uid')
526 528 try:
527 529 IssueTrackerSettingsModel().delete_entries(uid)
528 530 except Exception:
529 531 log.exception('Failed to delete issue tracker setting %s', uid)
530 532 raise HTTPNotFound()
531 533
532 534 SettingsModel().invalidate_settings_cache()
533 535 h.flash(_('Removed issue tracker entry.'), category='success')
534 536
535 537 return {'deleted': uid}
536 538
537 539 @LoginRequired()
538 540 @HasPermissionAllDecorator('hg.admin')
539 541 @view_config(
540 542 route_name='admin_settings_email', request_method='GET',
541 543 renderer='rhodecode:templates/admin/settings/settings.mako')
542 544 def settings_email(self):
543 545 c = self.load_default_context()
544 546 c.active = 'email'
545 547 c.rhodecode_ini = rhodecode.CONFIG
546 548
547 549 data = render('rhodecode:templates/admin/settings/settings.mako',
548 550 self._get_template_context(c), self.request)
549 551 html = formencode.htmlfill.render(
550 552 data,
551 553 defaults=self._form_defaults(),
552 554 encoding="UTF-8",
553 555 force_defaults=False
554 556 )
555 557 return Response(html)
556 558
557 559 @LoginRequired()
558 560 @HasPermissionAllDecorator('hg.admin')
559 561 @CSRFRequired()
560 562 @view_config(
561 563 route_name='admin_settings_email_update', request_method='POST',
562 564 renderer='rhodecode:templates/admin/settings/settings.mako')
563 565 def settings_email_update(self):
564 566 _ = self.request.translate
565 567 c = self.load_default_context()
566 568 c.active = 'email'
567 569
568 570 test_email = self.request.POST.get('test_email')
569 571
570 572 if not test_email:
571 573 h.flash(_('Please enter email address'), category='error')
572 574 raise HTTPFound(h.route_path('admin_settings_email'))
573 575
574 576 email_kwargs = {
575 577 'date': datetime.datetime.now(),
576 578 'user': self._rhodecode_db_user
577 579 }
578 580
579 (subject, headers, email_body,
580 email_body_plaintext) = EmailNotificationModel().render_email(
581 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
581 582 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
582 583
583 584 recipients = [test_email] if test_email else None
584 585
585 586 run_task(tasks.send_email, recipients, subject,
586 587 email_body_plaintext, email_body)
587 588
588 589 h.flash(_('Send email task created'), category='success')
589 590 raise HTTPFound(h.route_path('admin_settings_email'))
590 591
591 592 @LoginRequired()
592 593 @HasPermissionAllDecorator('hg.admin')
593 594 @view_config(
594 595 route_name='admin_settings_hooks', request_method='GET',
595 596 renderer='rhodecode:templates/admin/settings/settings.mako')
596 597 def settings_hooks(self):
597 598 c = self.load_default_context()
598 599 c.active = 'hooks'
599 600
600 601 model = SettingsModel()
601 602 c.hooks = model.get_builtin_hooks()
602 603 c.custom_hooks = model.get_custom_hooks()
603 604
604 605 data = render('rhodecode:templates/admin/settings/settings.mako',
605 606 self._get_template_context(c), self.request)
606 607 html = formencode.htmlfill.render(
607 608 data,
608 609 defaults=self._form_defaults(),
609 610 encoding="UTF-8",
610 611 force_defaults=False
611 612 )
612 613 return Response(html)
613 614
614 615 @LoginRequired()
615 616 @HasPermissionAllDecorator('hg.admin')
616 617 @CSRFRequired()
617 618 @view_config(
618 619 route_name='admin_settings_hooks_update', request_method='POST',
619 620 renderer='rhodecode:templates/admin/settings/settings.mako')
620 621 @view_config(
621 622 route_name='admin_settings_hooks_delete', request_method='POST',
622 623 renderer='rhodecode:templates/admin/settings/settings.mako')
623 624 def settings_hooks_update(self):
624 625 _ = self.request.translate
625 626 c = self.load_default_context()
626 627 c.active = 'hooks'
627 628 if c.visual.allow_custom_hooks_settings:
628 629 ui_key = self.request.POST.get('new_hook_ui_key')
629 630 ui_value = self.request.POST.get('new_hook_ui_value')
630 631
631 632 hook_id = self.request.POST.get('hook_id')
632 633 new_hook = False
633 634
634 635 model = SettingsModel()
635 636 try:
636 637 if ui_value and ui_key:
637 638 model.create_or_update_hook(ui_key, ui_value)
638 639 h.flash(_('Added new hook'), category='success')
639 640 new_hook = True
640 641 elif hook_id:
641 642 RhodeCodeUi.delete(hook_id)
642 643 Session().commit()
643 644
644 645 # check for edits
645 646 update = False
646 647 _d = self.request.POST.dict_of_lists()
647 648 for k, v in zip(_d.get('hook_ui_key', []),
648 649 _d.get('hook_ui_value_new', [])):
649 650 model.create_or_update_hook(k, v)
650 651 update = True
651 652
652 653 if update and not new_hook:
653 654 h.flash(_('Updated hooks'), category='success')
654 655 Session().commit()
655 656 except Exception:
656 657 log.exception("Exception during hook creation")
657 658 h.flash(_('Error occurred during hook creation'),
658 659 category='error')
659 660
660 661 raise HTTPFound(h.route_path('admin_settings_hooks'))
661 662
662 663 @LoginRequired()
663 664 @HasPermissionAllDecorator('hg.admin')
664 665 @view_config(
665 666 route_name='admin_settings_search', request_method='GET',
666 667 renderer='rhodecode:templates/admin/settings/settings.mako')
667 668 def settings_search(self):
668 669 c = self.load_default_context()
669 670 c.active = 'search'
670 671
671 672 c.searcher = searcher_from_config(self.request.registry.settings)
672 673 c.statistics = c.searcher.statistics(self.request.translate)
673 674
674 675 return self._get_template_context(c)
675 676
676 677 @LoginRequired()
677 678 @HasPermissionAllDecorator('hg.admin')
678 679 @view_config(
679 680 route_name='admin_settings_automation', request_method='GET',
680 681 renderer='rhodecode:templates/admin/settings/settings.mako')
681 682 def settings_automation(self):
682 683 c = self.load_default_context()
683 684 c.active = 'automation'
684 685
685 686 return self._get_template_context(c)
686 687
687 688 @LoginRequired()
688 689 @HasPermissionAllDecorator('hg.admin')
689 690 @view_config(
690 691 route_name='admin_settings_labs', request_method='GET',
691 692 renderer='rhodecode:templates/admin/settings/settings.mako')
692 693 def settings_labs(self):
693 694 c = self.load_default_context()
694 695 if not c.labs_active:
695 696 raise HTTPFound(h.route_path('admin_settings'))
696 697
697 698 c.active = 'labs'
698 699 c.lab_settings = _LAB_SETTINGS
699 700
700 701 data = render('rhodecode:templates/admin/settings/settings.mako',
701 702 self._get_template_context(c), self.request)
702 703 html = formencode.htmlfill.render(
703 704 data,
704 705 defaults=self._form_defaults(),
705 706 encoding="UTF-8",
706 707 force_defaults=False
707 708 )
708 709 return Response(html)
709 710
710 711 @LoginRequired()
711 712 @HasPermissionAllDecorator('hg.admin')
712 713 @CSRFRequired()
713 714 @view_config(
714 715 route_name='admin_settings_labs_update', request_method='POST',
715 716 renderer='rhodecode:templates/admin/settings/settings.mako')
716 717 def settings_labs_update(self):
717 718 _ = self.request.translate
718 719 c = self.load_default_context()
719 720 c.active = 'labs'
720 721
721 722 application_form = LabsSettingsForm(self.request.translate)()
722 723 try:
723 724 form_result = application_form.to_python(dict(self.request.POST))
724 725 except formencode.Invalid as errors:
725 726 h.flash(
726 727 _("Some form inputs contain invalid data."),
727 728 category='error')
728 729 data = render('rhodecode:templates/admin/settings/settings.mako',
729 730 self._get_template_context(c), self.request)
730 731 html = formencode.htmlfill.render(
731 732 data,
732 733 defaults=errors.value,
733 734 errors=errors.error_dict or {},
734 735 prefix_error=False,
735 736 encoding="UTF-8",
736 737 force_defaults=False
737 738 )
738 739 return Response(html)
739 740
740 741 try:
741 742 session = Session()
742 743 for setting in _LAB_SETTINGS:
743 744 setting_name = setting.key[len('rhodecode_'):]
744 745 sett = SettingsModel().create_or_update_setting(
745 746 setting_name, form_result[setting.key], setting.type)
746 747 session.add(sett)
747 748
748 749 except Exception:
749 750 log.exception('Exception while updating lab settings')
750 751 h.flash(_('Error occurred during updating labs settings'),
751 752 category='error')
752 753 else:
753 754 Session().commit()
754 755 SettingsModel().invalidate_settings_cache()
755 756 h.flash(_('Updated Labs settings'), category='success')
756 757 raise HTTPFound(h.route_path('admin_settings_labs'))
757 758
758 759 data = render('rhodecode:templates/admin/settings/settings.mako',
759 760 self._get_template_context(c), self.request)
760 761 html = formencode.htmlfill.render(
761 762 data,
762 763 defaults=self._form_defaults(),
763 764 encoding="UTF-8",
764 765 force_defaults=False
765 766 )
766 767 return Response(html)
767 768
768 769
769 770 # :param key: name of the setting including the 'rhodecode_' prefix
770 771 # :param type: the RhodeCodeSetting type to use.
771 772 # :param group: the i18ned group in which we should dispaly this setting
772 773 # :param label: the i18ned label we should display for this setting
773 774 # :param help: the i18ned help we should dispaly for this setting
774 775 LabSetting = collections.namedtuple(
775 776 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
776 777
777 778
778 779 # This list has to be kept in sync with the form
779 780 # rhodecode.model.forms.LabsSettingsForm.
780 781 _LAB_SETTINGS = [
781 782
782 783 ]
@@ -1,419 +1,418 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import logging
23 23 import datetime
24 24
25 25 from pyramid.view import view_config
26 26 from pyramid.renderers import render_to_response
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib.celerylib import run_task, tasks
29 29 from rhodecode.lib.utils2 import AttributeDict
30 30 from rhodecode.model.db import User
31 31 from rhodecode.model.notification import EmailNotificationModel
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 class DebugStyleView(BaseAppView):
37 37 def load_default_context(self):
38 38 c = self._get_local_tmpl_context()
39 39
40 40 return c
41 41
42 42 @view_config(
43 43 route_name='debug_style_home', request_method='GET',
44 44 renderer=None)
45 45 def index(self):
46 46 c = self.load_default_context()
47 47 c.active = 'index'
48 48
49 49 return render_to_response(
50 50 'debug_style/index.html', self._get_template_context(c),
51 51 request=self.request)
52 52
53 53 @view_config(
54 54 route_name='debug_style_email', request_method='GET',
55 55 renderer=None)
56 56 @view_config(
57 57 route_name='debug_style_email_plain_rendered', request_method='GET',
58 58 renderer=None)
59 59 def render_email(self):
60 60 c = self.load_default_context()
61 61 email_id = self.request.matchdict['email_id']
62 62 c.active = 'emails'
63 63
64 64 pr = AttributeDict(
65 65 pull_request_id=123,
66 66 title='digital_ocean: fix redis, elastic search start on boot, '
67 67 'fix fd limits on supervisor, set postgres 11 version',
68 68 description='''
69 69 Check if we should use full-topic or mini-topic.
70 70
71 71 - full topic produces some problems with merge states etc
72 72 - server-mini-topic needs probably tweeks.
73 73 ''',
74 74 repo_name='foobar',
75 75 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
76 76 target_ref_parts=AttributeDict(type='branch', name='master'),
77 77 )
78 78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
79 79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
80 80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
81 81 # file/commit changes for PR update
82 82 commit_changes = AttributeDict({
83 83 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
84 84 'removed': ['eeeeeeeeeee'],
85 85 })
86 86 file_changes = AttributeDict({
87 87 'added': ['a/file1.md', 'file2.py'],
88 88 'modified': ['b/modified_file.rst'],
89 89 'removed': ['.idea'],
90 90 })
91 91
92 92 exc_traceback = {
93 93 'exc_utc_date': '2020-03-26T12:54:50.683281',
94 94 'exc_id': 139638856342656,
95 95 'exc_timestamp': '1585227290.683288',
96 96 'version': 'v1',
97 97 'exc_message': 'Traceback (most recent call last):\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/tweens.py", line 41, in excview_tween\n response = handler(request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/router.py", line 148, in handle_request\n registry, request, context, context_iface, view_name\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/view.py", line 667, in _call_view\n response = view_callable(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 188, in attr_view\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 214, in predicate_wrapper\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 401, in viewresult_to_response\n result = view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 132, in _class_view\n response = getattr(inst, attr)()\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/apps/debug_style/views.py", line 355, in render_email\n template_type, **email_kwargs.get(email_id, {}))\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/model/notification.py", line 402, in render_email\n body = email_template.render(None, **_kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 95, in render\n return self._render_with_exc(tmpl, args, kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 79, in _render_with_exc\n return render_func.render(*args, **kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/template.py", line 476, in render\n return runtime._render(self, self.callable_, args, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 883, in _render\n **_kwargs_for_callable(callable_, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 920, in _render_context\n _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 947, in _exec_template\n callable_(context, *args, **kwargs)\n File "rhodecode_templates_email_templates_base_mako", line 63, in render_body\n File "rhodecode_templates_email_templates_exception_tracker_mako", line 43, in render_body\nAttributeError: \'str\' object has no attribute \'get\'\n',
98 98 'exc_type': 'AttributeError'
99 99 }
100 100 email_kwargs = {
101 101 'test': {},
102 102 'message': {
103 103 'body': 'message body !'
104 104 },
105 105 'email_test': {
106 106 'user': user,
107 107 'date': datetime.datetime.now(),
108 108 },
109 109 'exception': {
110 110 'email_prefix': '[RHODECODE ERROR]',
111 111 'exc_id': exc_traceback['exc_id'],
112 112 'exc_url': 'http://server-url/{}'.format(exc_traceback['exc_id']),
113 113 'exc_type_name': 'NameError',
114 114 'exc_traceback': exc_traceback,
115 115 },
116 116 'password_reset': {
117 117 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
118 118
119 119 'user': user,
120 120 'date': datetime.datetime.now(),
121 121 'email': 'test@rhodecode.com',
122 122 'first_admin_email': User.get_first_super_admin().email
123 123 },
124 124 'password_reset_confirmation': {
125 125 'new_password': 'new-password-example',
126 126 'user': user,
127 127 'date': datetime.datetime.now(),
128 128 'email': 'test@rhodecode.com',
129 129 'first_admin_email': User.get_first_super_admin().email
130 130 },
131 131 'registration': {
132 132 'user': user,
133 133 'date': datetime.datetime.now(),
134 134 },
135 135
136 136 'pull_request_comment': {
137 137 'user': user,
138 138
139 139 'status_change': None,
140 140 'status_change_type': None,
141 141
142 142 'pull_request': pr,
143 143 'pull_request_commits': [],
144 144
145 145 'pull_request_target_repo': target_repo,
146 146 'pull_request_target_repo_url': 'http://target-repo/url',
147 147
148 148 'pull_request_source_repo': source_repo,
149 149 'pull_request_source_repo_url': 'http://source-repo/url',
150 150
151 151 'pull_request_url': 'http://localhost/pr1',
152 152 'pr_comment_url': 'http://comment-url',
153 153 'pr_comment_reply_url': 'http://comment-url#reply',
154 154
155 155 'comment_file': None,
156 156 'comment_line': None,
157 157 'comment_type': 'note',
158 158 'comment_body': 'This is my comment body. *I like !*',
159 159 'comment_id': 2048,
160 160 'renderer_type': 'markdown',
161 161 'mention': True,
162 162
163 163 },
164 164 'pull_request_comment+status': {
165 165 'user': user,
166 166
167 167 'status_change': 'approved',
168 168 'status_change_type': 'approved',
169 169
170 170 'pull_request': pr,
171 171 'pull_request_commits': [],
172 172
173 173 'pull_request_target_repo': target_repo,
174 174 'pull_request_target_repo_url': 'http://target-repo/url',
175 175
176 176 'pull_request_source_repo': source_repo,
177 177 'pull_request_source_repo_url': 'http://source-repo/url',
178 178
179 179 'pull_request_url': 'http://localhost/pr1',
180 180 'pr_comment_url': 'http://comment-url',
181 181 'pr_comment_reply_url': 'http://comment-url#reply',
182 182
183 183 'comment_type': 'todo',
184 184 'comment_file': None,
185 185 'comment_line': None,
186 186 'comment_body': '''
187 187 I think something like this would be better
188 188
189 189 ```py
190 190 // markdown renderer
191 191
192 192 def db():
193 193 global connection
194 194 return connection
195 195
196 196 ```
197 197
198 198 ''',
199 199 'comment_id': 2048,
200 200 'renderer_type': 'markdown',
201 201 'mention': True,
202 202
203 203 },
204 204 'pull_request_comment+file': {
205 205 'user': user,
206 206
207 207 'status_change': None,
208 208 'status_change_type': None,
209 209
210 210 'pull_request': pr,
211 211 'pull_request_commits': [],
212 212
213 213 'pull_request_target_repo': target_repo,
214 214 'pull_request_target_repo_url': 'http://target-repo/url',
215 215
216 216 'pull_request_source_repo': source_repo,
217 217 'pull_request_source_repo_url': 'http://source-repo/url',
218 218
219 219 'pull_request_url': 'http://localhost/pr1',
220 220
221 221 'pr_comment_url': 'http://comment-url',
222 222 'pr_comment_reply_url': 'http://comment-url#reply',
223 223
224 224 'comment_file': 'rhodecode/model/get_flow_commits',
225 225 'comment_line': 'o1210',
226 226 'comment_type': 'todo',
227 227 'comment_body': '''
228 228 I like this !
229 229
230 230 But please check this code
231 231
232 232 .. code-block:: javascript
233 233
234 234 // THIS IS RST CODE
235 235
236 236 this.createResolutionComment = function(commentId) {
237 237 // hide the trigger text
238 238 $('#resolve-comment-{0}'.format(commentId)).hide();
239 239
240 240 var comment = $('#comment-'+commentId);
241 241 var commentData = comment.data();
242 242 if (commentData.commentInline) {
243 243 this.createComment(comment, commentId)
244 244 } else {
245 245 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
246 246 }
247 247
248 248 return false;
249 249 };
250 250
251 251 This should work better !
252 252 ''',
253 253 'comment_id': 2048,
254 254 'renderer_type': 'rst',
255 255 'mention': True,
256 256
257 257 },
258 258
259 259 'pull_request_update': {
260 260 'updating_user': user,
261 261
262 262 'status_change': None,
263 263 'status_change_type': None,
264 264
265 265 'pull_request': pr,
266 266 'pull_request_commits': [],
267 267
268 268 'pull_request_target_repo': target_repo,
269 269 'pull_request_target_repo_url': 'http://target-repo/url',
270 270
271 271 'pull_request_source_repo': source_repo,
272 272 'pull_request_source_repo_url': 'http://source-repo/url',
273 273
274 274 'pull_request_url': 'http://localhost/pr1',
275 275
276 276 # update comment links
277 277 'pr_comment_url': 'http://comment-url',
278 278 'pr_comment_reply_url': 'http://comment-url#reply',
279 279 'ancestor_commit_id': 'f39bd443',
280 280 'added_commits': commit_changes.added,
281 281 'removed_commits': commit_changes.removed,
282 282 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
283 283 'added_files': file_changes.added,
284 284 'modified_files': file_changes.modified,
285 285 'removed_files': file_changes.removed,
286 286 },
287 287
288 288 'cs_comment': {
289 289 'user': user,
290 290 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
291 291 'status_change': None,
292 292 'status_change_type': None,
293 293
294 294 'commit_target_repo_url': 'http://foo.example.com/#comment1',
295 295 'repo_name': 'test-repo',
296 296 'comment_type': 'note',
297 297 'comment_file': None,
298 298 'comment_line': None,
299 299 'commit_comment_url': 'http://comment-url',
300 300 'commit_comment_reply_url': 'http://comment-url#reply',
301 301 'comment_body': 'This is my comment body. *I like !*',
302 302 'comment_id': 2048,
303 303 'renderer_type': 'markdown',
304 304 'mention': True,
305 305 },
306 306 'cs_comment+status': {
307 307 'user': user,
308 308 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
309 309 'status_change': 'approved',
310 310 'status_change_type': 'approved',
311 311
312 312 'commit_target_repo_url': 'http://foo.example.com/#comment1',
313 313 'repo_name': 'test-repo',
314 314 'comment_type': 'note',
315 315 'comment_file': None,
316 316 'comment_line': None,
317 317 'commit_comment_url': 'http://comment-url',
318 318 'commit_comment_reply_url': 'http://comment-url#reply',
319 319 'comment_body': '''
320 320 Hello **world**
321 321
322 322 This is a multiline comment :)
323 323
324 324 - list
325 325 - list2
326 326 ''',
327 327 'comment_id': 2048,
328 328 'renderer_type': 'markdown',
329 329 'mention': True,
330 330 },
331 331 'cs_comment+file': {
332 332 'user': user,
333 333 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
334 334 'status_change': None,
335 335 'status_change_type': None,
336 336
337 337 'commit_target_repo_url': 'http://foo.example.com/#comment1',
338 338 'repo_name': 'test-repo',
339 339
340 340 'comment_type': 'note',
341 341 'comment_file': 'test-file.py',
342 342 'comment_line': 'n100',
343 343
344 344 'commit_comment_url': 'http://comment-url',
345 345 'commit_comment_reply_url': 'http://comment-url#reply',
346 346 'comment_body': 'This is my comment body. *I like !*',
347 347 'comment_id': 2048,
348 348 'renderer_type': 'markdown',
349 349 'mention': True,
350 350 },
351 351
352 352 'pull_request': {
353 353 'user': user,
354 354 'pull_request': pr,
355 355 'pull_request_commits': [
356 356 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
357 357 my-account: moved email closer to profile as it's similar data just moved outside.
358 358 '''),
359 359 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
360 360 users: description edit fixes
361 361
362 362 - tests
363 363 - added metatags info
364 364 '''),
365 365 ],
366 366
367 367 'pull_request_target_repo': target_repo,
368 368 'pull_request_target_repo_url': 'http://target-repo/url',
369 369
370 370 'pull_request_source_repo': source_repo,
371 371 'pull_request_source_repo_url': 'http://source-repo/url',
372 372
373 373 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
374 374 }
375 375
376 376 }
377 377
378 378 template_type = email_id.split('+')[0]
379 (c.subject, c.headers, c.email_body,
380 c.email_body_plaintext) = EmailNotificationModel().render_email(
379 (c.subject, c.email_body, c.email_body_plaintext) = EmailNotificationModel().render_email(
381 380 template_type, **email_kwargs.get(email_id, {}))
382 381
383 382 test_email = self.request.GET.get('email')
384 383 if test_email:
385 384 recipients = [test_email]
386 385 run_task(tasks.send_email, recipients, c.subject,
387 386 c.email_body_plaintext, c.email_body)
388 387
389 388 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
390 389 template = 'debug_style/email_plain_rendered.mako'
391 390 else:
392 391 template = 'debug_style/email.mako'
393 392 return render_to_response(
394 393 template, self._get_template_context(c),
395 394 request=self.request)
396 395
397 396 @view_config(
398 397 route_name='debug_style_template', request_method='GET',
399 398 renderer=None)
400 399 def template(self):
401 400 t_path = self.request.matchdict['t_path']
402 401 c = self.load_default_context()
403 402 c.active = os.path.splitext(t_path)[0]
404 403 c.came_from = ''
405 404 c.email_types = {
406 405 'cs_comment+file': {},
407 406 'cs_comment+status': {},
408 407
409 408 'pull_request_comment+file': {},
410 409 'pull_request_comment+status': {},
411 410
412 411 'pull_request_update': {},
413 412 }
414 413 c.email_types.update(EmailNotificationModel.email_types)
415 414
416 415 return render_to_response(
417 416 'debug_style/' + t_path, self._get_template_context(c),
418 417 request=self.request)
419 418
@@ -1,391 +1,391 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib import helpers as h
25 25 from rhodecode.model.db import User, Gist
26 26 from rhodecode.model.gist import GistModel
27 27 from rhodecode.model.meta import Session
28 28 from rhodecode.tests import (
29 29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
30 30 TestController, assert_session_flash)
31 31
32 32
33 33 def route_path(name, params=None, **kwargs):
34 34 import urllib
35 35 from rhodecode.apps._base import ADMIN_PREFIX
36 36
37 37 base_url = {
38 38 'gists_show': ADMIN_PREFIX + '/gists',
39 39 'gists_new': ADMIN_PREFIX + '/gists/new',
40 40 'gists_create': ADMIN_PREFIX + '/gists/create',
41 41 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}',
42 42 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete',
43 43 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit',
44 44 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision',
45 45 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update',
46 46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/{revision}',
47 47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
48 48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
49 49
50 50 }[name].format(**kwargs)
51 51
52 52 if params:
53 53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 54 return base_url
55 55
56 56
57 57 class GistUtility(object):
58 58
59 59 def __init__(self):
60 60 self._gist_ids = []
61 61
62 62 def __call__(
63 63 self, f_name, content='some gist', lifetime=-1,
64 64 description='gist-desc', gist_type='public',
65 65 acl_level=Gist.GIST_PUBLIC, owner=TEST_USER_ADMIN_LOGIN):
66 66 gist_mapping = {
67 67 f_name: {'content': content}
68 68 }
69 69 user = User.get_by_username(owner)
70 70 gist = GistModel().create(
71 71 description, owner=user, gist_mapping=gist_mapping,
72 72 gist_type=gist_type, lifetime=lifetime, gist_acl_level=acl_level)
73 73 Session().commit()
74 74 self._gist_ids.append(gist.gist_id)
75 75 return gist
76 76
77 77 def cleanup(self):
78 78 for gist_id in self._gist_ids:
79 79 gist = Gist.get(gist_id)
80 80 if gist:
81 81 Session().delete(gist)
82 82
83 83 Session().commit()
84 84
85 85
86 86 @pytest.fixture()
87 87 def create_gist(request):
88 88 gist_utility = GistUtility()
89 89 request.addfinalizer(gist_utility.cleanup)
90 90 return gist_utility
91 91
92 92
93 93 class TestGistsController(TestController):
94 94
95 95 def test_index_empty(self, create_gist):
96 96 self.log_user()
97 97 response = self.app.get(route_path('gists_show'))
98 98 response.mustcontain('data: [],')
99 99
100 100 def test_index(self, create_gist):
101 101 self.log_user()
102 102 g1 = create_gist('gist1')
103 103 g2 = create_gist('gist2', lifetime=1400)
104 104 g3 = create_gist('gist3', description='gist3-desc')
105 105 g4 = create_gist('gist4', gist_type='private').gist_access_id
106 106 response = self.app.get(route_path('gists_show'))
107 107
108 108 response.mustcontain(g1.gist_access_id)
109 109 response.mustcontain(g2.gist_access_id)
110 110 response.mustcontain(g3.gist_access_id)
111 111 response.mustcontain('gist3-desc')
112 112 response.mustcontain(no=[g4])
113 113
114 114 # Expiration information should be visible
115 115 expires_tag = '%s' % h.age_component(
116 116 h.time_to_utcdatetime(g2.gist_expires))
117 117 response.mustcontain(expires_tag.replace('"', '\\"'))
118 118
119 119 def test_index_private_gists(self, create_gist):
120 120 self.log_user()
121 121 gist = create_gist('gist5', gist_type='private')
122 122 response = self.app.get(route_path('gists_show', params=dict(private=1)))
123 123
124 124 # and privates
125 125 response.mustcontain(gist.gist_access_id)
126 126
127 127 def test_index_show_all(self, create_gist):
128 128 self.log_user()
129 129 create_gist('gist1')
130 130 create_gist('gist2', lifetime=1400)
131 131 create_gist('gist3', description='gist3-desc')
132 132 create_gist('gist4', gist_type='private')
133 133
134 134 response = self.app.get(route_path('gists_show', params=dict(all=1)))
135 135
136 136 assert len(GistModel.get_all()) == 4
137 137 # and privates
138 138 for gist in GistModel.get_all():
139 139 response.mustcontain(gist.gist_access_id)
140 140
141 141 def test_index_show_all_hidden_from_regular(self, create_gist):
142 142 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
143 143 create_gist('gist2', gist_type='private')
144 144 create_gist('gist3', gist_type='private')
145 145 create_gist('gist4', gist_type='private')
146 146
147 147 response = self.app.get(route_path('gists_show', params=dict(all=1)))
148 148
149 149 assert len(GistModel.get_all()) == 3
150 150 # since we don't have access to private in this view, we
151 151 # should see nothing
152 152 for gist in GistModel.get_all():
153 153 response.mustcontain(no=[gist.gist_access_id])
154 154
155 155 def test_create(self):
156 156 self.log_user()
157 157 response = self.app.post(
158 158 route_path('gists_create'),
159 159 params={'lifetime': -1,
160 160 'content': 'gist test',
161 161 'filename': 'foo',
162 162 'gist_type': 'public',
163 163 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
164 164 'csrf_token': self.csrf_token},
165 165 status=302)
166 166 response = response.follow()
167 167 response.mustcontain('added file: foo')
168 168 response.mustcontain('gist test')
169 169
170 170 def test_create_with_path_with_dirs(self):
171 171 self.log_user()
172 172 response = self.app.post(
173 173 route_path('gists_create'),
174 174 params={'lifetime': -1,
175 175 'content': 'gist test',
176 176 'filename': '/home/foo',
177 177 'gist_type': 'public',
178 178 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
179 179 'csrf_token': self.csrf_token},
180 180 status=200)
181 181 response.mustcontain('Filename /home/foo cannot be inside a directory')
182 182
183 183 def test_access_expired_gist(self, create_gist):
184 184 self.log_user()
185 185 gist = create_gist('never-see-me')
186 186 gist.gist_expires = 0 # 1970
187 187 Session().add(gist)
188 188 Session().commit()
189 189
190 190 self.app.get(route_path('gist_show', gist_id=gist.gist_access_id),
191 191 status=404)
192 192
193 193 def test_create_private(self):
194 194 self.log_user()
195 195 response = self.app.post(
196 196 route_path('gists_create'),
197 197 params={'lifetime': -1,
198 198 'content': 'private gist test',
199 199 'filename': 'private-foo',
200 200 'gist_type': 'private',
201 201 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
202 202 'csrf_token': self.csrf_token},
203 203 status=302)
204 204 response = response.follow()
205 205 response.mustcontain('added file: private-foo<')
206 206 response.mustcontain('private gist test')
207 207 response.mustcontain('Private Gist')
208 208 # Make sure private gists are not indexed by robots
209 209 response.mustcontain(
210 210 '<meta name="robots" content="noindex, nofollow">')
211 211
212 212 def test_create_private_acl_private(self):
213 213 self.log_user()
214 214 response = self.app.post(
215 215 route_path('gists_create'),
216 216 params={'lifetime': -1,
217 217 'content': 'private gist test',
218 218 'filename': 'private-foo',
219 219 'gist_type': 'private',
220 220 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
221 221 'csrf_token': self.csrf_token},
222 222 status=302)
223 223 response = response.follow()
224 224 response.mustcontain('added file: private-foo<')
225 225 response.mustcontain('private gist test')
226 226 response.mustcontain('Private Gist')
227 227 # Make sure private gists are not indexed by robots
228 228 response.mustcontain(
229 229 '<meta name="robots" content="noindex, nofollow">')
230 230
231 231 def test_create_with_description(self):
232 232 self.log_user()
233 233 response = self.app.post(
234 234 route_path('gists_create'),
235 235 params={'lifetime': -1,
236 236 'content': 'gist test',
237 237 'filename': 'foo-desc',
238 238 'description': 'gist-desc',
239 239 'gist_type': 'public',
240 240 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
241 241 'csrf_token': self.csrf_token},
242 242 status=302)
243 243 response = response.follow()
244 244 response.mustcontain('added file: foo-desc')
245 245 response.mustcontain('gist test')
246 246 response.mustcontain('gist-desc')
247 247
248 248 def test_create_public_with_anonymous_access(self):
249 249 self.log_user()
250 250 params = {
251 251 'lifetime': -1,
252 252 'content': 'gist test',
253 253 'filename': 'foo-desc',
254 254 'description': 'gist-desc',
255 255 'gist_type': 'public',
256 256 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
257 257 'csrf_token': self.csrf_token
258 258 }
259 259 response = self.app.post(
260 260 route_path('gists_create'), params=params, status=302)
261 261 self.logout_user()
262 262 response = response.follow()
263 263 response.mustcontain('added file: foo-desc')
264 264 response.mustcontain('gist test')
265 265 response.mustcontain('gist-desc')
266 266
267 267 def test_new(self):
268 268 self.log_user()
269 269 self.app.get(route_path('gists_new'))
270 270
271 271 def test_delete(self, create_gist):
272 272 self.log_user()
273 273 gist = create_gist('delete-me')
274 274 response = self.app.post(
275 275 route_path('gist_delete', gist_id=gist.gist_id),
276 276 params={'csrf_token': self.csrf_token})
277 277 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
278 278
279 279 def test_delete_normal_user_his_gist(self, create_gist):
280 280 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
281 281 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
282 282
283 283 response = self.app.post(
284 284 route_path('gist_delete', gist_id=gist.gist_id),
285 285 params={'csrf_token': self.csrf_token})
286 286 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
287 287
288 288 def test_delete_normal_user_not_his_own_gist(self, create_gist):
289 289 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
290 290 gist = create_gist('delete-me-2')
291 291
292 292 self.app.post(
293 293 route_path('gist_delete', gist_id=gist.gist_id),
294 294 params={'csrf_token': self.csrf_token}, status=404)
295 295
296 296 def test_show(self, create_gist):
297 297 gist = create_gist('gist-show-me')
298 298 response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
299 299
300 300 response.mustcontain('added file: gist-show-me<')
301 301
302 302 assert_response = response.assert_response()
303 303 assert_response.element_equals_to(
304 304 'div.rc-user span.user',
305 '<a href="/_profiles/test_admin">test_admin</a></span>')
305 '<a href="/_profiles/test_admin">test_admin</a>')
306 306
307 307 response.mustcontain('gist-desc')
308 308
309 309 def test_show_without_hg(self, create_gist):
310 310 with mock.patch(
311 311 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
312 312 gist = create_gist('gist-show-me-again')
313 313 self.app.get(
314 314 route_path('gist_show', gist_id=gist.gist_access_id), status=200)
315 315
316 316 def test_show_acl_private(self, create_gist):
317 317 gist = create_gist('gist-show-me-only-when-im-logged-in',
318 318 acl_level=Gist.ACL_LEVEL_PRIVATE)
319 319 self.app.get(
320 320 route_path('gist_show', gist_id=gist.gist_access_id), status=404)
321 321
322 322 # now we log-in we should see thi gist
323 323 self.log_user()
324 324 response = self.app.get(
325 325 route_path('gist_show', gist_id=gist.gist_access_id))
326 326 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
327 327
328 328 assert_response = response.assert_response()
329 329 assert_response.element_equals_to(
330 330 'div.rc-user span.user',
331 '<a href="/_profiles/test_admin">test_admin</a></span>')
331 '<a href="/_profiles/test_admin">test_admin</a>')
332 332 response.mustcontain('gist-desc')
333 333
334 334 def test_show_as_raw(self, create_gist):
335 335 gist = create_gist('gist-show-me', content='GIST CONTENT')
336 336 response = self.app.get(
337 337 route_path('gist_show_formatted',
338 338 gist_id=gist.gist_access_id, revision='tip',
339 339 format='raw'))
340 340 assert response.body == 'GIST CONTENT'
341 341
342 342 def test_show_as_raw_individual_file(self, create_gist):
343 343 gist = create_gist('gist-show-me-raw', content='GIST BODY')
344 344 response = self.app.get(
345 345 route_path('gist_show_formatted_path',
346 346 gist_id=gist.gist_access_id, format='raw',
347 347 revision='tip', f_path='gist-show-me-raw'))
348 348 assert response.body == 'GIST BODY'
349 349
350 350 def test_edit_page(self, create_gist):
351 351 self.log_user()
352 352 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
353 353 response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id))
354 354 response.mustcontain('GIST EDIT BODY')
355 355
356 356 def test_edit_page_non_logged_user(self, create_gist):
357 357 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
358 358 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
359 359 status=302)
360 360
361 361 def test_edit_normal_user_his_gist(self, create_gist):
362 362 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
363 363 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
364 364 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id,
365 365 status=200))
366 366
367 367 def test_edit_normal_user_not_his_own_gist(self, create_gist):
368 368 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
369 369 gist = create_gist('delete-me')
370 370 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
371 371 status=404)
372 372
373 373 def test_user_first_name_is_escaped(self, user_util, create_gist):
374 374 xss_atack_string = '"><script>alert(\'First Name\')</script>'
375 375 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
376 376 password = 'test'
377 377 user = user_util.create_user(
378 378 firstname=xss_atack_string, password=password)
379 379 create_gist('gist', gist_type='public', owner=user.username)
380 380 response = self.app.get(route_path('gists_show'))
381 381 response.mustcontain(xss_escaped_string)
382 382
383 383 def test_user_last_name_is_escaped(self, user_util, create_gist):
384 384 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
385 385 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
386 386 password = 'test'
387 387 user = user_util.create_user(
388 388 lastname=xss_atack_string, password=password)
389 389 create_gist('gist', gist_type='public', owner=user.username)
390 390 response = self.app.get(route_path('gists_show'))
391 391 response.mustcontain(xss_escaped_string)
@@ -1,520 +1,533 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 from rhodecode.apps._base import add_route_with_slash
21 21
22 22
23 23 def includeme(config):
24 24
25 25 # repo creating checks, special cases that aren't repo routes
26 26 config.add_route(
27 27 name='repo_creating',
28 28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29 29
30 30 config.add_route(
31 31 name='repo_creating_check',
32 32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33 33
34 34 # Summary
35 35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 36 # all pattern
37 37 config.add_route(
38 38 name='repo_summary_explicit',
39 39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 40 config.add_route(
41 41 name='repo_summary_commits',
42 42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43 43
44 44 # Commits
45 45 config.add_route(
46 46 name='repo_commit',
47 47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48 48
49 49 config.add_route(
50 50 name='repo_commit_children',
51 51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52 52
53 53 config.add_route(
54 54 name='repo_commit_parents',
55 55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56 56
57 57 config.add_route(
58 58 name='repo_commit_raw',
59 59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60 60
61 61 config.add_route(
62 62 name='repo_commit_patch',
63 63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64 64
65 65 config.add_route(
66 66 name='repo_commit_download',
67 67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68 68
69 69 config.add_route(
70 70 name='repo_commit_data',
71 71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72 72
73 73 config.add_route(
74 74 name='repo_commit_comment_create',
75 75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76 76
77 77 config.add_route(
78 78 name='repo_commit_comment_preview',
79 79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80 80
81 81 config.add_route(
82 name='repo_commit_comment_history_view',
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_history_id}/history_view', repo_route=True)
84
85 config.add_route(
82 86 name='repo_commit_comment_attachment_upload',
83 87 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
84 88
85 89 config.add_route(
86 90 name='repo_commit_comment_delete',
87 91 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
88 92
93 config.add_route(
94 name='repo_commit_comment_edit',
95 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/edit', repo_route=True)
96
89 97 # still working url for backward compat.
90 98 config.add_route(
91 99 name='repo_commit_raw_deprecated',
92 100 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
93 101
94 102 # Files
95 103 config.add_route(
96 104 name='repo_archivefile',
97 105 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
98 106
99 107 config.add_route(
100 108 name='repo_files_diff',
101 109 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
102 110 config.add_route( # legacy route to make old links work
103 111 name='repo_files_diff_2way_redirect',
104 112 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
105 113
106 114 config.add_route(
107 115 name='repo_files',
108 116 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
109 117 config.add_route(
110 118 name='repo_files:default_path',
111 119 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
112 120 config.add_route(
113 121 name='repo_files:default_commit',
114 122 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
115 123
116 124 config.add_route(
117 125 name='repo_files:rendered',
118 126 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
119 127
120 128 config.add_route(
121 129 name='repo_files:annotated',
122 130 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
123 131 config.add_route(
124 132 name='repo_files:annotated_previous',
125 133 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
126 134
127 135 config.add_route(
128 136 name='repo_nodetree_full',
129 137 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
130 138 config.add_route(
131 139 name='repo_nodetree_full:default_path',
132 140 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
133 141
134 142 config.add_route(
135 143 name='repo_files_nodelist',
136 144 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
137 145
138 146 config.add_route(
139 147 name='repo_file_raw',
140 148 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
141 149
142 150 config.add_route(
143 151 name='repo_file_download',
144 152 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
145 153 config.add_route( # backward compat to keep old links working
146 154 name='repo_file_download:legacy',
147 155 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
148 156 repo_route=True)
149 157
150 158 config.add_route(
151 159 name='repo_file_history',
152 160 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
153 161
154 162 config.add_route(
155 163 name='repo_file_authors',
156 164 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
157 165
158 166 config.add_route(
159 167 name='repo_files_check_head',
160 168 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
161 169 repo_route=True)
162 170 config.add_route(
163 171 name='repo_files_remove_file',
164 172 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
165 173 repo_route=True)
166 174 config.add_route(
167 175 name='repo_files_delete_file',
168 176 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
169 177 repo_route=True)
170 178 config.add_route(
171 179 name='repo_files_edit_file',
172 180 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
173 181 repo_route=True)
174 182 config.add_route(
175 183 name='repo_files_update_file',
176 184 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
177 185 repo_route=True)
178 186 config.add_route(
179 187 name='repo_files_add_file',
180 188 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
181 189 repo_route=True)
182 190 config.add_route(
183 191 name='repo_files_upload_file',
184 192 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
185 193 repo_route=True)
186 194 config.add_route(
187 195 name='repo_files_create_file',
188 196 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
189 197 repo_route=True)
190 198
191 199 # Refs data
192 200 config.add_route(
193 201 name='repo_refs_data',
194 202 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
195 203
196 204 config.add_route(
197 205 name='repo_refs_changelog_data',
198 206 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
199 207
200 208 config.add_route(
201 209 name='repo_stats',
202 210 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
203 211
204 212 # Commits
205 213 config.add_route(
206 214 name='repo_commits',
207 215 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
208 216 config.add_route(
209 217 name='repo_commits_file',
210 218 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
211 219 config.add_route(
212 220 name='repo_commits_elements',
213 221 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
214 222 config.add_route(
215 223 name='repo_commits_elements_file',
216 224 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
217 225
218 226 # Changelog (old deprecated name for commits page)
219 227 config.add_route(
220 228 name='repo_changelog',
221 229 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
222 230 config.add_route(
223 231 name='repo_changelog_file',
224 232 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
225 233
226 234 # Compare
227 235 config.add_route(
228 236 name='repo_compare_select',
229 237 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
230 238
231 239 config.add_route(
232 240 name='repo_compare',
233 241 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
234 242
235 243 # Tags
236 244 config.add_route(
237 245 name='tags_home',
238 246 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
239 247
240 248 # Branches
241 249 config.add_route(
242 250 name='branches_home',
243 251 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
244 252
245 253 # Bookmarks
246 254 config.add_route(
247 255 name='bookmarks_home',
248 256 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
249 257
250 258 # Forks
251 259 config.add_route(
252 260 name='repo_fork_new',
253 261 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
254 262 repo_forbid_when_archived=True,
255 263 repo_accepted_types=['hg', 'git'])
256 264
257 265 config.add_route(
258 266 name='repo_fork_create',
259 267 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
260 268 repo_forbid_when_archived=True,
261 269 repo_accepted_types=['hg', 'git'])
262 270
263 271 config.add_route(
264 272 name='repo_forks_show_all',
265 273 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
266 274 repo_accepted_types=['hg', 'git'])
267 275 config.add_route(
268 276 name='repo_forks_data',
269 277 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
270 278 repo_accepted_types=['hg', 'git'])
271 279
272 280 # Pull Requests
273 281 config.add_route(
274 282 name='pullrequest_show',
275 283 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
276 284 repo_route=True)
277 285
278 286 config.add_route(
279 287 name='pullrequest_show_all',
280 288 pattern='/{repo_name:.*?[^/]}/pull-request',
281 289 repo_route=True, repo_accepted_types=['hg', 'git'])
282 290
283 291 config.add_route(
284 292 name='pullrequest_show_all_data',
285 293 pattern='/{repo_name:.*?[^/]}/pull-request-data',
286 294 repo_route=True, repo_accepted_types=['hg', 'git'])
287 295
288 296 config.add_route(
289 297 name='pullrequest_repo_refs',
290 298 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
291 299 repo_route=True)
292 300
293 301 config.add_route(
294 302 name='pullrequest_repo_targets',
295 303 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
296 304 repo_route=True)
297 305
298 306 config.add_route(
299 307 name='pullrequest_new',
300 308 pattern='/{repo_name:.*?[^/]}/pull-request/new',
301 309 repo_route=True, repo_accepted_types=['hg', 'git'],
302 310 repo_forbid_when_archived=True)
303 311
304 312 config.add_route(
305 313 name='pullrequest_create',
306 314 pattern='/{repo_name:.*?[^/]}/pull-request/create',
307 315 repo_route=True, repo_accepted_types=['hg', 'git'],
308 316 repo_forbid_when_archived=True)
309 317
310 318 config.add_route(
311 319 name='pullrequest_update',
312 320 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
313 321 repo_route=True, repo_forbid_when_archived=True)
314 322
315 323 config.add_route(
316 324 name='pullrequest_merge',
317 325 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
318 326 repo_route=True, repo_forbid_when_archived=True)
319 327
320 328 config.add_route(
321 329 name='pullrequest_delete',
322 330 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
323 331 repo_route=True, repo_forbid_when_archived=True)
324 332
325 333 config.add_route(
326 334 name='pullrequest_comment_create',
327 335 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
328 336 repo_route=True)
329 337
330 338 config.add_route(
339 name='pullrequest_comment_edit',
340 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/edit',
341 repo_route=True, repo_accepted_types=['hg', 'git'])
342
343 config.add_route(
331 344 name='pullrequest_comment_delete',
332 345 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
333 346 repo_route=True, repo_accepted_types=['hg', 'git'])
334 347
335 348 # Artifacts, (EE feature)
336 349 config.add_route(
337 350 name='repo_artifacts_list',
338 351 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
339 352
340 353 # Settings
341 354 config.add_route(
342 355 name='edit_repo',
343 356 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
344 357 # update is POST on edit_repo
345 358
346 359 # Settings advanced
347 360 config.add_route(
348 361 name='edit_repo_advanced',
349 362 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
350 363 config.add_route(
351 364 name='edit_repo_advanced_archive',
352 365 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
353 366 config.add_route(
354 367 name='edit_repo_advanced_delete',
355 368 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
356 369 config.add_route(
357 370 name='edit_repo_advanced_locking',
358 371 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
359 372 config.add_route(
360 373 name='edit_repo_advanced_journal',
361 374 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
362 375 config.add_route(
363 376 name='edit_repo_advanced_fork',
364 377 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
365 378
366 379 config.add_route(
367 380 name='edit_repo_advanced_hooks',
368 381 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
369 382
370 383 # Caches
371 384 config.add_route(
372 385 name='edit_repo_caches',
373 386 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
374 387
375 388 # Permissions
376 389 config.add_route(
377 390 name='edit_repo_perms',
378 391 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
379 392
380 393 config.add_route(
381 394 name='edit_repo_perms_set_private',
382 395 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
383 396
384 397 # Permissions Branch (EE feature)
385 398 config.add_route(
386 399 name='edit_repo_perms_branch',
387 400 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
388 401 config.add_route(
389 402 name='edit_repo_perms_branch_delete',
390 403 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
391 404 repo_route=True)
392 405
393 406 # Maintenance
394 407 config.add_route(
395 408 name='edit_repo_maintenance',
396 409 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
397 410
398 411 config.add_route(
399 412 name='edit_repo_maintenance_execute',
400 413 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
401 414
402 415 # Fields
403 416 config.add_route(
404 417 name='edit_repo_fields',
405 418 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
406 419 config.add_route(
407 420 name='edit_repo_fields_create',
408 421 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
409 422 config.add_route(
410 423 name='edit_repo_fields_delete',
411 424 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
412 425
413 426 # Locking
414 427 config.add_route(
415 428 name='repo_edit_toggle_locking',
416 429 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
417 430
418 431 # Remote
419 432 config.add_route(
420 433 name='edit_repo_remote',
421 434 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
422 435 config.add_route(
423 436 name='edit_repo_remote_pull',
424 437 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
425 438 config.add_route(
426 439 name='edit_repo_remote_push',
427 440 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
428 441
429 442 # Statistics
430 443 config.add_route(
431 444 name='edit_repo_statistics',
432 445 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
433 446 config.add_route(
434 447 name='edit_repo_statistics_reset',
435 448 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
436 449
437 450 # Issue trackers
438 451 config.add_route(
439 452 name='edit_repo_issuetracker',
440 453 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
441 454 config.add_route(
442 455 name='edit_repo_issuetracker_test',
443 456 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
444 457 config.add_route(
445 458 name='edit_repo_issuetracker_delete',
446 459 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
447 460 config.add_route(
448 461 name='edit_repo_issuetracker_update',
449 462 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
450 463
451 464 # VCS Settings
452 465 config.add_route(
453 466 name='edit_repo_vcs',
454 467 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
455 468 config.add_route(
456 469 name='edit_repo_vcs_update',
457 470 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
458 471
459 472 # svn pattern
460 473 config.add_route(
461 474 name='edit_repo_vcs_svn_pattern_delete',
462 475 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
463 476
464 477 # Repo Review Rules (EE feature)
465 478 config.add_route(
466 479 name='repo_reviewers',
467 480 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
468 481
469 482 config.add_route(
470 483 name='repo_default_reviewers_data',
471 484 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
472 485
473 486 # Repo Automation (EE feature)
474 487 config.add_route(
475 488 name='repo_automation',
476 489 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
477 490
478 491 # Strip
479 492 config.add_route(
480 493 name='edit_repo_strip',
481 494 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
482 495
483 496 config.add_route(
484 497 name='strip_check',
485 498 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
486 499
487 500 config.add_route(
488 501 name='strip_execute',
489 502 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
490 503
491 504 # Audit logs
492 505 config.add_route(
493 506 name='edit_repo_audit_logs',
494 507 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
495 508
496 509 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
497 510 config.add_route(
498 511 name='rss_feed_home',
499 512 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
500 513
501 514 config.add_route(
502 515 name='atom_feed_home',
503 516 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
504 517
505 518 config.add_route(
506 519 name='rss_feed_home_old',
507 520 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
508 521
509 522 config.add_route(
510 523 name='atom_feed_home_old',
511 524 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
512 525
513 526 # NOTE(marcink): needs to be at the end for catch-all
514 527 add_route_with_slash(
515 528 config,
516 529 name='repo_summary',
517 530 pattern='/{repo_name:.*?[^/]}', repo_route=True)
518 531
519 532 # Scan module for configuration decorators.
520 533 config.scan('.views', ignore='.tests')
@@ -1,348 +1,507 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.tests import TestController
24 24
25 25 from rhodecode.model.db import ChangesetComment, Notification
26 26 from rhodecode.model.meta import Session
27 27 from rhodecode.lib import helpers as h
28 28
29 29
30 30 def route_path(name, params=None, **kwargs):
31 31 import urllib
32 32
33 33 base_url = {
34 34 'repo_commit': '/{repo_name}/changeset/{commit_id}',
35 35 'repo_commit_comment_create': '/{repo_name}/changeset/{commit_id}/comment/create',
36 36 'repo_commit_comment_preview': '/{repo_name}/changeset/{commit_id}/comment/preview',
37 37 'repo_commit_comment_delete': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete',
38 'repo_commit_comment_edit': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/edit',
38 39 }[name].format(**kwargs)
39 40
40 41 if params:
41 42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 43 return base_url
43 44
44 45
45 46 @pytest.mark.backends("git", "hg", "svn")
46 47 class TestRepoCommitCommentsView(TestController):
47 48
48 49 @pytest.fixture(autouse=True)
49 50 def prepare(self, request, baseapp):
50 51 for x in ChangesetComment.query().all():
51 52 Session().delete(x)
52 53 Session().commit()
53 54
54 55 for x in Notification.query().all():
55 56 Session().delete(x)
56 57 Session().commit()
57 58
58 59 request.addfinalizer(self.cleanup)
59 60
60 61 def cleanup(self):
61 62 for x in ChangesetComment.query().all():
62 63 Session().delete(x)
63 64 Session().commit()
64 65
65 66 for x in Notification.query().all():
66 67 Session().delete(x)
67 68 Session().commit()
68 69
69 70 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
70 71 def test_create(self, comment_type, backend):
71 72 self.log_user()
72 73 commit = backend.repo.get_commit('300')
73 74 commit_id = commit.raw_id
74 75 text = u'CommentOnCommit'
75 76
76 77 params = {'text': text, 'csrf_token': self.csrf_token,
77 78 'comment_type': comment_type}
78 79 self.app.post(
79 80 route_path('repo_commit_comment_create',
80 81 repo_name=backend.repo_name, commit_id=commit_id),
81 82 params=params)
82 83
83 84 response = self.app.get(
84 85 route_path('repo_commit',
85 86 repo_name=backend.repo_name, commit_id=commit_id))
86 87
87 88 # test DB
88 89 assert ChangesetComment.query().count() == 1
89 90 assert_comment_links(response, ChangesetComment.query().count(), 0)
90 91
91 92 assert Notification.query().count() == 1
92 93 assert ChangesetComment.query().count() == 1
93 94
94 95 notification = Notification.query().all()[0]
95 96
96 97 comment_id = ChangesetComment.query().first().comment_id
97 98 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
98 99
99 100 author = notification.created_by_user.username_and_name
100 101 sbj = '@{0} left a {1} on commit `{2}` in the `{3}` repository'.format(
101 102 author, comment_type, h.show_id(commit), backend.repo_name)
102 103 assert sbj == notification.subject
103 104
104 105 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
105 106 backend.repo_name, commit_id, comment_id))
106 107 assert lnk in notification.body
107 108
108 109 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
109 110 def test_create_inline(self, comment_type, backend):
110 111 self.log_user()
111 112 commit = backend.repo.get_commit('300')
112 113 commit_id = commit.raw_id
113 114 text = u'CommentOnCommit'
114 115 f_path = 'vcs/web/simplevcs/views/repository.py'
115 116 line = 'n1'
116 117
117 118 params = {'text': text, 'f_path': f_path, 'line': line,
118 119 'comment_type': comment_type,
119 120 'csrf_token': self.csrf_token}
120 121
121 122 self.app.post(
122 123 route_path('repo_commit_comment_create',
123 124 repo_name=backend.repo_name, commit_id=commit_id),
124 125 params=params)
125 126
126 127 response = self.app.get(
127 128 route_path('repo_commit',
128 129 repo_name=backend.repo_name, commit_id=commit_id))
129 130
130 131 # test DB
131 132 assert ChangesetComment.query().count() == 1
132 133 assert_comment_links(response, 0, ChangesetComment.query().count())
133 134
134 135 if backend.alias == 'svn':
135 136 response.mustcontain(
136 137 '''data-f-path="vcs/commands/summary.py" '''
137 138 '''data-anchor-id="c-300-ad05457a43f8"'''
138 139 )
139 140 if backend.alias == 'git':
140 141 response.mustcontain(
141 142 '''data-f-path="vcs/backends/hg.py" '''
142 143 '''data-anchor-id="c-883e775e89ea-9c390eb52cd6"'''
143 144 )
144 145
145 146 if backend.alias == 'hg':
146 147 response.mustcontain(
147 148 '''data-f-path="vcs/backends/hg.py" '''
148 149 '''data-anchor-id="c-e58d85a3973b-9c390eb52cd6"'''
149 150 )
150 151
151 152 assert Notification.query().count() == 1
152 153 assert ChangesetComment.query().count() == 1
153 154
154 155 notification = Notification.query().all()[0]
155 156 comment = ChangesetComment.query().first()
156 157 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
157 158
158 159 assert comment.revision == commit_id
159 160
160 161 author = notification.created_by_user.username_and_name
161 162 sbj = '@{0} left a {1} on file `{2}` in commit `{3}` in the `{4}` repository'.format(
162 163 author, comment_type, f_path, h.show_id(commit), backend.repo_name)
163 164
164 165 assert sbj == notification.subject
165 166
166 167 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
167 168 backend.repo_name, commit_id, comment.comment_id))
168 169 assert lnk in notification.body
169 170 assert 'on line n1' in notification.body
170 171
171 172 def test_create_with_mention(self, backend):
172 173 self.log_user()
173 174
174 175 commit_id = backend.repo.get_commit('300').raw_id
175 176 text = u'@test_regular check CommentOnCommit'
176 177
177 178 params = {'text': text, 'csrf_token': self.csrf_token}
178 179 self.app.post(
179 180 route_path('repo_commit_comment_create',
180 181 repo_name=backend.repo_name, commit_id=commit_id),
181 182 params=params)
182 183
183 184 response = self.app.get(
184 185 route_path('repo_commit',
185 186 repo_name=backend.repo_name, commit_id=commit_id))
186 187 # test DB
187 188 assert ChangesetComment.query().count() == 1
188 189 assert_comment_links(response, ChangesetComment.query().count(), 0)
189 190
190 191 notification = Notification.query().one()
191 192
192 193 assert len(notification.recipients) == 2
193 194 users = [x.username for x in notification.recipients]
194 195
195 196 # test_regular gets notification by @mention
196 197 assert sorted(users) == [u'test_admin', u'test_regular']
197 198
198 199 def test_create_with_status_change(self, backend):
199 200 self.log_user()
200 201 commit = backend.repo.get_commit('300')
201 202 commit_id = commit.raw_id
202 203 text = u'CommentOnCommit'
203 204 f_path = 'vcs/web/simplevcs/views/repository.py'
204 205 line = 'n1'
205 206
206 207 params = {'text': text, 'changeset_status': 'approved',
207 208 'csrf_token': self.csrf_token}
208 209
209 210 self.app.post(
210 211 route_path(
211 212 'repo_commit_comment_create',
212 213 repo_name=backend.repo_name, commit_id=commit_id),
213 214 params=params)
214 215
215 216 response = self.app.get(
216 217 route_path('repo_commit',
217 218 repo_name=backend.repo_name, commit_id=commit_id))
218 219
219 220 # test DB
220 221 assert ChangesetComment.query().count() == 1
221 222 assert_comment_links(response, ChangesetComment.query().count(), 0)
222 223
223 224 assert Notification.query().count() == 1
224 225 assert ChangesetComment.query().count() == 1
225 226
226 227 notification = Notification.query().all()[0]
227 228
228 229 comment_id = ChangesetComment.query().first().comment_id
229 230 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
230 231
231 232 author = notification.created_by_user.username_and_name
232 233 sbj = '[status: Approved] @{0} left a note on commit `{1}` in the `{2}` repository'.format(
233 234 author, h.show_id(commit), backend.repo_name)
234 235 assert sbj == notification.subject
235 236
236 237 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
237 238 backend.repo_name, commit_id, comment_id))
238 239 assert lnk in notification.body
239 240
240 241 def test_delete(self, backend):
241 242 self.log_user()
242 243 commit_id = backend.repo.get_commit('300').raw_id
243 244 text = u'CommentOnCommit'
244 245
245 246 params = {'text': text, 'csrf_token': self.csrf_token}
246 247 self.app.post(
247 248 route_path(
248 249 'repo_commit_comment_create',
249 250 repo_name=backend.repo_name, commit_id=commit_id),
250 251 params=params)
251 252
252 253 comments = ChangesetComment.query().all()
253 254 assert len(comments) == 1
254 255 comment_id = comments[0].comment_id
255 256
256 257 self.app.post(
257 258 route_path('repo_commit_comment_delete',
258 259 repo_name=backend.repo_name,
259 260 commit_id=commit_id,
260 261 comment_id=comment_id),
261 262 params={'csrf_token': self.csrf_token})
262 263
263 264 comments = ChangesetComment.query().all()
264 265 assert len(comments) == 0
265 266
266 267 response = self.app.get(
267 268 route_path('repo_commit',
268 269 repo_name=backend.repo_name, commit_id=commit_id))
269 270 assert_comment_links(response, 0, 0)
270 271
272 def test_edit(self, backend):
273 self.log_user()
274 commit_id = backend.repo.get_commit('300').raw_id
275 text = u'CommentOnCommit'
276
277 params = {'text': text, 'csrf_token': self.csrf_token}
278 self.app.post(
279 route_path(
280 'repo_commit_comment_create',
281 repo_name=backend.repo_name, commit_id=commit_id),
282 params=params)
283
284 comments = ChangesetComment.query().all()
285 assert len(comments) == 1
286 comment_id = comments[0].comment_id
287 test_text = 'test_text'
288 self.app.post(
289 route_path(
290 'repo_commit_comment_edit',
291 repo_name=backend.repo_name,
292 commit_id=commit_id,
293 comment_id=comment_id,
294 ),
295 params={
296 'csrf_token': self.csrf_token,
297 'text': test_text,
298 'version': '0',
299 })
300
301 text_form_db = ChangesetComment.query().filter(
302 ChangesetComment.comment_id == comment_id).first().text
303 assert test_text == text_form_db
304
305 def test_edit_without_change(self, backend):
306 self.log_user()
307 commit_id = backend.repo.get_commit('300').raw_id
308 text = u'CommentOnCommit'
309
310 params = {'text': text, 'csrf_token': self.csrf_token}
311 self.app.post(
312 route_path(
313 'repo_commit_comment_create',
314 repo_name=backend.repo_name, commit_id=commit_id),
315 params=params)
316
317 comments = ChangesetComment.query().all()
318 assert len(comments) == 1
319 comment_id = comments[0].comment_id
320
321 response = self.app.post(
322 route_path(
323 'repo_commit_comment_edit',
324 repo_name=backend.repo_name,
325 commit_id=commit_id,
326 comment_id=comment_id,
327 ),
328 params={
329 'csrf_token': self.csrf_token,
330 'text': text,
331 'version': '0',
332 },
333 status=404,
334 )
335 assert response.status_int == 404
336
337 def test_edit_try_edit_already_edited(self, backend):
338 self.log_user()
339 commit_id = backend.repo.get_commit('300').raw_id
340 text = u'CommentOnCommit'
341
342 params = {'text': text, 'csrf_token': self.csrf_token}
343 self.app.post(
344 route_path(
345 'repo_commit_comment_create',
346 repo_name=backend.repo_name, commit_id=commit_id
347 ),
348 params=params,
349 )
350
351 comments = ChangesetComment.query().all()
352 assert len(comments) == 1
353 comment_id = comments[0].comment_id
354 test_text = 'test_text'
355 self.app.post(
356 route_path(
357 'repo_commit_comment_edit',
358 repo_name=backend.repo_name,
359 commit_id=commit_id,
360 comment_id=comment_id,
361 ),
362 params={
363 'csrf_token': self.csrf_token,
364 'text': test_text,
365 'version': '0',
366 }
367 )
368 test_text_v2 = 'test_v2'
369 response = self.app.post(
370 route_path(
371 'repo_commit_comment_edit',
372 repo_name=backend.repo_name,
373 commit_id=commit_id,
374 comment_id=comment_id,
375 ),
376 params={
377 'csrf_token': self.csrf_token,
378 'text': test_text_v2,
379 'version': '0',
380 },
381 status=409,
382 )
383 assert response.status_int == 409
384
385 text_form_db = ChangesetComment.query().filter(
386 ChangesetComment.comment_id == comment_id).first().text
387
388 assert test_text == text_form_db
389 assert test_text_v2 != text_form_db
390
391 def test_edit_forbidden_for_immutable_comments(self, backend):
392 self.log_user()
393 commit_id = backend.repo.get_commit('300').raw_id
394 text = u'CommentOnCommit'
395
396 params = {'text': text, 'csrf_token': self.csrf_token, 'version': '0'}
397 self.app.post(
398 route_path(
399 'repo_commit_comment_create',
400 repo_name=backend.repo_name,
401 commit_id=commit_id,
402 ),
403 params=params
404 )
405
406 comments = ChangesetComment.query().all()
407 assert len(comments) == 1
408 comment_id = comments[0].comment_id
409
410 comment = ChangesetComment.get(comment_id)
411 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
412 Session().add(comment)
413 Session().commit()
414
415 response = self.app.post(
416 route_path(
417 'repo_commit_comment_edit',
418 repo_name=backend.repo_name,
419 commit_id=commit_id,
420 comment_id=comment_id,
421 ),
422 params={
423 'csrf_token': self.csrf_token,
424 'text': 'test_text',
425 },
426 status=403,
427 )
428 assert response.status_int == 403
429
271 430 def test_delete_forbidden_for_immutable_comments(self, backend):
272 431 self.log_user()
273 432 commit_id = backend.repo.get_commit('300').raw_id
274 433 text = u'CommentOnCommit'
275 434
276 435 params = {'text': text, 'csrf_token': self.csrf_token}
277 436 self.app.post(
278 437 route_path(
279 438 'repo_commit_comment_create',
280 439 repo_name=backend.repo_name, commit_id=commit_id),
281 440 params=params)
282 441
283 442 comments = ChangesetComment.query().all()
284 443 assert len(comments) == 1
285 444 comment_id = comments[0].comment_id
286 445
287 446 comment = ChangesetComment.get(comment_id)
288 447 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
289 448 Session().add(comment)
290 449 Session().commit()
291 450
292 451 self.app.post(
293 452 route_path('repo_commit_comment_delete',
294 453 repo_name=backend.repo_name,
295 454 commit_id=commit_id,
296 455 comment_id=comment_id),
297 456 params={'csrf_token': self.csrf_token},
298 457 status=403)
299 458
300 459 @pytest.mark.parametrize('renderer, text_input, output', [
301 460 ('rst', 'plain text', '<p>plain text</p>'),
302 461 ('rst', 'header\n======', '<h1 class="title">header</h1>'),
303 462 ('rst', '*italics*', '<em>italics</em>'),
304 463 ('rst', '**bold**', '<strong>bold</strong>'),
305 464 ('markdown', 'plain text', '<p>plain text</p>'),
306 465 ('markdown', '# header', '<h1>header</h1>'),
307 466 ('markdown', '*italics*', '<em>italics</em>'),
308 467 ('markdown', '**bold**', '<strong>bold</strong>'),
309 468 ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain',
310 469 'md-header', 'md-italics', 'md-bold', ])
311 470 def test_preview(self, renderer, text_input, output, backend, xhr_header):
312 471 self.log_user()
313 472 params = {
314 473 'renderer': renderer,
315 474 'text': text_input,
316 475 'csrf_token': self.csrf_token
317 476 }
318 477 commit_id = '0' * 16 # fake this for tests
319 478 response = self.app.post(
320 479 route_path('repo_commit_comment_preview',
321 480 repo_name=backend.repo_name, commit_id=commit_id,),
322 481 params=params,
323 482 extra_environ=xhr_header)
324 483
325 484 response.mustcontain(output)
326 485
327 486
328 487 def assert_comment_links(response, comments, inline_comments):
329 488 if comments == 1:
330 489 comments_text = "%d General" % comments
331 490 else:
332 491 comments_text = "%d General" % comments
333 492
334 493 if inline_comments == 1:
335 494 inline_comments_text = "%d Inline" % inline_comments
336 495 else:
337 496 inline_comments_text = "%d Inline" % inline_comments
338 497
339 498 if comments:
340 499 response.mustcontain('<a href="#comments">%s</a>,' % comments_text)
341 500 else:
342 501 response.mustcontain(comments_text)
343 502
344 503 if inline_comments:
345 504 response.mustcontain(
346 505 'id="inline-comments-counter">%s' % inline_comments_text)
347 506 else:
348 507 response.mustcontain(inline_comments_text)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
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