##// 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
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,6 +1,5 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 4.19.3
2 current_version = 4.20.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:rhodecode/VERSION]
5 [bumpversion:file:rhodecode/VERSION]
6
@@ -1,33 +1,28 b''
1 [DEFAULT]
1 [DEFAULT]
2 done = false
2 done = false
3
3
4 [task:bump_version]
4 [task:bump_version]
5 done = true
5 done = true
6
6
7 [task:rc_tools_pinned]
7 [task:rc_tools_pinned]
8 done = true
9
8
10 [task:fixes_on_stable]
9 [task:fixes_on_stable]
11 done = true
12
10
13 [task:pip2nix_generated]
11 [task:pip2nix_generated]
14 done = true
15
12
16 [task:changelog_updated]
13 [task:changelog_updated]
17 done = true
18
14
19 [task:generate_api_docs]
15 [task:generate_api_docs]
20 done = true
16
17 [task:updated_translation]
21
18
22 [release]
19 [release]
23 state = prepared
20 state = in_progress
24 version = 4.19.3
21 version = 4.20.0
25
26 [task:updated_translation]
27
22
28 [task:generate_js_routes]
23 [task:generate_js_routes]
29
24
30 [task:updated_trial_license]
25 [task:updated_trial_license]
31
26
32 [task:generate_oss_licenses]
27 [task:generate_oss_licenses]
33
28
@@ -1,264 +1,264 b''
1 .. _admin-tricks:
1 .. _admin-tricks:
2
2
3 One-time Admin Tasks
3 One-time Admin Tasks
4 --------------------
4 --------------------
5
5
6 * :ref:`web-analytics`
6 * :ref:`web-analytics`
7 * :ref:`admin-tricks-license`
7 * :ref:`admin-tricks-license`
8 * :ref:`announcements`
8 * :ref:`announcements`
9 * :ref:`md-rst`
9 * :ref:`md-rst`
10 * :ref:`repo-stats`
10 * :ref:`repo-stats`
11 * :ref:`server-side-merge`
11 * :ref:`server-side-merge`
12 * :ref:`remap-rescan`
12 * :ref:`remap-rescan`
13 * :ref:`custom-hooks`
13 * :ref:`custom-hooks`
14 * :ref:`clear-repo-cache`
14 * :ref:`clear-repo-cache`
15 * :ref:`set-repo-pub`
15 * :ref:`set-repo-pub`
16 * :ref:`ping`
16 * :ref:`ping`
17
17
18 .. _web-analytics:
18 .. _web-analytics:
19
19
20 Adding Web Analytics
20 Adding Web Analytics
21 ^^^^^^^^^^^^^^^^^^^^
21 ^^^^^^^^^^^^^^^^^^^^
22
22
23 If you wish to add a Google Analytics, or any other kind of tracker to your
23 If you wish to add a Google Analytics, or any other kind of tracker to your
24 |RCE| instance you can add the necessary codes to the header or footer
24 |RCE| instance you can add the necessary codes to the header or footer
25 section of each instance using the following steps:
25 section of each instance using the following steps:
26
26
27 1. From the |RCE| interface, select
27 1. From the |RCE| interface, select
28 :menuselection:`Admin --> Settings --> Global`
28 :menuselection:`Admin --> Settings --> Global`
29 2. To add a tracking code to you instance, enter it in the header or footer
29 2. To add a tracking code to you instance, enter it in the header or footer
30 section and select **Save**
30 section and select **Save**
31
31
32 Use the example templates in the drop-down menu to set up your configuration.
32 Use the example templates in the drop-down menu to set up your configuration.
33
33
34 .. _admin-tricks-license:
34 .. _admin-tricks-license:
35
35
36 Licence Key Management
36 Licence Key Management
37 ^^^^^^^^^^^^^^^^^^^^^^
37 ^^^^^^^^^^^^^^^^^^^^^^
38
38
39 To manage your license key, go to
39 To manage your license key, go to
40 :menuselection:`Admin --> Settings --> License`.
40 :menuselection:`Admin --> Settings --> License`.
41 On this page you can see the license key details. If you need a new license,
41 On this page you can see the license key details. If you need a new license,
42 or have questions about your current one, contact support@rhodecode.com
42 or have questions about your current one, contact support@rhodecode.com
43
43
44 .. _announcements:
44 .. _announcements:
45
45
46 Server-wide Announcements
46 Server-wide Announcements
47 ^^^^^^^^^^^^^^^^^^^^^^^^^
47 ^^^^^^^^^^^^^^^^^^^^^^^^^
48
48
49 If you need to make a server-wide announcement to all users,
49 If you need to make a server-wide announcement to all users,
50 you can add a message to be displayed using the following steps:
50 you can add a message to be displayed using the following steps:
51
51
52 1. From the |RCE| interface, select
52 1. From the |RCE| interface, select
53 :menuselection:`Admin --> Settings --> Global`
53 :menuselection:`Admin --> Settings --> Global`
54 2. To add a message that will be displayed to all users,
54 2. To add a message that will be displayed to all users,
55 select :guilabel:`Server Announcement` from the drop-down menu and
55 select :guilabel:`Server Announcement` from the drop-down menu and
56 change the ``var message = "TYPE YOUR MESSAGE HERE";`` example line.
56 change the ``var message = "TYPE YOUR MESSAGE HERE";`` example line.
57 3. Select :guilabel:`Save`, and you will see the message once your page
57 3. Select :guilabel:`Save`, and you will see the message once your page
58 refreshes.
58 refreshes.
59
59
60 .. image:: ../../images/server-wide-announcement.png
60 .. image:: ../../images/server-wide-announcement.png
61 :alt: Server Wide Announcement
61 :alt: Server Wide Announcement
62
62
63 .. _md-rst:
63 .. _md-rst:
64
64
65
65
66 Suppress license warnings or errors
66 Suppress license warnings or errors
67 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
68
68
69 In case you're running on maximum allowed users, RhodeCode will display a
69 In case you're running on maximum allowed users, RhodeCode will display a
70 warning message on pages that you're close to the license limits.
70 warning message on pages that you're close to the license limits.
71 It's often not desired to show that all the time. Here's how you can suppress
71 It's often not desired to show that all the time. Here's how you can suppress
72 the license messages.
72 the license messages.
73
73
74 1. From the |RCE| interface, select
74 1. From the |RCE| interface, select
75 :menuselection:`Admin --> Settings --> Global`
75 :menuselection:`Admin --> Settings --> Global`
76 2. Select :guilabel:`Flash message filtering` from the drop-down menu.
76 2. Select :guilabel:`Flash message filtering` from the drop-down menu.
77 3. Select :guilabel:`Save`, and you will no longer see the license message
77 3. Select :guilabel:`Save`, and you will no longer see the license message
78 once your page refreshes.
78 once your page refreshes.
79
79
80 .. _admin-tricks-suppress-license-messages:
80 .. _admin-tricks-suppress-license-messages:
81
81
82
82
83 Markdown or RST Rendering
83 Markdown or RST Rendering
84 ^^^^^^^^^^^^^^^^^^^^^^^^^
84 ^^^^^^^^^^^^^^^^^^^^^^^^^
85
85
86 |RCE| can use `Markdown`_ or `reStructured Text`_ in commit message,
86 |RCE| can use `Markdown`_ or `reStructured Text`_ in commit message,
87 code review messages, and inline comments. To set the default to either,
87 code review messages, and inline comments. To set the default to either,
88 select your preference from the drop-down menu on the
88 select your preference from the drop-down menu on the
89 :menuselection:`Admin --> Settings --> Visual` page and select
89 :menuselection:`Admin --> Settings --> Visual` page and select
90 :guilabel:`Save settings`.
90 :guilabel:`Save settings`.
91
91
92 .. _repo-stats:
92 .. _repo-stats:
93
93
94 Enabling Repository Statistics
94 Enabling Repository Statistics
95 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
95 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
96
96
97 To enable |repo| statistics, use the following steps:
97 To enable |repo| statistics, use the following steps:
98
98
99 1. From the |RCE| interface, open
99 1. From the |RCE| interface, open
100 :menuselection:`Admin --> Repositories` and select
100 :menuselection:`Admin --> Repositories` and select
101 :guilabel:`Edit` beside the |repo| for which you wish to enable statistics.
101 :guilabel:`Edit` beside the |repo| for which you wish to enable statistics.
102 2. Check the :guilabel:`Enable statistics` box, and select :guilabel:`Save`
102 2. Check the :guilabel:`Enable statistics` box, and select :guilabel:`Save`
103
103
104 .. _server-side-merge:
104 .. _server-side-merge:
105
105
106 Enabling Server-side Merging
106 Enabling Server-side Merging
107 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
107 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108
108
109 To enable server-side merging, use the following steps:
109 To enable server-side merging, use the following steps:
110
110
111 1. From the |RCE| interface, open :menuselection:`Admin --> Settings --> VCS`
111 1. From the |RCE| interface, open :menuselection:`Admin --> Settings --> VCS`
112 2. Check the :guilabel:`Server-side merge` box, and select
112 2. Check the :guilabel:`Server-side merge` box, and select
113 :guilabel:`Save Settings`
113 :guilabel:`Save Settings`
114
114
115 If you encounter slow performance with server-side merging enabled, check the
115 If you encounter slow performance with server-side merging enabled, check the
116 speed at which your server is performing actions. When server-side merging is
116 speed at which your server is performing actions. When server-side merging is
117 enabled, the following actions occurs on the server.
117 enabled, the following actions occurs on the server.
118
118
119 * A |pr| is created in the database.
119 * A |pr| is created in the database.
120 * A shadow |repo| is created as a working environment for the |pr|.
120 * A shadow |repo| is created as a working environment for the |pr|.
121 * On display, |RCE| checks if the |pr| can be merged.
121 * On display, |RCE| checks if the |pr| can be merged.
122
122
123 To check how fast the shadow |repo| creation is occurring on your server, use
123 To check how fast the shadow |repo| creation is occurring on your server, use
124 the following steps:
124 the following steps:
125
125
126 1. Log into your server and create a directory in your |repos| folder.
126 1. Log into your server and create a directory in your |repos| folder.
127 2. Clone a |repo| that is showing slow performance and time the action.
127 2. Clone a |repo| that is showing slow performance and time the action.
128
128
129 .. code-block:: bash
129 .. code-block:: bash
130
130
131 # One option is to use the time command
131 # One option is to use the time command
132 $ time hg clone SOURCE_REPO TARGET
132 $ time hg clone SOURCE_REPO TARGET
133
133
134 .. _remap-rescan:
134 .. _remap-rescan:
135
135
136 Remap and Rescan Repositories
136 Remap and Rescan Repositories
137 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
137 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
138
138
139 You may want to Remap and rescan the |repos| that |RCE| is managing to ensure
139 You may want to Remap and rescan the |repos| that |RCE| is managing to ensure
140 the system is always up-to-date. This is useful after importing, deleting,
140 the system is always up-to-date. This is useful after importing, deleting,
141 or carrying out general cleaning up operations. To do this use the
141 or carrying out general cleaning up operations. To do this use the
142 following steps:
142 following steps:
143
143
144 1. From the |RCE|, open
144 1. From the |RCE|, open
145 :menuselection:`Admin --> Settings --> Remap and rescan`
145 :menuselection:`Admin --> Settings --> Remap and rescan`
146 2. Click :guilabel:`Rescan Repositories`
146 2. Click :guilabel:`Rescan Repositories`
147
147
148 Check the additional options if needed:
148 Check the additional options if needed:
149
149
150 * :guilabel:`Destroy old data`: Useful for purging deleted repository
150 * :guilabel:`Destroy old data`: Useful for purging deleted repository
151 information from the database.
151 information from the database.
152 * :guilabel:`Invalidate cache for all repositories`: Use this to completely
152 * :guilabel:`Invalidate cache for all repositories`: Use this to completely
153 remap all |repos|. Useful when importing or migrating |repos| to ensure all
153 remap all |repos|. Useful when importing or migrating |repos| to ensure all
154 new information is picked up.
154 new information is picked up.
155
155
156 .. _custom-hooks:
156 .. _custom-hooks:
157
157
158 Adding Custom Hooks
158 Adding Custom Hooks
159 ^^^^^^^^^^^^^^^^^^^
159 ^^^^^^^^^^^^^^^^^^^
160
160
161 To add custom hooks to your instance, use the following steps:
161 To add custom hooks to your instance, use the following steps:
162
162
163 1. Open :menuselection:`Admin --> Settings --> Hooks`
163 1. Open :menuselection:`Admin --> Settings --> Hooks`
164 2. Add your custom hook details, you can use a file path to specify custom
164 2. Add your custom hook details, you can use a file path to specify custom
165 hook scripts, for example:
165 hook scripts, for example:
166 ``pretxnchangegroup.example`` with value ``python:/path/to/custom_hook.py:my_func_name``
166 ``pretxnchangegroup.example`` with value ``python:/path/to/custom_hook.py:my_func_name``
167 3. Select :guilabel:`Save`
167 3. Select :guilabel:`Save`
168
168
169 Also, see the RhodeCode Extensions section of the :ref:`rc-tools` guide. RhodeCode
169 Also, see the RhodeCode Extensions section of the :ref:`rc-tools` guide. RhodeCode
170 Extensions can be used to add additional hooks to your instance and comes
170 Extensions can be used to add additional hooks to your instance and comes
171 with a number of pre-built plugins if you chose to install them.
171 with a number of pre-built plugins if you chose to install them.
172
172
173 .. _clear-repo-cache:
173 .. _clear-repo-cache:
174
174
175 Clearing |repo| cache
175 Clearing |repo| cache
176 ^^^^^^^^^^^^^^^^^^^^^
176 ^^^^^^^^^^^^^^^^^^^^^
177
177
178 If you need to clear the cache for a particular |repo|, use the following steps:
178 If you need to clear the cache for a particular |repo|, use the following steps:
179
179
180 1. Open :menuselection:`Admin --> Repositories` and select :guilabel:`Edit`
180 1. Open :menuselection:`Admin --> Repositories` and select :guilabel:`Edit`
181 beside the |repo| whose cache you wish to clear.
181 beside the |repo| whose cache you wish to clear.
182 2. On the |repo| settings page, go to the :guilabel:`Caches` tab and select
182 2. On the |repo| settings page, go to the :guilabel:`Caches` tab and select
183 :guilabel:`Invalidate repository cache`.
183 :guilabel:`Invalidate repository cache`.
184
184
185 .. _set-lang:
185 .. _set-lang:
186
186
187 Changing Default Language
187 Changing Default Language
188 ^^^^^^^^^^^^^^^^^^^^^^^^^
188 ^^^^^^^^^^^^^^^^^^^^^^^^^
189
189
190 To change the default language of a |RCE| instance, change the language code
190 To change the default language of a |RCE| instance, change the language code
191 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To
191 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To
192 do this, use the following steps.
192 do this, use the following steps.
193
193
194 1. Open the :file:`rhodecode.ini` file and set the required language code.
194 1. Open the :file:`rhodecode.ini` file and set the required language code.
195
195
196 .. code-block:: ini
196 .. code-block:: ini
197
197
198 ## Optional Languages
198 ## Optional Languages
199 ## en(default), de, fr, it, ja, pl, pt, ru, zh
199 ## en(default), de, fr, it, ja, pl, pt, ru, zh
200 lang = de
200 lang = de
201
201
202 2. Restart the |RCE| instance and check that the language has been updated.
202 2. Restart the |RCE| instance and check that the language has been updated.
203
203
204 .. code-block:: bash
204 .. code-block:: bash
205
205
206 $ rccontrol restart enterprise-2
206 $ rccontrol restart enterprise-2
207 Instance "enterprise-2" successfully stopped.
207 Instance "enterprise-2" successfully stopped.
208 Instance "enterprise-2" successfully started.
208 Instance "enterprise-2" successfully started.
209
209
210 .. image:: ../../images/language.png
210 .. image:: ../../images/language.png
211
211
212 .. _set-repo-pub:
212 .. _set-repo-pub:
213
213
214 Setting Repositories to Publish
214 Setting Repositories to Publish
215 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
215 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
216
216
217 To automatically promote your local |repos| to public after pushing to |RCE|,
217 To automatically promote your local |repos| to public after pushing to |RCE|,
218 enable the :guilabel:`Set repositories as publishing` option on the
218 enable the :guilabel:`Set repositories as publishing` option on the
219 :menuselection:`Admin --> Settings --> VCS` page.
219 :menuselection:`Admin --> Settings --> VCS` page.
220
220
221 .. note::
221 .. note::
222
222
223 This option is enabled by default on most |RCE| versions, but if upgrading
223 This option is enabled by default on most |RCE| versions, but if upgrading
224 from a 1.7.x version it could be disabled on upgrade due to inheriting
224 from a 1.7.x version it could be disabled on upgrade due to inheriting
225 older default settings.
225 older default settings.
226
226
227 .. _ping:
227 .. _ping:
228
228
229 Pinging the |RCE| Server
229 Pinging the |RCE| Server
230 ^^^^^^^^^^^^^^^^^^^^^^^^
230 ^^^^^^^^^^^^^^^^^^^^^^^^
231
231
232 You can check the IP Address of your |RCE| instance using the
232 You can check the IP Address of your |RCE| instance using the
233 following URL: ``{instance-URL}/_admin/ping``.
233 following URL: ``{instance-URL}/_admin/ping``.
234
234
235 .. code-block:: bash
235 .. code-block:: bash
236
236
237 $ curl https://your.rhodecode.url/_admin/ping
237 $ curl https://your.rhodecode.url/_admin/ping
238 pong[rce-7880] => 203.0.113.23
238 pong[rce-7880] => 203.0.113.23
239
239
240 .. _Markdown: http://daringfireball.net/projects/markdown/
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 Unarchiving a repository
244 Unarchiving a repository
245 ^^^^^^^^^^^^^^^^^^^^^^^^^
245 ^^^^^^^^^^^^^^^^^^^^^^^^^
246
246
247 Archive operation for the repository is similar as delete. Archive keeps the data for future references
247 Archive operation for the repository is similar as delete. Archive keeps the data for future references
248 but makes the repository read-only. After archiving the repository it shouldn't be modified in any way.
248 but makes the repository read-only. After archiving the repository it shouldn't be modified in any way.
249 This is why repository settings are disabled for an archived repository.
249 This is why repository settings are disabled for an archived repository.
250
250
251 If there's a need for unarchiving a repository for some reasons, the interactive
251 If there's a need for unarchiving a repository for some reasons, the interactive
252 ishell interface should be used.
252 ishell interface should be used.
253
253
254 .. code-block:: bash
254 .. code-block:: bash
255
255
256 # Open iShell from the terminal
256 # Open iShell from the terminal
257 $ rccontrol ishell enterprise-1/community-1
257 $ rccontrol ishell enterprise-1/community-1
258
258
259 .. code-block:: python
259 .. code-block:: python
260
260
261 # Set repository as un-archived
261 # Set repository as un-archived
262 In [1]: repo = Repository.get_by_repo_name('SOME_REPO_NAME')
262 In [1]: repo = Repository.get_by_repo_name('SOME_REPO_NAME')
263 In [2]: repo.archived = False
263 In [2]: repo.archived = False
264 In [3]: Session().add(repo);Session().commit()
264 In [3]: Session().add(repo);Session().commit()
@@ -1,107 +1,107 b''
1 .. _apache-conf-eg:
1 .. _apache-conf-eg:
2
2
3 Apache Configuration Example
3 Apache Configuration Example
4 ----------------------------
4 ----------------------------
5
5
6 Use the following example to configure Apache as a your web server.
6 Use the following example to configure Apache as a your web server.
7 Below config if for an Apache Reverse Proxy configuration.
7 Below config if for an Apache Reverse Proxy configuration.
8
8
9 .. note::
9 .. note::
10
10
11 Apache requires the following modules to be enabled. Below is an example
11 Apache requires the following modules to be enabled. Below is an example
12 how to enable them on Ubuntu Server
12 how to enable them on Ubuntu Server
13
13
14
14
15 .. code-block:: bash
15 .. code-block:: bash
16
16
17 $ sudo a2enmod proxy
17 $ sudo a2enmod proxy
18 $ sudo a2enmod proxy_http
18 $ sudo a2enmod proxy_http
19 $ sudo a2enmod proxy_balancer
19 $ sudo a2enmod proxy_balancer
20 $ sudo a2enmod headers
20 $ sudo a2enmod headers
21 $ sudo a2enmod ssl
21 $ sudo a2enmod ssl
22 $ sudo a2enmod rewrite
22 $ sudo a2enmod rewrite
23
23
24 # requires Apache 2.4+, required to handle websockets/channelstream
24 # requires Apache 2.4+, required to handle websockets/channelstream
25 $ sudo a2enmod proxy_wstunnel
25 $ sudo a2enmod proxy_wstunnel
26
26
27
27
28 .. code-block:: apache
28 .. code-block:: apache
29
29
30 ## HTTP to HTTPS rewrite
30 ## HTTP to HTTPS rewrite
31 <VirtualHost *:80>
31 <VirtualHost *:80>
32 ServerName rhodecode.myserver.com
32 ServerName rhodecode.myserver.com
33 DocumentRoot /var/www/html
33 DocumentRoot /var/www/html
34 Redirect permanent / https://rhodecode.myserver.com/
34 Redirect permanent / https://rhodecode.myserver.com/
35 </VirtualHost>
35 </VirtualHost>
36
36
37 ## MAIN SSL enabled server
37 ## MAIN SSL enabled server
38 <VirtualHost *:443>
38 <VirtualHost *:443>
39
39
40 ServerName rhodecode.myserver.com
40 ServerName rhodecode.myserver.com
41 ServerAlias rhodecode.myserver.com
41 ServerAlias rhodecode.myserver.com
42
42
43 ## Skip ProxyPass the _static to backend server
43 ## Skip ProxyPass the _static to backend server
44 #ProxyPass /_static !
44 #ProxyPass /_static !
45
45
46 ## serve static files by Apache, recommended for performance
46 ## serve static files by Apache, recommended for performance
47 #Alias /_static/rhodecode /home/ubuntu/.rccontrol/community-1/static
47 #Alias /_static/rhodecode /home/ubuntu/.rccontrol/community-1/static
48
48
49 ## Allow Apache to access the static files in this directory
49 ## Allow Apache to access the static files in this directory
50 #<Directory /home/ubuntu/.rccontrol/community-1/static/>
50 #<Directory /home/ubuntu/.rccontrol/community-1/static/>
51 # AllowOverride none
51 # AllowOverride none
52 # Require all granted
52 # Require all granted
53 #</Directory>
53 #</Directory>
54
54
55 RequestHeader set X-Forwarded-Proto "https"
55 RequestHeader set X-Forwarded-Proto "https"
56
56
57 ## channelstream websocket handling
57 ## channelstream websocket handling
58 ProxyPass /_channelstream ws://localhost:9800
58 ProxyPass /_channelstream ws://localhost:9800
59 ProxyPassReverse /_channelstream ws://localhost:9800
59 ProxyPassReverse /_channelstream ws://localhost:9800
60
60
61 <Proxy *>
61 <Proxy *>
62 Order allow,deny
62 Order allow,deny
63 Allow from all
63 Allow from all
64 </Proxy>
64 </Proxy>
65
65
66 # Directive to properly generate url (clone url) for RhodeCode
66 # Directive to properly generate url (clone url) for RhodeCode
67 ProxyPreserveHost On
67 ProxyPreserveHost On
68
68
69 # It allows request bodies to be sent to the backend using chunked transfer encoding.
69 # It allows request bodies to be sent to the backend using chunked transfer encoding.
70 SetEnv proxy-sendchunked 1
70 SetEnv proxy-sendchunked 1
71
71
72 # Increase headers size for large Mercurial headers sent with many branches
72 # Increase headers size for large Mercurial headers sent with many branches
73 LimitRequestLine 16380
73 LimitRequestLine 16380
74
74
75 # Url to running RhodeCode instance. This is shown as `- URL:` when
75 # Url to running RhodeCode instance. This is shown as `- URL:` when
76 # running rccontrol status.
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 ProxyPassReverse / http://127.0.0.1:10002/
79 ProxyPassReverse / http://127.0.0.1:10002/
80
80
81 # strict http prevents from https -> http downgrade
81 # strict http prevents from https -> http downgrade
82 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
82 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
83
83
84 # Set x-frame options
84 # Set x-frame options
85 Header always append X-Frame-Options SAMEORIGIN
85 Header always append X-Frame-Options SAMEORIGIN
86
86
87 # To enable https use line below
87 # To enable https use line below
88 # SetEnvIf X-Url-Scheme https HTTPS=1
88 # SetEnvIf X-Url-Scheme https HTTPS=1
89
89
90 # SSL setup
90 # SSL setup
91 SSLEngine On
91 SSLEngine On
92 SSLCertificateFile /etc/apache2/ssl/rhodecode.myserver.pem
92 SSLCertificateFile /etc/apache2/ssl/rhodecode.myserver.pem
93 SSLCertificateKeyFile /etc/apache2/ssl/rhodecode.myserver.key
93 SSLCertificateKeyFile /etc/apache2/ssl/rhodecode.myserver.key
94
94
95 SSLProtocol all -SSLv2 -SSLv3
95 SSLProtocol all -SSLv2 -SSLv3
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
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 SSLHonorCipherOrder on
97 SSLHonorCipherOrder on
98
98
99 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
99 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
100 #SSLOpenSSLConfCmd DHParameters "/etc/apache2/dhparam.pem"
100 #SSLOpenSSLConfCmd DHParameters "/etc/apache2/dhparam.pem"
101
101
102 ## custom 502 error page. Will be displayed while RhodeCode server
102 ## custom 502 error page. Will be displayed while RhodeCode server
103 ## is turned off
103 ## is turned off
104 ErrorDocument 502 /path/to/.rccontrol/enterprise-1/static/502.html
104 ErrorDocument 502 /path/to/.rccontrol/enterprise-1/static/502.html
105
105
106 </VirtualHost>
106 </VirtualHost>
107
107
@@ -1,447 +1,448 b''
1 .. _pull-request-methods-ref:
1 .. _pull-request-methods-ref:
2
2
3 pull_request methods
3 pull_request methods
4 ====================
4 ====================
5
5
6 close_pull_request
6 close_pull_request
7 ------------------
7 ------------------
8
8
9 .. py:function:: close_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>, message=<Optional:''>)
9 .. py:function:: close_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>, message=<Optional:''>)
10
10
11 Close the pull request specified by `pullrequestid`.
11 Close the pull request specified by `pullrequestid`.
12
12
13 :param apiuser: This is filled automatically from the |authtoken|.
13 :param apiuser: This is filled automatically from the |authtoken|.
14 :type apiuser: AuthUser
14 :type apiuser: AuthUser
15 :param repoid: Repository name or repository ID to which the pull
15 :param repoid: Repository name or repository ID to which the pull
16 request belongs.
16 request belongs.
17 :type repoid: str or int
17 :type repoid: str or int
18 :param pullrequestid: ID of the pull request to be closed.
18 :param pullrequestid: ID of the pull request to be closed.
19 :type pullrequestid: int
19 :type pullrequestid: int
20 :param userid: Close the pull request as this user.
20 :param userid: Close the pull request as this user.
21 :type userid: Optional(str or int)
21 :type userid: Optional(str or int)
22 :param message: Optional message to close the Pull Request with. If not
22 :param message: Optional message to close the Pull Request with. If not
23 specified it will be generated automatically.
23 specified it will be generated automatically.
24 :type message: Optional(str)
24 :type message: Optional(str)
25
25
26 Example output:
26 Example output:
27
27
28 .. code-block:: bash
28 .. code-block:: bash
29
29
30 "id": <id_given_in_input>,
30 "id": <id_given_in_input>,
31 "result": {
31 "result": {
32 "pull_request_id": "<int>",
32 "pull_request_id": "<int>",
33 "close_status": "<str:status_lbl>,
33 "close_status": "<str:status_lbl>,
34 "closed": "<bool>"
34 "closed": "<bool>"
35 },
35 },
36 "error": null
36 "error": null
37
37
38
38
39 comment_pull_request
39 comment_pull_request
40 --------------------
40 --------------------
41
41
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>)
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 Comment on the pull request specified with the `pullrequestid`,
44 Comment on the pull request specified with the `pullrequestid`,
45 in the |repo| specified by the `repoid`, and optionally change the
45 in the |repo| specified by the `repoid`, and optionally change the
46 review status.
46 review status.
47
47
48 :param apiuser: This is filled automatically from the |authtoken|.
48 :param apiuser: This is filled automatically from the |authtoken|.
49 :type apiuser: AuthUser
49 :type apiuser: AuthUser
50 :param repoid: Optional repository name or repository ID.
50 :param repoid: Optional repository name or repository ID.
51 :type repoid: str or int
51 :type repoid: str or int
52 :param pullrequestid: The pull request ID.
52 :param pullrequestid: The pull request ID.
53 :type pullrequestid: int
53 :type pullrequestid: int
54 :param commit_id: Specify the commit_id for which to set a comment. If
54 :param commit_id: Specify the commit_id for which to set a comment. If
55 given commit_id is different than latest in the PR status
55 given commit_id is different than latest in the PR status
56 change won't be performed.
56 change won't be performed.
57 :type commit_id: str
57 :type commit_id: str
58 :param message: The text content of the comment.
58 :param message: The text content of the comment.
59 :type message: str
59 :type message: str
60 :param status: (**Optional**) Set the approval status of the pull
60 :param status: (**Optional**) Set the approval status of the pull
61 request. One of: 'not_reviewed', 'approved', 'rejected',
61 request. One of: 'not_reviewed', 'approved', 'rejected',
62 'under_review'
62 'under_review'
63 :type status: str
63 :type status: str
64 :param comment_type: Comment type, one of: 'note', 'todo'
64 :param comment_type: Comment type, one of: 'note', 'todo'
65 :type comment_type: Optional(str), default: 'note'
65 :type comment_type: Optional(str), default: 'note'
66 :param resolves_comment_id: id of comment which this one will resolve
66 :param resolves_comment_id: id of comment which this one will resolve
67 :type resolves_comment_id: Optional(int)
67 :type resolves_comment_id: Optional(int)
68 :param extra_recipients: list of user ids or usernames to add
68 :param extra_recipients: list of user ids or usernames to add
69 notifications for this comment. Acts like a CC for notification
69 notifications for this comment. Acts like a CC for notification
70 :type extra_recipients: Optional(list)
70 :type extra_recipients: Optional(list)
71 :param userid: Comment on the pull request as this user
71 :param userid: Comment on the pull request as this user
72 :type userid: Optional(str or int)
72 :type userid: Optional(str or int)
73 :param send_email: Define if this comment should also send email notification
73 :param send_email: Define if this comment should also send email notification
74 :type send_email: Optional(bool)
74 :type send_email: Optional(bool)
75
75
76 Example output:
76 Example output:
77
77
78 .. code-block:: bash
78 .. code-block:: bash
79
79
80 id : <id_given_in_input>
80 id : <id_given_in_input>
81 result : {
81 result : {
82 "pull_request_id": "<Integer>",
82 "pull_request_id": "<Integer>",
83 "comment_id": "<Integer>",
83 "comment_id": "<Integer>",
84 "status": {"given": <given_status>,
84 "status": {"given": <given_status>,
85 "was_changed": <bool status_was_actually_changed> },
85 "was_changed": <bool status_was_actually_changed> },
86 },
86 },
87 error : null
87 error : null
88
88
89
89
90 create_pull_request
90 create_pull_request
91 -------------------
91 -------------------
92
92
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>)
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 Creates a new pull request.
95 Creates a new pull request.
96
96
97 Accepts refs in the following formats:
97 Accepts refs in the following formats:
98
98
99 * branch:<branch_name>:<sha>
99 * branch:<branch_name>:<sha>
100 * branch:<branch_name>
100 * branch:<branch_name>
101 * bookmark:<bookmark_name>:<sha> (Mercurial only)
101 * bookmark:<bookmark_name>:<sha> (Mercurial only)
102 * bookmark:<bookmark_name> (Mercurial only)
102 * bookmark:<bookmark_name> (Mercurial only)
103
103
104 :param apiuser: This is filled automatically from the |authtoken|.
104 :param apiuser: This is filled automatically from the |authtoken|.
105 :type apiuser: AuthUser
105 :type apiuser: AuthUser
106 :param source_repo: Set the source repository name.
106 :param source_repo: Set the source repository name.
107 :type source_repo: str
107 :type source_repo: str
108 :param target_repo: Set the target repository name.
108 :param target_repo: Set the target repository name.
109 :type target_repo: str
109 :type target_repo: str
110 :param source_ref: Set the source ref name.
110 :param source_ref: Set the source ref name.
111 :type source_ref: str
111 :type source_ref: str
112 :param target_ref: Set the target ref name.
112 :param target_ref: Set the target ref name.
113 :type target_ref: str
113 :type target_ref: str
114 :param owner: user_id or username
114 :param owner: user_id or username
115 :type owner: Optional(str)
115 :type owner: Optional(str)
116 :param title: Optionally Set the pull request title, it's generated otherwise
116 :param title: Optionally Set the pull request title, it's generated otherwise
117 :type title: str
117 :type title: str
118 :param description: Set the pull request description.
118 :param description: Set the pull request description.
119 :type description: Optional(str)
119 :type description: Optional(str)
120 :type description_renderer: Optional(str)
120 :type description_renderer: Optional(str)
121 :param description_renderer: Set pull request renderer for the description.
121 :param description_renderer: Set pull request renderer for the description.
122 It should be 'rst', 'markdown' or 'plain'. If not give default
122 It should be 'rst', 'markdown' or 'plain'. If not give default
123 system renderer will be used
123 system renderer will be used
124 :param reviewers: Set the new pull request reviewers list.
124 :param reviewers: Set the new pull request reviewers list.
125 Reviewer defined by review rules will be added automatically to the
125 Reviewer defined by review rules will be added automatically to the
126 defined list.
126 defined list.
127 :type reviewers: Optional(list)
127 :type reviewers: Optional(list)
128 Accepts username strings or objects of the format:
128 Accepts username strings or objects of the format:
129
129
130 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
130 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
131
131
132
132
133 get_pull_request
133 get_pull_request
134 ----------------
134 ----------------
135
135
136 .. py:function:: get_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, merge_state=<Optional:False>)
136 .. py:function:: get_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, merge_state=<Optional:False>)
137
137
138 Get a pull request based on the given ID.
138 Get a pull request based on the given ID.
139
139
140 :param apiuser: This is filled automatically from the |authtoken|.
140 :param apiuser: This is filled automatically from the |authtoken|.
141 :type apiuser: AuthUser
141 :type apiuser: AuthUser
142 :param repoid: Optional, repository name or repository ID from where
142 :param repoid: Optional, repository name or repository ID from where
143 the pull request was opened.
143 the pull request was opened.
144 :type repoid: str or int
144 :type repoid: str or int
145 :param pullrequestid: ID of the requested pull request.
145 :param pullrequestid: ID of the requested pull request.
146 :type pullrequestid: int
146 :type pullrequestid: int
147 :param merge_state: Optional calculate merge state for each repository.
147 :param merge_state: Optional calculate merge state for each repository.
148 This could result in longer time to fetch the data
148 This could result in longer time to fetch the data
149 :type merge_state: bool
149 :type merge_state: bool
150
150
151 Example output:
151 Example output:
152
152
153 .. code-block:: bash
153 .. code-block:: bash
154
154
155 "id": <id_given_in_input>,
155 "id": <id_given_in_input>,
156 "result":
156 "result":
157 {
157 {
158 "pull_request_id": "<pull_request_id>",
158 "pull_request_id": "<pull_request_id>",
159 "url": "<url>",
159 "url": "<url>",
160 "title": "<title>",
160 "title": "<title>",
161 "description": "<description>",
161 "description": "<description>",
162 "status" : "<status>",
162 "status" : "<status>",
163 "created_on": "<date_time_created>",
163 "created_on": "<date_time_created>",
164 "updated_on": "<date_time_updated>",
164 "updated_on": "<date_time_updated>",
165 "versions": "<number_or_versions_of_pr>",
165 "versions": "<number_or_versions_of_pr>",
166 "commit_ids": [
166 "commit_ids": [
167 ...
167 ...
168 "<commit_id>",
168 "<commit_id>",
169 "<commit_id>",
169 "<commit_id>",
170 ...
170 ...
171 ],
171 ],
172 "review_status": "<review_status>",
172 "review_status": "<review_status>",
173 "mergeable": {
173 "mergeable": {
174 "status": "<bool>",
174 "status": "<bool>",
175 "message": "<message>",
175 "message": "<message>",
176 },
176 },
177 "source": {
177 "source": {
178 "clone_url": "<clone_url>",
178 "clone_url": "<clone_url>",
179 "repository": "<repository_name>",
179 "repository": "<repository_name>",
180 "reference":
180 "reference":
181 {
181 {
182 "name": "<name>",
182 "name": "<name>",
183 "type": "<type>",
183 "type": "<type>",
184 "commit_id": "<commit_id>",
184 "commit_id": "<commit_id>",
185 }
185 }
186 },
186 },
187 "target": {
187 "target": {
188 "clone_url": "<clone_url>",
188 "clone_url": "<clone_url>",
189 "repository": "<repository_name>",
189 "repository": "<repository_name>",
190 "reference":
190 "reference":
191 {
191 {
192 "name": "<name>",
192 "name": "<name>",
193 "type": "<type>",
193 "type": "<type>",
194 "commit_id": "<commit_id>",
194 "commit_id": "<commit_id>",
195 }
195 }
196 },
196 },
197 "merge": {
197 "merge": {
198 "clone_url": "<clone_url>",
198 "clone_url": "<clone_url>",
199 "reference":
199 "reference":
200 {
200 {
201 "name": "<name>",
201 "name": "<name>",
202 "type": "<type>",
202 "type": "<type>",
203 "commit_id": "<commit_id>",
203 "commit_id": "<commit_id>",
204 }
204 }
205 },
205 },
206 "author": <user_obj>,
206 "author": <user_obj>,
207 "reviewers": [
207 "reviewers": [
208 ...
208 ...
209 {
209 {
210 "user": "<user_obj>",
210 "user": "<user_obj>",
211 "review_status": "<review_status>",
211 "review_status": "<review_status>",
212 }
212 }
213 ...
213 ...
214 ]
214 ]
215 },
215 },
216 "error": null
216 "error": null
217
217
218
218
219 get_pull_request_comments
219 get_pull_request_comments
220 -------------------------
220 -------------------------
221
221
222 .. py:function:: get_pull_request_comments(apiuser, pullrequestid, repoid=<Optional:None>)
222 .. py:function:: get_pull_request_comments(apiuser, pullrequestid, repoid=<Optional:None>)
223
223
224 Get all comments of pull request specified with the `pullrequestid`
224 Get all comments of pull request specified with the `pullrequestid`
225
225
226 :param apiuser: This is filled automatically from the |authtoken|.
226 :param apiuser: This is filled automatically from the |authtoken|.
227 :type apiuser: AuthUser
227 :type apiuser: AuthUser
228 :param repoid: Optional repository name or repository ID.
228 :param repoid: Optional repository name or repository ID.
229 :type repoid: str or int
229 :type repoid: str or int
230 :param pullrequestid: The pull request ID.
230 :param pullrequestid: The pull request ID.
231 :type pullrequestid: int
231 :type pullrequestid: int
232
232
233 Example output:
233 Example output:
234
234
235 .. code-block:: bash
235 .. code-block:: bash
236
236
237 id : <id_given_in_input>
237 id : <id_given_in_input>
238 result : [
238 result : [
239 {
239 {
240 "comment_author": {
240 "comment_author": {
241 "active": true,
241 "active": true,
242 "full_name_or_username": "Tom Gore",
242 "full_name_or_username": "Tom Gore",
243 "username": "admin"
243 "username": "admin"
244 },
244 },
245 "comment_created_on": "2017-01-02T18:43:45.533",
245 "comment_created_on": "2017-01-02T18:43:45.533",
246 "comment_f_path": null,
246 "comment_f_path": null,
247 "comment_id": 25,
247 "comment_id": 25,
248 "comment_lineno": null,
248 "comment_lineno": null,
249 "comment_status": {
249 "comment_status": {
250 "status": "under_review",
250 "status": "under_review",
251 "status_lbl": "Under Review"
251 "status_lbl": "Under Review"
252 },
252 },
253 "comment_text": "Example text",
253 "comment_text": "Example text",
254 "comment_type": null,
254 "comment_type": null,
255 "comment_last_version: 0,
255 "pull_request_version": null,
256 "pull_request_version": null,
256 "comment_commit_id": None,
257 "comment_commit_id": None,
257 "comment_pull_request_id": <pull_request_id>
258 "comment_pull_request_id": <pull_request_id>
258 }
259 }
259 ],
260 ],
260 error : null
261 error : null
261
262
262
263
263 get_pull_requests
264 get_pull_requests
264 -----------------
265 -----------------
265
266
266 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>, merge_state=<Optional:False>)
267 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>, merge_state=<Optional:False>)
267
268
268 Get all pull requests from the repository specified in `repoid`.
269 Get all pull requests from the repository specified in `repoid`.
269
270
270 :param apiuser: This is filled automatically from the |authtoken|.
271 :param apiuser: This is filled automatically from the |authtoken|.
271 :type apiuser: AuthUser
272 :type apiuser: AuthUser
272 :param repoid: Optional repository name or repository ID.
273 :param repoid: Optional repository name or repository ID.
273 :type repoid: str or int
274 :type repoid: str or int
274 :param status: Only return pull requests with the specified status.
275 :param status: Only return pull requests with the specified status.
275 Valid options are.
276 Valid options are.
276 * ``new`` (default)
277 * ``new`` (default)
277 * ``open``
278 * ``open``
278 * ``closed``
279 * ``closed``
279 :type status: str
280 :type status: str
280 :param merge_state: Optional calculate merge state for each repository.
281 :param merge_state: Optional calculate merge state for each repository.
281 This could result in longer time to fetch the data
282 This could result in longer time to fetch the data
282 :type merge_state: bool
283 :type merge_state: bool
283
284
284 Example output:
285 Example output:
285
286
286 .. code-block:: bash
287 .. code-block:: bash
287
288
288 "id": <id_given_in_input>,
289 "id": <id_given_in_input>,
289 "result":
290 "result":
290 [
291 [
291 ...
292 ...
292 {
293 {
293 "pull_request_id": "<pull_request_id>",
294 "pull_request_id": "<pull_request_id>",
294 "url": "<url>",
295 "url": "<url>",
295 "title" : "<title>",
296 "title" : "<title>",
296 "description": "<description>",
297 "description": "<description>",
297 "status": "<status>",
298 "status": "<status>",
298 "created_on": "<date_time_created>",
299 "created_on": "<date_time_created>",
299 "updated_on": "<date_time_updated>",
300 "updated_on": "<date_time_updated>",
300 "commit_ids": [
301 "commit_ids": [
301 ...
302 ...
302 "<commit_id>",
303 "<commit_id>",
303 "<commit_id>",
304 "<commit_id>",
304 ...
305 ...
305 ],
306 ],
306 "review_status": "<review_status>",
307 "review_status": "<review_status>",
307 "mergeable": {
308 "mergeable": {
308 "status": "<bool>",
309 "status": "<bool>",
309 "message: "<message>",
310 "message: "<message>",
310 },
311 },
311 "source": {
312 "source": {
312 "clone_url": "<clone_url>",
313 "clone_url": "<clone_url>",
313 "reference":
314 "reference":
314 {
315 {
315 "name": "<name>",
316 "name": "<name>",
316 "type": "<type>",
317 "type": "<type>",
317 "commit_id": "<commit_id>",
318 "commit_id": "<commit_id>",
318 }
319 }
319 },
320 },
320 "target": {
321 "target": {
321 "clone_url": "<clone_url>",
322 "clone_url": "<clone_url>",
322 "reference":
323 "reference":
323 {
324 {
324 "name": "<name>",
325 "name": "<name>",
325 "type": "<type>",
326 "type": "<type>",
326 "commit_id": "<commit_id>",
327 "commit_id": "<commit_id>",
327 }
328 }
328 },
329 },
329 "merge": {
330 "merge": {
330 "clone_url": "<clone_url>",
331 "clone_url": "<clone_url>",
331 "reference":
332 "reference":
332 {
333 {
333 "name": "<name>",
334 "name": "<name>",
334 "type": "<type>",
335 "type": "<type>",
335 "commit_id": "<commit_id>",
336 "commit_id": "<commit_id>",
336 }
337 }
337 },
338 },
338 "author": <user_obj>,
339 "author": <user_obj>,
339 "reviewers": [
340 "reviewers": [
340 ...
341 ...
341 {
342 {
342 "user": "<user_obj>",
343 "user": "<user_obj>",
343 "review_status": "<review_status>",
344 "review_status": "<review_status>",
344 }
345 }
345 ...
346 ...
346 ]
347 ]
347 }
348 }
348 ...
349 ...
349 ],
350 ],
350 "error": null
351 "error": null
351
352
352
353
353 merge_pull_request
354 merge_pull_request
354 ------------------
355 ------------------
355
356
356 .. py:function:: merge_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
357 .. py:function:: merge_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
357
358
358 Merge the pull request specified by `pullrequestid` into its target
359 Merge the pull request specified by `pullrequestid` into its target
359 repository.
360 repository.
360
361
361 :param apiuser: This is filled automatically from the |authtoken|.
362 :param apiuser: This is filled automatically from the |authtoken|.
362 :type apiuser: AuthUser
363 :type apiuser: AuthUser
363 :param repoid: Optional, repository name or repository ID of the
364 :param repoid: Optional, repository name or repository ID of the
364 target repository to which the |pr| is to be merged.
365 target repository to which the |pr| is to be merged.
365 :type repoid: str or int
366 :type repoid: str or int
366 :param pullrequestid: ID of the pull request which shall be merged.
367 :param pullrequestid: ID of the pull request which shall be merged.
367 :type pullrequestid: int
368 :type pullrequestid: int
368 :param userid: Merge the pull request as this user.
369 :param userid: Merge the pull request as this user.
369 :type userid: Optional(str or int)
370 :type userid: Optional(str or int)
370
371
371 Example output:
372 Example output:
372
373
373 .. code-block:: bash
374 .. code-block:: bash
374
375
375 "id": <id_given_in_input>,
376 "id": <id_given_in_input>,
376 "result": {
377 "result": {
377 "executed": "<bool>",
378 "executed": "<bool>",
378 "failure_reason": "<int>",
379 "failure_reason": "<int>",
379 "merge_status_message": "<str>",
380 "merge_status_message": "<str>",
380 "merge_commit_id": "<merge_commit_id>",
381 "merge_commit_id": "<merge_commit_id>",
381 "possible": "<bool>",
382 "possible": "<bool>",
382 "merge_ref": {
383 "merge_ref": {
383 "commit_id": "<commit_id>",
384 "commit_id": "<commit_id>",
384 "type": "<type>",
385 "type": "<type>",
385 "name": "<name>"
386 "name": "<name>"
386 }
387 }
387 },
388 },
388 "error": null
389 "error": null
389
390
390
391
391 update_pull_request
392 update_pull_request
392 -------------------
393 -------------------
393
394
394 .. 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 .. 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 Updates a pull request.
397 Updates a pull request.
397
398
398 :param apiuser: This is filled automatically from the |authtoken|.
399 :param apiuser: This is filled automatically from the |authtoken|.
399 :type apiuser: AuthUser
400 :type apiuser: AuthUser
400 :param repoid: Optional repository name or repository ID.
401 :param repoid: Optional repository name or repository ID.
401 :type repoid: str or int
402 :type repoid: str or int
402 :param pullrequestid: The pull request ID.
403 :param pullrequestid: The pull request ID.
403 :type pullrequestid: int
404 :type pullrequestid: int
404 :param title: Set the pull request title.
405 :param title: Set the pull request title.
405 :type title: str
406 :type title: str
406 :param description: Update pull request description.
407 :param description: Update pull request description.
407 :type description: Optional(str)
408 :type description: Optional(str)
408 :type description_renderer: Optional(str)
409 :type description_renderer: Optional(str)
409 :param description_renderer: Update pull request renderer for the description.
410 :param description_renderer: Update pull request renderer for the description.
410 It should be 'rst', 'markdown' or 'plain'
411 It should be 'rst', 'markdown' or 'plain'
411 :param reviewers: Update pull request reviewers list with new value.
412 :param reviewers: Update pull request reviewers list with new value.
412 :type reviewers: Optional(list)
413 :type reviewers: Optional(list)
413 Accepts username strings or objects of the format:
414 Accepts username strings or objects of the format:
414
415
415 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
416 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
416
417
417 :param update_commits: Trigger update of commits for this pull request
418 :param update_commits: Trigger update of commits for this pull request
418 :type: update_commits: Optional(bool)
419 :type: update_commits: Optional(bool)
419
420
420 Example output:
421 Example output:
421
422
422 .. code-block:: bash
423 .. code-block:: bash
423
424
424 id : <id_given_in_input>
425 id : <id_given_in_input>
425 result : {
426 result : {
426 "msg": "Updated pull request `63`",
427 "msg": "Updated pull request `63`",
427 "pull_request": <pull_request_object>,
428 "pull_request": <pull_request_object>,
428 "updated_reviewers": {
429 "updated_reviewers": {
429 "added": [
430 "added": [
430 "username"
431 "username"
431 ],
432 ],
432 "removed": []
433 "removed": []
433 },
434 },
434 "updated_commits": {
435 "updated_commits": {
435 "added": [
436 "added": [
436 "<sha1_hash>"
437 "<sha1_hash>"
437 ],
438 ],
438 "common": [
439 "common": [
439 "<sha1_hash>",
440 "<sha1_hash>",
440 "<sha1_hash>",
441 "<sha1_hash>",
441 ],
442 ],
442 "removed": []
443 "removed": []
443 }
444 }
444 }
445 }
445 error : null
446 error : null
446
447
447
448
@@ -1,1140 +1,1206 b''
1 .. _repo-methods-ref:
1 .. _repo-methods-ref:
2
2
3 repo methods
3 repo methods
4 ============
4 ============
5
5
6 add_field_to_repo
6 add_field_to_repo
7 -----------------
7 -----------------
8
8
9 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
9 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
10
10
11 Adds an extra field to a repository.
11 Adds an extra field to a repository.
12
12
13 This command can only be run using an |authtoken| with at least
13 This command can only be run using an |authtoken| with at least
14 write permissions to the |repo|.
14 write permissions to the |repo|.
15
15
16 :param apiuser: This is filled automatically from the |authtoken|.
16 :param apiuser: This is filled automatically from the |authtoken|.
17 :type apiuser: AuthUser
17 :type apiuser: AuthUser
18 :param repoid: Set the repository name or repository id.
18 :param repoid: Set the repository name or repository id.
19 :type repoid: str or int
19 :type repoid: str or int
20 :param key: Create a unique field key for this repository.
20 :param key: Create a unique field key for this repository.
21 :type key: str
21 :type key: str
22 :param label:
22 :param label:
23 :type label: Optional(str)
23 :type label: Optional(str)
24 :param description:
24 :param description:
25 :type description: Optional(str)
25 :type description: Optional(str)
26
26
27
27
28 comment_commit
28 comment_commit
29 --------------
29 --------------
30
30
31 .. py:function:: comment_commit(apiuser, repoid, commit_id, message, status=<Optional:None>, comment_type=<Optional:u'note'>, resolves_comment_id=<Optional:None>, extra_recipients=<Optional:[]>, userid=<Optional:<OptionalAttr:apiuser>>, send_email=<Optional:True>)
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 Set a commit comment, and optionally change the status of the commit.
33 Set a commit comment, and optionally change the status of the commit.
34
34
35 :param apiuser: This is filled automatically from the |authtoken|.
35 :param apiuser: This is filled automatically from the |authtoken|.
36 :type apiuser: AuthUser
36 :type apiuser: AuthUser
37 :param repoid: Set the repository name or repository ID.
37 :param repoid: Set the repository name or repository ID.
38 :type repoid: str or int
38 :type repoid: str or int
39 :param commit_id: Specify the commit_id for which to set a comment.
39 :param commit_id: Specify the commit_id for which to set a comment.
40 :type commit_id: str
40 :type commit_id: str
41 :param message: The comment text.
41 :param message: The comment text.
42 :type message: str
42 :type message: str
43 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
43 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
44 'approved', 'rejected', 'under_review'
44 'approved', 'rejected', 'under_review'
45 :type status: str
45 :type status: str
46 :param comment_type: Comment type, one of: 'note', 'todo'
46 :param comment_type: Comment type, one of: 'note', 'todo'
47 :type comment_type: Optional(str), default: 'note'
47 :type comment_type: Optional(str), default: 'note'
48 :param resolves_comment_id: id of comment which this one will resolve
48 :param resolves_comment_id: id of comment which this one will resolve
49 :type resolves_comment_id: Optional(int)
49 :type resolves_comment_id: Optional(int)
50 :param extra_recipients: list of user ids or usernames to add
50 :param extra_recipients: list of user ids or usernames to add
51 notifications for this comment. Acts like a CC for notification
51 notifications for this comment. Acts like a CC for notification
52 :type extra_recipients: Optional(list)
52 :type extra_recipients: Optional(list)
53 :param userid: Set the user name of the comment creator.
53 :param userid: Set the user name of the comment creator.
54 :type userid: Optional(str or int)
54 :type userid: Optional(str or int)
55 :param send_email: Define if this comment should also send email notification
55 :param send_email: Define if this comment should also send email notification
56 :type send_email: Optional(bool)
56 :type send_email: Optional(bool)
57
57
58 Example error output:
58 Example error output:
59
59
60 .. code-block:: bash
60 .. code-block:: bash
61
61
62 {
62 {
63 "id" : <id_given_in_input>,
63 "id" : <id_given_in_input>,
64 "result" : {
64 "result" : {
65 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
65 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
66 "status_change": null or <status>,
66 "status_change": null or <status>,
67 "success": true
67 "success": true
68 },
68 },
69 "error" : null
69 "error" : null
70 }
70 }
71
71
72
72
73 create_repo
73 create_repo
74 -----------
74 -----------
75
75
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>)
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 Creates a repository.
78 Creates a repository.
79
79
80 * If the repository name contains "/", repository will be created inside
80 * If the repository name contains "/", repository will be created inside
81 a repository group or nested repository groups
81 a repository group or nested repository groups
82
82
83 For example "foo/bar/repo1" will create |repo| called "repo1" inside
83 For example "foo/bar/repo1" will create |repo| called "repo1" inside
84 group "foo/bar". You have to have permissions to access and write to
84 group "foo/bar". You have to have permissions to access and write to
85 the last repository group ("bar" in this example)
85 the last repository group ("bar" in this example)
86
86
87 This command can only be run using an |authtoken| with at least
87 This command can only be run using an |authtoken| with at least
88 permissions to create repositories, or write permissions to
88 permissions to create repositories, or write permissions to
89 parent repository groups.
89 parent repository groups.
90
90
91 :param apiuser: This is filled automatically from the |authtoken|.
91 :param apiuser: This is filled automatically from the |authtoken|.
92 :type apiuser: AuthUser
92 :type apiuser: AuthUser
93 :param repo_name: Set the repository name.
93 :param repo_name: Set the repository name.
94 :type repo_name: str
94 :type repo_name: str
95 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
95 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
96 :type repo_type: str
96 :type repo_type: str
97 :param owner: user_id or username
97 :param owner: user_id or username
98 :type owner: Optional(str)
98 :type owner: Optional(str)
99 :param description: Set the repository description.
99 :param description: Set the repository description.
100 :type description: Optional(str)
100 :type description: Optional(str)
101 :param private: set repository as private
101 :param private: set repository as private
102 :type private: bool
102 :type private: bool
103 :param clone_uri: set clone_uri
103 :param clone_uri: set clone_uri
104 :type clone_uri: str
104 :type clone_uri: str
105 :param push_uri: set push_uri
105 :param push_uri: set push_uri
106 :type push_uri: str
106 :type push_uri: str
107 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
107 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
108 :type landing_rev: str
108 :type landing_rev: str
109 :param enable_locking:
109 :param enable_locking:
110 :type enable_locking: bool
110 :type enable_locking: bool
111 :param enable_downloads:
111 :param enable_downloads:
112 :type enable_downloads: bool
112 :type enable_downloads: bool
113 :param enable_statistics:
113 :param enable_statistics:
114 :type enable_statistics: bool
114 :type enable_statistics: bool
115 :param copy_permissions: Copy permission from group in which the
115 :param copy_permissions: Copy permission from group in which the
116 repository is being created.
116 repository is being created.
117 :type copy_permissions: bool
117 :type copy_permissions: bool
118
118
119
119
120 Example output:
120 Example output:
121
121
122 .. code-block:: bash
122 .. code-block:: bash
123
123
124 id : <id_given_in_input>
124 id : <id_given_in_input>
125 result: {
125 result: {
126 "msg": "Created new repository `<reponame>`",
126 "msg": "Created new repository `<reponame>`",
127 "success": true,
127 "success": true,
128 "task": "<celery task id or None if done sync>"
128 "task": "<celery task id or None if done sync>"
129 }
129 }
130 error: null
130 error: null
131
131
132
132
133 Example error output:
133 Example error output:
134
134
135 .. code-block:: bash
135 .. code-block:: bash
136
136
137 id : <id_given_in_input>
137 id : <id_given_in_input>
138 result : null
138 result : null
139 error : {
139 error : {
140 'failed to create repository `<repo_name>`'
140 'failed to create repository `<repo_name>`'
141 }
141 }
142
142
143
143
144 delete_repo
144 delete_repo
145 -----------
145 -----------
146
146
147 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
147 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
148
148
149 Deletes a repository.
149 Deletes a repository.
150
150
151 * When the `forks` parameter is set it's possible to detach or delete
151 * When the `forks` parameter is set it's possible to detach or delete
152 forks of deleted repository.
152 forks of deleted repository.
153
153
154 This command can only be run using an |authtoken| with admin
154 This command can only be run using an |authtoken| with admin
155 permissions on the |repo|.
155 permissions on the |repo|.
156
156
157 :param apiuser: This is filled automatically from the |authtoken|.
157 :param apiuser: This is filled automatically from the |authtoken|.
158 :type apiuser: AuthUser
158 :type apiuser: AuthUser
159 :param repoid: Set the repository name or repository ID.
159 :param repoid: Set the repository name or repository ID.
160 :type repoid: str or int
160 :type repoid: str or int
161 :param forks: Set to `detach` or `delete` forks from the |repo|.
161 :param forks: Set to `detach` or `delete` forks from the |repo|.
162 :type forks: Optional(str)
162 :type forks: Optional(str)
163
163
164 Example error output:
164 Example error output:
165
165
166 .. code-block:: bash
166 .. code-block:: bash
167
167
168 id : <id_given_in_input>
168 id : <id_given_in_input>
169 result: {
169 result: {
170 "msg": "Deleted repository `<reponame>`",
170 "msg": "Deleted repository `<reponame>`",
171 "success": true
171 "success": true
172 }
172 }
173 error: null
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 fork_repo
207 fork_repo
177 ---------
208 ---------
178
209
179 .. 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>)
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 Creates a fork of the specified |repo|.
212 Creates a fork of the specified |repo|.
182
213
183 * If the fork_name contains "/", fork will be created inside
214 * If the fork_name contains "/", fork will be created inside
184 a repository group or nested repository groups
215 a repository group or nested repository groups
185
216
186 For example "foo/bar/fork-repo" will create fork called "fork-repo"
217 For example "foo/bar/fork-repo" will create fork called "fork-repo"
187 inside group "foo/bar". You have to have permissions to access and
218 inside group "foo/bar". You have to have permissions to access and
188 write to the last repository group ("bar" in this example)
219 write to the last repository group ("bar" in this example)
189
220
190 This command can only be run using an |authtoken| with minimum
221 This command can only be run using an |authtoken| with minimum
191 read permissions of the forked repo, create fork permissions for an user.
222 read permissions of the forked repo, create fork permissions for an user.
192
223
193 :param apiuser: This is filled automatically from the |authtoken|.
224 :param apiuser: This is filled automatically from the |authtoken|.
194 :type apiuser: AuthUser
225 :type apiuser: AuthUser
195 :param repoid: Set repository name or repository ID.
226 :param repoid: Set repository name or repository ID.
196 :type repoid: str or int
227 :type repoid: str or int
197 :param fork_name: Set the fork name, including it's repository group membership.
228 :param fork_name: Set the fork name, including it's repository group membership.
198 :type fork_name: str
229 :type fork_name: str
199 :param owner: Set the fork owner.
230 :param owner: Set the fork owner.
200 :type owner: str
231 :type owner: str
201 :param description: Set the fork description.
232 :param description: Set the fork description.
202 :type description: str
233 :type description: str
203 :param copy_permissions: Copy permissions from parent |repo|. The
234 :param copy_permissions: Copy permissions from parent |repo|. The
204 default is False.
235 default is False.
205 :type copy_permissions: bool
236 :type copy_permissions: bool
206 :param private: Make the fork private. The default is False.
237 :param private: Make the fork private. The default is False.
207 :type private: bool
238 :type private: bool
208 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
239 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
209
240
210 Example output:
241 Example output:
211
242
212 .. code-block:: bash
243 .. code-block:: bash
213
244
214 id : <id_for_response>
245 id : <id_for_response>
215 api_key : "<api_key>"
246 api_key : "<api_key>"
216 args: {
247 args: {
217 "repoid" : "<reponame or repo_id>",
248 "repoid" : "<reponame or repo_id>",
218 "fork_name": "<forkname>",
249 "fork_name": "<forkname>",
219 "owner": "<username or user_id = Optional(=apiuser)>",
250 "owner": "<username or user_id = Optional(=apiuser)>",
220 "description": "<description>",
251 "description": "<description>",
221 "copy_permissions": "<bool>",
252 "copy_permissions": "<bool>",
222 "private": "<bool>",
253 "private": "<bool>",
223 "landing_rev": "<landing_rev>"
254 "landing_rev": "<landing_rev>"
224 }
255 }
225
256
226 Example error output:
257 Example error output:
227
258
228 .. code-block:: bash
259 .. code-block:: bash
229
260
230 id : <id_given_in_input>
261 id : <id_given_in_input>
231 result: {
262 result: {
232 "msg": "Created fork of `<reponame>` as `<forkname>`",
263 "msg": "Created fork of `<reponame>` as `<forkname>`",
233 "success": true,
264 "success": true,
234 "task": "<celery task id or None if done sync>"
265 "task": "<celery task id or None if done sync>"
235 }
266 }
236 error: null
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 get_repo
304 get_repo
240 --------
305 --------
241
306
242 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
307 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
243
308
244 Gets an existing repository by its name or repository_id.
309 Gets an existing repository by its name or repository_id.
245
310
246 The members section so the output returns users groups or users
311 The members section so the output returns users groups or users
247 associated with that repository.
312 associated with that repository.
248
313
249 This command can only be run using an |authtoken| with admin rights,
314 This command can only be run using an |authtoken| with admin rights,
250 or users with at least read rights to the |repo|.
315 or users with at least read rights to the |repo|.
251
316
252 :param apiuser: This is filled automatically from the |authtoken|.
317 :param apiuser: This is filled automatically from the |authtoken|.
253 :type apiuser: AuthUser
318 :type apiuser: AuthUser
254 :param repoid: The repository name or repository id.
319 :param repoid: The repository name or repository id.
255 :type repoid: str or int
320 :type repoid: str or int
256 :param cache: use the cached value for last changeset
321 :param cache: use the cached value for last changeset
257 :type: cache: Optional(bool)
322 :type: cache: Optional(bool)
258
323
259 Example output:
324 Example output:
260
325
261 .. code-block:: bash
326 .. code-block:: bash
262
327
263 {
328 {
264 "error": null,
329 "error": null,
265 "id": <repo_id>,
330 "id": <repo_id>,
266 "result": {
331 "result": {
267 "clone_uri": null,
332 "clone_uri": null,
268 "created_on": "timestamp",
333 "created_on": "timestamp",
269 "description": "repo description",
334 "description": "repo description",
270 "enable_downloads": false,
335 "enable_downloads": false,
271 "enable_locking": false,
336 "enable_locking": false,
272 "enable_statistics": false,
337 "enable_statistics": false,
273 "followers": [
338 "followers": [
274 {
339 {
275 "active": true,
340 "active": true,
276 "admin": false,
341 "admin": false,
277 "api_key": "****************************************",
342 "api_key": "****************************************",
278 "api_keys": [
343 "api_keys": [
279 "****************************************"
344 "****************************************"
280 ],
345 ],
281 "email": "user@example.com",
346 "email": "user@example.com",
282 "emails": [
347 "emails": [
283 "user@example.com"
348 "user@example.com"
284 ],
349 ],
285 "extern_name": "rhodecode",
350 "extern_name": "rhodecode",
286 "extern_type": "rhodecode",
351 "extern_type": "rhodecode",
287 "firstname": "username",
352 "firstname": "username",
288 "ip_addresses": [],
353 "ip_addresses": [],
289 "language": null,
354 "language": null,
290 "last_login": "2015-09-16T17:16:35.854",
355 "last_login": "2015-09-16T17:16:35.854",
291 "lastname": "surname",
356 "lastname": "surname",
292 "user_id": <user_id>,
357 "user_id": <user_id>,
293 "username": "name"
358 "username": "name"
294 }
359 }
295 ],
360 ],
296 "fork_of": "parent-repo",
361 "fork_of": "parent-repo",
297 "landing_rev": [
362 "landing_rev": [
298 "rev",
363 "rev",
299 "tip"
364 "tip"
300 ],
365 ],
301 "last_changeset": {
366 "last_changeset": {
302 "author": "User <user@example.com>",
367 "author": "User <user@example.com>",
303 "branch": "default",
368 "branch": "default",
304 "date": "timestamp",
369 "date": "timestamp",
305 "message": "last commit message",
370 "message": "last commit message",
306 "parents": [
371 "parents": [
307 {
372 {
308 "raw_id": "commit-id"
373 "raw_id": "commit-id"
309 }
374 }
310 ],
375 ],
311 "raw_id": "commit-id",
376 "raw_id": "commit-id",
312 "revision": <revision number>,
377 "revision": <revision number>,
313 "short_id": "short id"
378 "short_id": "short id"
314 },
379 },
315 "lock_reason": null,
380 "lock_reason": null,
316 "locked_by": null,
381 "locked_by": null,
317 "locked_date": null,
382 "locked_date": null,
318 "owner": "owner-name",
383 "owner": "owner-name",
319 "permissions": [
384 "permissions": [
320 {
385 {
321 "name": "super-admin-name",
386 "name": "super-admin-name",
322 "origin": "super-admin",
387 "origin": "super-admin",
323 "permission": "repository.admin",
388 "permission": "repository.admin",
324 "type": "user"
389 "type": "user"
325 },
390 },
326 {
391 {
327 "name": "owner-name",
392 "name": "owner-name",
328 "origin": "owner",
393 "origin": "owner",
329 "permission": "repository.admin",
394 "permission": "repository.admin",
330 "type": "user"
395 "type": "user"
331 },
396 },
332 {
397 {
333 "name": "user-group-name",
398 "name": "user-group-name",
334 "origin": "permission",
399 "origin": "permission",
335 "permission": "repository.write",
400 "permission": "repository.write",
336 "type": "user_group"
401 "type": "user_group"
337 }
402 }
338 ],
403 ],
339 "private": true,
404 "private": true,
340 "repo_id": 676,
405 "repo_id": 676,
341 "repo_name": "user-group/repo-name",
406 "repo_name": "user-group/repo-name",
342 "repo_type": "hg"
407 "repo_type": "hg"
343 }
408 }
344 }
409 }
345
410
346
411
347 get_repo_changeset
412 get_repo_changeset
348 ------------------
413 ------------------
349
414
350 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
415 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
351
416
352 Returns information about a changeset.
417 Returns information about a changeset.
353
418
354 Additionally parameters define the amount of details returned by
419 Additionally parameters define the amount of details returned by
355 this function.
420 this function.
356
421
357 This command can only be run using an |authtoken| with admin rights,
422 This command can only be run using an |authtoken| with admin rights,
358 or users with at least read rights to the |repo|.
423 or users with at least read rights to the |repo|.
359
424
360 :param apiuser: This is filled automatically from the |authtoken|.
425 :param apiuser: This is filled automatically from the |authtoken|.
361 :type apiuser: AuthUser
426 :type apiuser: AuthUser
362 :param repoid: The repository name or repository id
427 :param repoid: The repository name or repository id
363 :type repoid: str or int
428 :type repoid: str or int
364 :param revision: revision for which listing should be done
429 :param revision: revision for which listing should be done
365 :type revision: str
430 :type revision: str
366 :param details: details can be 'basic|extended|full' full gives diff
431 :param details: details can be 'basic|extended|full' full gives diff
367 info details like the diff itself, and number of changed files etc.
432 info details like the diff itself, and number of changed files etc.
368 :type details: Optional(str)
433 :type details: Optional(str)
369
434
370
435
371 get_repo_changesets
436 get_repo_changesets
372 -------------------
437 -------------------
373
438
374 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
439 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
375
440
376 Returns a set of commits limited by the number starting
441 Returns a set of commits limited by the number starting
377 from the `start_rev` option.
442 from the `start_rev` option.
378
443
379 Additional parameters define the amount of details returned by this
444 Additional parameters define the amount of details returned by this
380 function.
445 function.
381
446
382 This command can only be run using an |authtoken| with admin rights,
447 This command can only be run using an |authtoken| with admin rights,
383 or users with at least read rights to |repos|.
448 or users with at least read rights to |repos|.
384
449
385 :param apiuser: This is filled automatically from the |authtoken|.
450 :param apiuser: This is filled automatically from the |authtoken|.
386 :type apiuser: AuthUser
451 :type apiuser: AuthUser
387 :param repoid: The repository name or repository ID.
452 :param repoid: The repository name or repository ID.
388 :type repoid: str or int
453 :type repoid: str or int
389 :param start_rev: The starting revision from where to get changesets.
454 :param start_rev: The starting revision from where to get changesets.
390 :type start_rev: str
455 :type start_rev: str
391 :param limit: Limit the number of commits to this amount
456 :param limit: Limit the number of commits to this amount
392 :type limit: str or int
457 :type limit: str or int
393 :param details: Set the level of detail returned. Valid option are:
458 :param details: Set the level of detail returned. Valid option are:
394 ``basic``, ``extended`` and ``full``.
459 ``basic``, ``extended`` and ``full``.
395 :type details: Optional(str)
460 :type details: Optional(str)
396
461
397 .. note::
462 .. note::
398
463
399 Setting the parameter `details` to the value ``full`` is extensive
464 Setting the parameter `details` to the value ``full`` is extensive
400 and returns details like the diff itself, and the number
465 and returns details like the diff itself, and the number
401 of changed files.
466 of changed files.
402
467
403
468
404 get_repo_comments
469 get_repo_comments
405 -----------------
470 -----------------
406
471
407 .. py:function:: get_repo_comments(apiuser, repoid, commit_id=<Optional:None>, comment_type=<Optional:None>, userid=<Optional:None>)
472 .. py:function:: get_repo_comments(apiuser, repoid, commit_id=<Optional:None>, comment_type=<Optional:None>, userid=<Optional:None>)
408
473
409 Get all comments for a repository
474 Get all comments for a repository
410
475
411 :param apiuser: This is filled automatically from the |authtoken|.
476 :param apiuser: This is filled automatically from the |authtoken|.
412 :type apiuser: AuthUser
477 :type apiuser: AuthUser
413 :param repoid: Set the repository name or repository ID.
478 :param repoid: Set the repository name or repository ID.
414 :type repoid: str or int
479 :type repoid: str or int
415 :param commit_id: Optionally filter the comments by the commit_id
480 :param commit_id: Optionally filter the comments by the commit_id
416 :type commit_id: Optional(str), default: None
481 :type commit_id: Optional(str), default: None
417 :param comment_type: Optionally filter the comments by the comment_type
482 :param comment_type: Optionally filter the comments by the comment_type
418 one of: 'note', 'todo'
483 one of: 'note', 'todo'
419 :type comment_type: Optional(str), default: None
484 :type comment_type: Optional(str), default: None
420 :param userid: Optionally filter the comments by the author of comment
485 :param userid: Optionally filter the comments by the author of comment
421 :type userid: Optional(str or int), Default: None
486 :type userid: Optional(str or int), Default: None
422
487
423 Example error output:
488 Example error output:
424
489
425 .. code-block:: bash
490 .. code-block:: bash
426
491
427 {
492 {
428 "id" : <id_given_in_input>,
493 "id" : <id_given_in_input>,
429 "result" : [
494 "result" : [
430 {
495 {
431 "comment_author": <USER_DETAILS>,
496 "comment_author": <USER_DETAILS>,
432 "comment_created_on": "2017-02-01T14:38:16.309",
497 "comment_created_on": "2017-02-01T14:38:16.309",
433 "comment_f_path": "file.txt",
498 "comment_f_path": "file.txt",
434 "comment_id": 282,
499 "comment_id": 282,
435 "comment_lineno": "n1",
500 "comment_lineno": "n1",
436 "comment_resolved_by": null,
501 "comment_resolved_by": null,
437 "comment_status": [],
502 "comment_status": [],
438 "comment_text": "This file needs a header",
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 "error" : null
508 "error" : null
443 }
509 }
444
510
445
511
446 get_repo_file
512 get_repo_file
447 -------------
513 -------------
448
514
449 .. py:function:: get_repo_file(apiuser, repoid, commit_id, file_path, max_file_bytes=<Optional:None>, details=<Optional:'basic'>, cache=<Optional:True>)
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 Returns a single file from repository at given revision.
517 Returns a single file from repository at given revision.
452
518
453 This command can only be run using an |authtoken| with admin rights,
519 This command can only be run using an |authtoken| with admin rights,
454 or users with at least read rights to |repos|.
520 or users with at least read rights to |repos|.
455
521
456 :param apiuser: This is filled automatically from the |authtoken|.
522 :param apiuser: This is filled automatically from the |authtoken|.
457 :type apiuser: AuthUser
523 :type apiuser: AuthUser
458 :param repoid: The repository name or repository ID.
524 :param repoid: The repository name or repository ID.
459 :type repoid: str or int
525 :type repoid: str or int
460 :param commit_id: The revision for which listing should be done.
526 :param commit_id: The revision for which listing should be done.
461 :type commit_id: str
527 :type commit_id: str
462 :param file_path: The path from which to start displaying.
528 :param file_path: The path from which to start displaying.
463 :type file_path: str
529 :type file_path: str
464 :param details: Returns different set of information about nodes.
530 :param details: Returns different set of information about nodes.
465 The valid options are ``minimal`` ``basic`` and ``full``.
531 The valid options are ``minimal`` ``basic`` and ``full``.
466 :type details: Optional(str)
532 :type details: Optional(str)
467 :param max_file_bytes: Only return file content under this file size bytes
533 :param max_file_bytes: Only return file content under this file size bytes
468 :type max_file_bytes: Optional(int)
534 :type max_file_bytes: Optional(int)
469 :param cache: Use internal caches for fetching files. If disabled fetching
535 :param cache: Use internal caches for fetching files. If disabled fetching
470 files is slower but more memory efficient
536 files is slower but more memory efficient
471 :type cache: Optional(bool)
537 :type cache: Optional(bool)
472
538
473 Example output:
539 Example output:
474
540
475 .. code-block:: bash
541 .. code-block:: bash
476
542
477 id : <id_given_in_input>
543 id : <id_given_in_input>
478 result: {
544 result: {
479 "binary": false,
545 "binary": false,
480 "extension": "py",
546 "extension": "py",
481 "lines": 35,
547 "lines": 35,
482 "content": "....",
548 "content": "....",
483 "md5": "76318336366b0f17ee249e11b0c99c41",
549 "md5": "76318336366b0f17ee249e11b0c99c41",
484 "mimetype": "text/x-python",
550 "mimetype": "text/x-python",
485 "name": "python.py",
551 "name": "python.py",
486 "size": 817,
552 "size": 817,
487 "type": "file",
553 "type": "file",
488 }
554 }
489 error: null
555 error: null
490
556
491
557
492 get_repo_fts_tree
558 get_repo_fts_tree
493 -----------------
559 -----------------
494
560
495 .. py:function:: get_repo_fts_tree(apiuser, repoid, commit_id, root_path)
561 .. py:function:: get_repo_fts_tree(apiuser, repoid, commit_id, root_path)
496
562
497 Returns a list of tree nodes for path at given revision. This api is built
563 Returns a list of tree nodes for path at given revision. This api is built
498 strictly for usage in full text search building, and shouldn't be consumed
564 strictly for usage in full text search building, and shouldn't be consumed
499
565
500 This command can only be run using an |authtoken| with admin rights,
566 This command can only be run using an |authtoken| with admin rights,
501 or users with at least read rights to |repos|.
567 or users with at least read rights to |repos|.
502
568
503
569
504 get_repo_nodes
570 get_repo_nodes
505 --------------
571 --------------
506
572
507 .. py:function:: get_repo_nodes(apiuser, repoid, revision, root_path, ret_type=<Optional:'all'>, details=<Optional:'basic'>, max_file_bytes=<Optional:None>)
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 Returns a list of nodes and children in a flat list for a given
575 Returns a list of nodes and children in a flat list for a given
510 path at given revision.
576 path at given revision.
511
577
512 It's possible to specify ret_type to show only `files` or `dirs`.
578 It's possible to specify ret_type to show only `files` or `dirs`.
513
579
514 This command can only be run using an |authtoken| with admin rights,
580 This command can only be run using an |authtoken| with admin rights,
515 or users with at least read rights to |repos|.
581 or users with at least read rights to |repos|.
516
582
517 :param apiuser: This is filled automatically from the |authtoken|.
583 :param apiuser: This is filled automatically from the |authtoken|.
518 :type apiuser: AuthUser
584 :type apiuser: AuthUser
519 :param repoid: The repository name or repository ID.
585 :param repoid: The repository name or repository ID.
520 :type repoid: str or int
586 :type repoid: str or int
521 :param revision: The revision for which listing should be done.
587 :param revision: The revision for which listing should be done.
522 :type revision: str
588 :type revision: str
523 :param root_path: The path from which to start displaying.
589 :param root_path: The path from which to start displaying.
524 :type root_path: str
590 :type root_path: str
525 :param ret_type: Set the return type. Valid options are
591 :param ret_type: Set the return type. Valid options are
526 ``all`` (default), ``files`` and ``dirs``.
592 ``all`` (default), ``files`` and ``dirs``.
527 :type ret_type: Optional(str)
593 :type ret_type: Optional(str)
528 :param details: Returns extended information about nodes, such as
594 :param details: Returns extended information about nodes, such as
529 md5, binary, and or content.
595 md5, binary, and or content.
530 The valid options are ``basic`` and ``full``.
596 The valid options are ``basic`` and ``full``.
531 :type details: Optional(str)
597 :type details: Optional(str)
532 :param max_file_bytes: Only return file content under this file size bytes
598 :param max_file_bytes: Only return file content under this file size bytes
533 :type details: Optional(int)
599 :type details: Optional(int)
534
600
535 Example output:
601 Example output:
536
602
537 .. code-block:: bash
603 .. code-block:: bash
538
604
539 id : <id_given_in_input>
605 id : <id_given_in_input>
540 result: [
606 result: [
541 {
607 {
542 "binary": false,
608 "binary": false,
543 "content": "File line",
609 "content": "File line",
544 "extension": "md",
610 "extension": "md",
545 "lines": 2,
611 "lines": 2,
546 "md5": "059fa5d29b19c0657e384749480f6422",
612 "md5": "059fa5d29b19c0657e384749480f6422",
547 "mimetype": "text/x-minidsrc",
613 "mimetype": "text/x-minidsrc",
548 "name": "file.md",
614 "name": "file.md",
549 "size": 580,
615 "size": 580,
550 "type": "file"
616 "type": "file"
551 },
617 },
552 ...
618 ...
553 ]
619 ]
554 error: null
620 error: null
555
621
556
622
557 get_repo_refs
623 get_repo_refs
558 -------------
624 -------------
559
625
560 .. py:function:: get_repo_refs(apiuser, repoid)
626 .. py:function:: get_repo_refs(apiuser, repoid)
561
627
562 Returns a dictionary of current references. It returns
628 Returns a dictionary of current references. It returns
563 bookmarks, branches, closed_branches, and tags for given repository
629 bookmarks, branches, closed_branches, and tags for given repository
564
630
565 It's possible to specify ret_type to show only `files` or `dirs`.
631 It's possible to specify ret_type to show only `files` or `dirs`.
566
632
567 This command can only be run using an |authtoken| with admin rights,
633 This command can only be run using an |authtoken| with admin rights,
568 or users with at least read rights to |repos|.
634 or users with at least read rights to |repos|.
569
635
570 :param apiuser: This is filled automatically from the |authtoken|.
636 :param apiuser: This is filled automatically from the |authtoken|.
571 :type apiuser: AuthUser
637 :type apiuser: AuthUser
572 :param repoid: The repository name or repository ID.
638 :param repoid: The repository name or repository ID.
573 :type repoid: str or int
639 :type repoid: str or int
574
640
575 Example output:
641 Example output:
576
642
577 .. code-block:: bash
643 .. code-block:: bash
578
644
579 id : <id_given_in_input>
645 id : <id_given_in_input>
580 "result": {
646 "result": {
581 "bookmarks": {
647 "bookmarks": {
582 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
648 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
583 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
649 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
584 },
650 },
585 "branches": {
651 "branches": {
586 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
652 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
587 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
653 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
588 },
654 },
589 "branches_closed": {},
655 "branches_closed": {},
590 "tags": {
656 "tags": {
591 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
657 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
592 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
658 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
593 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
659 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
594 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
660 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
595 }
661 }
596 }
662 }
597 error: null
663 error: null
598
664
599
665
600 get_repo_settings
666 get_repo_settings
601 -----------------
667 -----------------
602
668
603 .. py:function:: get_repo_settings(apiuser, repoid, key=<Optional:None>)
669 .. py:function:: get_repo_settings(apiuser, repoid, key=<Optional:None>)
604
670
605 Returns all settings for a repository. If key is given it only returns the
671 Returns all settings for a repository. If key is given it only returns the
606 setting identified by the key or null.
672 setting identified by the key or null.
607
673
608 :param apiuser: This is filled automatically from the |authtoken|.
674 :param apiuser: This is filled automatically from the |authtoken|.
609 :type apiuser: AuthUser
675 :type apiuser: AuthUser
610 :param repoid: The repository name or repository id.
676 :param repoid: The repository name or repository id.
611 :type repoid: str or int
677 :type repoid: str or int
612 :param key: Key of the setting to return.
678 :param key: Key of the setting to return.
613 :type: key: Optional(str)
679 :type: key: Optional(str)
614
680
615 Example output:
681 Example output:
616
682
617 .. code-block:: bash
683 .. code-block:: bash
618
684
619 {
685 {
620 "error": null,
686 "error": null,
621 "id": 237,
687 "id": 237,
622 "result": {
688 "result": {
623 "extensions_largefiles": true,
689 "extensions_largefiles": true,
624 "extensions_evolve": true,
690 "extensions_evolve": true,
625 "hooks_changegroup_push_logger": true,
691 "hooks_changegroup_push_logger": true,
626 "hooks_changegroup_repo_size": false,
692 "hooks_changegroup_repo_size": false,
627 "hooks_outgoing_pull_logger": true,
693 "hooks_outgoing_pull_logger": true,
628 "phases_publish": "True",
694 "phases_publish": "True",
629 "rhodecode_hg_use_rebase_for_merging": true,
695 "rhodecode_hg_use_rebase_for_merging": true,
630 "rhodecode_pr_merge_enabled": true,
696 "rhodecode_pr_merge_enabled": true,
631 "rhodecode_use_outdated_comments": true
697 "rhodecode_use_outdated_comments": true
632 }
698 }
633 }
699 }
634
700
635
701
636 get_repos
702 get_repos
637 ---------
703 ---------
638
704
639 .. py:function:: get_repos(apiuser, root=<Optional:None>, traverse=<Optional:True>)
705 .. py:function:: get_repos(apiuser, root=<Optional:None>, traverse=<Optional:True>)
640
706
641 Lists all existing repositories.
707 Lists all existing repositories.
642
708
643 This command can only be run using an |authtoken| with admin rights,
709 This command can only be run using an |authtoken| with admin rights,
644 or users with at least read rights to |repos|.
710 or users with at least read rights to |repos|.
645
711
646 :param apiuser: This is filled automatically from the |authtoken|.
712 :param apiuser: This is filled automatically from the |authtoken|.
647 :type apiuser: AuthUser
713 :type apiuser: AuthUser
648 :param root: specify root repository group to fetch repositories.
714 :param root: specify root repository group to fetch repositories.
649 filters the returned repositories to be members of given root group.
715 filters the returned repositories to be members of given root group.
650 :type root: Optional(None)
716 :type root: Optional(None)
651 :param traverse: traverse given root into subrepositories. With this flag
717 :param traverse: traverse given root into subrepositories. With this flag
652 set to False, it will only return top-level repositories from `root`.
718 set to False, it will only return top-level repositories from `root`.
653 if root is empty it will return just top-level repositories.
719 if root is empty it will return just top-level repositories.
654 :type traverse: Optional(True)
720 :type traverse: Optional(True)
655
721
656
722
657 Example output:
723 Example output:
658
724
659 .. code-block:: bash
725 .. code-block:: bash
660
726
661 id : <id_given_in_input>
727 id : <id_given_in_input>
662 result: [
728 result: [
663 {
729 {
664 "repo_id" : "<repo_id>",
730 "repo_id" : "<repo_id>",
665 "repo_name" : "<reponame>"
731 "repo_name" : "<reponame>"
666 "repo_type" : "<repo_type>",
732 "repo_type" : "<repo_type>",
667 "clone_uri" : "<clone_uri>",
733 "clone_uri" : "<clone_uri>",
668 "private": : "<bool>",
734 "private": : "<bool>",
669 "created_on" : "<datetimecreated>",
735 "created_on" : "<datetimecreated>",
670 "description" : "<description>",
736 "description" : "<description>",
671 "landing_rev": "<landing_rev>",
737 "landing_rev": "<landing_rev>",
672 "owner": "<repo_owner>",
738 "owner": "<repo_owner>",
673 "fork_of": "<name_of_fork_parent>",
739 "fork_of": "<name_of_fork_parent>",
674 "enable_downloads": "<bool>",
740 "enable_downloads": "<bool>",
675 "enable_locking": "<bool>",
741 "enable_locking": "<bool>",
676 "enable_statistics": "<bool>",
742 "enable_statistics": "<bool>",
677 },
743 },
678 ...
744 ...
679 ]
745 ]
680 error: null
746 error: null
681
747
682
748
683 grant_user_group_permission
749 grant_user_group_permission
684 ---------------------------
750 ---------------------------
685
751
686 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
752 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
687
753
688 Grant permission for a user group on the specified repository,
754 Grant permission for a user group on the specified repository,
689 or update existing permissions.
755 or update existing permissions.
690
756
691 This command can only be run using an |authtoken| with admin
757 This command can only be run using an |authtoken| with admin
692 permissions on the |repo|.
758 permissions on the |repo|.
693
759
694 :param apiuser: This is filled automatically from the |authtoken|.
760 :param apiuser: This is filled automatically from the |authtoken|.
695 :type apiuser: AuthUser
761 :type apiuser: AuthUser
696 :param repoid: Set the repository name or repository ID.
762 :param repoid: Set the repository name or repository ID.
697 :type repoid: str or int
763 :type repoid: str or int
698 :param usergroupid: Specify the ID of the user group.
764 :param usergroupid: Specify the ID of the user group.
699 :type usergroupid: str or int
765 :type usergroupid: str or int
700 :param perm: Set the user group permissions using the following
766 :param perm: Set the user group permissions using the following
701 format: (repository.(none|read|write|admin))
767 format: (repository.(none|read|write|admin))
702 :type perm: str
768 :type perm: str
703
769
704 Example output:
770 Example output:
705
771
706 .. code-block:: bash
772 .. code-block:: bash
707
773
708 id : <id_given_in_input>
774 id : <id_given_in_input>
709 result : {
775 result : {
710 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
776 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
711 "success": true
777 "success": true
712
778
713 }
779 }
714 error : null
780 error : null
715
781
716 Example error output:
782 Example error output:
717
783
718 .. code-block:: bash
784 .. code-block:: bash
719
785
720 id : <id_given_in_input>
786 id : <id_given_in_input>
721 result : null
787 result : null
722 error : {
788 error : {
723 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
789 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
724 }
790 }
725
791
726
792
727 grant_user_permission
793 grant_user_permission
728 ---------------------
794 ---------------------
729
795
730 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
796 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
731
797
732 Grant permissions for the specified user on the given repository,
798 Grant permissions for the specified user on the given repository,
733 or update existing permissions if found.
799 or update existing permissions if found.
734
800
735 This command can only be run using an |authtoken| with admin
801 This command can only be run using an |authtoken| with admin
736 permissions on the |repo|.
802 permissions on the |repo|.
737
803
738 :param apiuser: This is filled automatically from the |authtoken|.
804 :param apiuser: This is filled automatically from the |authtoken|.
739 :type apiuser: AuthUser
805 :type apiuser: AuthUser
740 :param repoid: Set the repository name or repository ID.
806 :param repoid: Set the repository name or repository ID.
741 :type repoid: str or int
807 :type repoid: str or int
742 :param userid: Set the user name.
808 :param userid: Set the user name.
743 :type userid: str
809 :type userid: str
744 :param perm: Set the user permissions, using the following format
810 :param perm: Set the user permissions, using the following format
745 ``(repository.(none|read|write|admin))``
811 ``(repository.(none|read|write|admin))``
746 :type perm: str
812 :type perm: str
747
813
748 Example output:
814 Example output:
749
815
750 .. code-block:: bash
816 .. code-block:: bash
751
817
752 id : <id_given_in_input>
818 id : <id_given_in_input>
753 result: {
819 result: {
754 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
820 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
755 "success": true
821 "success": true
756 }
822 }
757 error: null
823 error: null
758
824
759
825
760 invalidate_cache
826 invalidate_cache
761 ----------------
827 ----------------
762
828
763 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
829 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
764
830
765 Invalidates the cache for the specified repository.
831 Invalidates the cache for the specified repository.
766
832
767 This command can only be run using an |authtoken| with admin rights to
833 This command can only be run using an |authtoken| with admin rights to
768 the specified repository.
834 the specified repository.
769
835
770 This command takes the following options:
836 This command takes the following options:
771
837
772 :param apiuser: This is filled automatically from |authtoken|.
838 :param apiuser: This is filled automatically from |authtoken|.
773 :type apiuser: AuthUser
839 :type apiuser: AuthUser
774 :param repoid: Sets the repository name or repository ID.
840 :param repoid: Sets the repository name or repository ID.
775 :type repoid: str or int
841 :type repoid: str or int
776 :param delete_keys: This deletes the invalidated keys instead of
842 :param delete_keys: This deletes the invalidated keys instead of
777 just flagging them.
843 just flagging them.
778 :type delete_keys: Optional(``True`` | ``False``)
844 :type delete_keys: Optional(``True`` | ``False``)
779
845
780 Example output:
846 Example output:
781
847
782 .. code-block:: bash
848 .. code-block:: bash
783
849
784 id : <id_given_in_input>
850 id : <id_given_in_input>
785 result : {
851 result : {
786 'msg': Cache for repository `<repository name>` was invalidated,
852 'msg': Cache for repository `<repository name>` was invalidated,
787 'repository': <repository name>
853 'repository': <repository name>
788 }
854 }
789 error : null
855 error : null
790
856
791 Example error output:
857 Example error output:
792
858
793 .. code-block:: bash
859 .. code-block:: bash
794
860
795 id : <id_given_in_input>
861 id : <id_given_in_input>
796 result : null
862 result : null
797 error : {
863 error : {
798 'Error occurred during cache invalidation action'
864 'Error occurred during cache invalidation action'
799 }
865 }
800
866
801
867
802 lock
868 lock
803 ----
869 ----
804
870
805 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
871 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
806
872
807 Sets the lock state of the specified |repo| by the given user.
873 Sets the lock state of the specified |repo| by the given user.
808 From more information, see :ref:`repo-locking`.
874 From more information, see :ref:`repo-locking`.
809
875
810 * If the ``userid`` option is not set, the repository is locked to the
876 * If the ``userid`` option is not set, the repository is locked to the
811 user who called the method.
877 user who called the method.
812 * If the ``locked`` parameter is not set, the current lock state of the
878 * If the ``locked`` parameter is not set, the current lock state of the
813 repository is displayed.
879 repository is displayed.
814
880
815 This command can only be run using an |authtoken| with admin rights to
881 This command can only be run using an |authtoken| with admin rights to
816 the specified repository.
882 the specified repository.
817
883
818 This command takes the following options:
884 This command takes the following options:
819
885
820 :param apiuser: This is filled automatically from the |authtoken|.
886 :param apiuser: This is filled automatically from the |authtoken|.
821 :type apiuser: AuthUser
887 :type apiuser: AuthUser
822 :param repoid: Sets the repository name or repository ID.
888 :param repoid: Sets the repository name or repository ID.
823 :type repoid: str or int
889 :type repoid: str or int
824 :param locked: Sets the lock state.
890 :param locked: Sets the lock state.
825 :type locked: Optional(``True`` | ``False``)
891 :type locked: Optional(``True`` | ``False``)
826 :param userid: Set the repository lock to this user.
892 :param userid: Set the repository lock to this user.
827 :type userid: Optional(str or int)
893 :type userid: Optional(str or int)
828
894
829 Example error output:
895 Example error output:
830
896
831 .. code-block:: bash
897 .. code-block:: bash
832
898
833 id : <id_given_in_input>
899 id : <id_given_in_input>
834 result : {
900 result : {
835 'repo': '<reponame>',
901 'repo': '<reponame>',
836 'locked': <bool: lock state>,
902 'locked': <bool: lock state>,
837 'locked_since': <int: lock timestamp>,
903 'locked_since': <int: lock timestamp>,
838 'locked_by': <username of person who made the lock>,
904 'locked_by': <username of person who made the lock>,
839 'lock_reason': <str: reason for locking>,
905 'lock_reason': <str: reason for locking>,
840 'lock_state_changed': <bool: True if lock state has been changed in this request>,
906 'lock_state_changed': <bool: True if lock state has been changed in this request>,
841 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
907 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
842 or
908 or
843 'msg': 'Repo `<repository name>` not locked.'
909 'msg': 'Repo `<repository name>` not locked.'
844 or
910 or
845 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
911 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
846 }
912 }
847 error : null
913 error : null
848
914
849 Example error output:
915 Example error output:
850
916
851 .. code-block:: bash
917 .. code-block:: bash
852
918
853 id : <id_given_in_input>
919 id : <id_given_in_input>
854 result : null
920 result : null
855 error : {
921 error : {
856 'Error occurred locking repository `<reponame>`'
922 'Error occurred locking repository `<reponame>`'
857 }
923 }
858
924
859
925
860 maintenance
926 maintenance
861 -----------
927 -----------
862
928
863 .. py:function:: maintenance(apiuser, repoid)
929 .. py:function:: maintenance(apiuser, repoid)
864
930
865 Triggers a maintenance on the given repository.
931 Triggers a maintenance on the given repository.
866
932
867 This command can only be run using an |authtoken| with admin
933 This command can only be run using an |authtoken| with admin
868 rights to the specified repository. For more information,
934 rights to the specified repository. For more information,
869 see :ref:`config-token-ref`.
935 see :ref:`config-token-ref`.
870
936
871 This command takes the following options:
937 This command takes the following options:
872
938
873 :param apiuser: This is filled automatically from the |authtoken|.
939 :param apiuser: This is filled automatically from the |authtoken|.
874 :type apiuser: AuthUser
940 :type apiuser: AuthUser
875 :param repoid: The repository name or repository ID.
941 :param repoid: The repository name or repository ID.
876 :type repoid: str or int
942 :type repoid: str or int
877
943
878 Example output:
944 Example output:
879
945
880 .. code-block:: bash
946 .. code-block:: bash
881
947
882 id : <id_given_in_input>
948 id : <id_given_in_input>
883 result : {
949 result : {
884 "msg": "executed maintenance command",
950 "msg": "executed maintenance command",
885 "executed_actions": [
951 "executed_actions": [
886 <action_message>, <action_message2>...
952 <action_message>, <action_message2>...
887 ],
953 ],
888 "repository": "<repository name>"
954 "repository": "<repository name>"
889 }
955 }
890 error : null
956 error : null
891
957
892 Example error output:
958 Example error output:
893
959
894 .. code-block:: bash
960 .. code-block:: bash
895
961
896 id : <id_given_in_input>
962 id : <id_given_in_input>
897 result : null
963 result : null
898 error : {
964 error : {
899 "Unable to execute maintenance on `<reponame>`"
965 "Unable to execute maintenance on `<reponame>`"
900 }
966 }
901
967
902
968
903 pull
969 pull
904 ----
970 ----
905
971
906 .. py:function:: pull(apiuser, repoid, remote_uri=<Optional:None>)
972 .. py:function:: pull(apiuser, repoid, remote_uri=<Optional:None>)
907
973
908 Triggers a pull on the given repository from a remote location. You
974 Triggers a pull on the given repository from a remote location. You
909 can use this to keep remote repositories up-to-date.
975 can use this to keep remote repositories up-to-date.
910
976
911 This command can only be run using an |authtoken| with admin
977 This command can only be run using an |authtoken| with admin
912 rights to the specified repository. For more information,
978 rights to the specified repository. For more information,
913 see :ref:`config-token-ref`.
979 see :ref:`config-token-ref`.
914
980
915 This command takes the following options:
981 This command takes the following options:
916
982
917 :param apiuser: This is filled automatically from the |authtoken|.
983 :param apiuser: This is filled automatically from the |authtoken|.
918 :type apiuser: AuthUser
984 :type apiuser: AuthUser
919 :param repoid: The repository name or repository ID.
985 :param repoid: The repository name or repository ID.
920 :type repoid: str or int
986 :type repoid: str or int
921 :param remote_uri: Optional remote URI to pass in for pull
987 :param remote_uri: Optional remote URI to pass in for pull
922 :type remote_uri: str
988 :type remote_uri: str
923
989
924 Example output:
990 Example output:
925
991
926 .. code-block:: bash
992 .. code-block:: bash
927
993
928 id : <id_given_in_input>
994 id : <id_given_in_input>
929 result : {
995 result : {
930 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
996 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
931 "repository": "<repository name>"
997 "repository": "<repository name>"
932 }
998 }
933 error : null
999 error : null
934
1000
935 Example error output:
1001 Example error output:
936
1002
937 .. code-block:: bash
1003 .. code-block:: bash
938
1004
939 id : <id_given_in_input>
1005 id : <id_given_in_input>
940 result : null
1006 result : null
941 error : {
1007 error : {
942 "Unable to push changes from `<remote_url>`"
1008 "Unable to push changes from `<remote_url>`"
943 }
1009 }
944
1010
945
1011
946 remove_field_from_repo
1012 remove_field_from_repo
947 ----------------------
1013 ----------------------
948
1014
949 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
1015 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
950
1016
951 Removes an extra field from a repository.
1017 Removes an extra field from a repository.
952
1018
953 This command can only be run using an |authtoken| with at least
1019 This command can only be run using an |authtoken| with at least
954 write permissions to the |repo|.
1020 write permissions to the |repo|.
955
1021
956 :param apiuser: This is filled automatically from the |authtoken|.
1022 :param apiuser: This is filled automatically from the |authtoken|.
957 :type apiuser: AuthUser
1023 :type apiuser: AuthUser
958 :param repoid: Set the repository name or repository ID.
1024 :param repoid: Set the repository name or repository ID.
959 :type repoid: str or int
1025 :type repoid: str or int
960 :param key: Set the unique field key for this repository.
1026 :param key: Set the unique field key for this repository.
961 :type key: str
1027 :type key: str
962
1028
963
1029
964 revoke_user_group_permission
1030 revoke_user_group_permission
965 ----------------------------
1031 ----------------------------
966
1032
967 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
1033 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
968
1034
969 Revoke the permissions of a user group on a given repository.
1035 Revoke the permissions of a user group on a given repository.
970
1036
971 This command can only be run using an |authtoken| with admin
1037 This command can only be run using an |authtoken| with admin
972 permissions on the |repo|.
1038 permissions on the |repo|.
973
1039
974 :param apiuser: This is filled automatically from the |authtoken|.
1040 :param apiuser: This is filled automatically from the |authtoken|.
975 :type apiuser: AuthUser
1041 :type apiuser: AuthUser
976 :param repoid: Set the repository name or repository ID.
1042 :param repoid: Set the repository name or repository ID.
977 :type repoid: str or int
1043 :type repoid: str or int
978 :param usergroupid: Specify the user group ID.
1044 :param usergroupid: Specify the user group ID.
979 :type usergroupid: str or int
1045 :type usergroupid: str or int
980
1046
981 Example output:
1047 Example output:
982
1048
983 .. code-block:: bash
1049 .. code-block:: bash
984
1050
985 id : <id_given_in_input>
1051 id : <id_given_in_input>
986 result: {
1052 result: {
987 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1053 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
988 "success": true
1054 "success": true
989 }
1055 }
990 error: null
1056 error: null
991
1057
992
1058
993 revoke_user_permission
1059 revoke_user_permission
994 ----------------------
1060 ----------------------
995
1061
996 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
1062 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
997
1063
998 Revoke permission for a user on the specified repository.
1064 Revoke permission for a user on the specified repository.
999
1065
1000 This command can only be run using an |authtoken| with admin
1066 This command can only be run using an |authtoken| with admin
1001 permissions on the |repo|.
1067 permissions on the |repo|.
1002
1068
1003 :param apiuser: This is filled automatically from the |authtoken|.
1069 :param apiuser: This is filled automatically from the |authtoken|.
1004 :type apiuser: AuthUser
1070 :type apiuser: AuthUser
1005 :param repoid: Set the repository name or repository ID.
1071 :param repoid: Set the repository name or repository ID.
1006 :type repoid: str or int
1072 :type repoid: str or int
1007 :param userid: Set the user name of revoked user.
1073 :param userid: Set the user name of revoked user.
1008 :type userid: str or int
1074 :type userid: str or int
1009
1075
1010 Example error output:
1076 Example error output:
1011
1077
1012 .. code-block:: bash
1078 .. code-block:: bash
1013
1079
1014 id : <id_given_in_input>
1080 id : <id_given_in_input>
1015 result: {
1081 result: {
1016 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1082 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1017 "success": true
1083 "success": true
1018 }
1084 }
1019 error: null
1085 error: null
1020
1086
1021
1087
1022 set_repo_settings
1088 set_repo_settings
1023 -----------------
1089 -----------------
1024
1090
1025 .. py:function:: set_repo_settings(apiuser, repoid, settings)
1091 .. py:function:: set_repo_settings(apiuser, repoid, settings)
1026
1092
1027 Update repository settings. Returns true on success.
1093 Update repository settings. Returns true on success.
1028
1094
1029 :param apiuser: This is filled automatically from the |authtoken|.
1095 :param apiuser: This is filled automatically from the |authtoken|.
1030 :type apiuser: AuthUser
1096 :type apiuser: AuthUser
1031 :param repoid: The repository name or repository id.
1097 :param repoid: The repository name or repository id.
1032 :type repoid: str or int
1098 :type repoid: str or int
1033 :param settings: The new settings for the repository.
1099 :param settings: The new settings for the repository.
1034 :type: settings: dict
1100 :type: settings: dict
1035
1101
1036 Example output:
1102 Example output:
1037
1103
1038 .. code-block:: bash
1104 .. code-block:: bash
1039
1105
1040 {
1106 {
1041 "error": null,
1107 "error": null,
1042 "id": 237,
1108 "id": 237,
1043 "result": true
1109 "result": true
1044 }
1110 }
1045
1111
1046
1112
1047 strip
1113 strip
1048 -----
1114 -----
1049
1115
1050 .. py:function:: strip(apiuser, repoid, revision, branch)
1116 .. py:function:: strip(apiuser, repoid, revision, branch)
1051
1117
1052 Strips the given revision from the specified repository.
1118 Strips the given revision from the specified repository.
1053
1119
1054 * This will remove the revision and all of its decendants.
1120 * This will remove the revision and all of its decendants.
1055
1121
1056 This command can only be run using an |authtoken| with admin rights to
1122 This command can only be run using an |authtoken| with admin rights to
1057 the specified repository.
1123 the specified repository.
1058
1124
1059 This command takes the following options:
1125 This command takes the following options:
1060
1126
1061 :param apiuser: This is filled automatically from the |authtoken|.
1127 :param apiuser: This is filled automatically from the |authtoken|.
1062 :type apiuser: AuthUser
1128 :type apiuser: AuthUser
1063 :param repoid: The repository name or repository ID.
1129 :param repoid: The repository name or repository ID.
1064 :type repoid: str or int
1130 :type repoid: str or int
1065 :param revision: The revision you wish to strip.
1131 :param revision: The revision you wish to strip.
1066 :type revision: str
1132 :type revision: str
1067 :param branch: The branch from which to strip the revision.
1133 :param branch: The branch from which to strip the revision.
1068 :type branch: str
1134 :type branch: str
1069
1135
1070 Example output:
1136 Example output:
1071
1137
1072 .. code-block:: bash
1138 .. code-block:: bash
1073
1139
1074 id : <id_given_in_input>
1140 id : <id_given_in_input>
1075 result : {
1141 result : {
1076 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1142 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1077 "repository": "<repository name>"
1143 "repository": "<repository name>"
1078 }
1144 }
1079 error : null
1145 error : null
1080
1146
1081 Example error output:
1147 Example error output:
1082
1148
1083 .. code-block:: bash
1149 .. code-block:: bash
1084
1150
1085 id : <id_given_in_input>
1151 id : <id_given_in_input>
1086 result : null
1152 result : null
1087 error : {
1153 error : {
1088 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1154 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1089 }
1155 }
1090
1156
1091
1157
1092 update_repo
1158 update_repo
1093 -----------
1159 -----------
1094
1160
1095 .. 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:''>)
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 Updates a repository with the given information.
1163 Updates a repository with the given information.
1098
1164
1099 This command can only be run using an |authtoken| with at least
1165 This command can only be run using an |authtoken| with at least
1100 admin permissions to the |repo|.
1166 admin permissions to the |repo|.
1101
1167
1102 * If the repository name contains "/", repository will be updated
1168 * If the repository name contains "/", repository will be updated
1103 accordingly with a repository group or nested repository groups
1169 accordingly with a repository group or nested repository groups
1104
1170
1105 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
1171 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
1106 called "repo-test" and place it inside group "foo/bar".
1172 called "repo-test" and place it inside group "foo/bar".
1107 You have to have permissions to access and write to the last repository
1173 You have to have permissions to access and write to the last repository
1108 group ("bar" in this example)
1174 group ("bar" in this example)
1109
1175
1110 :param apiuser: This is filled automatically from the |authtoken|.
1176 :param apiuser: This is filled automatically from the |authtoken|.
1111 :type apiuser: AuthUser
1177 :type apiuser: AuthUser
1112 :param repoid: repository name or repository ID.
1178 :param repoid: repository name or repository ID.
1113 :type repoid: str or int
1179 :type repoid: str or int
1114 :param repo_name: Update the |repo| name, including the
1180 :param repo_name: Update the |repo| name, including the
1115 repository group it's in.
1181 repository group it's in.
1116 :type repo_name: str
1182 :type repo_name: str
1117 :param owner: Set the |repo| owner.
1183 :param owner: Set the |repo| owner.
1118 :type owner: str
1184 :type owner: str
1119 :param fork_of: Set the |repo| as fork of another |repo|.
1185 :param fork_of: Set the |repo| as fork of another |repo|.
1120 :type fork_of: str
1186 :type fork_of: str
1121 :param description: Update the |repo| description.
1187 :param description: Update the |repo| description.
1122 :type description: str
1188 :type description: str
1123 :param private: Set the |repo| as private. (True | False)
1189 :param private: Set the |repo| as private. (True | False)
1124 :type private: bool
1190 :type private: bool
1125 :param clone_uri: Update the |repo| clone URI.
1191 :param clone_uri: Update the |repo| clone URI.
1126 :type clone_uri: str
1192 :type clone_uri: str
1127 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1193 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1128 :type landing_rev: str
1194 :type landing_rev: str
1129 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1195 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1130 :type enable_statistics: bool
1196 :type enable_statistics: bool
1131 :param enable_locking: Enable |repo| locking.
1197 :param enable_locking: Enable |repo| locking.
1132 :type enable_locking: bool
1198 :type enable_locking: bool
1133 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1199 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1134 :type enable_downloads: bool
1200 :type enable_downloads: bool
1135 :param fields: Add extra fields to the |repo|. Use the following
1201 :param fields: Add extra fields to the |repo|. Use the following
1136 example format: ``field_key=field_val,field_key2=fieldval2``.
1202 example format: ``field_key=field_val,field_key2=fieldval2``.
1137 Escape ', ' with \,
1203 Escape ', ' with \,
1138 :type fields: str
1204 :type fields: str
1139
1205
1140
1206
@@ -1,30 +1,45 b''
1 .. _set-up-mail:
1 .. _set-up-mail:
2
2
3 Set up Email
3 Set up Email
4 ------------
4 ------------
5
5
6 To setup email with your |RCE| instance, open the default
6 To setup email with your |RCE| instance, open the default
7 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
7 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
8 file and uncomment and configure the email section. If it is not there,
8 file and uncomment and configure the email section. If it is not there,
9 use the below example to insert it.
9 use the below example to insert it.
10
10
11 Once configured you can check the settings for your |RCE| instance on the
11 Once configured you can check the settings for your |RCE| instance on the
12 :menuselection:`Admin --> Settings --> Email` page.
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 .. code-block:: ini
17 .. code-block:: ini
15
18
16 ################################################################################
19 [DEFAULT]
17 ## Uncomment and replace with the email address which should receive ##
20 ; ########################################################################
18 ## any error reports after an application crash ##
21 ; EMAIL CONFIGURATION
19 ## Additionally these settings will be used by the RhodeCode mailing system ##
22 ; These settings will be used by the RhodeCode mailing system
20 ################################################################################
23 ; ########################################################################
21 #email_to = admin@localhost
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 #app_email_from = rhodecode-noreply@localhost
29 #app_email_from = rhodecode-noreply@localhost
23 #email_prefix = [RhodeCode]
24
30
25 #smtp_server = mail.server.com
31 #smtp_server = mail.server.com
26 #smtp_username =
32 #smtp_username =
27 #smtp_password =
33 #smtp_password =
28 #smtp_port =
34 #smtp_port =
29 #smtp_use_tls = false
35 #smtp_use_tls = false
30 #smtp_use_ssl = true
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 .. _rhodecode-release-notes-ref:
1 .. _rhodecode-release-notes-ref:
2
2
3 Release Notes
3 Release Notes
4 =============
4 =============
5
5
6 |RCE| 4.x Versions
6 |RCE| 4.x Versions
7 ------------------
7 ------------------
8
8
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12 release-notes-4.20.0.rst
12 release-notes-4.19.3.rst
13 release-notes-4.19.3.rst
13 release-notes-4.19.2.rst
14 release-notes-4.19.2.rst
14 release-notes-4.19.1.rst
15 release-notes-4.19.1.rst
15 release-notes-4.19.0.rst
16 release-notes-4.19.0.rst
16 release-notes-4.18.3.rst
17 release-notes-4.18.3.rst
17 release-notes-4.18.2.rst
18 release-notes-4.18.2.rst
18 release-notes-4.18.1.rst
19 release-notes-4.18.1.rst
19 release-notes-4.18.0.rst
20 release-notes-4.18.0.rst
20 release-notes-4.17.4.rst
21 release-notes-4.17.4.rst
21 release-notes-4.17.3.rst
22 release-notes-4.17.3.rst
22 release-notes-4.17.2.rst
23 release-notes-4.17.2.rst
23 release-notes-4.17.1.rst
24 release-notes-4.17.1.rst
24 release-notes-4.17.0.rst
25 release-notes-4.17.0.rst
25 release-notes-4.16.2.rst
26 release-notes-4.16.2.rst
26 release-notes-4.16.1.rst
27 release-notes-4.16.1.rst
27 release-notes-4.16.0.rst
28 release-notes-4.16.0.rst
28 release-notes-4.15.2.rst
29 release-notes-4.15.2.rst
29 release-notes-4.15.1.rst
30 release-notes-4.15.1.rst
30 release-notes-4.15.0.rst
31 release-notes-4.15.0.rst
31 release-notes-4.14.1.rst
32 release-notes-4.14.1.rst
32 release-notes-4.14.0.rst
33 release-notes-4.14.0.rst
33 release-notes-4.13.3.rst
34 release-notes-4.13.3.rst
34 release-notes-4.13.2.rst
35 release-notes-4.13.2.rst
35 release-notes-4.13.1.rst
36 release-notes-4.13.1.rst
36 release-notes-4.13.0.rst
37 release-notes-4.13.0.rst
37 release-notes-4.12.4.rst
38 release-notes-4.12.4.rst
38 release-notes-4.12.3.rst
39 release-notes-4.12.3.rst
39 release-notes-4.12.2.rst
40 release-notes-4.12.2.rst
40 release-notes-4.12.1.rst
41 release-notes-4.12.1.rst
41 release-notes-4.12.0.rst
42 release-notes-4.12.0.rst
42 release-notes-4.11.6.rst
43 release-notes-4.11.6.rst
43 release-notes-4.11.5.rst
44 release-notes-4.11.5.rst
44 release-notes-4.11.4.rst
45 release-notes-4.11.4.rst
45 release-notes-4.11.3.rst
46 release-notes-4.11.3.rst
46 release-notes-4.11.2.rst
47 release-notes-4.11.2.rst
47 release-notes-4.11.1.rst
48 release-notes-4.11.1.rst
48 release-notes-4.11.0.rst
49 release-notes-4.11.0.rst
49 release-notes-4.10.6.rst
50 release-notes-4.10.6.rst
50 release-notes-4.10.5.rst
51 release-notes-4.10.5.rst
51 release-notes-4.10.4.rst
52 release-notes-4.10.4.rst
52 release-notes-4.10.3.rst
53 release-notes-4.10.3.rst
53 release-notes-4.10.2.rst
54 release-notes-4.10.2.rst
54 release-notes-4.10.1.rst
55 release-notes-4.10.1.rst
55 release-notes-4.10.0.rst
56 release-notes-4.10.0.rst
56 release-notes-4.9.1.rst
57 release-notes-4.9.1.rst
57 release-notes-4.9.0.rst
58 release-notes-4.9.0.rst
58 release-notes-4.8.0.rst
59 release-notes-4.8.0.rst
59 release-notes-4.7.2.rst
60 release-notes-4.7.2.rst
60 release-notes-4.7.1.rst
61 release-notes-4.7.1.rst
61 release-notes-4.7.0.rst
62 release-notes-4.7.0.rst
62 release-notes-4.6.1.rst
63 release-notes-4.6.1.rst
63 release-notes-4.6.0.rst
64 release-notes-4.6.0.rst
64 release-notes-4.5.2.rst
65 release-notes-4.5.2.rst
65 release-notes-4.5.1.rst
66 release-notes-4.5.1.rst
66 release-notes-4.5.0.rst
67 release-notes-4.5.0.rst
67 release-notes-4.4.2.rst
68 release-notes-4.4.2.rst
68 release-notes-4.4.1.rst
69 release-notes-4.4.1.rst
69 release-notes-4.4.0.rst
70 release-notes-4.4.0.rst
70 release-notes-4.3.1.rst
71 release-notes-4.3.1.rst
71 release-notes-4.3.0.rst
72 release-notes-4.3.0.rst
72 release-notes-4.2.1.rst
73 release-notes-4.2.1.rst
73 release-notes-4.2.0.rst
74 release-notes-4.2.0.rst
74 release-notes-4.1.2.rst
75 release-notes-4.1.2.rst
75 release-notes-4.1.1.rst
76 release-notes-4.1.1.rst
76 release-notes-4.1.0.rst
77 release-notes-4.1.0.rst
77 release-notes-4.0.1.rst
78 release-notes-4.0.1.rst
78 release-notes-4.0.0.rst
79 release-notes-4.0.0.rst
79
80
80 |RCE| 3.x Versions
81 |RCE| 3.x Versions
81 ------------------
82 ------------------
82
83
83 .. toctree::
84 .. toctree::
84 :maxdepth: 1
85 :maxdepth: 1
85
86
86 release-notes-3.8.4.rst
87 release-notes-3.8.4.rst
87 release-notes-3.8.3.rst
88 release-notes-3.8.3.rst
88 release-notes-3.8.2.rst
89 release-notes-3.8.2.rst
89 release-notes-3.8.1.rst
90 release-notes-3.8.1.rst
90 release-notes-3.8.0.rst
91 release-notes-3.8.0.rst
91 release-notes-3.7.1.rst
92 release-notes-3.7.1.rst
92 release-notes-3.7.0.rst
93 release-notes-3.7.0.rst
93 release-notes-3.6.1.rst
94 release-notes-3.6.1.rst
94 release-notes-3.6.0.rst
95 release-notes-3.6.0.rst
95 release-notes-3.5.2.rst
96 release-notes-3.5.2.rst
96 release-notes-3.5.1.rst
97 release-notes-3.5.1.rst
97 release-notes-3.5.0.rst
98 release-notes-3.5.0.rst
98 release-notes-3.4.1.rst
99 release-notes-3.4.1.rst
99 release-notes-3.4.0.rst
100 release-notes-3.4.0.rst
100 release-notes-3.3.4.rst
101 release-notes-3.3.4.rst
101 release-notes-3.3.3.rst
102 release-notes-3.3.3.rst
102 release-notes-3.3.2.rst
103 release-notes-3.3.2.rst
103 release-notes-3.3.1.rst
104 release-notes-3.3.1.rst
104 release-notes-3.3.0.rst
105 release-notes-3.3.0.rst
105 release-notes-3.2.3.rst
106 release-notes-3.2.3.rst
106 release-notes-3.2.2.rst
107 release-notes-3.2.2.rst
107 release-notes-3.2.1.rst
108 release-notes-3.2.1.rst
108 release-notes-3.2.0.rst
109 release-notes-3.2.0.rst
109 release-notes-3.1.1.rst
110 release-notes-3.1.1.rst
110 release-notes-3.1.0.rst
111 release-notes-3.1.0.rst
111 release-notes-3.0.2.rst
112 release-notes-3.0.2.rst
112 release-notes-3.0.1.rst
113 release-notes-3.0.1.rst
113 release-notes-3.0.0.rst
114 release-notes-3.0.0.rst
114
115
115 |RCE| 2.x Versions
116 |RCE| 2.x Versions
116 ------------------
117 ------------------
117
118
118 .. toctree::
119 .. toctree::
119 :maxdepth: 1
120 :maxdepth: 1
120
121
121 release-notes-2.2.8.rst
122 release-notes-2.2.8.rst
122 release-notes-2.2.7.rst
123 release-notes-2.2.7.rst
123 release-notes-2.2.6.rst
124 release-notes-2.2.6.rst
124 release-notes-2.2.5.rst
125 release-notes-2.2.5.rst
125 release-notes-2.2.4.rst
126 release-notes-2.2.4.rst
126 release-notes-2.2.3.rst
127 release-notes-2.2.3.rst
127 release-notes-2.2.2.rst
128 release-notes-2.2.2.rst
128 release-notes-2.2.1.rst
129 release-notes-2.2.1.rst
129 release-notes-2.2.0.rst
130 release-notes-2.2.0.rst
130 release-notes-2.1.0.rst
131 release-notes-2.1.0.rst
131 release-notes-2.0.2.rst
132 release-notes-2.0.2.rst
132 release-notes-2.0.1.rst
133 release-notes-2.0.1.rst
133 release-notes-2.0.0.rst
134 release-notes-2.0.0.rst
134
135
135 |RCE| 1.x Versions
136 |RCE| 1.x Versions
136 ------------------
137 ------------------
137
138
138 .. toctree::
139 .. toctree::
139 :maxdepth: 1
140 :maxdepth: 1
140
141
141 release-notes-1.7.2.rst
142 release-notes-1.7.2.rst
142 release-notes-1.7.1.rst
143 release-notes-1.7.1.rst
143 release-notes-1.7.0.rst
144 release-notes-1.7.0.rst
144 release-notes-1.6.0.rst
145 release-notes-1.6.0.rst
@@ -1,184 +1,187 b''
1 {
1 {
2 "dirs": {
2 "dirs": {
3 "css": {
3 "css": {
4 "src": "rhodecode/public/css",
4 "src": "rhodecode/public/css",
5 "dest": "rhodecode/public/css"
5 "dest": "rhodecode/public/css"
6 },
6 },
7 "js": {
7 "js": {
8 "src": "rhodecode/public/js/src",
8 "src": "rhodecode/public/js/src",
9 "src_rc": "rhodecode/public/js/rhodecode",
9 "src_rc": "rhodecode/public/js/rhodecode",
10 "dest": "rhodecode/public/js",
10 "dest": "rhodecode/public/js",
11 "node_modules": "node_modules"
11 "node_modules": "node_modules"
12 }
12 }
13 },
13 },
14 "copy": {
14 "copy": {
15 "main": {
15 "main": {
16 "files": [
16 "files": [
17 {
17 {
18 "expand": true,
18 "expand": true,
19 "cwd": "node_modules/@webcomponents",
19 "cwd": "node_modules/@webcomponents",
20 "src": "webcomponentsjs/*.*",
20 "src": "webcomponentsjs/*.*",
21 "dest": "<%= dirs.js.dest %>/vendors"
21 "dest": "<%= dirs.js.dest %>/vendors"
22 },
22 },
23 {
23 {
24 "src": "<%= dirs.css.src %>/style-polymer.css",
24 "src": "<%= dirs.css.src %>/style-polymer.css",
25 "dest": "<%= dirs.js.dest %>/src/components/style-polymer.css"
25 "dest": "<%= dirs.js.dest %>/src/components/style-polymer.css"
26 }
26 }
27 ]
27 ]
28 }
28 }
29 },
29 },
30 "concat": {
30 "concat": {
31 "dist": {
31 "dist": {
32 "src": [
32 "src": [
33 "<%= dirs.js.node_modules %>/jquery/dist/jquery.min.js",
33 "<%= dirs.js.node_modules %>/jquery/dist/jquery.min.js",
34 "<%= dirs.js.node_modules %>/mousetrap/mousetrap.min.js",
34 "<%= dirs.js.node_modules %>/mousetrap/mousetrap.min.js",
35 "<%= dirs.js.node_modules %>/moment/min/moment.min.js",
35 "<%= dirs.js.node_modules %>/moment/min/moment.min.js",
36 "<%= dirs.js.node_modules %>/clipboard/dist/clipboard.min.js",
36 "<%= dirs.js.node_modules %>/clipboard/dist/clipboard.min.js",
37 "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js",
37 "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js",
38 "<%= dirs.js.node_modules %>/dropzone/dist/min/dropzone.min.js",
38 "<%= dirs.js.node_modules %>/dropzone/dist/min/dropzone.min.js",
39 "<%= dirs.js.node_modules %>/sweetalert2/dist/sweetalert2.min.js",
39 "<%= dirs.js.node_modules %>/sweetalert2/dist/sweetalert2.min.js",
40 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/sticky-sidebar.min.js",
40 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/sticky-sidebar.min.js",
41 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/jquery.sticky-sidebar.min.js",
41 "<%= dirs.js.node_modules %>/sticky-sidebar/dist/jquery.sticky-sidebar.min.js",
42 "<%= dirs.js.node_modules %>/waypoints/lib/noframework.waypoints.min.js",
42 "<%= dirs.js.node_modules %>/waypoints/lib/noframework.waypoints.min.js",
43 "<%= dirs.js.node_modules %>/waypoints/lib/jquery.waypoints.min.js",
43 "<%= dirs.js.node_modules %>/waypoints/lib/jquery.waypoints.min.js",
44 "<%= dirs.js.node_modules %>/appenlight-client/appenlight-client.min.js",
44 "<%= dirs.js.node_modules %>/appenlight-client/appenlight-client.min.js",
45 "<%= dirs.js.src %>/logging.js",
45 "<%= dirs.js.src %>/logging.js",
46 "<%= dirs.js.src %>/bootstrap.js",
46 "<%= dirs.js.src %>/bootstrap.js",
47 "<%= dirs.js.src %>/i18n_utils.js",
47 "<%= dirs.js.src %>/i18n_utils.js",
48 "<%= dirs.js.src %>/deform.js",
48 "<%= dirs.js.src %>/deform.js",
49 "<%= dirs.js.src %>/ejs.js",
49 "<%= dirs.js.src %>/ejs.js",
50 "<%= dirs.js.src %>/ejs_templates/utils.js",
50 "<%= dirs.js.src %>/ejs_templates/utils.js",
51 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
51 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
52 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
52 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
53 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
53 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
54 "<%= dirs.js.src %>/plugins/within_viewport.js",
54 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
55 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
55 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
56 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
56 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
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 "<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
60 "<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
58 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
61 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
59 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
62 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
60 "<%= dirs.js.src %>/select2/select2.js",
63 "<%= dirs.js.src %>/select2/select2.js",
61 "<%= dirs.js.src %>/codemirror/codemirror.js",
64 "<%= dirs.js.src %>/codemirror/codemirror.js",
62 "<%= dirs.js.src %>/codemirror/codemirror_loadmode.js",
65 "<%= dirs.js.src %>/codemirror/codemirror_loadmode.js",
63 "<%= dirs.js.src %>/codemirror/codemirror_hint.js",
66 "<%= dirs.js.src %>/codemirror/codemirror_hint.js",
64 "<%= dirs.js.src %>/codemirror/codemirror_overlay.js",
67 "<%= dirs.js.src %>/codemirror/codemirror_overlay.js",
65 "<%= dirs.js.src %>/codemirror/codemirror_placeholder.js",
68 "<%= dirs.js.src %>/codemirror/codemirror_placeholder.js",
66 "<%= dirs.js.src %>/codemirror/codemirror_simplemode.js",
69 "<%= dirs.js.src %>/codemirror/codemirror_simplemode.js",
67 "<%= dirs.js.dest %>/mode/meta.js",
70 "<%= dirs.js.dest %>/mode/meta.js",
68 "<%= dirs.js.dest %>/mode/meta_ext.js",
71 "<%= dirs.js.dest %>/mode/meta_ext.js",
69 "<%= dirs.js.src_rc %>/i18n/select2/translations.js",
72 "<%= dirs.js.src_rc %>/i18n/select2/translations.js",
70 "<%= dirs.js.src %>/rhodecode/utils/array.js",
73 "<%= dirs.js.src %>/rhodecode/utils/array.js",
71 "<%= dirs.js.src %>/rhodecode/utils/string.js",
74 "<%= dirs.js.src %>/rhodecode/utils/string.js",
72 "<%= dirs.js.src %>/rhodecode/utils/pyroutes.js",
75 "<%= dirs.js.src %>/rhodecode/utils/pyroutes.js",
73 "<%= dirs.js.src %>/rhodecode/utils/ajax.js",
76 "<%= dirs.js.src %>/rhodecode/utils/ajax.js",
74 "<%= dirs.js.src %>/rhodecode/utils/autocomplete.js",
77 "<%= dirs.js.src %>/rhodecode/utils/autocomplete.js",
75 "<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js",
78 "<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js",
76 "<%= dirs.js.src %>/rhodecode/utils/ie.js",
79 "<%= dirs.js.src %>/rhodecode/utils/ie.js",
77 "<%= dirs.js.src %>/rhodecode/utils/os.js",
80 "<%= dirs.js.src %>/rhodecode/utils/os.js",
78 "<%= dirs.js.src %>/rhodecode/utils/topics.js",
81 "<%= dirs.js.src %>/rhodecode/utils/topics.js",
79 "<%= dirs.js.src %>/rhodecode/init.js",
82 "<%= dirs.js.src %>/rhodecode/init.js",
80 "<%= dirs.js.src %>/rhodecode/changelog.js",
83 "<%= dirs.js.src %>/rhodecode/changelog.js",
81 "<%= dirs.js.src %>/rhodecode/codemirror.js",
84 "<%= dirs.js.src %>/rhodecode/codemirror.js",
82 "<%= dirs.js.src %>/rhodecode/comments.js",
85 "<%= dirs.js.src %>/rhodecode/comments.js",
83 "<%= dirs.js.src %>/rhodecode/constants.js",
86 "<%= dirs.js.src %>/rhodecode/constants.js",
84 "<%= dirs.js.src %>/rhodecode/files.js",
87 "<%= dirs.js.src %>/rhodecode/files.js",
85 "<%= dirs.js.src %>/rhodecode/followers.js",
88 "<%= dirs.js.src %>/rhodecode/followers.js",
86 "<%= dirs.js.src %>/rhodecode/menus.js",
89 "<%= dirs.js.src %>/rhodecode/menus.js",
87 "<%= dirs.js.src %>/rhodecode/notifications.js",
90 "<%= dirs.js.src %>/rhodecode/notifications.js",
88 "<%= dirs.js.src %>/rhodecode/permissions.js",
91 "<%= dirs.js.src %>/rhodecode/permissions.js",
89 "<%= dirs.js.src %>/rhodecode/pjax.js",
92 "<%= dirs.js.src %>/rhodecode/pjax.js",
90 "<%= dirs.js.src %>/rhodecode/pullrequests.js",
93 "<%= dirs.js.src %>/rhodecode/pullrequests.js",
91 "<%= dirs.js.src %>/rhodecode/settings.js",
94 "<%= dirs.js.src %>/rhodecode/settings.js",
92 "<%= dirs.js.src %>/rhodecode/select2_widgets.js",
95 "<%= dirs.js.src %>/rhodecode/select2_widgets.js",
93 "<%= dirs.js.src %>/rhodecode/tooltips.js",
96 "<%= dirs.js.src %>/rhodecode/tooltips.js",
94 "<%= dirs.js.src %>/rhodecode/users.js",
97 "<%= dirs.js.src %>/rhodecode/users.js",
95 "<%= dirs.js.src %>/rhodecode/appenlight.js",
98 "<%= dirs.js.src %>/rhodecode/appenlight.js",
96 "<%= dirs.js.src %>/rhodecode.js",
99 "<%= dirs.js.src %>/rhodecode.js",
97 "<%= dirs.js.dest %>/rhodecode-components.js"
100 "<%= dirs.js.dest %>/rhodecode-components.js"
98 ],
101 ],
99 "dest": "<%= dirs.js.dest %>/scripts.js",
102 "dest": "<%= dirs.js.dest %>/scripts.js",
100 "nonull": true
103 "nonull": true
101 }
104 }
102 },
105 },
103 "uglify": {
106 "uglify": {
104 "dist": {
107 "dist": {
105 "src": "<%= dirs.js.dest %>/scripts.js",
108 "src": "<%= dirs.js.dest %>/scripts.js",
106 "dest": "<%= dirs.js.dest %>/scripts.min.js"
109 "dest": "<%= dirs.js.dest %>/scripts.min.js"
107 }
110 }
108 },
111 },
109 "less": {
112 "less": {
110 "development": {
113 "development": {
111 "options": {
114 "options": {
112 "compress": false,
115 "compress": false,
113 "yuicompress": false,
116 "yuicompress": false,
114 "optimization": 0
117 "optimization": 0
115 },
118 },
116 "files": {
119 "files": {
117 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
120 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
118 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
121 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
119 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
122 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
120 }
123 }
121 },
124 },
122 "production": {
125 "production": {
123 "options": {
126 "options": {
124 "compress": true,
127 "compress": true,
125 "yuicompress": true,
128 "yuicompress": true,
126 "optimization": 2
129 "optimization": 2
127 },
130 },
128 "files": {
131 "files": {
129 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
132 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
130 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
133 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less",
131 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
134 "<%= dirs.css.dest %>/style-ipython.css": "<%= dirs.css.src %>/ipython.less"
132 }
135 }
133 },
136 },
134 "components": {
137 "components": {
135 "files": [
138 "files": [
136 {
139 {
137 "cwd": "<%= dirs.js.src %>/components/",
140 "cwd": "<%= dirs.js.src %>/components/",
138 "dest": "<%= dirs.js.src %>/components/",
141 "dest": "<%= dirs.js.src %>/components/",
139 "src": [
142 "src": [
140 "**/*.less"
143 "**/*.less"
141 ],
144 ],
142 "expand": true,
145 "expand": true,
143 "ext": ".css"
146 "ext": ".css"
144 }
147 }
145 ]
148 ]
146 }
149 }
147 },
150 },
148 "watch": {
151 "watch": {
149 "less": {
152 "less": {
150 "files": [
153 "files": [
151 "<%= dirs.css.src %>/**/*.less",
154 "<%= dirs.css.src %>/**/*.less",
152 "<%= dirs.js.src %>/components/**/*.less"
155 "<%= dirs.js.src %>/components/**/*.less"
153 ],
156 ],
154 "tasks": [
157 "tasks": [
155 "less:development",
158 "less:development",
156 "less:components",
159 "less:components",
157 "concat:polymercss",
160 "concat:polymercss",
158 "webpack",
161 "webpack",
159 "concat:dist"
162 "concat:dist"
160 ]
163 ]
161 },
164 },
162 "js": {
165 "js": {
163 "files": [
166 "files": [
164 "!<%= dirs.js.src %>/components/root-styles.gen.html",
167 "!<%= dirs.js.src %>/components/root-styles.gen.html",
165 "<%= dirs.js.src %>/**/*.js",
168 "<%= dirs.js.src %>/**/*.js",
166 "<%= dirs.js.src %>/components/**/*.html"
169 "<%= dirs.js.src %>/components/**/*.html"
167 ],
170 ],
168 "tasks": [
171 "tasks": [
169 "less:components",
172 "less:components",
170 "concat:polymercss",
173 "concat:polymercss",
171 "webpack",
174 "webpack",
172 "concat:dist"
175 "concat:dist"
173 ]
176 ]
174 }
177 }
175 },
178 },
176 "jshint": {
179 "jshint": {
177 "rhodecode": {
180 "rhodecode": {
178 "src": "<%= dirs.js.src %>/rhodecode/**/*.js",
181 "src": "<%= dirs.js.src %>/rhodecode/**/*.js",
179 "options": {
182 "options": {
180 "jshintrc": ".jshintrc"
183 "jshintrc": ".jshintrc"
181 }
184 }
182 }
185 }
183 }
186 }
184 }
187 }
@@ -1,2444 +1,2444 b''
1 # Generated by pip2nix 0.8.0.dev1
1 # Generated by pip2nix 0.8.0.dev1
2 # See https://github.com/johbo/pip2nix
2 # See https://github.com/johbo/pip2nix
3
3
4 { pkgs, fetchurl, fetchgit, fetchhg }:
4 { pkgs, fetchurl, fetchgit, fetchhg }:
5
5
6 self: super: {
6 self: super: {
7 "alembic" = super.buildPythonPackage {
7 "alembic" = super.buildPythonPackage {
8 name = "alembic-1.4.2";
8 name = "alembic-1.4.2";
9 doCheck = false;
9 doCheck = false;
10 propagatedBuildInputs = [
10 propagatedBuildInputs = [
11 self."sqlalchemy"
11 self."sqlalchemy"
12 self."mako"
12 self."mako"
13 self."python-editor"
13 self."python-editor"
14 self."python-dateutil"
14 self."python-dateutil"
15 ];
15 ];
16 src = fetchurl {
16 src = fetchurl {
17 url = "https://files.pythonhosted.org/packages/60/1e/cabc75a189de0fbb2841d0975243e59bde8b7822bacbb95008ac6fe9ad47/alembic-1.4.2.tar.gz";
17 url = "https://files.pythonhosted.org/packages/60/1e/cabc75a189de0fbb2841d0975243e59bde8b7822bacbb95008ac6fe9ad47/alembic-1.4.2.tar.gz";
18 sha256 = "1gsdrzx9h7wfva200qvvsc9sn4w79mk2vs0bbnzjhxi1jw2b0nh3";
18 sha256 = "1gsdrzx9h7wfva200qvvsc9sn4w79mk2vs0bbnzjhxi1jw2b0nh3";
19 };
19 };
20 meta = {
20 meta = {
21 license = [ pkgs.lib.licenses.mit ];
21 license = [ pkgs.lib.licenses.mit ];
22 };
22 };
23 };
23 };
24 "amqp" = super.buildPythonPackage {
24 "amqp" = super.buildPythonPackage {
25 name = "amqp-2.5.2";
25 name = "amqp-2.5.2";
26 doCheck = false;
26 doCheck = false;
27 propagatedBuildInputs = [
27 propagatedBuildInputs = [
28 self."vine"
28 self."vine"
29 ];
29 ];
30 src = fetchurl {
30 src = fetchurl {
31 url = "https://files.pythonhosted.org/packages/92/1d/433541994a5a69f4ad2fff39746ddbb0bdedb0ea0d85673eb0db68a7edd9/amqp-2.5.2.tar.gz";
31 url = "https://files.pythonhosted.org/packages/92/1d/433541994a5a69f4ad2fff39746ddbb0bdedb0ea0d85673eb0db68a7edd9/amqp-2.5.2.tar.gz";
32 sha256 = "13dhhfxjrqcjybnq4zahg92mydhpg2l76nxcmq7d560687wsxwbp";
32 sha256 = "13dhhfxjrqcjybnq4zahg92mydhpg2l76nxcmq7d560687wsxwbp";
33 };
33 };
34 meta = {
34 meta = {
35 license = [ pkgs.lib.licenses.bsdOriginal ];
35 license = [ pkgs.lib.licenses.bsdOriginal ];
36 };
36 };
37 };
37 };
38 "appenlight-client" = super.buildPythonPackage {
38 "appenlight-client" = super.buildPythonPackage {
39 name = "appenlight-client-0.6.26";
39 name = "appenlight-client-0.6.26";
40 doCheck = false;
40 doCheck = false;
41 propagatedBuildInputs = [
41 propagatedBuildInputs = [
42 self."webob"
42 self."webob"
43 self."requests"
43 self."requests"
44 self."six"
44 self."six"
45 ];
45 ];
46 src = fetchurl {
46 src = fetchurl {
47 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
47 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
48 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
48 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
49 };
49 };
50 meta = {
50 meta = {
51 license = [ pkgs.lib.licenses.bsdOriginal ];
51 license = [ pkgs.lib.licenses.bsdOriginal ];
52 };
52 };
53 };
53 };
54 "asn1crypto" = super.buildPythonPackage {
54 "asn1crypto" = super.buildPythonPackage {
55 name = "asn1crypto-0.24.0";
55 name = "asn1crypto-0.24.0";
56 doCheck = false;
56 doCheck = false;
57 src = fetchurl {
57 src = fetchurl {
58 url = "https://files.pythonhosted.org/packages/fc/f1/8db7daa71f414ddabfa056c4ef792e1461ff655c2ae2928a2b675bfed6b4/asn1crypto-0.24.0.tar.gz";
58 url = "https://files.pythonhosted.org/packages/fc/f1/8db7daa71f414ddabfa056c4ef792e1461ff655c2ae2928a2b675bfed6b4/asn1crypto-0.24.0.tar.gz";
59 sha256 = "0jaf8rf9dx1lf23xfv2cdd5h52f1qr3w8k63985bc35g3d220p4x";
59 sha256 = "0jaf8rf9dx1lf23xfv2cdd5h52f1qr3w8k63985bc35g3d220p4x";
60 };
60 };
61 meta = {
61 meta = {
62 license = [ pkgs.lib.licenses.mit ];
62 license = [ pkgs.lib.licenses.mit ];
63 };
63 };
64 };
64 };
65 "atomicwrites" = super.buildPythonPackage {
65 "atomicwrites" = super.buildPythonPackage {
66 name = "atomicwrites-1.3.0";
66 name = "atomicwrites-1.3.0";
67 doCheck = false;
67 doCheck = false;
68 src = fetchurl {
68 src = fetchurl {
69 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
69 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
70 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
70 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
71 };
71 };
72 meta = {
72 meta = {
73 license = [ pkgs.lib.licenses.mit ];
73 license = [ pkgs.lib.licenses.mit ];
74 };
74 };
75 };
75 };
76 "attrs" = super.buildPythonPackage {
76 "attrs" = super.buildPythonPackage {
77 name = "attrs-19.3.0";
77 name = "attrs-19.3.0";
78 doCheck = false;
78 doCheck = false;
79 src = fetchurl {
79 src = fetchurl {
80 url = "https://files.pythonhosted.org/packages/98/c3/2c227e66b5e896e15ccdae2e00bbc69aa46e9a8ce8869cc5fa96310bf612/attrs-19.3.0.tar.gz";
80 url = "https://files.pythonhosted.org/packages/98/c3/2c227e66b5e896e15ccdae2e00bbc69aa46e9a8ce8869cc5fa96310bf612/attrs-19.3.0.tar.gz";
81 sha256 = "0wky4h28n7xnr6xv69p9z6kv8bzn50d10c3drmd9ds8gawbcxdzp";
81 sha256 = "0wky4h28n7xnr6xv69p9z6kv8bzn50d10c3drmd9ds8gawbcxdzp";
82 };
82 };
83 meta = {
83 meta = {
84 license = [ pkgs.lib.licenses.mit ];
84 license = [ pkgs.lib.licenses.mit ];
85 };
85 };
86 };
86 };
87 "babel" = super.buildPythonPackage {
87 "babel" = super.buildPythonPackage {
88 name = "babel-1.3";
88 name = "babel-1.3";
89 doCheck = false;
89 doCheck = false;
90 propagatedBuildInputs = [
90 propagatedBuildInputs = [
91 self."pytz"
91 self."pytz"
92 ];
92 ];
93 src = fetchurl {
93 src = fetchurl {
94 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
94 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
95 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
95 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
96 };
96 };
97 meta = {
97 meta = {
98 license = [ pkgs.lib.licenses.bsdOriginal ];
98 license = [ pkgs.lib.licenses.bsdOriginal ];
99 };
99 };
100 };
100 };
101 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
101 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
102 name = "backports.shutil-get-terminal-size-1.0.0";
102 name = "backports.shutil-get-terminal-size-1.0.0";
103 doCheck = false;
103 doCheck = false;
104 src = fetchurl {
104 src = fetchurl {
105 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
105 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
106 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
106 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
107 };
107 };
108 meta = {
108 meta = {
109 license = [ pkgs.lib.licenses.mit ];
109 license = [ pkgs.lib.licenses.mit ];
110 };
110 };
111 };
111 };
112 "beaker" = super.buildPythonPackage {
112 "beaker" = super.buildPythonPackage {
113 name = "beaker-1.9.1";
113 name = "beaker-1.9.1";
114 doCheck = false;
114 doCheck = false;
115 propagatedBuildInputs = [
115 propagatedBuildInputs = [
116 self."funcsigs"
116 self."funcsigs"
117 ];
117 ];
118 src = fetchurl {
118 src = fetchurl {
119 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
119 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
120 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
120 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
121 };
121 };
122 meta = {
122 meta = {
123 license = [ pkgs.lib.licenses.bsdOriginal ];
123 license = [ pkgs.lib.licenses.bsdOriginal ];
124 };
124 };
125 };
125 };
126 "beautifulsoup4" = super.buildPythonPackage {
126 "beautifulsoup4" = super.buildPythonPackage {
127 name = "beautifulsoup4-4.6.3";
127 name = "beautifulsoup4-4.6.3";
128 doCheck = false;
128 doCheck = false;
129 src = fetchurl {
129 src = fetchurl {
130 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
130 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
131 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
131 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
132 };
132 };
133 meta = {
133 meta = {
134 license = [ pkgs.lib.licenses.mit ];
134 license = [ pkgs.lib.licenses.mit ];
135 };
135 };
136 };
136 };
137 "billiard" = super.buildPythonPackage {
137 "billiard" = super.buildPythonPackage {
138 name = "billiard-3.6.1.0";
138 name = "billiard-3.6.1.0";
139 doCheck = false;
139 doCheck = false;
140 src = fetchurl {
140 src = fetchurl {
141 url = "https://files.pythonhosted.org/packages/68/1d/2aea8fbb0b1e1260a8a2e77352de2983d36d7ac01207cf14c2b9c6cc860e/billiard-3.6.1.0.tar.gz";
141 url = "https://files.pythonhosted.org/packages/68/1d/2aea8fbb0b1e1260a8a2e77352de2983d36d7ac01207cf14c2b9c6cc860e/billiard-3.6.1.0.tar.gz";
142 sha256 = "09hzy3aqi7visy4vmf4xiish61n0rq5nd3iwjydydps8yrs9r05q";
142 sha256 = "09hzy3aqi7visy4vmf4xiish61n0rq5nd3iwjydydps8yrs9r05q";
143 };
143 };
144 meta = {
144 meta = {
145 license = [ pkgs.lib.licenses.bsdOriginal ];
145 license = [ pkgs.lib.licenses.bsdOriginal ];
146 };
146 };
147 };
147 };
148 "bleach" = super.buildPythonPackage {
148 "bleach" = super.buildPythonPackage {
149 name = "bleach-3.1.3";
149 name = "bleach-3.1.3";
150 doCheck = false;
150 doCheck = false;
151 propagatedBuildInputs = [
151 propagatedBuildInputs = [
152 self."six"
152 self."six"
153 self."webencodings"
153 self."webencodings"
154 ];
154 ];
155 src = fetchurl {
155 src = fetchurl {
156 url = "https://files.pythonhosted.org/packages/de/09/5267f8577a92487ed43bc694476c4629c6eca2e3c93fcf690a26bfe39e1d/bleach-3.1.3.tar.gz";
156 url = "https://files.pythonhosted.org/packages/de/09/5267f8577a92487ed43bc694476c4629c6eca2e3c93fcf690a26bfe39e1d/bleach-3.1.3.tar.gz";
157 sha256 = "0al437aw4p2xp83az5hhlrp913nsf0cg6kg4qj3fjhv4wakxipzq";
157 sha256 = "0al437aw4p2xp83az5hhlrp913nsf0cg6kg4qj3fjhv4wakxipzq";
158 };
158 };
159 meta = {
159 meta = {
160 license = [ pkgs.lib.licenses.asl20 ];
160 license = [ pkgs.lib.licenses.asl20 ];
161 };
161 };
162 };
162 };
163 "bumpversion" = super.buildPythonPackage {
163 "bumpversion" = super.buildPythonPackage {
164 name = "bumpversion-0.5.3";
164 name = "bumpversion-0.5.3";
165 doCheck = false;
165 doCheck = false;
166 src = fetchurl {
166 src = fetchurl {
167 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
167 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
168 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
168 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
169 };
169 };
170 meta = {
170 meta = {
171 license = [ pkgs.lib.licenses.mit ];
171 license = [ pkgs.lib.licenses.mit ];
172 };
172 };
173 };
173 };
174 "cachetools" = super.buildPythonPackage {
174 "cachetools" = super.buildPythonPackage {
175 name = "cachetools-3.1.1";
175 name = "cachetools-3.1.1";
176 doCheck = false;
176 doCheck = false;
177 src = fetchurl {
177 src = fetchurl {
178 url = "https://files.pythonhosted.org/packages/ae/37/7fd45996b19200e0cb2027a0b6bef4636951c4ea111bfad36c71287247f6/cachetools-3.1.1.tar.gz";
178 url = "https://files.pythonhosted.org/packages/ae/37/7fd45996b19200e0cb2027a0b6bef4636951c4ea111bfad36c71287247f6/cachetools-3.1.1.tar.gz";
179 sha256 = "16m69l6n6y1r1y7cklm92rr7v69ldig2n3lbl3j323w5jz7d78lf";
179 sha256 = "16m69l6n6y1r1y7cklm92rr7v69ldig2n3lbl3j323w5jz7d78lf";
180 };
180 };
181 meta = {
181 meta = {
182 license = [ pkgs.lib.licenses.mit ];
182 license = [ pkgs.lib.licenses.mit ];
183 };
183 };
184 };
184 };
185 "celery" = super.buildPythonPackage {
185 "celery" = super.buildPythonPackage {
186 name = "celery-4.3.0";
186 name = "celery-4.3.0";
187 doCheck = false;
187 doCheck = false;
188 propagatedBuildInputs = [
188 propagatedBuildInputs = [
189 self."pytz"
189 self."pytz"
190 self."billiard"
190 self."billiard"
191 self."kombu"
191 self."kombu"
192 self."vine"
192 self."vine"
193 ];
193 ];
194 src = fetchurl {
194 src = fetchurl {
195 url = "https://files.pythonhosted.org/packages/a2/4b/d020836f751617e907e84753a41c92231cd4b673ff991b8ee9da52361323/celery-4.3.0.tar.gz";
195 url = "https://files.pythonhosted.org/packages/a2/4b/d020836f751617e907e84753a41c92231cd4b673ff991b8ee9da52361323/celery-4.3.0.tar.gz";
196 sha256 = "1y8y0gbgkwimpxqnxq2rm5qz2vy01fvjiybnpm00y5rzd2m34iac";
196 sha256 = "1y8y0gbgkwimpxqnxq2rm5qz2vy01fvjiybnpm00y5rzd2m34iac";
197 };
197 };
198 meta = {
198 meta = {
199 license = [ pkgs.lib.licenses.bsdOriginal ];
199 license = [ pkgs.lib.licenses.bsdOriginal ];
200 };
200 };
201 };
201 };
202 "certifi" = super.buildPythonPackage {
202 "certifi" = super.buildPythonPackage {
203 name = "certifi-2020.4.5.1";
203 name = "certifi-2020.4.5.1";
204 doCheck = false;
204 doCheck = false;
205 src = fetchurl {
205 src = fetchurl {
206 url = "https://files.pythonhosted.org/packages/b8/e2/a3a86a67c3fc8249ed305fc7b7d290ebe5e4d46ad45573884761ef4dea7b/certifi-2020.4.5.1.tar.gz";
206 url = "https://files.pythonhosted.org/packages/b8/e2/a3a86a67c3fc8249ed305fc7b7d290ebe5e4d46ad45573884761ef4dea7b/certifi-2020.4.5.1.tar.gz";
207 sha256 = "06b5gfs7wmmipln8f3z928d2mmx2j4b3x7pnqmj6cvmyfh8v7z2i";
207 sha256 = "06b5gfs7wmmipln8f3z928d2mmx2j4b3x7pnqmj6cvmyfh8v7z2i";
208 };
208 };
209 meta = {
209 meta = {
210 license = [ pkgs.lib.licenses.mpl20 { fullName = "Mozilla Public License 2.0 (MPL 2.0)"; } ];
210 license = [ pkgs.lib.licenses.mpl20 { fullName = "Mozilla Public License 2.0 (MPL 2.0)"; } ];
211 };
211 };
212 };
212 };
213 "cffi" = super.buildPythonPackage {
213 "cffi" = super.buildPythonPackage {
214 name = "cffi-1.12.3";
214 name = "cffi-1.12.3";
215 doCheck = false;
215 doCheck = false;
216 propagatedBuildInputs = [
216 propagatedBuildInputs = [
217 self."pycparser"
217 self."pycparser"
218 ];
218 ];
219 src = fetchurl {
219 src = fetchurl {
220 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
220 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
221 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
221 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
222 };
222 };
223 meta = {
223 meta = {
224 license = [ pkgs.lib.licenses.mit ];
224 license = [ pkgs.lib.licenses.mit ];
225 };
225 };
226 };
226 };
227 "chameleon" = super.buildPythonPackage {
227 "chameleon" = super.buildPythonPackage {
228 name = "chameleon-2.24";
228 name = "chameleon-2.24";
229 doCheck = false;
229 doCheck = false;
230 src = fetchurl {
230 src = fetchurl {
231 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
231 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
232 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
232 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
233 };
233 };
234 meta = {
234 meta = {
235 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
235 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
236 };
236 };
237 };
237 };
238 "channelstream" = super.buildPythonPackage {
238 "channelstream" = super.buildPythonPackage {
239 name = "channelstream-0.5.2";
239 name = "channelstream-0.5.2";
240 doCheck = false;
240 doCheck = false;
241 propagatedBuildInputs = [
241 propagatedBuildInputs = [
242 self."gevent"
242 self."gevent"
243 self."ws4py"
243 self."ws4py"
244 self."pyramid"
244 self."pyramid"
245 self."pyramid-jinja2"
245 self."pyramid-jinja2"
246 self."itsdangerous"
246 self."itsdangerous"
247 self."requests"
247 self."requests"
248 self."six"
248 self."six"
249 ];
249 ];
250 src = fetchurl {
250 src = fetchurl {
251 url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
251 url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
252 sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
252 sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
253 };
253 };
254 meta = {
254 meta = {
255 license = [ pkgs.lib.licenses.bsdOriginal ];
255 license = [ pkgs.lib.licenses.bsdOriginal ];
256 };
256 };
257 };
257 };
258 "chardet" = super.buildPythonPackage {
258 "chardet" = super.buildPythonPackage {
259 name = "chardet-3.0.4";
259 name = "chardet-3.0.4";
260 doCheck = false;
260 doCheck = false;
261 src = fetchurl {
261 src = fetchurl {
262 url = "https://files.pythonhosted.org/packages/fc/bb/a5768c230f9ddb03acc9ef3f0d4a3cf93462473795d18e9535498c8f929d/chardet-3.0.4.tar.gz";
262 url = "https://files.pythonhosted.org/packages/fc/bb/a5768c230f9ddb03acc9ef3f0d4a3cf93462473795d18e9535498c8f929d/chardet-3.0.4.tar.gz";
263 sha256 = "1bpalpia6r5x1kknbk11p1fzph56fmmnp405ds8icksd3knr5aw4";
263 sha256 = "1bpalpia6r5x1kknbk11p1fzph56fmmnp405ds8icksd3knr5aw4";
264 };
264 };
265 meta = {
265 meta = {
266 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
266 license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
267 };
267 };
268 };
268 };
269 "click" = super.buildPythonPackage {
269 "click" = super.buildPythonPackage {
270 name = "click-7.0";
270 name = "click-7.0";
271 doCheck = false;
271 doCheck = false;
272 src = fetchurl {
272 src = fetchurl {
273 url = "https://files.pythonhosted.org/packages/f8/5c/f60e9d8a1e77005f664b76ff8aeaee5bc05d0a91798afd7f53fc998dbc47/Click-7.0.tar.gz";
273 url = "https://files.pythonhosted.org/packages/f8/5c/f60e9d8a1e77005f664b76ff8aeaee5bc05d0a91798afd7f53fc998dbc47/Click-7.0.tar.gz";
274 sha256 = "1mzjixd4vjbjvzb6vylki9w1556a9qmdh35kzmq6cign46av952v";
274 sha256 = "1mzjixd4vjbjvzb6vylki9w1556a9qmdh35kzmq6cign46av952v";
275 };
275 };
276 meta = {
276 meta = {
277 license = [ pkgs.lib.licenses.bsdOriginal ];
277 license = [ pkgs.lib.licenses.bsdOriginal ];
278 };
278 };
279 };
279 };
280 "colander" = super.buildPythonPackage {
280 "colander" = super.buildPythonPackage {
281 name = "colander-1.7.0";
281 name = "colander-1.7.0";
282 doCheck = false;
282 doCheck = false;
283 propagatedBuildInputs = [
283 propagatedBuildInputs = [
284 self."translationstring"
284 self."translationstring"
285 self."iso8601"
285 self."iso8601"
286 self."enum34"
286 self."enum34"
287 ];
287 ];
288 src = fetchurl {
288 src = fetchurl {
289 url = "https://files.pythonhosted.org/packages/db/e4/74ab06f54211917b41865cafc987ce511e35503de48da9bfe9358a1bdc3e/colander-1.7.0.tar.gz";
289 url = "https://files.pythonhosted.org/packages/db/e4/74ab06f54211917b41865cafc987ce511e35503de48da9bfe9358a1bdc3e/colander-1.7.0.tar.gz";
290 sha256 = "1wl1bqab307lbbcjx81i28s3yl6dlm4rf15fxawkjb6j48x1cn6p";
290 sha256 = "1wl1bqab307lbbcjx81i28s3yl6dlm4rf15fxawkjb6j48x1cn6p";
291 };
291 };
292 meta = {
292 meta = {
293 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
293 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
294 };
294 };
295 };
295 };
296 "configobj" = super.buildPythonPackage {
296 "configobj" = super.buildPythonPackage {
297 name = "configobj-5.0.6";
297 name = "configobj-5.0.6";
298 doCheck = false;
298 doCheck = false;
299 propagatedBuildInputs = [
299 propagatedBuildInputs = [
300 self."six"
300 self."six"
301 ];
301 ];
302 src = fetchurl {
302 src = fetchurl {
303 url = "https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626";
303 url = "https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626";
304 sha256 = "0kqfrdfr14mw8yd8qwq14dv2xghpkjmd3yjsy8dfcbvpcc17xnxp";
304 sha256 = "0kqfrdfr14mw8yd8qwq14dv2xghpkjmd3yjsy8dfcbvpcc17xnxp";
305 };
305 };
306 meta = {
306 meta = {
307 license = [ pkgs.lib.licenses.bsdOriginal ];
307 license = [ pkgs.lib.licenses.bsdOriginal ];
308 };
308 };
309 };
309 };
310 "configparser" = super.buildPythonPackage {
310 "configparser" = super.buildPythonPackage {
311 name = "configparser-4.0.2";
311 name = "configparser-4.0.2";
312 doCheck = false;
312 doCheck = false;
313 src = fetchurl {
313 src = fetchurl {
314 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
314 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
315 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
315 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
316 };
316 };
317 meta = {
317 meta = {
318 license = [ pkgs.lib.licenses.mit ];
318 license = [ pkgs.lib.licenses.mit ];
319 };
319 };
320 };
320 };
321 "contextlib2" = super.buildPythonPackage {
321 "contextlib2" = super.buildPythonPackage {
322 name = "contextlib2-0.6.0.post1";
322 name = "contextlib2-0.6.0.post1";
323 doCheck = false;
323 doCheck = false;
324 src = fetchurl {
324 src = fetchurl {
325 url = "https://files.pythonhosted.org/packages/02/54/669207eb72e3d8ae8b38aa1f0703ee87a0e9f88f30d3c0a47bebdb6de242/contextlib2-0.6.0.post1.tar.gz";
325 url = "https://files.pythonhosted.org/packages/02/54/669207eb72e3d8ae8b38aa1f0703ee87a0e9f88f30d3c0a47bebdb6de242/contextlib2-0.6.0.post1.tar.gz";
326 sha256 = "0bhnr2ac7wy5l85ji909gyljyk85n92w8pdvslmrvc8qih4r1x01";
326 sha256 = "0bhnr2ac7wy5l85ji909gyljyk85n92w8pdvslmrvc8qih4r1x01";
327 };
327 };
328 meta = {
328 meta = {
329 license = [ pkgs.lib.licenses.psfl ];
329 license = [ pkgs.lib.licenses.psfl ];
330 };
330 };
331 };
331 };
332 "cov-core" = super.buildPythonPackage {
332 "cov-core" = super.buildPythonPackage {
333 name = "cov-core-1.15.0";
333 name = "cov-core-1.15.0";
334 doCheck = false;
334 doCheck = false;
335 propagatedBuildInputs = [
335 propagatedBuildInputs = [
336 self."coverage"
336 self."coverage"
337 ];
337 ];
338 src = fetchurl {
338 src = fetchurl {
339 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
339 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
340 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
340 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
341 };
341 };
342 meta = {
342 meta = {
343 license = [ pkgs.lib.licenses.mit ];
343 license = [ pkgs.lib.licenses.mit ];
344 };
344 };
345 };
345 };
346 "coverage" = super.buildPythonPackage {
346 "coverage" = super.buildPythonPackage {
347 name = "coverage-4.5.4";
347 name = "coverage-4.5.4";
348 doCheck = false;
348 doCheck = false;
349 src = fetchurl {
349 src = fetchurl {
350 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
350 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
351 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
351 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
352 };
352 };
353 meta = {
353 meta = {
354 license = [ pkgs.lib.licenses.asl20 ];
354 license = [ pkgs.lib.licenses.asl20 ];
355 };
355 };
356 };
356 };
357 "cryptography" = super.buildPythonPackage {
357 "cryptography" = super.buildPythonPackage {
358 name = "cryptography-2.6.1";
358 name = "cryptography-2.6.1";
359 doCheck = false;
359 doCheck = false;
360 propagatedBuildInputs = [
360 propagatedBuildInputs = [
361 self."asn1crypto"
361 self."asn1crypto"
362 self."six"
362 self."six"
363 self."cffi"
363 self."cffi"
364 self."enum34"
364 self."enum34"
365 self."ipaddress"
365 self."ipaddress"
366 ];
366 ];
367 src = fetchurl {
367 src = fetchurl {
368 url = "https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz";
368 url = "https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz";
369 sha256 = "19iwz5avym5zl6jrrrkym1rdaa9h61j20ph4cswsqgv8xg5j3j16";
369 sha256 = "19iwz5avym5zl6jrrrkym1rdaa9h61j20ph4cswsqgv8xg5j3j16";
370 };
370 };
371 meta = {
371 meta = {
372 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
372 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
373 };
373 };
374 };
374 };
375 "cssselect" = super.buildPythonPackage {
375 "cssselect" = super.buildPythonPackage {
376 name = "cssselect-1.0.3";
376 name = "cssselect-1.0.3";
377 doCheck = false;
377 doCheck = false;
378 src = fetchurl {
378 src = fetchurl {
379 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
379 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
380 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
380 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
381 };
381 };
382 meta = {
382 meta = {
383 license = [ pkgs.lib.licenses.bsdOriginal ];
383 license = [ pkgs.lib.licenses.bsdOriginal ];
384 };
384 };
385 };
385 };
386 "cssutils" = super.buildPythonPackage {
386 "cssutils" = super.buildPythonPackage {
387 name = "cssutils-1.0.2";
387 name = "cssutils-1.0.2";
388 doCheck = false;
388 doCheck = false;
389 src = fetchurl {
389 src = fetchurl {
390 url = "https://files.pythonhosted.org/packages/5c/0b/c5f29d29c037e97043770b5e7c740b6252993e4b57f029b3cd03c78ddfec/cssutils-1.0.2.tar.gz";
390 url = "https://files.pythonhosted.org/packages/5c/0b/c5f29d29c037e97043770b5e7c740b6252993e4b57f029b3cd03c78ddfec/cssutils-1.0.2.tar.gz";
391 sha256 = "1bxchrbqzapwijap0yhlxdil1w9bmwvgx77aizlkhc2mcxjg1z52";
391 sha256 = "1bxchrbqzapwijap0yhlxdil1w9bmwvgx77aizlkhc2mcxjg1z52";
392 };
392 };
393 meta = {
393 meta = {
394 license = [ { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL 2.1 or later, see also http://cthedot.de/cssutils/"; } ];
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 "decorator" = super.buildPythonPackage {
397 "decorator" = super.buildPythonPackage {
398 name = "decorator-4.1.2";
398 name = "decorator-4.1.2";
399 doCheck = false;
399 doCheck = false;
400 src = fetchurl {
400 src = fetchurl {
401 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
401 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
402 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
402 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
403 };
403 };
404 meta = {
404 meta = {
405 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
405 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
406 };
406 };
407 };
407 };
408 "deform" = super.buildPythonPackage {
408 "deform" = super.buildPythonPackage {
409 name = "deform-2.0.8";
409 name = "deform-2.0.8";
410 doCheck = false;
410 doCheck = false;
411 propagatedBuildInputs = [
411 propagatedBuildInputs = [
412 self."chameleon"
412 self."chameleon"
413 self."colander"
413 self."colander"
414 self."iso8601"
414 self."iso8601"
415 self."peppercorn"
415 self."peppercorn"
416 self."translationstring"
416 self."translationstring"
417 self."zope.deprecation"
417 self."zope.deprecation"
418 ];
418 ];
419 src = fetchurl {
419 src = fetchurl {
420 url = "https://files.pythonhosted.org/packages/21/d0/45fdf891a82722c02fc2da319cf2d1ae6b5abf9e470ad3762135a895a868/deform-2.0.8.tar.gz";
420 url = "https://files.pythonhosted.org/packages/21/d0/45fdf891a82722c02fc2da319cf2d1ae6b5abf9e470ad3762135a895a868/deform-2.0.8.tar.gz";
421 sha256 = "0wbjv98sib96649aqaygzxnrkclyy50qij2rha6fn1i4c86bfdl9";
421 sha256 = "0wbjv98sib96649aqaygzxnrkclyy50qij2rha6fn1i4c86bfdl9";
422 };
422 };
423 meta = {
423 meta = {
424 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
424 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
425 };
425 };
426 };
426 };
427 "defusedxml" = super.buildPythonPackage {
427 "defusedxml" = super.buildPythonPackage {
428 name = "defusedxml-0.6.0";
428 name = "defusedxml-0.6.0";
429 doCheck = false;
429 doCheck = false;
430 src = fetchurl {
430 src = fetchurl {
431 url = "https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz";
431 url = "https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz";
432 sha256 = "1xbp8fivl3wlbyg2jrvs4lalaqv1xp9a9f29p75wdx2s2d6h717n";
432 sha256 = "1xbp8fivl3wlbyg2jrvs4lalaqv1xp9a9f29p75wdx2s2d6h717n";
433 };
433 };
434 meta = {
434 meta = {
435 license = [ pkgs.lib.licenses.psfl ];
435 license = [ pkgs.lib.licenses.psfl ];
436 };
436 };
437 };
437 };
438 "dm.xmlsec.binding" = super.buildPythonPackage {
438 "dm.xmlsec.binding" = super.buildPythonPackage {
439 name = "dm.xmlsec.binding-1.3.7";
439 name = "dm.xmlsec.binding-1.3.7";
440 doCheck = false;
440 doCheck = false;
441 propagatedBuildInputs = [
441 propagatedBuildInputs = [
442 self."setuptools"
442 self."setuptools"
443 self."lxml"
443 self."lxml"
444 ];
444 ];
445 src = fetchurl {
445 src = fetchurl {
446 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
446 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
447 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
447 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
448 };
448 };
449 meta = {
449 meta = {
450 license = [ pkgs.lib.licenses.bsdOriginal ];
450 license = [ pkgs.lib.licenses.bsdOriginal ];
451 };
451 };
452 };
452 };
453 "docutils" = super.buildPythonPackage {
453 "docutils" = super.buildPythonPackage {
454 name = "docutils-0.16";
454 name = "docutils-0.16";
455 doCheck = false;
455 doCheck = false;
456 src = fetchurl {
456 src = fetchurl {
457 url = "https://files.pythonhosted.org/packages/2f/e0/3d435b34abd2d62e8206171892f174b180cd37b09d57b924ca5c2ef2219d/docutils-0.16.tar.gz";
457 url = "https://files.pythonhosted.org/packages/2f/e0/3d435b34abd2d62e8206171892f174b180cd37b09d57b924ca5c2ef2219d/docutils-0.16.tar.gz";
458 sha256 = "1z3qliszqca9m719q3qhdkh0ghh90g500avzdgi7pl77x5h3mpn2";
458 sha256 = "1z3qliszqca9m719q3qhdkh0ghh90g500avzdgi7pl77x5h3mpn2";
459 };
459 };
460 meta = {
460 meta = {
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 ];
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 "dogpile.cache" = super.buildPythonPackage {
464 "dogpile.cache" = super.buildPythonPackage {
465 name = "dogpile.cache-0.9.0";
465 name = "dogpile.cache-0.9.0";
466 doCheck = false;
466 doCheck = false;
467 propagatedBuildInputs = [
467 propagatedBuildInputs = [
468 self."decorator"
468 self."decorator"
469 ];
469 ];
470 src = fetchurl {
470 src = fetchurl {
471 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
471 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
472 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
472 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
473 };
473 };
474 meta = {
474 meta = {
475 license = [ pkgs.lib.licenses.bsdOriginal ];
475 license = [ pkgs.lib.licenses.bsdOriginal ];
476 };
476 };
477 };
477 };
478 "dogpile.core" = super.buildPythonPackage {
478 "dogpile.core" = super.buildPythonPackage {
479 name = "dogpile.core-0.4.1";
479 name = "dogpile.core-0.4.1";
480 doCheck = false;
480 doCheck = false;
481 src = fetchurl {
481 src = fetchurl {
482 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
482 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
483 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
483 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
484 };
484 };
485 meta = {
485 meta = {
486 license = [ pkgs.lib.licenses.bsdOriginal ];
486 license = [ pkgs.lib.licenses.bsdOriginal ];
487 };
487 };
488 };
488 };
489 "ecdsa" = super.buildPythonPackage {
489 "ecdsa" = super.buildPythonPackage {
490 name = "ecdsa-0.13.2";
490 name = "ecdsa-0.13.2";
491 doCheck = false;
491 doCheck = false;
492 src = fetchurl {
492 src = fetchurl {
493 url = "https://files.pythonhosted.org/packages/51/76/139bf6e9b7b6684d5891212cdbd9e0739f2bfc03f380a1a6ffa700f392ac/ecdsa-0.13.2.tar.gz";
493 url = "https://files.pythonhosted.org/packages/51/76/139bf6e9b7b6684d5891212cdbd9e0739f2bfc03f380a1a6ffa700f392ac/ecdsa-0.13.2.tar.gz";
494 sha256 = "116qaq7bh4lcynzi613960jhsnn19v0kmsqwahiwjfj14gx4y0sw";
494 sha256 = "116qaq7bh4lcynzi613960jhsnn19v0kmsqwahiwjfj14gx4y0sw";
495 };
495 };
496 meta = {
496 meta = {
497 license = [ pkgs.lib.licenses.mit ];
497 license = [ pkgs.lib.licenses.mit ];
498 };
498 };
499 };
499 };
500 "elasticsearch" = super.buildPythonPackage {
500 "elasticsearch" = super.buildPythonPackage {
501 name = "elasticsearch-6.3.1";
501 name = "elasticsearch-6.3.1";
502 doCheck = false;
502 doCheck = false;
503 propagatedBuildInputs = [
503 propagatedBuildInputs = [
504 self."urllib3"
504 self."urllib3"
505 ];
505 ];
506 src = fetchurl {
506 src = fetchurl {
507 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
507 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
508 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
508 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
509 };
509 };
510 meta = {
510 meta = {
511 license = [ pkgs.lib.licenses.asl20 ];
511 license = [ pkgs.lib.licenses.asl20 ];
512 };
512 };
513 };
513 };
514 "elasticsearch-dsl" = super.buildPythonPackage {
514 "elasticsearch-dsl" = super.buildPythonPackage {
515 name = "elasticsearch-dsl-6.3.1";
515 name = "elasticsearch-dsl-6.3.1";
516 doCheck = false;
516 doCheck = false;
517 propagatedBuildInputs = [
517 propagatedBuildInputs = [
518 self."six"
518 self."six"
519 self."python-dateutil"
519 self."python-dateutil"
520 self."elasticsearch"
520 self."elasticsearch"
521 self."ipaddress"
521 self."ipaddress"
522 ];
522 ];
523 src = fetchurl {
523 src = fetchurl {
524 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
524 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
525 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
525 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
526 };
526 };
527 meta = {
527 meta = {
528 license = [ pkgs.lib.licenses.asl20 ];
528 license = [ pkgs.lib.licenses.asl20 ];
529 };
529 };
530 };
530 };
531 "elasticsearch1" = super.buildPythonPackage {
531 "elasticsearch1" = super.buildPythonPackage {
532 name = "elasticsearch1-1.10.0";
532 name = "elasticsearch1-1.10.0";
533 doCheck = false;
533 doCheck = false;
534 propagatedBuildInputs = [
534 propagatedBuildInputs = [
535 self."urllib3"
535 self."urllib3"
536 ];
536 ];
537 src = fetchurl {
537 src = fetchurl {
538 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
538 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
539 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
539 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
540 };
540 };
541 meta = {
541 meta = {
542 license = [ pkgs.lib.licenses.asl20 ];
542 license = [ pkgs.lib.licenses.asl20 ];
543 };
543 };
544 };
544 };
545 "elasticsearch1-dsl" = super.buildPythonPackage {
545 "elasticsearch1-dsl" = super.buildPythonPackage {
546 name = "elasticsearch1-dsl-0.0.12";
546 name = "elasticsearch1-dsl-0.0.12";
547 doCheck = false;
547 doCheck = false;
548 propagatedBuildInputs = [
548 propagatedBuildInputs = [
549 self."six"
549 self."six"
550 self."python-dateutil"
550 self."python-dateutil"
551 self."elasticsearch1"
551 self."elasticsearch1"
552 ];
552 ];
553 src = fetchurl {
553 src = fetchurl {
554 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
554 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
555 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
555 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
556 };
556 };
557 meta = {
557 meta = {
558 license = [ pkgs.lib.licenses.asl20 ];
558 license = [ pkgs.lib.licenses.asl20 ];
559 };
559 };
560 };
560 };
561 "elasticsearch2" = super.buildPythonPackage {
561 "elasticsearch2" = super.buildPythonPackage {
562 name = "elasticsearch2-2.5.1";
562 name = "elasticsearch2-2.5.1";
563 doCheck = false;
563 doCheck = false;
564 propagatedBuildInputs = [
564 propagatedBuildInputs = [
565 self."urllib3"
565 self."urllib3"
566 ];
566 ];
567 src = fetchurl {
567 src = fetchurl {
568 url = "https://files.pythonhosted.org/packages/f6/09/f9b24aa6b1120bea371cd57ef6f57c7694cf16660469456a8be6c2bdbe22/elasticsearch2-2.5.1.tar.gz";
568 url = "https://files.pythonhosted.org/packages/f6/09/f9b24aa6b1120bea371cd57ef6f57c7694cf16660469456a8be6c2bdbe22/elasticsearch2-2.5.1.tar.gz";
569 sha256 = "19k2znpjfyp0hrq73cz7pjyj289040xpsxsm0xhh4jfh6y551g7k";
569 sha256 = "19k2znpjfyp0hrq73cz7pjyj289040xpsxsm0xhh4jfh6y551g7k";
570 };
570 };
571 meta = {
571 meta = {
572 license = [ pkgs.lib.licenses.asl20 ];
572 license = [ pkgs.lib.licenses.asl20 ];
573 };
573 };
574 };
574 };
575 "entrypoints" = super.buildPythonPackage {
575 "entrypoints" = super.buildPythonPackage {
576 name = "entrypoints-0.2.2";
576 name = "entrypoints-0.2.2";
577 doCheck = false;
577 doCheck = false;
578 propagatedBuildInputs = [
578 propagatedBuildInputs = [
579 self."configparser"
579 self."configparser"
580 ];
580 ];
581 src = fetchurl {
581 src = fetchurl {
582 url = "https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d";
582 url = "https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d";
583 sha256 = "0qih72n2myclanplqipqxpgpj9d2yhff1pz5d02zq1cfqyd173w5";
583 sha256 = "0qih72n2myclanplqipqxpgpj9d2yhff1pz5d02zq1cfqyd173w5";
584 };
584 };
585 meta = {
585 meta = {
586 license = [ pkgs.lib.licenses.mit ];
586 license = [ pkgs.lib.licenses.mit ];
587 };
587 };
588 };
588 };
589 "enum34" = super.buildPythonPackage {
589 "enum34" = super.buildPythonPackage {
590 name = "enum34-1.1.10";
590 name = "enum34-1.1.10";
591 doCheck = false;
591 doCheck = false;
592 src = fetchurl {
592 src = fetchurl {
593 url = "https://files.pythonhosted.org/packages/11/c4/2da1f4952ba476677a42f25cd32ab8aaf0e1c0d0e00b89822b835c7e654c/enum34-1.1.10.tar.gz";
593 url = "https://files.pythonhosted.org/packages/11/c4/2da1f4952ba476677a42f25cd32ab8aaf0e1c0d0e00b89822b835c7e654c/enum34-1.1.10.tar.gz";
594 sha256 = "0j7ji699fwswm4vg6w1v07fkbf8dkzdm6gfh88jvs5nqgr3sgrnc";
594 sha256 = "0j7ji699fwswm4vg6w1v07fkbf8dkzdm6gfh88jvs5nqgr3sgrnc";
595 };
595 };
596 meta = {
596 meta = {
597 license = [ pkgs.lib.licenses.bsdOriginal ];
597 license = [ pkgs.lib.licenses.bsdOriginal ];
598 };
598 };
599 };
599 };
600 "formencode" = super.buildPythonPackage {
600 "formencode" = super.buildPythonPackage {
601 name = "formencode-1.2.4";
601 name = "formencode-1.2.4";
602 doCheck = false;
602 doCheck = false;
603 src = fetchurl {
603 src = fetchurl {
604 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
604 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
605 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
605 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
606 };
606 };
607 meta = {
607 meta = {
608 license = [ pkgs.lib.licenses.psfl ];
608 license = [ pkgs.lib.licenses.psfl ];
609 };
609 };
610 };
610 };
611 "funcsigs" = super.buildPythonPackage {
611 "funcsigs" = super.buildPythonPackage {
612 name = "funcsigs-1.0.2";
612 name = "funcsigs-1.0.2";
613 doCheck = false;
613 doCheck = false;
614 src = fetchurl {
614 src = fetchurl {
615 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
615 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
616 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
616 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
617 };
617 };
618 meta = {
618 meta = {
619 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
619 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
620 };
620 };
621 };
621 };
622 "functools32" = super.buildPythonPackage {
622 "functools32" = super.buildPythonPackage {
623 name = "functools32-3.2.3.post2";
623 name = "functools32-3.2.3.post2";
624 doCheck = false;
624 doCheck = false;
625 src = fetchurl {
625 src = fetchurl {
626 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
626 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
627 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
627 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
628 };
628 };
629 meta = {
629 meta = {
630 license = [ pkgs.lib.licenses.psfl ];
630 license = [ pkgs.lib.licenses.psfl ];
631 };
631 };
632 };
632 };
633 "future" = super.buildPythonPackage {
633 "future" = super.buildPythonPackage {
634 name = "future-0.14.3";
634 name = "future-0.14.3";
635 doCheck = false;
635 doCheck = false;
636 src = fetchurl {
636 src = fetchurl {
637 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
637 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
638 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
638 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
639 };
639 };
640 meta = {
640 meta = {
641 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
641 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
642 };
642 };
643 };
643 };
644 "futures" = super.buildPythonPackage {
644 "futures" = super.buildPythonPackage {
645 name = "futures-3.0.2";
645 name = "futures-3.0.2";
646 doCheck = false;
646 doCheck = false;
647 src = fetchurl {
647 src = fetchurl {
648 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
648 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
649 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
649 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
650 };
650 };
651 meta = {
651 meta = {
652 license = [ pkgs.lib.licenses.bsdOriginal ];
652 license = [ pkgs.lib.licenses.bsdOriginal ];
653 };
653 };
654 };
654 };
655 "gevent" = super.buildPythonPackage {
655 "gevent" = super.buildPythonPackage {
656 name = "gevent-1.5.0";
656 name = "gevent-1.5.0";
657 doCheck = false;
657 doCheck = false;
658 propagatedBuildInputs = [
658 propagatedBuildInputs = [
659 self."greenlet"
659 self."greenlet"
660 ];
660 ];
661 src = fetchurl {
661 src = fetchurl {
662 url = "https://files.pythonhosted.org/packages/5a/79/2c63d385d017b5dd7d70983a463dfd25befae70c824fedb857df6e72eff2/gevent-1.5.0.tar.gz";
662 url = "https://files.pythonhosted.org/packages/5a/79/2c63d385d017b5dd7d70983a463dfd25befae70c824fedb857df6e72eff2/gevent-1.5.0.tar.gz";
663 sha256 = "0aac3d4vhv5n4rsb6cqzq0d1xx9immqz4fmpddw35yxkwdc450dj";
663 sha256 = "0aac3d4vhv5n4rsb6cqzq0d1xx9immqz4fmpddw35yxkwdc450dj";
664 };
664 };
665 meta = {
665 meta = {
666 license = [ pkgs.lib.licenses.mit ];
666 license = [ pkgs.lib.licenses.mit ];
667 };
667 };
668 };
668 };
669 "gnureadline" = super.buildPythonPackage {
669 "gnureadline" = super.buildPythonPackage {
670 name = "gnureadline-6.3.8";
670 name = "gnureadline-6.3.8";
671 doCheck = false;
671 doCheck = false;
672 src = fetchurl {
672 src = fetchurl {
673 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
673 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
674 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
674 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
675 };
675 };
676 meta = {
676 meta = {
677 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
677 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
678 };
678 };
679 };
679 };
680 "gprof2dot" = super.buildPythonPackage {
680 "gprof2dot" = super.buildPythonPackage {
681 name = "gprof2dot-2017.9.19";
681 name = "gprof2dot-2017.9.19";
682 doCheck = false;
682 doCheck = false;
683 src = fetchurl {
683 src = fetchurl {
684 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
684 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
685 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
685 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
686 };
686 };
687 meta = {
687 meta = {
688 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
688 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
689 };
689 };
690 };
690 };
691 "greenlet" = super.buildPythonPackage {
691 "greenlet" = super.buildPythonPackage {
692 name = "greenlet-0.4.15";
692 name = "greenlet-0.4.15";
693 doCheck = false;
693 doCheck = false;
694 src = fetchurl {
694 src = fetchurl {
695 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
695 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
696 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
696 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
697 };
697 };
698 meta = {
698 meta = {
699 license = [ pkgs.lib.licenses.mit ];
699 license = [ pkgs.lib.licenses.mit ];
700 };
700 };
701 };
701 };
702 "gunicorn" = super.buildPythonPackage {
702 "gunicorn" = super.buildPythonPackage {
703 name = "gunicorn-19.9.0";
703 name = "gunicorn-19.9.0";
704 doCheck = false;
704 doCheck = false;
705 src = fetchurl {
705 src = fetchurl {
706 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
706 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
707 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
707 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
708 };
708 };
709 meta = {
709 meta = {
710 license = [ pkgs.lib.licenses.mit ];
710 license = [ pkgs.lib.licenses.mit ];
711 };
711 };
712 };
712 };
713 "hupper" = super.buildPythonPackage {
713 "hupper" = super.buildPythonPackage {
714 name = "hupper-1.10.2";
714 name = "hupper-1.10.2";
715 doCheck = false;
715 doCheck = false;
716 src = fetchurl {
716 src = fetchurl {
717 url = "https://files.pythonhosted.org/packages/41/24/ea90fef04706e54bd1635c05c50dc9cf87cda543c59303a03e7aa7dda0ce/hupper-1.10.2.tar.gz";
717 url = "https://files.pythonhosted.org/packages/41/24/ea90fef04706e54bd1635c05c50dc9cf87cda543c59303a03e7aa7dda0ce/hupper-1.10.2.tar.gz";
718 sha256 = "0am0p6g5cz6xmcaf04xq8q6dzdd9qz0phj6gcmpsckf2mcyza61q";
718 sha256 = "0am0p6g5cz6xmcaf04xq8q6dzdd9qz0phj6gcmpsckf2mcyza61q";
719 };
719 };
720 meta = {
720 meta = {
721 license = [ pkgs.lib.licenses.mit ];
721 license = [ pkgs.lib.licenses.mit ];
722 };
722 };
723 };
723 };
724 "idna" = super.buildPythonPackage {
724 "idna" = super.buildPythonPackage {
725 name = "idna-2.8";
725 name = "idna-2.8";
726 doCheck = false;
726 doCheck = false;
727 src = fetchurl {
727 src = fetchurl {
728 url = "https://files.pythonhosted.org/packages/ad/13/eb56951b6f7950cadb579ca166e448ba77f9d24efc03edd7e55fa57d04b7/idna-2.8.tar.gz";
728 url = "https://files.pythonhosted.org/packages/ad/13/eb56951b6f7950cadb579ca166e448ba77f9d24efc03edd7e55fa57d04b7/idna-2.8.tar.gz";
729 sha256 = "01rlkigdxg17sf9yar1jl8n18ls59367wqh59hnawlyg53vb6my3";
729 sha256 = "01rlkigdxg17sf9yar1jl8n18ls59367wqh59hnawlyg53vb6my3";
730 };
730 };
731 meta = {
731 meta = {
732 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD-like"; } ];
732 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD-like"; } ];
733 };
733 };
734 };
734 };
735 "importlib-metadata" = super.buildPythonPackage {
735 "importlib-metadata" = super.buildPythonPackage {
736 name = "importlib-metadata-1.6.0";
736 name = "importlib-metadata-1.6.0";
737 doCheck = false;
737 doCheck = false;
738 propagatedBuildInputs = [
738 propagatedBuildInputs = [
739 self."zipp"
739 self."zipp"
740 self."pathlib2"
740 self."pathlib2"
741 self."contextlib2"
741 self."contextlib2"
742 self."configparser"
742 self."configparser"
743 ];
743 ];
744 src = fetchurl {
744 src = fetchurl {
745 url = "https://files.pythonhosted.org/packages/b4/1b/baab42e3cd64c9d5caac25a9d6c054f8324cdc38975a44d600569f1f7158/importlib_metadata-1.6.0.tar.gz";
745 url = "https://files.pythonhosted.org/packages/b4/1b/baab42e3cd64c9d5caac25a9d6c054f8324cdc38975a44d600569f1f7158/importlib_metadata-1.6.0.tar.gz";
746 sha256 = "07icyggasn38yv2swdrd8z6i0plazmc9adavsdkbqqj91j53ll9l";
746 sha256 = "07icyggasn38yv2swdrd8z6i0plazmc9adavsdkbqqj91j53ll9l";
747 };
747 };
748 meta = {
748 meta = {
749 license = [ pkgs.lib.licenses.asl20 ];
749 license = [ pkgs.lib.licenses.asl20 ];
750 };
750 };
751 };
751 };
752 "infrae.cache" = super.buildPythonPackage {
752 "infrae.cache" = super.buildPythonPackage {
753 name = "infrae.cache-1.0.1";
753 name = "infrae.cache-1.0.1";
754 doCheck = false;
754 doCheck = false;
755 propagatedBuildInputs = [
755 propagatedBuildInputs = [
756 self."beaker"
756 self."beaker"
757 self."repoze.lru"
757 self."repoze.lru"
758 ];
758 ];
759 src = fetchurl {
759 src = fetchurl {
760 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
760 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
761 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
761 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
762 };
762 };
763 meta = {
763 meta = {
764 license = [ pkgs.lib.licenses.zpl21 ];
764 license = [ pkgs.lib.licenses.zpl21 ];
765 };
765 };
766 };
766 };
767 "invoke" = super.buildPythonPackage {
767 "invoke" = super.buildPythonPackage {
768 name = "invoke-0.13.0";
768 name = "invoke-0.13.0";
769 doCheck = false;
769 doCheck = false;
770 src = fetchurl {
770 src = fetchurl {
771 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
771 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
772 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
772 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
773 };
773 };
774 meta = {
774 meta = {
775 license = [ pkgs.lib.licenses.bsdOriginal ];
775 license = [ pkgs.lib.licenses.bsdOriginal ];
776 };
776 };
777 };
777 };
778 "ipaddress" = super.buildPythonPackage {
778 "ipaddress" = super.buildPythonPackage {
779 name = "ipaddress-1.0.23";
779 name = "ipaddress-1.0.23";
780 doCheck = false;
780 doCheck = false;
781 src = fetchurl {
781 src = fetchurl {
782 url = "https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz";
782 url = "https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz";
783 sha256 = "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p";
783 sha256 = "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p";
784 };
784 };
785 meta = {
785 meta = {
786 license = [ pkgs.lib.licenses.psfl ];
786 license = [ pkgs.lib.licenses.psfl ];
787 };
787 };
788 };
788 };
789 "ipdb" = super.buildPythonPackage {
789 "ipdb" = super.buildPythonPackage {
790 name = "ipdb-0.13.2";
790 name = "ipdb-0.13.2";
791 doCheck = false;
791 doCheck = false;
792 propagatedBuildInputs = [
792 propagatedBuildInputs = [
793 self."setuptools"
793 self."setuptools"
794 self."ipython"
794 self."ipython"
795 ];
795 ];
796 src = fetchurl {
796 src = fetchurl {
797 url = "https://files.pythonhosted.org/packages/2c/bb/a3e1a441719ebd75c6dac8170d3ddba884b7ee8a5c0f9aefa7297386627a/ipdb-0.13.2.tar.gz";
797 url = "https://files.pythonhosted.org/packages/2c/bb/a3e1a441719ebd75c6dac8170d3ddba884b7ee8a5c0f9aefa7297386627a/ipdb-0.13.2.tar.gz";
798 sha256 = "0jcd849rx30y3wcgzsqbn06v0yjlzvb9x3076q0yxpycdwm1ryvp";
798 sha256 = "0jcd849rx30y3wcgzsqbn06v0yjlzvb9x3076q0yxpycdwm1ryvp";
799 };
799 };
800 meta = {
800 meta = {
801 license = [ pkgs.lib.licenses.bsdOriginal ];
801 license = [ pkgs.lib.licenses.bsdOriginal ];
802 };
802 };
803 };
803 };
804 "ipython" = super.buildPythonPackage {
804 "ipython" = super.buildPythonPackage {
805 name = "ipython-5.1.0";
805 name = "ipython-5.1.0";
806 doCheck = false;
806 doCheck = false;
807 propagatedBuildInputs = [
807 propagatedBuildInputs = [
808 self."setuptools"
808 self."setuptools"
809 self."decorator"
809 self."decorator"
810 self."pickleshare"
810 self."pickleshare"
811 self."simplegeneric"
811 self."simplegeneric"
812 self."traitlets"
812 self."traitlets"
813 self."prompt-toolkit"
813 self."prompt-toolkit"
814 self."pygments"
814 self."pygments"
815 self."pexpect"
815 self."pexpect"
816 self."backports.shutil-get-terminal-size"
816 self."backports.shutil-get-terminal-size"
817 self."pathlib2"
817 self."pathlib2"
818 self."pexpect"
818 self."pexpect"
819 ];
819 ];
820 src = fetchurl {
820 src = fetchurl {
821 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
821 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
822 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
822 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
823 };
823 };
824 meta = {
824 meta = {
825 license = [ pkgs.lib.licenses.bsdOriginal ];
825 license = [ pkgs.lib.licenses.bsdOriginal ];
826 };
826 };
827 };
827 };
828 "ipython-genutils" = super.buildPythonPackage {
828 "ipython-genutils" = super.buildPythonPackage {
829 name = "ipython-genutils-0.2.0";
829 name = "ipython-genutils-0.2.0";
830 doCheck = false;
830 doCheck = false;
831 src = fetchurl {
831 src = fetchurl {
832 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
832 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
833 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
833 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
834 };
834 };
835 meta = {
835 meta = {
836 license = [ pkgs.lib.licenses.bsdOriginal ];
836 license = [ pkgs.lib.licenses.bsdOriginal ];
837 };
837 };
838 };
838 };
839 "iso8601" = super.buildPythonPackage {
839 "iso8601" = super.buildPythonPackage {
840 name = "iso8601-0.1.12";
840 name = "iso8601-0.1.12";
841 doCheck = false;
841 doCheck = false;
842 src = fetchurl {
842 src = fetchurl {
843 url = "https://files.pythonhosted.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
843 url = "https://files.pythonhosted.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
844 sha256 = "10nyvvnrhw2w3p09v1ica4lgj6f4g9j3kkfx17qmraiq3w7b5i29";
844 sha256 = "10nyvvnrhw2w3p09v1ica4lgj6f4g9j3kkfx17qmraiq3w7b5i29";
845 };
845 };
846 meta = {
846 meta = {
847 license = [ pkgs.lib.licenses.mit ];
847 license = [ pkgs.lib.licenses.mit ];
848 };
848 };
849 };
849 };
850 "isodate" = super.buildPythonPackage {
850 "isodate" = super.buildPythonPackage {
851 name = "isodate-0.6.0";
851 name = "isodate-0.6.0";
852 doCheck = false;
852 doCheck = false;
853 propagatedBuildInputs = [
853 propagatedBuildInputs = [
854 self."six"
854 self."six"
855 ];
855 ];
856 src = fetchurl {
856 src = fetchurl {
857 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
857 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
858 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
858 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
859 };
859 };
860 meta = {
860 meta = {
861 license = [ pkgs.lib.licenses.bsdOriginal ];
861 license = [ pkgs.lib.licenses.bsdOriginal ];
862 };
862 };
863 };
863 };
864 "itsdangerous" = super.buildPythonPackage {
864 "itsdangerous" = super.buildPythonPackage {
865 name = "itsdangerous-0.24";
865 name = "itsdangerous-0.24";
866 doCheck = false;
866 doCheck = false;
867 src = fetchurl {
867 src = fetchurl {
868 url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
868 url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
869 sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
869 sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
870 };
870 };
871 meta = {
871 meta = {
872 license = [ pkgs.lib.licenses.bsdOriginal ];
872 license = [ pkgs.lib.licenses.bsdOriginal ];
873 };
873 };
874 };
874 };
875 "jinja2" = super.buildPythonPackage {
875 "jinja2" = super.buildPythonPackage {
876 name = "jinja2-2.9.6";
876 name = "jinja2-2.9.6";
877 doCheck = false;
877 doCheck = false;
878 propagatedBuildInputs = [
878 propagatedBuildInputs = [
879 self."markupsafe"
879 self."markupsafe"
880 ];
880 ];
881 src = fetchurl {
881 src = fetchurl {
882 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
882 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
883 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
883 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
884 };
884 };
885 meta = {
885 meta = {
886 license = [ pkgs.lib.licenses.bsdOriginal ];
886 license = [ pkgs.lib.licenses.bsdOriginal ];
887 };
887 };
888 };
888 };
889 "jsonschema" = super.buildPythonPackage {
889 "jsonschema" = super.buildPythonPackage {
890 name = "jsonschema-2.6.0";
890 name = "jsonschema-2.6.0";
891 doCheck = false;
891 doCheck = false;
892 propagatedBuildInputs = [
892 propagatedBuildInputs = [
893 self."functools32"
893 self."functools32"
894 ];
894 ];
895 src = fetchurl {
895 src = fetchurl {
896 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
896 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
897 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
897 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
898 };
898 };
899 meta = {
899 meta = {
900 license = [ pkgs.lib.licenses.mit ];
900 license = [ pkgs.lib.licenses.mit ];
901 };
901 };
902 };
902 };
903 "jupyter-client" = super.buildPythonPackage {
903 "jupyter-client" = super.buildPythonPackage {
904 name = "jupyter-client-5.0.0";
904 name = "jupyter-client-5.0.0";
905 doCheck = false;
905 doCheck = false;
906 propagatedBuildInputs = [
906 propagatedBuildInputs = [
907 self."traitlets"
907 self."traitlets"
908 self."jupyter-core"
908 self."jupyter-core"
909 self."pyzmq"
909 self."pyzmq"
910 self."python-dateutil"
910 self."python-dateutil"
911 ];
911 ];
912 src = fetchurl {
912 src = fetchurl {
913 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
913 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
914 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
914 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
915 };
915 };
916 meta = {
916 meta = {
917 license = [ pkgs.lib.licenses.bsdOriginal ];
917 license = [ pkgs.lib.licenses.bsdOriginal ];
918 };
918 };
919 };
919 };
920 "jupyter-core" = super.buildPythonPackage {
920 "jupyter-core" = super.buildPythonPackage {
921 name = "jupyter-core-4.5.0";
921 name = "jupyter-core-4.5.0";
922 doCheck = false;
922 doCheck = false;
923 propagatedBuildInputs = [
923 propagatedBuildInputs = [
924 self."traitlets"
924 self."traitlets"
925 ];
925 ];
926 src = fetchurl {
926 src = fetchurl {
927 url = "https://files.pythonhosted.org/packages/4a/de/ff4ca734656d17ebe0450807b59d728f45277e2e7f4b82bc9aae6cb82961/jupyter_core-4.5.0.tar.gz";
927 url = "https://files.pythonhosted.org/packages/4a/de/ff4ca734656d17ebe0450807b59d728f45277e2e7f4b82bc9aae6cb82961/jupyter_core-4.5.0.tar.gz";
928 sha256 = "1xr4pbghwk5hayn5wwnhb7z95380r45p79gf5if5pi1akwg7qvic";
928 sha256 = "1xr4pbghwk5hayn5wwnhb7z95380r45p79gf5if5pi1akwg7qvic";
929 };
929 };
930 meta = {
930 meta = {
931 license = [ pkgs.lib.licenses.bsdOriginal ];
931 license = [ pkgs.lib.licenses.bsdOriginal ];
932 };
932 };
933 };
933 };
934 "kombu" = super.buildPythonPackage {
934 "kombu" = super.buildPythonPackage {
935 name = "kombu-4.6.6";
935 name = "kombu-4.6.6";
936 doCheck = false;
936 doCheck = false;
937 propagatedBuildInputs = [
937 propagatedBuildInputs = [
938 self."amqp"
938 self."amqp"
939 self."importlib-metadata"
939 self."importlib-metadata"
940 ];
940 ];
941 src = fetchurl {
941 src = fetchurl {
942 url = "https://files.pythonhosted.org/packages/20/e6/bc2d9affba6138a1dc143f77fef253e9e08e238fa7c0688d917c09005e96/kombu-4.6.6.tar.gz";
942 url = "https://files.pythonhosted.org/packages/20/e6/bc2d9affba6138a1dc143f77fef253e9e08e238fa7c0688d917c09005e96/kombu-4.6.6.tar.gz";
943 sha256 = "11mxpcy8mg1l35bgbhba70v29bydr2hrhdbdlb4lg98m3m5vaq0p";
943 sha256 = "11mxpcy8mg1l35bgbhba70v29bydr2hrhdbdlb4lg98m3m5vaq0p";
944 };
944 };
945 meta = {
945 meta = {
946 license = [ pkgs.lib.licenses.bsdOriginal ];
946 license = [ pkgs.lib.licenses.bsdOriginal ];
947 };
947 };
948 };
948 };
949 "lxml" = super.buildPythonPackage {
949 "lxml" = super.buildPythonPackage {
950 name = "lxml-4.2.5";
950 name = "lxml-4.2.5";
951 doCheck = false;
951 doCheck = false;
952 src = fetchurl {
952 src = fetchurl {
953 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
953 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
954 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
954 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
955 };
955 };
956 meta = {
956 meta = {
957 license = [ pkgs.lib.licenses.bsdOriginal ];
957 license = [ pkgs.lib.licenses.bsdOriginal ];
958 };
958 };
959 };
959 };
960 "mako" = super.buildPythonPackage {
960 "mako" = super.buildPythonPackage {
961 name = "mako-1.1.0";
961 name = "mako-1.1.0";
962 doCheck = false;
962 doCheck = false;
963 propagatedBuildInputs = [
963 propagatedBuildInputs = [
964 self."markupsafe"
964 self."markupsafe"
965 ];
965 ];
966 src = fetchurl {
966 src = fetchurl {
967 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
967 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
968 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
968 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
969 };
969 };
970 meta = {
970 meta = {
971 license = [ pkgs.lib.licenses.mit ];
971 license = [ pkgs.lib.licenses.mit ];
972 };
972 };
973 };
973 };
974 "markdown" = super.buildPythonPackage {
974 "markdown" = super.buildPythonPackage {
975 name = "markdown-2.6.11";
975 name = "markdown-2.6.11";
976 doCheck = false;
976 doCheck = false;
977 src = fetchurl {
977 src = fetchurl {
978 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
978 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
979 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
979 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
980 };
980 };
981 meta = {
981 meta = {
982 license = [ pkgs.lib.licenses.bsdOriginal ];
982 license = [ pkgs.lib.licenses.bsdOriginal ];
983 };
983 };
984 };
984 };
985 "markupsafe" = super.buildPythonPackage {
985 "markupsafe" = super.buildPythonPackage {
986 name = "markupsafe-1.1.1";
986 name = "markupsafe-1.1.1";
987 doCheck = false;
987 doCheck = false;
988 src = fetchurl {
988 src = fetchurl {
989 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
989 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
990 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
990 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
991 };
991 };
992 meta = {
992 meta = {
993 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
993 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
994 };
994 };
995 };
995 };
996 "mistune" = super.buildPythonPackage {
996 "mistune" = super.buildPythonPackage {
997 name = "mistune-0.8.4";
997 name = "mistune-0.8.4";
998 doCheck = false;
998 doCheck = false;
999 src = fetchurl {
999 src = fetchurl {
1000 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
1000 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
1001 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
1001 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
1002 };
1002 };
1003 meta = {
1003 meta = {
1004 license = [ pkgs.lib.licenses.bsdOriginal ];
1004 license = [ pkgs.lib.licenses.bsdOriginal ];
1005 };
1005 };
1006 };
1006 };
1007 "mock" = super.buildPythonPackage {
1007 "mock" = super.buildPythonPackage {
1008 name = "mock-3.0.5";
1008 name = "mock-3.0.5";
1009 doCheck = false;
1009 doCheck = false;
1010 propagatedBuildInputs = [
1010 propagatedBuildInputs = [
1011 self."six"
1011 self."six"
1012 self."funcsigs"
1012 self."funcsigs"
1013 ];
1013 ];
1014 src = fetchurl {
1014 src = fetchurl {
1015 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
1015 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
1016 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
1016 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
1017 };
1017 };
1018 meta = {
1018 meta = {
1019 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
1019 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
1020 };
1020 };
1021 };
1021 };
1022 "more-itertools" = super.buildPythonPackage {
1022 "more-itertools" = super.buildPythonPackage {
1023 name = "more-itertools-5.0.0";
1023 name = "more-itertools-5.0.0";
1024 doCheck = false;
1024 doCheck = false;
1025 propagatedBuildInputs = [
1025 propagatedBuildInputs = [
1026 self."six"
1026 self."six"
1027 ];
1027 ];
1028 src = fetchurl {
1028 src = fetchurl {
1029 url = "https://files.pythonhosted.org/packages/dd/26/30fc0d541d9fdf55faf5ba4b0fd68f81d5bd2447579224820ad525934178/more-itertools-5.0.0.tar.gz";
1029 url = "https://files.pythonhosted.org/packages/dd/26/30fc0d541d9fdf55faf5ba4b0fd68f81d5bd2447579224820ad525934178/more-itertools-5.0.0.tar.gz";
1030 sha256 = "1r12cm6mcdwdzz7d47a6g4l437xsvapdlgyhqay3i2nrlv03da9q";
1030 sha256 = "1r12cm6mcdwdzz7d47a6g4l437xsvapdlgyhqay3i2nrlv03da9q";
1031 };
1031 };
1032 meta = {
1032 meta = {
1033 license = [ pkgs.lib.licenses.mit ];
1033 license = [ pkgs.lib.licenses.mit ];
1034 };
1034 };
1035 };
1035 };
1036 "msgpack-python" = super.buildPythonPackage {
1036 "msgpack-python" = super.buildPythonPackage {
1037 name = "msgpack-python-0.5.6";
1037 name = "msgpack-python-0.5.6";
1038 doCheck = false;
1038 doCheck = false;
1039 src = fetchurl {
1039 src = fetchurl {
1040 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
1040 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
1041 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
1041 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
1042 };
1042 };
1043 meta = {
1043 meta = {
1044 license = [ pkgs.lib.licenses.asl20 ];
1044 license = [ pkgs.lib.licenses.asl20 ];
1045 };
1045 };
1046 };
1046 };
1047 "mysql-python" = super.buildPythonPackage {
1047 "mysql-python" = super.buildPythonPackage {
1048 name = "mysql-python-1.2.5";
1048 name = "mysql-python-1.2.5";
1049 doCheck = false;
1049 doCheck = false;
1050 src = fetchurl {
1050 src = fetchurl {
1051 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
1051 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
1052 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
1052 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
1053 };
1053 };
1054 meta = {
1054 meta = {
1055 license = [ pkgs.lib.licenses.gpl1 ];
1055 license = [ pkgs.lib.licenses.gpl1 ];
1056 };
1056 };
1057 };
1057 };
1058 "nbconvert" = super.buildPythonPackage {
1058 "nbconvert" = super.buildPythonPackage {
1059 name = "nbconvert-5.3.1";
1059 name = "nbconvert-5.3.1";
1060 doCheck = false;
1060 doCheck = false;
1061 propagatedBuildInputs = [
1061 propagatedBuildInputs = [
1062 self."mistune"
1062 self."mistune"
1063 self."jinja2"
1063 self."jinja2"
1064 self."pygments"
1064 self."pygments"
1065 self."traitlets"
1065 self."traitlets"
1066 self."jupyter-core"
1066 self."jupyter-core"
1067 self."nbformat"
1067 self."nbformat"
1068 self."entrypoints"
1068 self."entrypoints"
1069 self."bleach"
1069 self."bleach"
1070 self."pandocfilters"
1070 self."pandocfilters"
1071 self."testpath"
1071 self."testpath"
1072 ];
1072 ];
1073 src = fetchurl {
1073 src = fetchurl {
1074 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
1074 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
1075 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
1075 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
1076 };
1076 };
1077 meta = {
1077 meta = {
1078 license = [ pkgs.lib.licenses.bsdOriginal ];
1078 license = [ pkgs.lib.licenses.bsdOriginal ];
1079 };
1079 };
1080 };
1080 };
1081 "nbformat" = super.buildPythonPackage {
1081 "nbformat" = super.buildPythonPackage {
1082 name = "nbformat-4.4.0";
1082 name = "nbformat-4.4.0";
1083 doCheck = false;
1083 doCheck = false;
1084 propagatedBuildInputs = [
1084 propagatedBuildInputs = [
1085 self."ipython-genutils"
1085 self."ipython-genutils"
1086 self."traitlets"
1086 self."traitlets"
1087 self."jsonschema"
1087 self."jsonschema"
1088 self."jupyter-core"
1088 self."jupyter-core"
1089 ];
1089 ];
1090 src = fetchurl {
1090 src = fetchurl {
1091 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
1091 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
1092 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
1092 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
1093 };
1093 };
1094 meta = {
1094 meta = {
1095 license = [ pkgs.lib.licenses.bsdOriginal ];
1095 license = [ pkgs.lib.licenses.bsdOriginal ];
1096 };
1096 };
1097 };
1097 };
1098 "packaging" = super.buildPythonPackage {
1098 "packaging" = super.buildPythonPackage {
1099 name = "packaging-20.3";
1099 name = "packaging-20.3";
1100 doCheck = false;
1100 doCheck = false;
1101 propagatedBuildInputs = [
1101 propagatedBuildInputs = [
1102 self."pyparsing"
1102 self."pyparsing"
1103 self."six"
1103 self."six"
1104 ];
1104 ];
1105 src = fetchurl {
1105 src = fetchurl {
1106 url = "https://files.pythonhosted.org/packages/65/37/83e3f492eb52d771e2820e88105f605335553fe10422cba9d256faeb1702/packaging-20.3.tar.gz";
1106 url = "https://files.pythonhosted.org/packages/65/37/83e3f492eb52d771e2820e88105f605335553fe10422cba9d256faeb1702/packaging-20.3.tar.gz";
1107 sha256 = "18xpablq278janh03bai9xd4kz9b0yfp6vflazn725ns9x3jna9w";
1107 sha256 = "18xpablq278janh03bai9xd4kz9b0yfp6vflazn725ns9x3jna9w";
1108 };
1108 };
1109 meta = {
1109 meta = {
1110 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
1110 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
1111 };
1111 };
1112 };
1112 };
1113 "pandocfilters" = super.buildPythonPackage {
1113 "pandocfilters" = super.buildPythonPackage {
1114 name = "pandocfilters-1.4.2";
1114 name = "pandocfilters-1.4.2";
1115 doCheck = false;
1115 doCheck = false;
1116 src = fetchurl {
1116 src = fetchurl {
1117 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1117 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1118 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
1118 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
1119 };
1119 };
1120 meta = {
1120 meta = {
1121 license = [ pkgs.lib.licenses.bsdOriginal ];
1121 license = [ pkgs.lib.licenses.bsdOriginal ];
1122 };
1122 };
1123 };
1123 };
1124 "paste" = super.buildPythonPackage {
1124 "paste" = super.buildPythonPackage {
1125 name = "paste-3.4.0";
1125 name = "paste-3.4.0";
1126 doCheck = false;
1126 doCheck = false;
1127 propagatedBuildInputs = [
1127 propagatedBuildInputs = [
1128 self."six"
1128 self."six"
1129 ];
1129 ];
1130 src = fetchurl {
1130 src = fetchurl {
1131 url = "https://files.pythonhosted.org/packages/79/4a/45821b71dd40000507549afd1491546afad8279c0a87527c88776a794158/Paste-3.4.0.tar.gz";
1131 url = "https://files.pythonhosted.org/packages/79/4a/45821b71dd40000507549afd1491546afad8279c0a87527c88776a794158/Paste-3.4.0.tar.gz";
1132 sha256 = "16sichvhyci1gaarkjs35mai8vphh7b244qm14hj1isw38nx4c03";
1132 sha256 = "16sichvhyci1gaarkjs35mai8vphh7b244qm14hj1isw38nx4c03";
1133 };
1133 };
1134 meta = {
1134 meta = {
1135 license = [ pkgs.lib.licenses.mit ];
1135 license = [ pkgs.lib.licenses.mit ];
1136 };
1136 };
1137 };
1137 };
1138 "pastedeploy" = super.buildPythonPackage {
1138 "pastedeploy" = super.buildPythonPackage {
1139 name = "pastedeploy-2.1.0";
1139 name = "pastedeploy-2.1.0";
1140 doCheck = false;
1140 doCheck = false;
1141 src = fetchurl {
1141 src = fetchurl {
1142 url = "https://files.pythonhosted.org/packages/c4/e9/972a1c20318b3ae9edcab11a6cef64308fbae5d0d45ab52c6f8b2b8f35b8/PasteDeploy-2.1.0.tar.gz";
1142 url = "https://files.pythonhosted.org/packages/c4/e9/972a1c20318b3ae9edcab11a6cef64308fbae5d0d45ab52c6f8b2b8f35b8/PasteDeploy-2.1.0.tar.gz";
1143 sha256 = "16qsq5y6mryslmbp5pn35x4z8z3ndp5rpgl42h226879nrw9hmg7";
1143 sha256 = "16qsq5y6mryslmbp5pn35x4z8z3ndp5rpgl42h226879nrw9hmg7";
1144 };
1144 };
1145 meta = {
1145 meta = {
1146 license = [ pkgs.lib.licenses.mit ];
1146 license = [ pkgs.lib.licenses.mit ];
1147 };
1147 };
1148 };
1148 };
1149 "pastescript" = super.buildPythonPackage {
1149 "pastescript" = super.buildPythonPackage {
1150 name = "pastescript-3.2.0";
1150 name = "pastescript-3.2.0";
1151 doCheck = false;
1151 doCheck = false;
1152 propagatedBuildInputs = [
1152 propagatedBuildInputs = [
1153 self."paste"
1153 self."paste"
1154 self."pastedeploy"
1154 self."pastedeploy"
1155 self."six"
1155 self."six"
1156 ];
1156 ];
1157 src = fetchurl {
1157 src = fetchurl {
1158 url = "https://files.pythonhosted.org/packages/ff/47/45c6f5a3cb8f5abf786fea98dbb8d02400a55768a9b623afb7df12346c61/PasteScript-3.2.0.tar.gz";
1158 url = "https://files.pythonhosted.org/packages/ff/47/45c6f5a3cb8f5abf786fea98dbb8d02400a55768a9b623afb7df12346c61/PasteScript-3.2.0.tar.gz";
1159 sha256 = "1b3jq7xh383nvrrlblk05m37345bv97xrhx77wshllba3h7mq3wv";
1159 sha256 = "1b3jq7xh383nvrrlblk05m37345bv97xrhx77wshllba3h7mq3wv";
1160 };
1160 };
1161 meta = {
1161 meta = {
1162 license = [ pkgs.lib.licenses.mit ];
1162 license = [ pkgs.lib.licenses.mit ];
1163 };
1163 };
1164 };
1164 };
1165 "pathlib2" = super.buildPythonPackage {
1165 "pathlib2" = super.buildPythonPackage {
1166 name = "pathlib2-2.3.5";
1166 name = "pathlib2-2.3.5";
1167 doCheck = false;
1167 doCheck = false;
1168 propagatedBuildInputs = [
1168 propagatedBuildInputs = [
1169 self."six"
1169 self."six"
1170 self."scandir"
1170 self."scandir"
1171 ];
1171 ];
1172 src = fetchurl {
1172 src = fetchurl {
1173 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
1173 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
1174 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
1174 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
1175 };
1175 };
1176 meta = {
1176 meta = {
1177 license = [ pkgs.lib.licenses.mit ];
1177 license = [ pkgs.lib.licenses.mit ];
1178 };
1178 };
1179 };
1179 };
1180 "peppercorn" = super.buildPythonPackage {
1180 "peppercorn" = super.buildPythonPackage {
1181 name = "peppercorn-0.6";
1181 name = "peppercorn-0.6";
1182 doCheck = false;
1182 doCheck = false;
1183 src = fetchurl {
1183 src = fetchurl {
1184 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1184 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1185 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1185 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1186 };
1186 };
1187 meta = {
1187 meta = {
1188 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1188 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1189 };
1189 };
1190 };
1190 };
1191 "pexpect" = super.buildPythonPackage {
1191 "pexpect" = super.buildPythonPackage {
1192 name = "pexpect-4.8.0";
1192 name = "pexpect-4.8.0";
1193 doCheck = false;
1193 doCheck = false;
1194 propagatedBuildInputs = [
1194 propagatedBuildInputs = [
1195 self."ptyprocess"
1195 self."ptyprocess"
1196 ];
1196 ];
1197 src = fetchurl {
1197 src = fetchurl {
1198 url = "https://files.pythonhosted.org/packages/e5/9b/ff402e0e930e70467a7178abb7c128709a30dfb22d8777c043e501bc1b10/pexpect-4.8.0.tar.gz";
1198 url = "https://files.pythonhosted.org/packages/e5/9b/ff402e0e930e70467a7178abb7c128709a30dfb22d8777c043e501bc1b10/pexpect-4.8.0.tar.gz";
1199 sha256 = "032cg337h8awydgypz6f4wx848lw8dyrj4zy988x0lyib4ws8rgw";
1199 sha256 = "032cg337h8awydgypz6f4wx848lw8dyrj4zy988x0lyib4ws8rgw";
1200 };
1200 };
1201 meta = {
1201 meta = {
1202 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1202 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1203 };
1203 };
1204 };
1204 };
1205 "pickleshare" = super.buildPythonPackage {
1205 "pickleshare" = super.buildPythonPackage {
1206 name = "pickleshare-0.7.5";
1206 name = "pickleshare-0.7.5";
1207 doCheck = false;
1207 doCheck = false;
1208 propagatedBuildInputs = [
1208 propagatedBuildInputs = [
1209 self."pathlib2"
1209 self."pathlib2"
1210 ];
1210 ];
1211 src = fetchurl {
1211 src = fetchurl {
1212 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1212 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1213 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1213 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1214 };
1214 };
1215 meta = {
1215 meta = {
1216 license = [ pkgs.lib.licenses.mit ];
1216 license = [ pkgs.lib.licenses.mit ];
1217 };
1217 };
1218 };
1218 };
1219 "plaster" = super.buildPythonPackage {
1219 "plaster" = super.buildPythonPackage {
1220 name = "plaster-1.0";
1220 name = "plaster-1.0";
1221 doCheck = false;
1221 doCheck = false;
1222 propagatedBuildInputs = [
1222 propagatedBuildInputs = [
1223 self."setuptools"
1223 self."setuptools"
1224 ];
1224 ];
1225 src = fetchurl {
1225 src = fetchurl {
1226 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1226 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1227 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1227 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1228 };
1228 };
1229 meta = {
1229 meta = {
1230 license = [ pkgs.lib.licenses.mit ];
1230 license = [ pkgs.lib.licenses.mit ];
1231 };
1231 };
1232 };
1232 };
1233 "plaster-pastedeploy" = super.buildPythonPackage {
1233 "plaster-pastedeploy" = super.buildPythonPackage {
1234 name = "plaster-pastedeploy-0.7";
1234 name = "plaster-pastedeploy-0.7";
1235 doCheck = false;
1235 doCheck = false;
1236 propagatedBuildInputs = [
1236 propagatedBuildInputs = [
1237 self."pastedeploy"
1237 self."pastedeploy"
1238 self."plaster"
1238 self."plaster"
1239 ];
1239 ];
1240 src = fetchurl {
1240 src = fetchurl {
1241 url = "https://files.pythonhosted.org/packages/99/69/2d3bc33091249266a1bd3cf24499e40ab31d54dffb4a7d76fe647950b98c/plaster_pastedeploy-0.7.tar.gz";
1241 url = "https://files.pythonhosted.org/packages/99/69/2d3bc33091249266a1bd3cf24499e40ab31d54dffb4a7d76fe647950b98c/plaster_pastedeploy-0.7.tar.gz";
1242 sha256 = "1zg7gcsvc1kzay1ry5p699rg2qavfsxqwl17mqxzr0gzw6j9679r";
1242 sha256 = "1zg7gcsvc1kzay1ry5p699rg2qavfsxqwl17mqxzr0gzw6j9679r";
1243 };
1243 };
1244 meta = {
1244 meta = {
1245 license = [ pkgs.lib.licenses.mit ];
1245 license = [ pkgs.lib.licenses.mit ];
1246 };
1246 };
1247 };
1247 };
1248 "pluggy" = super.buildPythonPackage {
1248 "pluggy" = super.buildPythonPackage {
1249 name = "pluggy-0.13.1";
1249 name = "pluggy-0.13.1";
1250 doCheck = false;
1250 doCheck = false;
1251 propagatedBuildInputs = [
1251 propagatedBuildInputs = [
1252 self."importlib-metadata"
1252 self."importlib-metadata"
1253 ];
1253 ];
1254 src = fetchurl {
1254 src = fetchurl {
1255 url = "https://files.pythonhosted.org/packages/f8/04/7a8542bed4b16a65c2714bf76cf5a0b026157da7f75e87cc88774aa10b14/pluggy-0.13.1.tar.gz";
1255 url = "https://files.pythonhosted.org/packages/f8/04/7a8542bed4b16a65c2714bf76cf5a0b026157da7f75e87cc88774aa10b14/pluggy-0.13.1.tar.gz";
1256 sha256 = "1c35qyhvy27q9ih9n899f3h4sdnpgq027dbiilly2qb5cvgarchm";
1256 sha256 = "1c35qyhvy27q9ih9n899f3h4sdnpgq027dbiilly2qb5cvgarchm";
1257 };
1257 };
1258 meta = {
1258 meta = {
1259 license = [ pkgs.lib.licenses.mit ];
1259 license = [ pkgs.lib.licenses.mit ];
1260 };
1260 };
1261 };
1261 };
1262 "premailer" = super.buildPythonPackage {
1262 "premailer" = super.buildPythonPackage {
1263 name = "premailer-3.6.1";
1263 name = "premailer-3.6.1";
1264 doCheck = false;
1264 doCheck = false;
1265 propagatedBuildInputs = [
1265 propagatedBuildInputs = [
1266 self."lxml"
1266 self."lxml"
1267 self."cssselect"
1267 self."cssselect"
1268 self."cssutils"
1268 self."cssutils"
1269 self."requests"
1269 self."requests"
1270 self."cachetools"
1270 self."cachetools"
1271 ];
1271 ];
1272 src = fetchurl {
1272 src = fetchurl {
1273 url = "https://files.pythonhosted.org/packages/62/da/2f43cdf9d3d79c80c4856a12389a1f257d65fe9ccc44bc6b4383c8a18e33/premailer-3.6.1.tar.gz";
1273 url = "https://files.pythonhosted.org/packages/62/da/2f43cdf9d3d79c80c4856a12389a1f257d65fe9ccc44bc6b4383c8a18e33/premailer-3.6.1.tar.gz";
1274 sha256 = "08pshx7a110k4ll20x0xhpvyn3kkipkrbgxjjn7ncdxs54ihdhgw";
1274 sha256 = "08pshx7a110k4ll20x0xhpvyn3kkipkrbgxjjn7ncdxs54ihdhgw";
1275 };
1275 };
1276 meta = {
1276 meta = {
1277 license = [ pkgs.lib.licenses.psfl { fullName = "Python"; } ];
1277 license = [ pkgs.lib.licenses.psfl { fullName = "Python"; } ];
1278 };
1278 };
1279 };
1279 };
1280 "prompt-toolkit" = super.buildPythonPackage {
1280 "prompt-toolkit" = super.buildPythonPackage {
1281 name = "prompt-toolkit-1.0.18";
1281 name = "prompt-toolkit-1.0.18";
1282 doCheck = false;
1282 doCheck = false;
1283 propagatedBuildInputs = [
1283 propagatedBuildInputs = [
1284 self."six"
1284 self."six"
1285 self."wcwidth"
1285 self."wcwidth"
1286 ];
1286 ];
1287 src = fetchurl {
1287 src = fetchurl {
1288 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
1288 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
1289 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
1289 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
1290 };
1290 };
1291 meta = {
1291 meta = {
1292 license = [ pkgs.lib.licenses.bsdOriginal ];
1292 license = [ pkgs.lib.licenses.bsdOriginal ];
1293 };
1293 };
1294 };
1294 };
1295 "psutil" = super.buildPythonPackage {
1295 "psutil" = super.buildPythonPackage {
1296 name = "psutil-5.7.0";
1296 name = "psutil-5.7.0";
1297 doCheck = false;
1297 doCheck = false;
1298 src = fetchurl {
1298 src = fetchurl {
1299 url = "https://files.pythonhosted.org/packages/c4/b8/3512f0e93e0db23a71d82485ba256071ebef99b227351f0f5540f744af41/psutil-5.7.0.tar.gz";
1299 url = "https://files.pythonhosted.org/packages/c4/b8/3512f0e93e0db23a71d82485ba256071ebef99b227351f0f5540f744af41/psutil-5.7.0.tar.gz";
1300 sha256 = "03jykdi3dgf1cdal9bv4fq9zjvzj9l9bs99gi5ar81sdl5nc2pk8";
1300 sha256 = "03jykdi3dgf1cdal9bv4fq9zjvzj9l9bs99gi5ar81sdl5nc2pk8";
1301 };
1301 };
1302 meta = {
1302 meta = {
1303 license = [ pkgs.lib.licenses.bsdOriginal ];
1303 license = [ pkgs.lib.licenses.bsdOriginal ];
1304 };
1304 };
1305 };
1305 };
1306 "psycopg2" = super.buildPythonPackage {
1306 "psycopg2" = super.buildPythonPackage {
1307 name = "psycopg2-2.8.4";
1307 name = "psycopg2-2.8.4";
1308 doCheck = false;
1308 doCheck = false;
1309 src = fetchurl {
1309 src = fetchurl {
1310 url = "https://files.pythonhosted.org/packages/84/d7/6a93c99b5ba4d4d22daa3928b983cec66df4536ca50b22ce5dcac65e4e71/psycopg2-2.8.4.tar.gz";
1310 url = "https://files.pythonhosted.org/packages/84/d7/6a93c99b5ba4d4d22daa3928b983cec66df4536ca50b22ce5dcac65e4e71/psycopg2-2.8.4.tar.gz";
1311 sha256 = "1djvh98pi4hjd8rxbq8qzc63bg8v78k33yg6pl99wak61b6fb67q";
1311 sha256 = "1djvh98pi4hjd8rxbq8qzc63bg8v78k33yg6pl99wak61b6fb67q";
1312 };
1312 };
1313 meta = {
1313 meta = {
1314 license = [ pkgs.lib.licenses.zpl21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
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 "ptyprocess" = super.buildPythonPackage {
1317 "ptyprocess" = super.buildPythonPackage {
1318 name = "ptyprocess-0.6.0";
1318 name = "ptyprocess-0.6.0";
1319 doCheck = false;
1319 doCheck = false;
1320 src = fetchurl {
1320 src = fetchurl {
1321 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1321 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1322 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1322 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1323 };
1323 };
1324 meta = {
1324 meta = {
1325 license = [ ];
1325 license = [ ];
1326 };
1326 };
1327 };
1327 };
1328 "py" = super.buildPythonPackage {
1328 "py" = super.buildPythonPackage {
1329 name = "py-1.8.0";
1329 name = "py-1.8.0";
1330 doCheck = false;
1330 doCheck = false;
1331 src = fetchurl {
1331 src = fetchurl {
1332 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
1332 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
1333 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
1333 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
1334 };
1334 };
1335 meta = {
1335 meta = {
1336 license = [ pkgs.lib.licenses.mit ];
1336 license = [ pkgs.lib.licenses.mit ];
1337 };
1337 };
1338 };
1338 };
1339 "py-bcrypt" = super.buildPythonPackage {
1339 "py-bcrypt" = super.buildPythonPackage {
1340 name = "py-bcrypt-0.4";
1340 name = "py-bcrypt-0.4";
1341 doCheck = false;
1341 doCheck = false;
1342 src = fetchurl {
1342 src = fetchurl {
1343 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1343 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1344 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1344 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1345 };
1345 };
1346 meta = {
1346 meta = {
1347 license = [ pkgs.lib.licenses.bsdOriginal ];
1347 license = [ pkgs.lib.licenses.bsdOriginal ];
1348 };
1348 };
1349 };
1349 };
1350 "py-gfm" = super.buildPythonPackage {
1350 "py-gfm" = super.buildPythonPackage {
1351 name = "py-gfm-0.1.4";
1351 name = "py-gfm-0.1.4";
1352 doCheck = false;
1352 doCheck = false;
1353 propagatedBuildInputs = [
1353 propagatedBuildInputs = [
1354 self."setuptools"
1354 self."setuptools"
1355 self."markdown"
1355 self."markdown"
1356 ];
1356 ];
1357 src = fetchurl {
1357 src = fetchurl {
1358 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1358 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1359 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1359 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1360 };
1360 };
1361 meta = {
1361 meta = {
1362 license = [ pkgs.lib.licenses.bsdOriginal ];
1362 license = [ pkgs.lib.licenses.bsdOriginal ];
1363 };
1363 };
1364 };
1364 };
1365 "pyasn1" = super.buildPythonPackage {
1365 "pyasn1" = super.buildPythonPackage {
1366 name = "pyasn1-0.4.8";
1366 name = "pyasn1-0.4.8";
1367 doCheck = false;
1367 doCheck = false;
1368 src = fetchurl {
1368 src = fetchurl {
1369 url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz";
1369 url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz";
1370 sha256 = "1fnhbi3rmk47l9851gbik0flfr64vs5j0hbqx24cafjap6gprxxf";
1370 sha256 = "1fnhbi3rmk47l9851gbik0flfr64vs5j0hbqx24cafjap6gprxxf";
1371 };
1371 };
1372 meta = {
1372 meta = {
1373 license = [ pkgs.lib.licenses.bsdOriginal ];
1373 license = [ pkgs.lib.licenses.bsdOriginal ];
1374 };
1374 };
1375 };
1375 };
1376 "pyasn1-modules" = super.buildPythonPackage {
1376 "pyasn1-modules" = super.buildPythonPackage {
1377 name = "pyasn1-modules-0.2.6";
1377 name = "pyasn1-modules-0.2.6";
1378 doCheck = false;
1378 doCheck = false;
1379 propagatedBuildInputs = [
1379 propagatedBuildInputs = [
1380 self."pyasn1"
1380 self."pyasn1"
1381 ];
1381 ];
1382 src = fetchurl {
1382 src = fetchurl {
1383 url = "https://files.pythonhosted.org/packages/f1/a9/a1ef72a0e43feff643cf0130a08123dea76205e7a0dda37e3efb5f054a31/pyasn1-modules-0.2.6.tar.gz";
1383 url = "https://files.pythonhosted.org/packages/f1/a9/a1ef72a0e43feff643cf0130a08123dea76205e7a0dda37e3efb5f054a31/pyasn1-modules-0.2.6.tar.gz";
1384 sha256 = "08hph9j1r018drnrny29l7dl2q0cin78csswrhwrh8jmq61pmha3";
1384 sha256 = "08hph9j1r018drnrny29l7dl2q0cin78csswrhwrh8jmq61pmha3";
1385 };
1385 };
1386 meta = {
1386 meta = {
1387 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
1387 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
1388 };
1388 };
1389 };
1389 };
1390 "pycparser" = super.buildPythonPackage {
1390 "pycparser" = super.buildPythonPackage {
1391 name = "pycparser-2.20";
1391 name = "pycparser-2.20";
1392 doCheck = false;
1392 doCheck = false;
1393 src = fetchurl {
1393 src = fetchurl {
1394 url = "https://files.pythonhosted.org/packages/0f/86/e19659527668d70be91d0369aeaa055b4eb396b0f387a4f92293a20035bd/pycparser-2.20.tar.gz";
1394 url = "https://files.pythonhosted.org/packages/0f/86/e19659527668d70be91d0369aeaa055b4eb396b0f387a4f92293a20035bd/pycparser-2.20.tar.gz";
1395 sha256 = "1w0m3xvlrzq4lkbvd1ngfm8mdw64r1yxy6n7djlw6qj5d0km6ird";
1395 sha256 = "1w0m3xvlrzq4lkbvd1ngfm8mdw64r1yxy6n7djlw6qj5d0km6ird";
1396 };
1396 };
1397 meta = {
1397 meta = {
1398 license = [ pkgs.lib.licenses.bsdOriginal ];
1398 license = [ pkgs.lib.licenses.bsdOriginal ];
1399 };
1399 };
1400 };
1400 };
1401 "pycrypto" = super.buildPythonPackage {
1401 "pycrypto" = super.buildPythonPackage {
1402 name = "pycrypto-2.6.1";
1402 name = "pycrypto-2.6.1";
1403 doCheck = false;
1403 doCheck = false;
1404 src = fetchurl {
1404 src = fetchurl {
1405 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1405 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1406 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1406 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1407 };
1407 };
1408 meta = {
1408 meta = {
1409 license = [ pkgs.lib.licenses.publicDomain ];
1409 license = [ pkgs.lib.licenses.publicDomain ];
1410 };
1410 };
1411 };
1411 };
1412 "pycurl" = super.buildPythonPackage {
1412 "pycurl" = super.buildPythonPackage {
1413 name = "pycurl-7.43.0.3";
1413 name = "pycurl-7.43.0.3";
1414 doCheck = false;
1414 doCheck = false;
1415 src = fetchurl {
1415 src = fetchurl {
1416 url = "https://files.pythonhosted.org/packages/ac/b3/0f3979633b7890bab6098d84c84467030b807a1e2b31f5d30103af5a71ca/pycurl-7.43.0.3.tar.gz";
1416 url = "https://files.pythonhosted.org/packages/ac/b3/0f3979633b7890bab6098d84c84467030b807a1e2b31f5d30103af5a71ca/pycurl-7.43.0.3.tar.gz";
1417 sha256 = "13nsvqhvnmnvfk75s8iynqsgszyv06cjp4drd3psi7zpbh63623g";
1417 sha256 = "13nsvqhvnmnvfk75s8iynqsgszyv06cjp4drd3psi7zpbh63623g";
1418 };
1418 };
1419 meta = {
1419 meta = {
1420 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1420 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1421 };
1421 };
1422 };
1422 };
1423 "pygments" = super.buildPythonPackage {
1423 "pygments" = super.buildPythonPackage {
1424 name = "pygments-2.4.2";
1424 name = "pygments-2.4.2";
1425 doCheck = false;
1425 doCheck = false;
1426 src = fetchurl {
1426 src = fetchurl {
1427 url = "https://files.pythonhosted.org/packages/7e/ae/26808275fc76bf2832deb10d3a3ed3107bc4de01b85dcccbe525f2cd6d1e/Pygments-2.4.2.tar.gz";
1427 url = "https://files.pythonhosted.org/packages/7e/ae/26808275fc76bf2832deb10d3a3ed3107bc4de01b85dcccbe525f2cd6d1e/Pygments-2.4.2.tar.gz";
1428 sha256 = "15v2sqm5g12bqa0c7wikfh9ck2nl97ayizy1hpqhmws5gqalq748";
1428 sha256 = "15v2sqm5g12bqa0c7wikfh9ck2nl97ayizy1hpqhmws5gqalq748";
1429 };
1429 };
1430 meta = {
1430 meta = {
1431 license = [ pkgs.lib.licenses.bsdOriginal ];
1431 license = [ pkgs.lib.licenses.bsdOriginal ];
1432 };
1432 };
1433 };
1433 };
1434 "pymysql" = super.buildPythonPackage {
1434 "pymysql" = super.buildPythonPackage {
1435 name = "pymysql-0.8.1";
1435 name = "pymysql-0.8.1";
1436 doCheck = false;
1436 doCheck = false;
1437 src = fetchurl {
1437 src = fetchurl {
1438 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1438 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1439 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1439 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1440 };
1440 };
1441 meta = {
1441 meta = {
1442 license = [ pkgs.lib.licenses.mit ];
1442 license = [ pkgs.lib.licenses.mit ];
1443 };
1443 };
1444 };
1444 };
1445 "pyotp" = super.buildPythonPackage {
1445 "pyotp" = super.buildPythonPackage {
1446 name = "pyotp-2.3.0";
1446 name = "pyotp-2.3.0";
1447 doCheck = false;
1447 doCheck = false;
1448 src = fetchurl {
1448 src = fetchurl {
1449 url = "https://files.pythonhosted.org/packages/f7/15/395c4945ea6bc37e8811280bb675615cb4c2b2c1cd70bdc43329da91a386/pyotp-2.3.0.tar.gz";
1449 url = "https://files.pythonhosted.org/packages/f7/15/395c4945ea6bc37e8811280bb675615cb4c2b2c1cd70bdc43329da91a386/pyotp-2.3.0.tar.gz";
1450 sha256 = "18d13ikra1iq0xyfqfm72zhgwxi2qi9ps6z1a6zmqp4qrn57wlzw";
1450 sha256 = "18d13ikra1iq0xyfqfm72zhgwxi2qi9ps6z1a6zmqp4qrn57wlzw";
1451 };
1451 };
1452 meta = {
1452 meta = {
1453 license = [ pkgs.lib.licenses.mit ];
1453 license = [ pkgs.lib.licenses.mit ];
1454 };
1454 };
1455 };
1455 };
1456 "pyparsing" = super.buildPythonPackage {
1456 "pyparsing" = super.buildPythonPackage {
1457 name = "pyparsing-2.4.7";
1457 name = "pyparsing-2.4.7";
1458 doCheck = false;
1458 doCheck = false;
1459 src = fetchurl {
1459 src = fetchurl {
1460 url = "https://files.pythonhosted.org/packages/c1/47/dfc9c342c9842bbe0036c7f763d2d6686bcf5eb1808ba3e170afdb282210/pyparsing-2.4.7.tar.gz";
1460 url = "https://files.pythonhosted.org/packages/c1/47/dfc9c342c9842bbe0036c7f763d2d6686bcf5eb1808ba3e170afdb282210/pyparsing-2.4.7.tar.gz";
1461 sha256 = "1hgc8qrbq1ymxbwfbjghv01fm3fbpjwpjwi0bcailxxzhf3yq0y2";
1461 sha256 = "1hgc8qrbq1ymxbwfbjghv01fm3fbpjwpjwi0bcailxxzhf3yq0y2";
1462 };
1462 };
1463 meta = {
1463 meta = {
1464 license = [ pkgs.lib.licenses.mit ];
1464 license = [ pkgs.lib.licenses.mit ];
1465 };
1465 };
1466 };
1466 };
1467 "pyramid" = super.buildPythonPackage {
1467 "pyramid" = super.buildPythonPackage {
1468 name = "pyramid-1.10.4";
1468 name = "pyramid-1.10.4";
1469 doCheck = false;
1469 doCheck = false;
1470 propagatedBuildInputs = [
1470 propagatedBuildInputs = [
1471 self."hupper"
1471 self."hupper"
1472 self."plaster"
1472 self."plaster"
1473 self."plaster-pastedeploy"
1473 self."plaster-pastedeploy"
1474 self."setuptools"
1474 self."setuptools"
1475 self."translationstring"
1475 self."translationstring"
1476 self."venusian"
1476 self."venusian"
1477 self."webob"
1477 self."webob"
1478 self."zope.deprecation"
1478 self."zope.deprecation"
1479 self."zope.interface"
1479 self."zope.interface"
1480 self."repoze.lru"
1480 self."repoze.lru"
1481 ];
1481 ];
1482 src = fetchurl {
1482 src = fetchurl {
1483 url = "https://files.pythonhosted.org/packages/c2/43/1ae701c9c6bb3a434358e678a5e72c96e8aa55cf4cb1d2fa2041b5dd38b7/pyramid-1.10.4.tar.gz";
1483 url = "https://files.pythonhosted.org/packages/c2/43/1ae701c9c6bb3a434358e678a5e72c96e8aa55cf4cb1d2fa2041b5dd38b7/pyramid-1.10.4.tar.gz";
1484 sha256 = "0rkxs1ajycg2zh1c94xlmls56mx5m161sn8112skj0amza6cn36q";
1484 sha256 = "0rkxs1ajycg2zh1c94xlmls56mx5m161sn8112skj0amza6cn36q";
1485 };
1485 };
1486 meta = {
1486 meta = {
1487 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1487 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1488 };
1488 };
1489 };
1489 };
1490 "pyramid-debugtoolbar" = super.buildPythonPackage {
1490 "pyramid-debugtoolbar" = super.buildPythonPackage {
1491 name = "pyramid-debugtoolbar-4.6.1";
1491 name = "pyramid-debugtoolbar-4.6.1";
1492 doCheck = false;
1492 doCheck = false;
1493 propagatedBuildInputs = [
1493 propagatedBuildInputs = [
1494 self."pyramid"
1494 self."pyramid"
1495 self."pyramid-mako"
1495 self."pyramid-mako"
1496 self."repoze.lru"
1496 self."repoze.lru"
1497 self."pygments"
1497 self."pygments"
1498 self."ipaddress"
1498 self."ipaddress"
1499 ];
1499 ];
1500 src = fetchurl {
1500 src = fetchurl {
1501 url = "https://files.pythonhosted.org/packages/99/f6/b8603f82c18275be293921bc3a2184205056ca505747bf64ab8a0c08e124/pyramid_debugtoolbar-4.6.1.tar.gz";
1501 url = "https://files.pythonhosted.org/packages/99/f6/b8603f82c18275be293921bc3a2184205056ca505747bf64ab8a0c08e124/pyramid_debugtoolbar-4.6.1.tar.gz";
1502 sha256 = "185z7q8n959ga5331iczwra2iljwkidfx4qn6bbd7vm3rm4w6llv";
1502 sha256 = "185z7q8n959ga5331iczwra2iljwkidfx4qn6bbd7vm3rm4w6llv";
1503 };
1503 };
1504 meta = {
1504 meta = {
1505 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1505 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1506 };
1506 };
1507 };
1507 };
1508 "pyramid-jinja2" = super.buildPythonPackage {
1508 "pyramid-jinja2" = super.buildPythonPackage {
1509 name = "pyramid-jinja2-2.7";
1509 name = "pyramid-jinja2-2.7";
1510 doCheck = false;
1510 doCheck = false;
1511 propagatedBuildInputs = [
1511 propagatedBuildInputs = [
1512 self."pyramid"
1512 self."pyramid"
1513 self."zope.deprecation"
1513 self."zope.deprecation"
1514 self."jinja2"
1514 self."jinja2"
1515 self."markupsafe"
1515 self."markupsafe"
1516 ];
1516 ];
1517 src = fetchurl {
1517 src = fetchurl {
1518 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1518 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1519 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1519 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1520 };
1520 };
1521 meta = {
1521 meta = {
1522 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1522 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1523 };
1523 };
1524 };
1524 };
1525 "pyramid-mailer" = super.buildPythonPackage {
1525 "pyramid-mailer" = super.buildPythonPackage {
1526 name = "pyramid-mailer-0.15.1";
1526 name = "pyramid-mailer-0.15.1";
1527 doCheck = false;
1527 doCheck = false;
1528 propagatedBuildInputs = [
1528 propagatedBuildInputs = [
1529 self."pyramid"
1529 self."pyramid"
1530 self."repoze.sendmail"
1530 self."repoze.sendmail"
1531 self."transaction"
1531 self."transaction"
1532 ];
1532 ];
1533 src = fetchurl {
1533 src = fetchurl {
1534 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1534 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1535 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1535 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1536 };
1536 };
1537 meta = {
1537 meta = {
1538 license = [ pkgs.lib.licenses.bsdOriginal ];
1538 license = [ pkgs.lib.licenses.bsdOriginal ];
1539 };
1539 };
1540 };
1540 };
1541 "pyramid-mako" = super.buildPythonPackage {
1541 "pyramid-mako" = super.buildPythonPackage {
1542 name = "pyramid-mako-1.1.0";
1542 name = "pyramid-mako-1.1.0";
1543 doCheck = false;
1543 doCheck = false;
1544 propagatedBuildInputs = [
1544 propagatedBuildInputs = [
1545 self."pyramid"
1545 self."pyramid"
1546 self."mako"
1546 self."mako"
1547 ];
1547 ];
1548 src = fetchurl {
1548 src = fetchurl {
1549 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
1549 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
1550 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
1550 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
1551 };
1551 };
1552 meta = {
1552 meta = {
1553 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1553 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1554 };
1554 };
1555 };
1555 };
1556 "pysqlite" = super.buildPythonPackage {
1556 "pysqlite" = super.buildPythonPackage {
1557 name = "pysqlite-2.8.3";
1557 name = "pysqlite-2.8.3";
1558 doCheck = false;
1558 doCheck = false;
1559 src = fetchurl {
1559 src = fetchurl {
1560 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1560 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1561 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1561 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1562 };
1562 };
1563 meta = {
1563 meta = {
1564 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1564 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1565 };
1565 };
1566 };
1566 };
1567 "pytest" = super.buildPythonPackage {
1567 "pytest" = super.buildPythonPackage {
1568 name = "pytest-4.6.5";
1568 name = "pytest-4.6.5";
1569 doCheck = false;
1569 doCheck = false;
1570 propagatedBuildInputs = [
1570 propagatedBuildInputs = [
1571 self."py"
1571 self."py"
1572 self."six"
1572 self."six"
1573 self."packaging"
1573 self."packaging"
1574 self."attrs"
1574 self."attrs"
1575 self."atomicwrites"
1575 self."atomicwrites"
1576 self."pluggy"
1576 self."pluggy"
1577 self."importlib-metadata"
1577 self."importlib-metadata"
1578 self."wcwidth"
1578 self."wcwidth"
1579 self."funcsigs"
1579 self."funcsigs"
1580 self."pathlib2"
1580 self."pathlib2"
1581 self."more-itertools"
1581 self."more-itertools"
1582 ];
1582 ];
1583 src = fetchurl {
1583 src = fetchurl {
1584 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
1584 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
1585 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
1585 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
1586 };
1586 };
1587 meta = {
1587 meta = {
1588 license = [ pkgs.lib.licenses.mit ];
1588 license = [ pkgs.lib.licenses.mit ];
1589 };
1589 };
1590 };
1590 };
1591 "pytest-cov" = super.buildPythonPackage {
1591 "pytest-cov" = super.buildPythonPackage {
1592 name = "pytest-cov-2.7.1";
1592 name = "pytest-cov-2.7.1";
1593 doCheck = false;
1593 doCheck = false;
1594 propagatedBuildInputs = [
1594 propagatedBuildInputs = [
1595 self."pytest"
1595 self."pytest"
1596 self."coverage"
1596 self."coverage"
1597 ];
1597 ];
1598 src = fetchurl {
1598 src = fetchurl {
1599 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
1599 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
1600 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
1600 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
1601 };
1601 };
1602 meta = {
1602 meta = {
1603 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1603 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1604 };
1604 };
1605 };
1605 };
1606 "pytest-profiling" = super.buildPythonPackage {
1606 "pytest-profiling" = super.buildPythonPackage {
1607 name = "pytest-profiling-1.7.0";
1607 name = "pytest-profiling-1.7.0";
1608 doCheck = false;
1608 doCheck = false;
1609 propagatedBuildInputs = [
1609 propagatedBuildInputs = [
1610 self."six"
1610 self."six"
1611 self."pytest"
1611 self."pytest"
1612 self."gprof2dot"
1612 self."gprof2dot"
1613 ];
1613 ];
1614 src = fetchurl {
1614 src = fetchurl {
1615 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
1615 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
1616 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
1616 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
1617 };
1617 };
1618 meta = {
1618 meta = {
1619 license = [ pkgs.lib.licenses.mit ];
1619 license = [ pkgs.lib.licenses.mit ];
1620 };
1620 };
1621 };
1621 };
1622 "pytest-runner" = super.buildPythonPackage {
1622 "pytest-runner" = super.buildPythonPackage {
1623 name = "pytest-runner-5.1";
1623 name = "pytest-runner-5.1";
1624 doCheck = false;
1624 doCheck = false;
1625 src = fetchurl {
1625 src = fetchurl {
1626 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
1626 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
1627 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
1627 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
1628 };
1628 };
1629 meta = {
1629 meta = {
1630 license = [ pkgs.lib.licenses.mit ];
1630 license = [ pkgs.lib.licenses.mit ];
1631 };
1631 };
1632 };
1632 };
1633 "pytest-sugar" = super.buildPythonPackage {
1633 "pytest-sugar" = super.buildPythonPackage {
1634 name = "pytest-sugar-0.9.2";
1634 name = "pytest-sugar-0.9.2";
1635 doCheck = false;
1635 doCheck = false;
1636 propagatedBuildInputs = [
1636 propagatedBuildInputs = [
1637 self."pytest"
1637 self."pytest"
1638 self."termcolor"
1638 self."termcolor"
1639 self."packaging"
1639 self."packaging"
1640 ];
1640 ];
1641 src = fetchurl {
1641 src = fetchurl {
1642 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
1642 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
1643 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
1643 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
1644 };
1644 };
1645 meta = {
1645 meta = {
1646 license = [ pkgs.lib.licenses.bsdOriginal ];
1646 license = [ pkgs.lib.licenses.bsdOriginal ];
1647 };
1647 };
1648 };
1648 };
1649 "pytest-timeout" = super.buildPythonPackage {
1649 "pytest-timeout" = super.buildPythonPackage {
1650 name = "pytest-timeout-1.3.3";
1650 name = "pytest-timeout-1.3.3";
1651 doCheck = false;
1651 doCheck = false;
1652 propagatedBuildInputs = [
1652 propagatedBuildInputs = [
1653 self."pytest"
1653 self."pytest"
1654 ];
1654 ];
1655 src = fetchurl {
1655 src = fetchurl {
1656 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
1656 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
1657 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
1657 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
1658 };
1658 };
1659 meta = {
1659 meta = {
1660 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1660 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1661 };
1661 };
1662 };
1662 };
1663 "python-dateutil" = super.buildPythonPackage {
1663 "python-dateutil" = super.buildPythonPackage {
1664 name = "python-dateutil-2.8.1";
1664 name = "python-dateutil-2.8.1";
1665 doCheck = false;
1665 doCheck = false;
1666 propagatedBuildInputs = [
1666 propagatedBuildInputs = [
1667 self."six"
1667 self."six"
1668 ];
1668 ];
1669 src = fetchurl {
1669 src = fetchurl {
1670 url = "https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz";
1670 url = "https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz";
1671 sha256 = "0g42w7k5007iv9dam6gnja2ry8ydwirh99mgdll35s12pyfzxsvk";
1671 sha256 = "0g42w7k5007iv9dam6gnja2ry8ydwirh99mgdll35s12pyfzxsvk";
1672 };
1672 };
1673 meta = {
1673 meta = {
1674 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1674 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1675 };
1675 };
1676 };
1676 };
1677 "python-editor" = super.buildPythonPackage {
1677 "python-editor" = super.buildPythonPackage {
1678 name = "python-editor-1.0.4";
1678 name = "python-editor-1.0.4";
1679 doCheck = false;
1679 doCheck = false;
1680 src = fetchurl {
1680 src = fetchurl {
1681 url = "https://files.pythonhosted.org/packages/0a/85/78f4a216d28343a67b7397c99825cff336330893f00601443f7c7b2f2234/python-editor-1.0.4.tar.gz";
1681 url = "https://files.pythonhosted.org/packages/0a/85/78f4a216d28343a67b7397c99825cff336330893f00601443f7c7b2f2234/python-editor-1.0.4.tar.gz";
1682 sha256 = "0yrjh8w72ivqxi4i7xsg5b1vz15x8fg51xra7c3bgfyxqnyadzai";
1682 sha256 = "0yrjh8w72ivqxi4i7xsg5b1vz15x8fg51xra7c3bgfyxqnyadzai";
1683 };
1683 };
1684 meta = {
1684 meta = {
1685 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1685 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1686 };
1686 };
1687 };
1687 };
1688 "python-ldap" = super.buildPythonPackage {
1688 "python-ldap" = super.buildPythonPackage {
1689 name = "python-ldap-3.2.0";
1689 name = "python-ldap-3.2.0";
1690 doCheck = false;
1690 doCheck = false;
1691 propagatedBuildInputs = [
1691 propagatedBuildInputs = [
1692 self."pyasn1"
1692 self."pyasn1"
1693 self."pyasn1-modules"
1693 self."pyasn1-modules"
1694 ];
1694 ];
1695 src = fetchurl {
1695 src = fetchurl {
1696 url = "https://files.pythonhosted.org/packages/ea/93/596f875e003c770447f4b99267820a0c769dd2dc3ae3ed19afe460fcbad0/python-ldap-3.2.0.tar.gz";
1696 url = "https://files.pythonhosted.org/packages/ea/93/596f875e003c770447f4b99267820a0c769dd2dc3ae3ed19afe460fcbad0/python-ldap-3.2.0.tar.gz";
1697 sha256 = "13nvrhp85yr0jyxixcjj012iw8l9wynxxlykm9j3alss6waln73x";
1697 sha256 = "13nvrhp85yr0jyxixcjj012iw8l9wynxxlykm9j3alss6waln73x";
1698 };
1698 };
1699 meta = {
1699 meta = {
1700 license = [ pkgs.lib.licenses.psfl ];
1700 license = [ pkgs.lib.licenses.psfl ];
1701 };
1701 };
1702 };
1702 };
1703 "python-memcached" = super.buildPythonPackage {
1703 "python-memcached" = super.buildPythonPackage {
1704 name = "python-memcached-1.59";
1704 name = "python-memcached-1.59";
1705 doCheck = false;
1705 doCheck = false;
1706 propagatedBuildInputs = [
1706 propagatedBuildInputs = [
1707 self."six"
1707 self."six"
1708 ];
1708 ];
1709 src = fetchurl {
1709 src = fetchurl {
1710 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1710 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1711 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1711 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1712 };
1712 };
1713 meta = {
1713 meta = {
1714 license = [ pkgs.lib.licenses.psfl ];
1714 license = [ pkgs.lib.licenses.psfl ];
1715 };
1715 };
1716 };
1716 };
1717 "python-pam" = super.buildPythonPackage {
1717 "python-pam" = super.buildPythonPackage {
1718 name = "python-pam-1.8.4";
1718 name = "python-pam-1.8.4";
1719 doCheck = false;
1719 doCheck = false;
1720 src = fetchurl {
1720 src = fetchurl {
1721 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1721 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1722 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1722 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1723 };
1723 };
1724 meta = {
1724 meta = {
1725 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1725 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1726 };
1726 };
1727 };
1727 };
1728 "python-saml" = super.buildPythonPackage {
1728 "python-saml" = super.buildPythonPackage {
1729 name = "python-saml-2.4.2";
1729 name = "python-saml-2.4.2";
1730 doCheck = false;
1730 doCheck = false;
1731 propagatedBuildInputs = [
1731 propagatedBuildInputs = [
1732 self."dm.xmlsec.binding"
1732 self."dm.xmlsec.binding"
1733 self."isodate"
1733 self."isodate"
1734 self."defusedxml"
1734 self."defusedxml"
1735 ];
1735 ];
1736 src = fetchurl {
1736 src = fetchurl {
1737 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1737 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1738 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1738 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1739 };
1739 };
1740 meta = {
1740 meta = {
1741 license = [ pkgs.lib.licenses.mit ];
1741 license = [ pkgs.lib.licenses.mit ];
1742 };
1742 };
1743 };
1743 };
1744 "pytz" = super.buildPythonPackage {
1744 "pytz" = super.buildPythonPackage {
1745 name = "pytz-2019.3";
1745 name = "pytz-2019.3";
1746 doCheck = false;
1746 doCheck = false;
1747 src = fetchurl {
1747 src = fetchurl {
1748 url = "https://files.pythonhosted.org/packages/82/c3/534ddba230bd4fbbd3b7a3d35f3341d014cca213f369a9940925e7e5f691/pytz-2019.3.tar.gz";
1748 url = "https://files.pythonhosted.org/packages/82/c3/534ddba230bd4fbbd3b7a3d35f3341d014cca213f369a9940925e7e5f691/pytz-2019.3.tar.gz";
1749 sha256 = "1ghrk1wg45d3nymj7bf4zj03n3bh64xmczhk4pfi577hdkdhcb5h";
1749 sha256 = "1ghrk1wg45d3nymj7bf4zj03n3bh64xmczhk4pfi577hdkdhcb5h";
1750 };
1750 };
1751 meta = {
1751 meta = {
1752 license = [ pkgs.lib.licenses.mit ];
1752 license = [ pkgs.lib.licenses.mit ];
1753 };
1753 };
1754 };
1754 };
1755 "pyzmq" = super.buildPythonPackage {
1755 "pyzmq" = super.buildPythonPackage {
1756 name = "pyzmq-14.6.0";
1756 name = "pyzmq-14.6.0";
1757 doCheck = false;
1757 doCheck = false;
1758 src = fetchurl {
1758 src = fetchurl {
1759 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1759 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1760 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1760 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1761 };
1761 };
1762 meta = {
1762 meta = {
1763 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1763 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1764 };
1764 };
1765 };
1765 };
1766 "redis" = super.buildPythonPackage {
1766 "redis" = super.buildPythonPackage {
1767 name = "redis-3.4.1";
1767 name = "redis-3.4.1";
1768 doCheck = false;
1768 doCheck = false;
1769 src = fetchurl {
1769 src = fetchurl {
1770 url = "https://files.pythonhosted.org/packages/ef/2e/2c0f59891db7db087a7eeaa79bc7c7f2c039e71a2b5b0a41391e9d462926/redis-3.4.1.tar.gz";
1770 url = "https://files.pythonhosted.org/packages/ef/2e/2c0f59891db7db087a7eeaa79bc7c7f2c039e71a2b5b0a41391e9d462926/redis-3.4.1.tar.gz";
1771 sha256 = "07yaj0j9fs7xdkg5bg926fa990khyigjbp31si8ai20vj8sv7kqd";
1771 sha256 = "07yaj0j9fs7xdkg5bg926fa990khyigjbp31si8ai20vj8sv7kqd";
1772 };
1772 };
1773 meta = {
1773 meta = {
1774 license = [ pkgs.lib.licenses.mit ];
1774 license = [ pkgs.lib.licenses.mit ];
1775 };
1775 };
1776 };
1776 };
1777 "repoze.lru" = super.buildPythonPackage {
1777 "repoze.lru" = super.buildPythonPackage {
1778 name = "repoze.lru-0.7";
1778 name = "repoze.lru-0.7";
1779 doCheck = false;
1779 doCheck = false;
1780 src = fetchurl {
1780 src = fetchurl {
1781 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1781 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1782 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1782 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1783 };
1783 };
1784 meta = {
1784 meta = {
1785 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1785 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1786 };
1786 };
1787 };
1787 };
1788 "repoze.sendmail" = super.buildPythonPackage {
1788 "repoze.sendmail" = super.buildPythonPackage {
1789 name = "repoze.sendmail-4.4.1";
1789 name = "repoze.sendmail-4.4.1";
1790 doCheck = false;
1790 doCheck = false;
1791 propagatedBuildInputs = [
1791 propagatedBuildInputs = [
1792 self."setuptools"
1792 self."setuptools"
1793 self."zope.interface"
1793 self."zope.interface"
1794 self."transaction"
1794 self."transaction"
1795 ];
1795 ];
1796 src = fetchurl {
1796 src = fetchurl {
1797 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1797 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1798 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1798 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1799 };
1799 };
1800 meta = {
1800 meta = {
1801 license = [ pkgs.lib.licenses.zpl21 ];
1801 license = [ pkgs.lib.licenses.zpl21 ];
1802 };
1802 };
1803 };
1803 };
1804 "requests" = super.buildPythonPackage {
1804 "requests" = super.buildPythonPackage {
1805 name = "requests-2.22.0";
1805 name = "requests-2.22.0";
1806 doCheck = false;
1806 doCheck = false;
1807 propagatedBuildInputs = [
1807 propagatedBuildInputs = [
1808 self."chardet"
1808 self."chardet"
1809 self."idna"
1809 self."idna"
1810 self."urllib3"
1810 self."urllib3"
1811 self."certifi"
1811 self."certifi"
1812 ];
1812 ];
1813 src = fetchurl {
1813 src = fetchurl {
1814 url = "https://files.pythonhosted.org/packages/01/62/ddcf76d1d19885e8579acb1b1df26a852b03472c0e46d2b959a714c90608/requests-2.22.0.tar.gz";
1814 url = "https://files.pythonhosted.org/packages/01/62/ddcf76d1d19885e8579acb1b1df26a852b03472c0e46d2b959a714c90608/requests-2.22.0.tar.gz";
1815 sha256 = "1d5ybh11jr5sm7xp6mz8fyc7vrp4syifds91m7sj60xalal0gq0i";
1815 sha256 = "1d5ybh11jr5sm7xp6mz8fyc7vrp4syifds91m7sj60xalal0gq0i";
1816 };
1816 };
1817 meta = {
1817 meta = {
1818 license = [ pkgs.lib.licenses.asl20 ];
1818 license = [ pkgs.lib.licenses.asl20 ];
1819 };
1819 };
1820 };
1820 };
1821 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1821 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1822 name = "rhodecode-enterprise-ce-4.19.3";
1822 name = "rhodecode-enterprise-ce-4.20.0";
1823 buildInputs = [
1823 buildInputs = [
1824 self."pytest"
1824 self."pytest"
1825 self."py"
1825 self."py"
1826 self."pytest-cov"
1826 self."pytest-cov"
1827 self."pytest-sugar"
1827 self."pytest-sugar"
1828 self."pytest-runner"
1828 self."pytest-runner"
1829 self."pytest-profiling"
1829 self."pytest-profiling"
1830 self."pytest-timeout"
1830 self."pytest-timeout"
1831 self."gprof2dot"
1831 self."gprof2dot"
1832 self."mock"
1832 self."mock"
1833 self."cov-core"
1833 self."cov-core"
1834 self."coverage"
1834 self."coverage"
1835 self."webtest"
1835 self."webtest"
1836 self."beautifulsoup4"
1836 self."beautifulsoup4"
1837 self."configobj"
1837 self."configobj"
1838 ];
1838 ];
1839 doCheck = true;
1839 doCheck = true;
1840 propagatedBuildInputs = [
1840 propagatedBuildInputs = [
1841 self."amqp"
1841 self."amqp"
1842 self."babel"
1842 self."babel"
1843 self."beaker"
1843 self."beaker"
1844 self."bleach"
1844 self."bleach"
1845 self."celery"
1845 self."celery"
1846 self."channelstream"
1846 self."channelstream"
1847 self."click"
1847 self."click"
1848 self."colander"
1848 self."colander"
1849 self."configobj"
1849 self."configobj"
1850 self."cssselect"
1850 self."cssselect"
1851 self."cryptography"
1851 self."cryptography"
1852 self."decorator"
1852 self."decorator"
1853 self."deform"
1853 self."deform"
1854 self."docutils"
1854 self."docutils"
1855 self."dogpile.cache"
1855 self."dogpile.cache"
1856 self."dogpile.core"
1856 self."dogpile.core"
1857 self."formencode"
1857 self."formencode"
1858 self."future"
1858 self."future"
1859 self."futures"
1859 self."futures"
1860 self."infrae.cache"
1860 self."infrae.cache"
1861 self."iso8601"
1861 self."iso8601"
1862 self."itsdangerous"
1862 self."itsdangerous"
1863 self."kombu"
1863 self."kombu"
1864 self."lxml"
1864 self."lxml"
1865 self."mako"
1865 self."mako"
1866 self."markdown"
1866 self."markdown"
1867 self."markupsafe"
1867 self."markupsafe"
1868 self."msgpack-python"
1868 self."msgpack-python"
1869 self."pyotp"
1869 self."pyotp"
1870 self."packaging"
1870 self."packaging"
1871 self."pathlib2"
1871 self."pathlib2"
1872 self."paste"
1872 self."paste"
1873 self."pastedeploy"
1873 self."pastedeploy"
1874 self."pastescript"
1874 self."pastescript"
1875 self."peppercorn"
1875 self."peppercorn"
1876 self."premailer"
1876 self."premailer"
1877 self."psutil"
1877 self."psutil"
1878 self."py-bcrypt"
1878 self."py-bcrypt"
1879 self."pycurl"
1879 self."pycurl"
1880 self."pycrypto"
1880 self."pycrypto"
1881 self."pygments"
1881 self."pygments"
1882 self."pyparsing"
1882 self."pyparsing"
1883 self."pyramid-debugtoolbar"
1883 self."pyramid-debugtoolbar"
1884 self."pyramid-mako"
1884 self."pyramid-mako"
1885 self."pyramid"
1885 self."pyramid"
1886 self."pyramid-mailer"
1886 self."pyramid-mailer"
1887 self."python-dateutil"
1887 self."python-dateutil"
1888 self."python-ldap"
1888 self."python-ldap"
1889 self."python-memcached"
1889 self."python-memcached"
1890 self."python-pam"
1890 self."python-pam"
1891 self."python-saml"
1891 self."python-saml"
1892 self."pytz"
1892 self."pytz"
1893 self."tzlocal"
1893 self."tzlocal"
1894 self."pyzmq"
1894 self."pyzmq"
1895 self."py-gfm"
1895 self."py-gfm"
1896 self."redis"
1896 self."redis"
1897 self."repoze.lru"
1897 self."repoze.lru"
1898 self."requests"
1898 self."requests"
1899 self."routes"
1899 self."routes"
1900 self."simplejson"
1900 self."simplejson"
1901 self."six"
1901 self."six"
1902 self."sqlalchemy"
1902 self."sqlalchemy"
1903 self."sshpubkeys"
1903 self."sshpubkeys"
1904 self."subprocess32"
1904 self."subprocess32"
1905 self."supervisor"
1905 self."supervisor"
1906 self."translationstring"
1906 self."translationstring"
1907 self."urllib3"
1907 self."urllib3"
1908 self."urlobject"
1908 self."urlobject"
1909 self."venusian"
1909 self."venusian"
1910 self."weberror"
1910 self."weberror"
1911 self."webhelpers2"
1911 self."webhelpers2"
1912 self."webob"
1912 self."webob"
1913 self."whoosh"
1913 self."whoosh"
1914 self."wsgiref"
1914 self."wsgiref"
1915 self."zope.cachedescriptors"
1915 self."zope.cachedescriptors"
1916 self."zope.deprecation"
1916 self."zope.deprecation"
1917 self."zope.event"
1917 self."zope.event"
1918 self."zope.interface"
1918 self."zope.interface"
1919 self."mysql-python"
1919 self."mysql-python"
1920 self."pymysql"
1920 self."pymysql"
1921 self."pysqlite"
1921 self."pysqlite"
1922 self."psycopg2"
1922 self."psycopg2"
1923 self."nbconvert"
1923 self."nbconvert"
1924 self."nbformat"
1924 self."nbformat"
1925 self."jupyter-client"
1925 self."jupyter-client"
1926 self."jupyter-core"
1926 self."jupyter-core"
1927 self."alembic"
1927 self."alembic"
1928 self."invoke"
1928 self."invoke"
1929 self."bumpversion"
1929 self."bumpversion"
1930 self."gevent"
1930 self."gevent"
1931 self."greenlet"
1931 self."greenlet"
1932 self."gunicorn"
1932 self."gunicorn"
1933 self."waitress"
1933 self."waitress"
1934 self."ipdb"
1934 self."ipdb"
1935 self."ipython"
1935 self."ipython"
1936 self."rhodecode-tools"
1936 self."rhodecode-tools"
1937 self."appenlight-client"
1937 self."appenlight-client"
1938 self."pytest"
1938 self."pytest"
1939 self."py"
1939 self."py"
1940 self."pytest-cov"
1940 self."pytest-cov"
1941 self."pytest-sugar"
1941 self."pytest-sugar"
1942 self."pytest-runner"
1942 self."pytest-runner"
1943 self."pytest-profiling"
1943 self."pytest-profiling"
1944 self."pytest-timeout"
1944 self."pytest-timeout"
1945 self."gprof2dot"
1945 self."gprof2dot"
1946 self."mock"
1946 self."mock"
1947 self."cov-core"
1947 self."cov-core"
1948 self."coverage"
1948 self."coverage"
1949 self."webtest"
1949 self."webtest"
1950 self."beautifulsoup4"
1950 self."beautifulsoup4"
1951 ];
1951 ];
1952 src = ./.;
1952 src = ./.;
1953 meta = {
1953 meta = {
1954 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1954 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1955 };
1955 };
1956 };
1956 };
1957 "rhodecode-tools" = super.buildPythonPackage {
1957 "rhodecode-tools" = super.buildPythonPackage {
1958 name = "rhodecode-tools-1.4.0";
1958 name = "rhodecode-tools-1.4.0";
1959 doCheck = false;
1959 doCheck = false;
1960 propagatedBuildInputs = [
1960 propagatedBuildInputs = [
1961 self."click"
1961 self."click"
1962 self."future"
1962 self."future"
1963 self."six"
1963 self."six"
1964 self."mako"
1964 self."mako"
1965 self."markupsafe"
1965 self."markupsafe"
1966 self."requests"
1966 self."requests"
1967 self."urllib3"
1967 self."urllib3"
1968 self."whoosh"
1968 self."whoosh"
1969 self."elasticsearch"
1969 self."elasticsearch"
1970 self."elasticsearch-dsl"
1970 self."elasticsearch-dsl"
1971 self."elasticsearch2"
1971 self."elasticsearch2"
1972 self."elasticsearch1-dsl"
1972 self."elasticsearch1-dsl"
1973 ];
1973 ];
1974 src = fetchurl {
1974 src = fetchurl {
1975 url = "https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-ed54e749-2ef5-4bc7-ae7f-7900e3c2aa15.tar.gz?sha256=76f024bad3a1e55fdb3d64f13f5b77ff21a12fee699918de2110fe21effd5a3a";
1975 url = "https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-ed54e749-2ef5-4bc7-ae7f-7900e3c2aa15.tar.gz?sha256=76f024bad3a1e55fdb3d64f13f5b77ff21a12fee699918de2110fe21effd5a3a";
1976 sha256 = "0fjszppj3zhh47g1i6b9xqps28gzfxdkzwb47pdmzrd1sfx29w3n";
1976 sha256 = "0fjszppj3zhh47g1i6b9xqps28gzfxdkzwb47pdmzrd1sfx29w3n";
1977 };
1977 };
1978 meta = {
1978 meta = {
1979 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
1979 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
1980 };
1980 };
1981 };
1981 };
1982 "routes" = super.buildPythonPackage {
1982 "routes" = super.buildPythonPackage {
1983 name = "routes-2.4.1";
1983 name = "routes-2.4.1";
1984 doCheck = false;
1984 doCheck = false;
1985 propagatedBuildInputs = [
1985 propagatedBuildInputs = [
1986 self."six"
1986 self."six"
1987 self."repoze.lru"
1987 self."repoze.lru"
1988 ];
1988 ];
1989 src = fetchurl {
1989 src = fetchurl {
1990 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
1990 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
1991 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
1991 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
1992 };
1992 };
1993 meta = {
1993 meta = {
1994 license = [ pkgs.lib.licenses.mit ];
1994 license = [ pkgs.lib.licenses.mit ];
1995 };
1995 };
1996 };
1996 };
1997 "scandir" = super.buildPythonPackage {
1997 "scandir" = super.buildPythonPackage {
1998 name = "scandir-1.10.0";
1998 name = "scandir-1.10.0";
1999 doCheck = false;
1999 doCheck = false;
2000 src = fetchurl {
2000 src = fetchurl {
2001 url = "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz";
2001 url = "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz";
2002 sha256 = "1bkqwmf056pkchf05ywbnf659wqlp6lljcdb0y88wr9f0vv32ijd";
2002 sha256 = "1bkqwmf056pkchf05ywbnf659wqlp6lljcdb0y88wr9f0vv32ijd";
2003 };
2003 };
2004 meta = {
2004 meta = {
2005 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
2005 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
2006 };
2006 };
2007 };
2007 };
2008 "setproctitle" = super.buildPythonPackage {
2008 "setproctitle" = super.buildPythonPackage {
2009 name = "setproctitle-1.1.10";
2009 name = "setproctitle-1.1.10";
2010 doCheck = false;
2010 doCheck = false;
2011 src = fetchurl {
2011 src = fetchurl {
2012 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
2012 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
2013 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
2013 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
2014 };
2014 };
2015 meta = {
2015 meta = {
2016 license = [ pkgs.lib.licenses.bsdOriginal ];
2016 license = [ pkgs.lib.licenses.bsdOriginal ];
2017 };
2017 };
2018 };
2018 };
2019 "setuptools" = super.buildPythonPackage {
2019 "setuptools" = super.buildPythonPackage {
2020 name = "setuptools-44.1.0";
2020 name = "setuptools-44.1.0";
2021 doCheck = false;
2021 doCheck = false;
2022 src = fetchurl {
2022 src = fetchurl {
2023 url = "https://files.pythonhosted.org/packages/ed/7b/bbf89ca71e722b7f9464ebffe4b5ee20a9e5c9a555a56e2d3914bb9119a6/setuptools-44.1.0.zip";
2023 url = "https://files.pythonhosted.org/packages/ed/7b/bbf89ca71e722b7f9464ebffe4b5ee20a9e5c9a555a56e2d3914bb9119a6/setuptools-44.1.0.zip";
2024 sha256 = "1jja896zvd1ppccnjbhkgagxbwchgq6vfamp6qn1hvywq6q9cjkr";
2024 sha256 = "1jja896zvd1ppccnjbhkgagxbwchgq6vfamp6qn1hvywq6q9cjkr";
2025 };
2025 };
2026 meta = {
2026 meta = {
2027 license = [ pkgs.lib.licenses.mit ];
2027 license = [ pkgs.lib.licenses.mit ];
2028 };
2028 };
2029 };
2029 };
2030 "simplegeneric" = super.buildPythonPackage {
2030 "simplegeneric" = super.buildPythonPackage {
2031 name = "simplegeneric-0.8.1";
2031 name = "simplegeneric-0.8.1";
2032 doCheck = false;
2032 doCheck = false;
2033 src = fetchurl {
2033 src = fetchurl {
2034 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
2034 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
2035 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
2035 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
2036 };
2036 };
2037 meta = {
2037 meta = {
2038 license = [ pkgs.lib.licenses.zpl21 ];
2038 license = [ pkgs.lib.licenses.zpl21 ];
2039 };
2039 };
2040 };
2040 };
2041 "simplejson" = super.buildPythonPackage {
2041 "simplejson" = super.buildPythonPackage {
2042 name = "simplejson-3.16.0";
2042 name = "simplejson-3.16.0";
2043 doCheck = false;
2043 doCheck = false;
2044 src = fetchurl {
2044 src = fetchurl {
2045 url = "https://files.pythonhosted.org/packages/e3/24/c35fb1c1c315fc0fffe61ea00d3f88e85469004713dab488dee4f35b0aff/simplejson-3.16.0.tar.gz";
2045 url = "https://files.pythonhosted.org/packages/e3/24/c35fb1c1c315fc0fffe61ea00d3f88e85469004713dab488dee4f35b0aff/simplejson-3.16.0.tar.gz";
2046 sha256 = "19cws1syk8jzq2pw43878dv6fjkb0ifvjpx0i9aajix6kc9jkwxi";
2046 sha256 = "19cws1syk8jzq2pw43878dv6fjkb0ifvjpx0i9aajix6kc9jkwxi";
2047 };
2047 };
2048 meta = {
2048 meta = {
2049 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
2049 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
2050 };
2050 };
2051 };
2051 };
2052 "six" = super.buildPythonPackage {
2052 "six" = super.buildPythonPackage {
2053 name = "six-1.11.0";
2053 name = "six-1.11.0";
2054 doCheck = false;
2054 doCheck = false;
2055 src = fetchurl {
2055 src = fetchurl {
2056 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
2056 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
2057 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
2057 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
2058 };
2058 };
2059 meta = {
2059 meta = {
2060 license = [ pkgs.lib.licenses.mit ];
2060 license = [ pkgs.lib.licenses.mit ];
2061 };
2061 };
2062 };
2062 };
2063 "sqlalchemy" = super.buildPythonPackage {
2063 "sqlalchemy" = super.buildPythonPackage {
2064 name = "sqlalchemy-1.3.15";
2064 name = "sqlalchemy-1.3.15";
2065 doCheck = false;
2065 doCheck = false;
2066 src = fetchurl {
2066 src = fetchurl {
2067 url = "https://files.pythonhosted.org/packages/8c/30/4134e726dd5ed13728ff814fa91fc01c447ad8700504653fe99d91fdd34b/SQLAlchemy-1.3.15.tar.gz";
2067 url = "https://files.pythonhosted.org/packages/8c/30/4134e726dd5ed13728ff814fa91fc01c447ad8700504653fe99d91fdd34b/SQLAlchemy-1.3.15.tar.gz";
2068 sha256 = "0iglkvymfp35zm5pxy5kzqvcv96kkas0chqdx7xpla86sspa9k64";
2068 sha256 = "0iglkvymfp35zm5pxy5kzqvcv96kkas0chqdx7xpla86sspa9k64";
2069 };
2069 };
2070 meta = {
2070 meta = {
2071 license = [ pkgs.lib.licenses.mit ];
2071 license = [ pkgs.lib.licenses.mit ];
2072 };
2072 };
2073 };
2073 };
2074 "sshpubkeys" = super.buildPythonPackage {
2074 "sshpubkeys" = super.buildPythonPackage {
2075 name = "sshpubkeys-3.1.0";
2075 name = "sshpubkeys-3.1.0";
2076 doCheck = false;
2076 doCheck = false;
2077 propagatedBuildInputs = [
2077 propagatedBuildInputs = [
2078 self."cryptography"
2078 self."cryptography"
2079 self."ecdsa"
2079 self."ecdsa"
2080 ];
2080 ];
2081 src = fetchurl {
2081 src = fetchurl {
2082 url = "https://files.pythonhosted.org/packages/00/23/f7508a12007c96861c3da811992f14283d79c819d71a217b3e12d5196649/sshpubkeys-3.1.0.tar.gz";
2082 url = "https://files.pythonhosted.org/packages/00/23/f7508a12007c96861c3da811992f14283d79c819d71a217b3e12d5196649/sshpubkeys-3.1.0.tar.gz";
2083 sha256 = "105g2li04nm1hb15a2y6hm9m9k7fbrkd5l3gy12w3kgcmsf3k25k";
2083 sha256 = "105g2li04nm1hb15a2y6hm9m9k7fbrkd5l3gy12w3kgcmsf3k25k";
2084 };
2084 };
2085 meta = {
2085 meta = {
2086 license = [ pkgs.lib.licenses.bsdOriginal ];
2086 license = [ pkgs.lib.licenses.bsdOriginal ];
2087 };
2087 };
2088 };
2088 };
2089 "subprocess32" = super.buildPythonPackage {
2089 "subprocess32" = super.buildPythonPackage {
2090 name = "subprocess32-3.5.4";
2090 name = "subprocess32-3.5.4";
2091 doCheck = false;
2091 doCheck = false;
2092 src = fetchurl {
2092 src = fetchurl {
2093 url = "https://files.pythonhosted.org/packages/32/c8/564be4d12629b912ea431f1a50eb8b3b9d00f1a0b1ceff17f266be190007/subprocess32-3.5.4.tar.gz";
2093 url = "https://files.pythonhosted.org/packages/32/c8/564be4d12629b912ea431f1a50eb8b3b9d00f1a0b1ceff17f266be190007/subprocess32-3.5.4.tar.gz";
2094 sha256 = "17f7mvwx2271s1wrl0qac3wjqqnrqag866zs3qc8v5wp0k43fagb";
2094 sha256 = "17f7mvwx2271s1wrl0qac3wjqqnrqag866zs3qc8v5wp0k43fagb";
2095 };
2095 };
2096 meta = {
2096 meta = {
2097 license = [ pkgs.lib.licenses.psfl ];
2097 license = [ pkgs.lib.licenses.psfl ];
2098 };
2098 };
2099 };
2099 };
2100 "supervisor" = super.buildPythonPackage {
2100 "supervisor" = super.buildPythonPackage {
2101 name = "supervisor-4.1.0";
2101 name = "supervisor-4.1.0";
2102 doCheck = false;
2102 doCheck = false;
2103 src = fetchurl {
2103 src = fetchurl {
2104 url = "https://files.pythonhosted.org/packages/de/87/ee1ad8fa533a4b5f2c7623f4a2b585d3c1947af7bed8e65bc7772274320e/supervisor-4.1.0.tar.gz";
2104 url = "https://files.pythonhosted.org/packages/de/87/ee1ad8fa533a4b5f2c7623f4a2b585d3c1947af7bed8e65bc7772274320e/supervisor-4.1.0.tar.gz";
2105 sha256 = "10q36sa1jqljyyyl7cif52akpygl5kmlqq9x91hmx53f8zh6zj1d";
2105 sha256 = "10q36sa1jqljyyyl7cif52akpygl5kmlqq9x91hmx53f8zh6zj1d";
2106 };
2106 };
2107 meta = {
2107 meta = {
2108 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2108 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2109 };
2109 };
2110 };
2110 };
2111 "tempita" = super.buildPythonPackage {
2111 "tempita" = super.buildPythonPackage {
2112 name = "tempita-0.5.2";
2112 name = "tempita-0.5.2";
2113 doCheck = false;
2113 doCheck = false;
2114 src = fetchurl {
2114 src = fetchurl {
2115 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
2115 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
2116 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
2116 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
2117 };
2117 };
2118 meta = {
2118 meta = {
2119 license = [ pkgs.lib.licenses.mit ];
2119 license = [ pkgs.lib.licenses.mit ];
2120 };
2120 };
2121 };
2121 };
2122 "termcolor" = super.buildPythonPackage {
2122 "termcolor" = super.buildPythonPackage {
2123 name = "termcolor-1.1.0";
2123 name = "termcolor-1.1.0";
2124 doCheck = false;
2124 doCheck = false;
2125 src = fetchurl {
2125 src = fetchurl {
2126 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
2126 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
2127 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
2127 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
2128 };
2128 };
2129 meta = {
2129 meta = {
2130 license = [ pkgs.lib.licenses.mit ];
2130 license = [ pkgs.lib.licenses.mit ];
2131 };
2131 };
2132 };
2132 };
2133 "testpath" = super.buildPythonPackage {
2133 "testpath" = super.buildPythonPackage {
2134 name = "testpath-0.4.4";
2134 name = "testpath-0.4.4";
2135 doCheck = false;
2135 doCheck = false;
2136 src = fetchurl {
2136 src = fetchurl {
2137 url = "https://files.pythonhosted.org/packages/2c/b3/5d57205e896d8998d77ad12aa42ebce75cd97d8b9a97d00ba078c4c9ffeb/testpath-0.4.4.tar.gz";
2137 url = "https://files.pythonhosted.org/packages/2c/b3/5d57205e896d8998d77ad12aa42ebce75cd97d8b9a97d00ba078c4c9ffeb/testpath-0.4.4.tar.gz";
2138 sha256 = "0zpcmq22dz79ipvvsfnw1ykpjcaj6xyzy7ws77s5b5ql3hka7q30";
2138 sha256 = "0zpcmq22dz79ipvvsfnw1ykpjcaj6xyzy7ws77s5b5ql3hka7q30";
2139 };
2139 };
2140 meta = {
2140 meta = {
2141 license = [ ];
2141 license = [ ];
2142 };
2142 };
2143 };
2143 };
2144 "traitlets" = super.buildPythonPackage {
2144 "traitlets" = super.buildPythonPackage {
2145 name = "traitlets-4.3.3";
2145 name = "traitlets-4.3.3";
2146 doCheck = false;
2146 doCheck = false;
2147 propagatedBuildInputs = [
2147 propagatedBuildInputs = [
2148 self."ipython-genutils"
2148 self."ipython-genutils"
2149 self."six"
2149 self."six"
2150 self."decorator"
2150 self."decorator"
2151 self."enum34"
2151 self."enum34"
2152 ];
2152 ];
2153 src = fetchurl {
2153 src = fetchurl {
2154 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
2154 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
2155 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
2155 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
2156 };
2156 };
2157 meta = {
2157 meta = {
2158 license = [ pkgs.lib.licenses.bsdOriginal ];
2158 license = [ pkgs.lib.licenses.bsdOriginal ];
2159 };
2159 };
2160 };
2160 };
2161 "transaction" = super.buildPythonPackage {
2161 "transaction" = super.buildPythonPackage {
2162 name = "transaction-2.4.0";
2162 name = "transaction-2.4.0";
2163 doCheck = false;
2163 doCheck = false;
2164 propagatedBuildInputs = [
2164 propagatedBuildInputs = [
2165 self."zope.interface"
2165 self."zope.interface"
2166 ];
2166 ];
2167 src = fetchurl {
2167 src = fetchurl {
2168 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2168 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2169 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2169 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2170 };
2170 };
2171 meta = {
2171 meta = {
2172 license = [ pkgs.lib.licenses.zpl21 ];
2172 license = [ pkgs.lib.licenses.zpl21 ];
2173 };
2173 };
2174 };
2174 };
2175 "translationstring" = super.buildPythonPackage {
2175 "translationstring" = super.buildPythonPackage {
2176 name = "translationstring-1.3";
2176 name = "translationstring-1.3";
2177 doCheck = false;
2177 doCheck = false;
2178 src = fetchurl {
2178 src = fetchurl {
2179 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2179 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2180 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2180 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2181 };
2181 };
2182 meta = {
2182 meta = {
2183 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2183 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2184 };
2184 };
2185 };
2185 };
2186 "tzlocal" = super.buildPythonPackage {
2186 "tzlocal" = super.buildPythonPackage {
2187 name = "tzlocal-1.5.1";
2187 name = "tzlocal-1.5.1";
2188 doCheck = false;
2188 doCheck = false;
2189 propagatedBuildInputs = [
2189 propagatedBuildInputs = [
2190 self."pytz"
2190 self."pytz"
2191 ];
2191 ];
2192 src = fetchurl {
2192 src = fetchurl {
2193 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2193 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2194 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2194 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2195 };
2195 };
2196 meta = {
2196 meta = {
2197 license = [ pkgs.lib.licenses.mit ];
2197 license = [ pkgs.lib.licenses.mit ];
2198 };
2198 };
2199 };
2199 };
2200 "urllib3" = super.buildPythonPackage {
2200 "urllib3" = super.buildPythonPackage {
2201 name = "urllib3-1.25.2";
2201 name = "urllib3-1.25.2";
2202 doCheck = false;
2202 doCheck = false;
2203 src = fetchurl {
2203 src = fetchurl {
2204 url = "https://files.pythonhosted.org/packages/9a/8b/ea6d2beb2da6e331e9857d0a60b79ed4f72dcbc4e2c7f2d2521b0480fda2/urllib3-1.25.2.tar.gz";
2204 url = "https://files.pythonhosted.org/packages/9a/8b/ea6d2beb2da6e331e9857d0a60b79ed4f72dcbc4e2c7f2d2521b0480fda2/urllib3-1.25.2.tar.gz";
2205 sha256 = "1nq2k4pss1ihsjh02r41sqpjpm5rfqkjfysyq7g7n2i1p7c66c55";
2205 sha256 = "1nq2k4pss1ihsjh02r41sqpjpm5rfqkjfysyq7g7n2i1p7c66c55";
2206 };
2206 };
2207 meta = {
2207 meta = {
2208 license = [ pkgs.lib.licenses.mit ];
2208 license = [ pkgs.lib.licenses.mit ];
2209 };
2209 };
2210 };
2210 };
2211 "urlobject" = super.buildPythonPackage {
2211 "urlobject" = super.buildPythonPackage {
2212 name = "urlobject-2.4.3";
2212 name = "urlobject-2.4.3";
2213 doCheck = false;
2213 doCheck = false;
2214 src = fetchurl {
2214 src = fetchurl {
2215 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2215 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2216 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2216 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2217 };
2217 };
2218 meta = {
2218 meta = {
2219 license = [ pkgs.lib.licenses.publicDomain ];
2219 license = [ pkgs.lib.licenses.publicDomain ];
2220 };
2220 };
2221 };
2221 };
2222 "venusian" = super.buildPythonPackage {
2222 "venusian" = super.buildPythonPackage {
2223 name = "venusian-1.2.0";
2223 name = "venusian-1.2.0";
2224 doCheck = false;
2224 doCheck = false;
2225 src = fetchurl {
2225 src = fetchurl {
2226 url = "https://files.pythonhosted.org/packages/7e/6f/40a9d43ac77cb51cb62be5b5662d170f43f8037bdc4eab56336c4ca92bb7/venusian-1.2.0.tar.gz";
2226 url = "https://files.pythonhosted.org/packages/7e/6f/40a9d43ac77cb51cb62be5b5662d170f43f8037bdc4eab56336c4ca92bb7/venusian-1.2.0.tar.gz";
2227 sha256 = "0ghyx66g8ikx9nx1mnwqvdcqm11i1vlq0hnvwl50s48bp22q5v34";
2227 sha256 = "0ghyx66g8ikx9nx1mnwqvdcqm11i1vlq0hnvwl50s48bp22q5v34";
2228 };
2228 };
2229 meta = {
2229 meta = {
2230 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2230 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2231 };
2231 };
2232 };
2232 };
2233 "vine" = super.buildPythonPackage {
2233 "vine" = super.buildPythonPackage {
2234 name = "vine-1.3.0";
2234 name = "vine-1.3.0";
2235 doCheck = false;
2235 doCheck = false;
2236 src = fetchurl {
2236 src = fetchurl {
2237 url = "https://files.pythonhosted.org/packages/1c/e1/79fb8046e607dd6c2ad05c9b8ebac9d0bd31d086a08f02699e96fc5b3046/vine-1.3.0.tar.gz";
2237 url = "https://files.pythonhosted.org/packages/1c/e1/79fb8046e607dd6c2ad05c9b8ebac9d0bd31d086a08f02699e96fc5b3046/vine-1.3.0.tar.gz";
2238 sha256 = "11ydsbhl1vabndc2r979dv61s6j2b0giq6dgvryifvq1m7bycghk";
2238 sha256 = "11ydsbhl1vabndc2r979dv61s6j2b0giq6dgvryifvq1m7bycghk";
2239 };
2239 };
2240 meta = {
2240 meta = {
2241 license = [ pkgs.lib.licenses.bsdOriginal ];
2241 license = [ pkgs.lib.licenses.bsdOriginal ];
2242 };
2242 };
2243 };
2243 };
2244 "waitress" = super.buildPythonPackage {
2244 "waitress" = super.buildPythonPackage {
2245 name = "waitress-1.3.1";
2245 name = "waitress-1.3.1";
2246 doCheck = false;
2246 doCheck = false;
2247 src = fetchurl {
2247 src = fetchurl {
2248 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
2248 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
2249 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
2249 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
2250 };
2250 };
2251 meta = {
2251 meta = {
2252 license = [ pkgs.lib.licenses.zpl21 ];
2252 license = [ pkgs.lib.licenses.zpl21 ];
2253 };
2253 };
2254 };
2254 };
2255 "wcwidth" = super.buildPythonPackage {
2255 "wcwidth" = super.buildPythonPackage {
2256 name = "wcwidth-0.1.9";
2256 name = "wcwidth-0.1.9";
2257 doCheck = false;
2257 doCheck = false;
2258 src = fetchurl {
2258 src = fetchurl {
2259 url = "https://files.pythonhosted.org/packages/25/9d/0acbed6e4a4be4fc99148f275488580968f44ddb5e69b8ceb53fc9df55a0/wcwidth-0.1.9.tar.gz";
2259 url = "https://files.pythonhosted.org/packages/25/9d/0acbed6e4a4be4fc99148f275488580968f44ddb5e69b8ceb53fc9df55a0/wcwidth-0.1.9.tar.gz";
2260 sha256 = "1wf5ycjx8s066rdvr0fgz4xds9a8zhs91c4jzxvvymm1c8l8cwzf";
2260 sha256 = "1wf5ycjx8s066rdvr0fgz4xds9a8zhs91c4jzxvvymm1c8l8cwzf";
2261 };
2261 };
2262 meta = {
2262 meta = {
2263 license = [ pkgs.lib.licenses.mit ];
2263 license = [ pkgs.lib.licenses.mit ];
2264 };
2264 };
2265 };
2265 };
2266 "webencodings" = super.buildPythonPackage {
2266 "webencodings" = super.buildPythonPackage {
2267 name = "webencodings-0.5.1";
2267 name = "webencodings-0.5.1";
2268 doCheck = false;
2268 doCheck = false;
2269 src = fetchurl {
2269 src = fetchurl {
2270 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2270 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2271 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2271 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2272 };
2272 };
2273 meta = {
2273 meta = {
2274 license = [ pkgs.lib.licenses.bsdOriginal ];
2274 license = [ pkgs.lib.licenses.bsdOriginal ];
2275 };
2275 };
2276 };
2276 };
2277 "weberror" = super.buildPythonPackage {
2277 "weberror" = super.buildPythonPackage {
2278 name = "weberror-0.13.1";
2278 name = "weberror-0.13.1";
2279 doCheck = false;
2279 doCheck = false;
2280 propagatedBuildInputs = [
2280 propagatedBuildInputs = [
2281 self."webob"
2281 self."webob"
2282 self."tempita"
2282 self."tempita"
2283 self."pygments"
2283 self."pygments"
2284 self."paste"
2284 self."paste"
2285 ];
2285 ];
2286 src = fetchurl {
2286 src = fetchurl {
2287 url = "https://files.pythonhosted.org/packages/07/0a/09ca5eb0fab5c0d17b380026babe81c96ecebb13f2b06c3203432dd7be72/WebError-0.13.1.tar.gz";
2287 url = "https://files.pythonhosted.org/packages/07/0a/09ca5eb0fab5c0d17b380026babe81c96ecebb13f2b06c3203432dd7be72/WebError-0.13.1.tar.gz";
2288 sha256 = "0r4qvnf2r92gfnpa1kwygh4j2x6j3axg2i4an6hyxwg2gpaqp7y1";
2288 sha256 = "0r4qvnf2r92gfnpa1kwygh4j2x6j3axg2i4an6hyxwg2gpaqp7y1";
2289 };
2289 };
2290 meta = {
2290 meta = {
2291 license = [ pkgs.lib.licenses.mit ];
2291 license = [ pkgs.lib.licenses.mit ];
2292 };
2292 };
2293 };
2293 };
2294 "webhelpers2" = super.buildPythonPackage {
2294 "webhelpers2" = super.buildPythonPackage {
2295 name = "webhelpers2-2.0";
2295 name = "webhelpers2-2.0";
2296 doCheck = false;
2296 doCheck = false;
2297 propagatedBuildInputs = [
2297 propagatedBuildInputs = [
2298 self."markupsafe"
2298 self."markupsafe"
2299 self."six"
2299 self."six"
2300 ];
2300 ];
2301 src = fetchurl {
2301 src = fetchurl {
2302 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2302 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2303 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2303 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2304 };
2304 };
2305 meta = {
2305 meta = {
2306 license = [ pkgs.lib.licenses.mit ];
2306 license = [ pkgs.lib.licenses.mit ];
2307 };
2307 };
2308 };
2308 };
2309 "webob" = super.buildPythonPackage {
2309 "webob" = super.buildPythonPackage {
2310 name = "webob-1.8.5";
2310 name = "webob-1.8.5";
2311 doCheck = false;
2311 doCheck = false;
2312 src = fetchurl {
2312 src = fetchurl {
2313 url = "https://files.pythonhosted.org/packages/9d/1a/0c89c070ee2829c934cb6c7082287c822e28236a4fcf90063e6be7c35532/WebOb-1.8.5.tar.gz";
2313 url = "https://files.pythonhosted.org/packages/9d/1a/0c89c070ee2829c934cb6c7082287c822e28236a4fcf90063e6be7c35532/WebOb-1.8.5.tar.gz";
2314 sha256 = "11khpzaxc88q31v25ic330gsf56fwmbdc9b30br8mvp0fmwspah5";
2314 sha256 = "11khpzaxc88q31v25ic330gsf56fwmbdc9b30br8mvp0fmwspah5";
2315 };
2315 };
2316 meta = {
2316 meta = {
2317 license = [ pkgs.lib.licenses.mit ];
2317 license = [ pkgs.lib.licenses.mit ];
2318 };
2318 };
2319 };
2319 };
2320 "webtest" = super.buildPythonPackage {
2320 "webtest" = super.buildPythonPackage {
2321 name = "webtest-2.0.34";
2321 name = "webtest-2.0.34";
2322 doCheck = false;
2322 doCheck = false;
2323 propagatedBuildInputs = [
2323 propagatedBuildInputs = [
2324 self."six"
2324 self."six"
2325 self."webob"
2325 self."webob"
2326 self."waitress"
2326 self."waitress"
2327 self."beautifulsoup4"
2327 self."beautifulsoup4"
2328 ];
2328 ];
2329 src = fetchurl {
2329 src = fetchurl {
2330 url = "https://files.pythonhosted.org/packages/2c/74/a0e63feee438735d628631e2b70d82280276a930637ac535479e5fad9427/WebTest-2.0.34.tar.gz";
2330 url = "https://files.pythonhosted.org/packages/2c/74/a0e63feee438735d628631e2b70d82280276a930637ac535479e5fad9427/WebTest-2.0.34.tar.gz";
2331 sha256 = "0x1y2c8z4fmpsny4hbp6ka37si2g10r5r2jwxhvv5mx7g3blq4bi";
2331 sha256 = "0x1y2c8z4fmpsny4hbp6ka37si2g10r5r2jwxhvv5mx7g3blq4bi";
2332 };
2332 };
2333 meta = {
2333 meta = {
2334 license = [ pkgs.lib.licenses.mit ];
2334 license = [ pkgs.lib.licenses.mit ];
2335 };
2335 };
2336 };
2336 };
2337 "whoosh" = super.buildPythonPackage {
2337 "whoosh" = super.buildPythonPackage {
2338 name = "whoosh-2.7.4";
2338 name = "whoosh-2.7.4";
2339 doCheck = false;
2339 doCheck = false;
2340 src = fetchurl {
2340 src = fetchurl {
2341 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2341 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2342 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2342 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2343 };
2343 };
2344 meta = {
2344 meta = {
2345 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2345 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2346 };
2346 };
2347 };
2347 };
2348 "ws4py" = super.buildPythonPackage {
2348 "ws4py" = super.buildPythonPackage {
2349 name = "ws4py-0.5.1";
2349 name = "ws4py-0.5.1";
2350 doCheck = false;
2350 doCheck = false;
2351 src = fetchurl {
2351 src = fetchurl {
2352 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2352 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2353 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2353 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2354 };
2354 };
2355 meta = {
2355 meta = {
2356 license = [ pkgs.lib.licenses.bsdOriginal ];
2356 license = [ pkgs.lib.licenses.bsdOriginal ];
2357 };
2357 };
2358 };
2358 };
2359 "wsgiref" = super.buildPythonPackage {
2359 "wsgiref" = super.buildPythonPackage {
2360 name = "wsgiref-0.1.2";
2360 name = "wsgiref-0.1.2";
2361 doCheck = false;
2361 doCheck = false;
2362 src = fetchurl {
2362 src = fetchurl {
2363 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2363 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2364 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2364 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2365 };
2365 };
2366 meta = {
2366 meta = {
2367 license = [ { fullName = "PSF or ZPL"; } ];
2367 license = [ { fullName = "PSF or ZPL"; } ];
2368 };
2368 };
2369 };
2369 };
2370 "zipp" = super.buildPythonPackage {
2370 "zipp" = super.buildPythonPackage {
2371 name = "zipp-1.2.0";
2371 name = "zipp-1.2.0";
2372 doCheck = false;
2372 doCheck = false;
2373 propagatedBuildInputs = [
2373 propagatedBuildInputs = [
2374 self."contextlib2"
2374 self."contextlib2"
2375 ];
2375 ];
2376 src = fetchurl {
2376 src = fetchurl {
2377 url = "https://files.pythonhosted.org/packages/78/08/d52f0ea643bc1068d6dc98b412f4966a9b63255d20911a23ac3220c033c4/zipp-1.2.0.tar.gz";
2377 url = "https://files.pythonhosted.org/packages/78/08/d52f0ea643bc1068d6dc98b412f4966a9b63255d20911a23ac3220c033c4/zipp-1.2.0.tar.gz";
2378 sha256 = "1c91lnv1bxjimh8as27hz7bghsjkkbxn1d37xq7in9c82iai0167";
2378 sha256 = "1c91lnv1bxjimh8as27hz7bghsjkkbxn1d37xq7in9c82iai0167";
2379 };
2379 };
2380 meta = {
2380 meta = {
2381 license = [ pkgs.lib.licenses.mit ];
2381 license = [ pkgs.lib.licenses.mit ];
2382 };
2382 };
2383 };
2383 };
2384 "zope.cachedescriptors" = super.buildPythonPackage {
2384 "zope.cachedescriptors" = super.buildPythonPackage {
2385 name = "zope.cachedescriptors-4.3.1";
2385 name = "zope.cachedescriptors-4.3.1";
2386 doCheck = false;
2386 doCheck = false;
2387 propagatedBuildInputs = [
2387 propagatedBuildInputs = [
2388 self."setuptools"
2388 self."setuptools"
2389 ];
2389 ];
2390 src = fetchurl {
2390 src = fetchurl {
2391 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2391 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2392 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2392 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2393 };
2393 };
2394 meta = {
2394 meta = {
2395 license = [ pkgs.lib.licenses.zpl21 ];
2395 license = [ pkgs.lib.licenses.zpl21 ];
2396 };
2396 };
2397 };
2397 };
2398 "zope.deprecation" = super.buildPythonPackage {
2398 "zope.deprecation" = super.buildPythonPackage {
2399 name = "zope.deprecation-4.4.0";
2399 name = "zope.deprecation-4.4.0";
2400 doCheck = false;
2400 doCheck = false;
2401 propagatedBuildInputs = [
2401 propagatedBuildInputs = [
2402 self."setuptools"
2402 self."setuptools"
2403 ];
2403 ];
2404 src = fetchurl {
2404 src = fetchurl {
2405 url = "https://files.pythonhosted.org/packages/34/da/46e92d32d545dd067b9436279d84c339e8b16de2ca393d7b892bc1e1e9fd/zope.deprecation-4.4.0.tar.gz";
2405 url = "https://files.pythonhosted.org/packages/34/da/46e92d32d545dd067b9436279d84c339e8b16de2ca393d7b892bc1e1e9fd/zope.deprecation-4.4.0.tar.gz";
2406 sha256 = "1pz2cv7gv9y1r3m0bdv7ks1alagmrn5msm5spwdzkb2by0w36i8d";
2406 sha256 = "1pz2cv7gv9y1r3m0bdv7ks1alagmrn5msm5spwdzkb2by0w36i8d";
2407 };
2407 };
2408 meta = {
2408 meta = {
2409 license = [ pkgs.lib.licenses.zpl21 ];
2409 license = [ pkgs.lib.licenses.zpl21 ];
2410 };
2410 };
2411 };
2411 };
2412 "zope.event" = super.buildPythonPackage {
2412 "zope.event" = super.buildPythonPackage {
2413 name = "zope.event-4.4";
2413 name = "zope.event-4.4";
2414 doCheck = false;
2414 doCheck = false;
2415 propagatedBuildInputs = [
2415 propagatedBuildInputs = [
2416 self."setuptools"
2416 self."setuptools"
2417 ];
2417 ];
2418 src = fetchurl {
2418 src = fetchurl {
2419 url = "https://files.pythonhosted.org/packages/4c/b2/51c0369adcf5be2334280eed230192ab3b03f81f8efda9ddea6f65cc7b32/zope.event-4.4.tar.gz";
2419 url = "https://files.pythonhosted.org/packages/4c/b2/51c0369adcf5be2334280eed230192ab3b03f81f8efda9ddea6f65cc7b32/zope.event-4.4.tar.gz";
2420 sha256 = "1ksbc726av9xacml6jhcfyn828hlhb9xlddpx6fcvnlvmpmpvhk9";
2420 sha256 = "1ksbc726av9xacml6jhcfyn828hlhb9xlddpx6fcvnlvmpmpvhk9";
2421 };
2421 };
2422 meta = {
2422 meta = {
2423 license = [ pkgs.lib.licenses.zpl21 ];
2423 license = [ pkgs.lib.licenses.zpl21 ];
2424 };
2424 };
2425 };
2425 };
2426 "zope.interface" = super.buildPythonPackage {
2426 "zope.interface" = super.buildPythonPackage {
2427 name = "zope.interface-4.6.0";
2427 name = "zope.interface-4.6.0";
2428 doCheck = false;
2428 doCheck = false;
2429 propagatedBuildInputs = [
2429 propagatedBuildInputs = [
2430 self."setuptools"
2430 self."setuptools"
2431 ];
2431 ];
2432 src = fetchurl {
2432 src = fetchurl {
2433 url = "https://files.pythonhosted.org/packages/4e/d0/c9d16bd5b38de44a20c6dc5d5ed80a49626fafcb3db9f9efdc2a19026db6/zope.interface-4.6.0.tar.gz";
2433 url = "https://files.pythonhosted.org/packages/4e/d0/c9d16bd5b38de44a20c6dc5d5ed80a49626fafcb3db9f9efdc2a19026db6/zope.interface-4.6.0.tar.gz";
2434 sha256 = "1rgh2x3rcl9r0v0499kf78xy86rnmanajf4ywmqb943wpk50sg8v";
2434 sha256 = "1rgh2x3rcl9r0v0499kf78xy86rnmanajf4ywmqb943wpk50sg8v";
2435 };
2435 };
2436 meta = {
2436 meta = {
2437 license = [ pkgs.lib.licenses.zpl21 ];
2437 license = [ pkgs.lib.licenses.zpl21 ];
2438 };
2438 };
2439 };
2439 };
2440
2440
2441 ### Test requirements
2441 ### Test requirements
2442
2442
2443
2443
2444 }
2444 }
@@ -1,19 +1,21 b''
1 [pytest]
1 [pytest]
2 testpaths = rhodecode
2 testpaths = rhodecode
3 norecursedirs = rhodecode/public rhodecode/templates tests/scripts
3 norecursedirs = rhodecode/public rhodecode/templates tests/scripts
4 cache_dir = /tmp/.pytest_cache
4 cache_dir = /tmp/.pytest_cache
5
5
6 pyramid_config = rhodecode/tests/rhodecode.ini
6 pyramid_config = rhodecode/tests/rhodecode.ini
7 vcsserver_protocol = http
7 vcsserver_protocol = http
8 vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
8 vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
9
9
10 addopts =
10 addopts =
11 --pdbcls=IPython.terminal.debugger:TerminalPdb
11 --pdbcls=IPython.terminal.debugger:TerminalPdb
12 --strict-markers
12 --strict-markers
13 --capture=no
14 --show-capture=no
13
15
14 markers =
16 markers =
15 vcs_operations: Mark tests depending on a running RhodeCode instance.
17 vcs_operations: Mark tests depending on a running RhodeCode instance.
16 xfail_backends: Mark tests as xfail for given backends.
18 xfail_backends: Mark tests as xfail for given backends.
17 skip_backends: Mark tests as skipped for given backends.
19 skip_backends: Mark tests as skipped for given backends.
18 backends: Mark backends
20 backends: Mark backends
19 dbs: database markers for running tests for given DB
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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 from collections import OrderedDict
22 from collections import OrderedDict
23
23
24 import sys
24 import sys
25 import platform
25 import platform
26
26
27 VERSION = tuple(open(os.path.join(
27 VERSION = tuple(open(os.path.join(
28 os.path.dirname(__file__), 'VERSION')).read().split('.'))
28 os.path.dirname(__file__), 'VERSION')).read().split('.'))
29
29
30 BACKENDS = OrderedDict()
30 BACKENDS = OrderedDict()
31
31
32 BACKENDS['hg'] = 'Mercurial repository'
32 BACKENDS['hg'] = 'Mercurial repository'
33 BACKENDS['git'] = 'Git repository'
33 BACKENDS['git'] = 'Git repository'
34 BACKENDS['svn'] = 'Subversion repository'
34 BACKENDS['svn'] = 'Subversion repository'
35
35
36
36
37 CELERY_ENABLED = False
37 CELERY_ENABLED = False
38 CELERY_EAGER = False
38 CELERY_EAGER = False
39
39
40 # link to config for pyramid
40 # link to config for pyramid
41 CONFIG = {}
41 CONFIG = {}
42
42
43 # Populated with the settings dictionary from application init in
43 # Populated with the settings dictionary from application init in
44 # rhodecode.conf.environment.load_pyramid_environment
44 # rhodecode.conf.environment.load_pyramid_environment
45 PYRAMID_SETTINGS = {}
45 PYRAMID_SETTINGS = {}
46
46
47 # Linked module for extensions
47 # Linked module for extensions
48 EXTENSIONS = {}
48 EXTENSIONS = {}
49
49
50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
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 __platform__ = platform.system()
52 __platform__ = platform.system()
53 __license__ = 'AGPLv3, and Commercial License'
53 __license__ = 'AGPLv3, and Commercial License'
54 __author__ = 'RhodeCode GmbH'
54 __author__ = 'RhodeCode GmbH'
55 __url__ = 'https://code.rhodecode.com'
55 __url__ = 'https://code.rhodecode.com'
56
56
57 is_windows = __platform__ in ['Windows']
57 is_windows = __platform__ in ['Windows']
58 is_unix = not is_windows
58 is_unix = not is_windows
59 is_test = False
59 is_test = False
60 disable_error_handler = False
60 disable_error_handler = False
@@ -1,133 +1,134 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.utils import Optional, OAttr
23 from rhodecode.api.utils import Optional, OAttr
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok)
25 build_data, api_call, assert_error, assert_ok)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApi(object):
29 class TestApi(object):
30 maxDiff = None
30 maxDiff = None
31
31
32 def test_Optional_object(self):
32 def test_Optional_object(self):
33
33
34 option1 = Optional(None)
34 option1 = Optional(None)
35 assert '<Optional:%s>' % (None,) == repr(option1)
35 assert '<Optional:%s>' % (None,) == repr(option1)
36 assert option1() is None
36 assert option1() is None
37
37
38 assert 1 == Optional.extract(Optional(1))
38 assert 1 == Optional.extract(Optional(1))
39 assert 'example' == Optional.extract('example')
39 assert 'example' == Optional.extract('example')
40
40
41 def test_Optional_OAttr(self):
41 def test_Optional_OAttr(self):
42 option1 = Optional(OAttr('apiuser'))
42 option1 = Optional(OAttr('apiuser'))
43 assert 'apiuser' == Optional.extract(option1)
43 assert 'apiuser' == Optional.extract(option1)
44
44
45 def test_OAttr_object(self):
45 def test_OAttr_object(self):
46 oattr1 = OAttr('apiuser')
46 oattr1 = OAttr('apiuser')
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 assert oattr1() == oattr1
48 assert oattr1() == oattr1
49
49
50 def test_api_wrong_key(self):
50 def test_api_wrong_key(self):
51 id_, params = build_data('trololo', 'get_user')
51 id_, params = build_data('trololo', 'get_user')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 expected = 'Invalid API KEY'
54 expected = 'Invalid API KEY'
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 def test_api_missing_non_optional_param(self):
57 def test_api_missing_non_optional_param(self):
58 id_, params = build_data(self.apikey, 'get_repo')
58 id_, params = build_data(self.apikey, 'get_repo')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
63
63
64 def test_api_missing_non_optional_param_args_null(self):
64 def test_api_missing_non_optional_param_args_null(self):
65 id_, params = build_data(self.apikey, 'get_repo')
65 id_, params = build_data(self.apikey, 'get_repo')
66 params = params.replace('"args": {}', '"args": null')
66 params = params.replace('"args": {}', '"args": null')
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 def test_api_missing_non_optional_param_args_bad(self):
72 def test_api_missing_non_optional_param_args_bad(self):
73 id_, params = build_data(self.apikey, 'get_repo')
73 id_, params = build_data(self.apikey, 'get_repo')
74 params = params.replace('"args": {}', '"args": 1')
74 params = params.replace('"args": {}', '"args": 1')
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76
76
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 assert_error(id_, expected, given=response.body)
78 assert_error(id_, expected, given=response.body)
79
79
80 def test_api_non_existing_method(self, request):
80 def test_api_non_existing_method(self, request):
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = 'No such method: not_existing. Similar methods: none'
83 expected = 'No such method: not_existing. Similar methods: none'
84 assert_error(id_, expected, given=response.body)
84 assert_error(id_, expected, given=response.body)
85
85
86 def test_api_non_existing_method_have_similar(self, request):
86 def test_api_non_existing_method_have_similar(self, request):
87 id_, params = build_data(self.apikey, 'comment', args='xx')
87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 expected = 'No such method: comment. ' \
89 expected = 'No such method: comment. ' \
90 'Similar methods: changeset_comment, comment_pull_request, ' \
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 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
93
94
94 def test_api_disabled_user(self, request):
95 def test_api_disabled_user(self, request):
95
96
96 def set_active(active):
97 def set_active(active):
97 from rhodecode.model.db import Session, User
98 from rhodecode.model.db import Session, User
98 user = User.get_by_auth_token(self.apikey)
99 user = User.get_by_auth_token(self.apikey)
99 user.active = active
100 user.active = active
100 Session().add(user)
101 Session().add(user)
101 Session().commit()
102 Session().commit()
102
103
103 request.addfinalizer(lambda: set_active(True))
104 request.addfinalizer(lambda: set_active(True))
104
105
105 set_active(False)
106 set_active(False)
106 id_, params = build_data(self.apikey, 'test', args='xx')
107 id_, params = build_data(self.apikey, 'test', args='xx')
107 response = api_call(self.app, params)
108 response = api_call(self.app, params)
108 expected = 'Request from this user not allowed'
109 expected = 'Request from this user not allowed'
109 assert_error(id_, expected, given=response.body)
110 assert_error(id_, expected, given=response.body)
110
111
111 def test_api_args_is_null(self):
112 def test_api_args_is_null(self):
112 __, params = build_data(self.apikey, 'get_users', )
113 __, params = build_data(self.apikey, 'get_users', )
113 params = params.replace('"args": {}', '"args": null')
114 params = params.replace('"args": {}', '"args": null')
114 response = api_call(self.app, params)
115 response = api_call(self.app, params)
115 assert response.status == '200 OK'
116 assert response.status == '200 OK'
116
117
117 def test_api_args_is_bad(self):
118 def test_api_args_is_bad(self):
118 __, params = build_data(self.apikey, 'get_users', )
119 __, params = build_data(self.apikey, 'get_users', )
119 params = params.replace('"args": {}', '"args": 1')
120 params = params.replace('"args": {}', '"args": 1')
120 response = api_call(self.app, params)
121 response = api_call(self.app, params)
121 assert response.status == '200 OK'
122 assert response.status == '200 OK'
122
123
123 def test_api_args_different_args(self):
124 def test_api_args_different_args(self):
124 import string
125 import string
125 expected = {
126 expected = {
126 'ascii_letters': string.ascii_letters,
127 'ascii_letters': string.ascii_letters,
127 'ws': string.whitespace,
128 'ws': string.whitespace,
128 'printables': string.printable
129 'printables': string.printable
129 }
130 }
130 id_, params = build_data(self.apikey, 'test', args=expected)
131 id_, params = build_data(self.apikey, 'test', args=expected)
131 response = api_call(self.app, params)
132 response = api_call(self.app, params)
132 assert response.status == '200 OK'
133 assert response.status == '200 OK'
133 assert_ok(id_, expected, response.body)
134 assert_ok(id_, expected, response.body)
@@ -1,246 +1,390 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.comment import CommentsModel
23 from rhodecode.model.comment import CommentsModel
24 from rhodecode.model.db import UserLog, User
24 from rhodecode.model.db import UserLog, User, ChangesetComment
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCommentPullRequest(object):
32 class TestCommentPullRequest(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 @pytest.mark.backends("git", "hg")
41 @pytest.mark.backends("git", "hg")
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 pull_request = pr_util.create_pull_request()
43 pull_request = pr_util.create_pull_request()
44 pull_request_id = pull_request.pull_request_id
44 pull_request_id = pull_request.pull_request_id
45 author = pull_request.user_id
45 author = pull_request.user_id
46 repo = pull_request.target_repo.repo_id
46 repo = pull_request.target_repo.repo_id
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'comment_pull_request',
48 self.apikey, 'comment_pull_request',
49 repoid=pull_request.target_repo.repo_name,
49 repoid=pull_request.target_repo.repo_name,
50 pullrequestid=pull_request.pull_request_id,
50 pullrequestid=pull_request.pull_request_id,
51 message='test message')
51 message='test message')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54
54
55 comments = CommentsModel().get_comments(
55 comments = CommentsModel().get_comments(
56 pull_request.target_repo.repo_id, pull_request=pull_request)
56 pull_request.target_repo.repo_id, pull_request=pull_request)
57
57
58 expected = {
58 expected = {
59 'pull_request_id': pull_request.pull_request_id,
59 'pull_request_id': pull_request.pull_request_id,
60 'comment_id': comments[-1].comment_id,
60 'comment_id': comments[-1].comment_id,
61 'status': {'given': None, 'was_changed': None}
61 'status': {'given': None, 'was_changed': None}
62 }
62 }
63 assert_ok(id_, expected, response.body)
63 assert_ok(id_, expected, response.body)
64
64
65 journal = UserLog.query()\
65 journal = UserLog.query()\
66 .filter(UserLog.user_id == author)\
66 .filter(UserLog.user_id == author)\
67 .filter(UserLog.repository_id == repo) \
67 .filter(UserLog.repository_id == repo) \
68 .order_by(UserLog.user_log_id.asc()) \
68 .order_by(UserLog.user_log_id.asc()) \
69 .all()
69 .all()
70 assert journal[-1].action == 'repo.pull_request.comment.create'
70 assert journal[-1].action == 'repo.pull_request.comment.create'
71
71
72 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
73 def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util):
73 def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util):
74 pull_request = pr_util.create_pull_request()
74 pull_request = pr_util.create_pull_request()
75
75
76 user1 = user_util.create_user()
76 user1 = user_util.create_user()
77 user1_id = user1.user_id
77 user1_id = user1.user_id
78 user2 = user_util.create_user()
78 user2 = user_util.create_user()
79 user2_id = user2.user_id
79 user2_id = user2.user_id
80
80
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey, 'comment_pull_request',
82 self.apikey, 'comment_pull_request',
83 repoid=pull_request.target_repo.repo_name,
83 repoid=pull_request.target_repo.repo_name,
84 pullrequestid=pull_request.pull_request_id,
84 pullrequestid=pull_request.pull_request_id,
85 message='test message',
85 message='test message',
86 extra_recipients=[user1.user_id, user2.username]
86 extra_recipients=[user1.user_id, user2.username]
87 )
87 )
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 pull_request = PullRequestModel().get(pull_request.pull_request_id)
89 pull_request = PullRequestModel().get(pull_request.pull_request_id)
90
90
91 comments = CommentsModel().get_comments(
91 comments = CommentsModel().get_comments(
92 pull_request.target_repo.repo_id, pull_request=pull_request)
92 pull_request.target_repo.repo_id, pull_request=pull_request)
93
93
94 expected = {
94 expected = {
95 'pull_request_id': pull_request.pull_request_id,
95 'pull_request_id': pull_request.pull_request_id,
96 'comment_id': comments[-1].comment_id,
96 'comment_id': comments[-1].comment_id,
97 'status': {'given': None, 'was_changed': None}
97 'status': {'given': None, 'was_changed': None}
98 }
98 }
99 assert_ok(id_, expected, response.body)
99 assert_ok(id_, expected, response.body)
100 # check user1/user2 inbox for notification
100 # check user1/user2 inbox for notification
101 user1 = User.get(user1_id)
101 user1 = User.get(user1_id)
102 assert 1 == len(user1.notifications)
102 assert 1 == len(user1.notifications)
103 assert 'test message' in user1.notifications[0].notification.body
103 assert 'test message' in user1.notifications[0].notification.body
104
104
105 user2 = User.get(user2_id)
105 user2 = User.get(user2_id)
106 assert 1 == len(user2.notifications)
106 assert 1 == len(user2.notifications)
107 assert 'test message' in user2.notifications[0].notification.body
107 assert 'test message' in user2.notifications[0].notification.body
108
108
109 @pytest.mark.backends("git", "hg")
109 @pytest.mark.backends("git", "hg")
110 def test_api_comment_pull_request_change_status(
110 def test_api_comment_pull_request_change_status(
111 self, pr_util, no_notifications):
111 self, pr_util, no_notifications):
112 pull_request = pr_util.create_pull_request()
112 pull_request = pr_util.create_pull_request()
113 pull_request_id = pull_request.pull_request_id
113 pull_request_id = pull_request.pull_request_id
114 id_, params = build_data(
114 id_, params = build_data(
115 self.apikey, 'comment_pull_request',
115 self.apikey, 'comment_pull_request',
116 repoid=pull_request.target_repo.repo_name,
116 repoid=pull_request.target_repo.repo_name,
117 pullrequestid=pull_request.pull_request_id,
117 pullrequestid=pull_request.pull_request_id,
118 status='rejected')
118 status='rejected')
119 response = api_call(self.app, params)
119 response = api_call(self.app, params)
120 pull_request = PullRequestModel().get(pull_request_id)
120 pull_request = PullRequestModel().get(pull_request_id)
121
121
122 comments = CommentsModel().get_comments(
122 comments = CommentsModel().get_comments(
123 pull_request.target_repo.repo_id, pull_request=pull_request)
123 pull_request.target_repo.repo_id, pull_request=pull_request)
124 expected = {
124 expected = {
125 'pull_request_id': pull_request.pull_request_id,
125 'pull_request_id': pull_request.pull_request_id,
126 'comment_id': comments[-1].comment_id,
126 'comment_id': comments[-1].comment_id,
127 'status': {'given': 'rejected', 'was_changed': True}
127 'status': {'given': 'rejected', 'was_changed': True}
128 }
128 }
129 assert_ok(id_, expected, response.body)
129 assert_ok(id_, expected, response.body)
130
130
131 @pytest.mark.backends("git", "hg")
131 @pytest.mark.backends("git", "hg")
132 def test_api_comment_pull_request_change_status_with_specific_commit_id(
132 def test_api_comment_pull_request_change_status_with_specific_commit_id(
133 self, pr_util, no_notifications):
133 self, pr_util, no_notifications):
134 pull_request = pr_util.create_pull_request()
134 pull_request = pr_util.create_pull_request()
135 pull_request_id = pull_request.pull_request_id
135 pull_request_id = pull_request.pull_request_id
136 latest_commit_id = 'test_commit'
136 latest_commit_id = 'test_commit'
137 # inject additional revision, to fail test the status change on
137 # inject additional revision, to fail test the status change on
138 # non-latest commit
138 # non-latest commit
139 pull_request.revisions = pull_request.revisions + ['test_commit']
139 pull_request.revisions = pull_request.revisions + ['test_commit']
140
140
141 id_, params = build_data(
141 id_, params = build_data(
142 self.apikey, 'comment_pull_request',
142 self.apikey, 'comment_pull_request',
143 repoid=pull_request.target_repo.repo_name,
143 repoid=pull_request.target_repo.repo_name,
144 pullrequestid=pull_request.pull_request_id,
144 pullrequestid=pull_request.pull_request_id,
145 status='approved', commit_id=latest_commit_id)
145 status='approved', commit_id=latest_commit_id)
146 response = api_call(self.app, params)
146 response = api_call(self.app, params)
147 pull_request = PullRequestModel().get(pull_request_id)
147 pull_request = PullRequestModel().get(pull_request_id)
148
148
149 expected = {
149 expected = {
150 'pull_request_id': pull_request.pull_request_id,
150 'pull_request_id': pull_request.pull_request_id,
151 'comment_id': None,
151 'comment_id': None,
152 'status': {'given': 'approved', 'was_changed': False}
152 'status': {'given': 'approved', 'was_changed': False}
153 }
153 }
154 assert_ok(id_, expected, response.body)
154 assert_ok(id_, expected, response.body)
155
155
156 @pytest.mark.backends("git", "hg")
156 @pytest.mark.backends("git", "hg")
157 def test_api_comment_pull_request_change_status_with_specific_commit_id(
157 def test_api_comment_pull_request_change_status_with_specific_commit_id(
158 self, pr_util, no_notifications):
158 self, pr_util, no_notifications):
159 pull_request = pr_util.create_pull_request()
159 pull_request = pr_util.create_pull_request()
160 pull_request_id = pull_request.pull_request_id
160 pull_request_id = pull_request.pull_request_id
161 latest_commit_id = pull_request.revisions[0]
161 latest_commit_id = pull_request.revisions[0]
162
162
163 id_, params = build_data(
163 id_, params = build_data(
164 self.apikey, 'comment_pull_request',
164 self.apikey, 'comment_pull_request',
165 repoid=pull_request.target_repo.repo_name,
165 repoid=pull_request.target_repo.repo_name,
166 pullrequestid=pull_request.pull_request_id,
166 pullrequestid=pull_request.pull_request_id,
167 status='approved', commit_id=latest_commit_id)
167 status='approved', commit_id=latest_commit_id)
168 response = api_call(self.app, params)
168 response = api_call(self.app, params)
169 pull_request = PullRequestModel().get(pull_request_id)
169 pull_request = PullRequestModel().get(pull_request_id)
170
170
171 comments = CommentsModel().get_comments(
171 comments = CommentsModel().get_comments(
172 pull_request.target_repo.repo_id, pull_request=pull_request)
172 pull_request.target_repo.repo_id, pull_request=pull_request)
173 expected = {
173 expected = {
174 'pull_request_id': pull_request.pull_request_id,
174 'pull_request_id': pull_request.pull_request_id,
175 'comment_id': comments[-1].comment_id,
175 'comment_id': comments[-1].comment_id,
176 'status': {'given': 'approved', 'was_changed': True}
176 'status': {'given': 'approved', 'was_changed': True}
177 }
177 }
178 assert_ok(id_, expected, response.body)
178 assert_ok(id_, expected, response.body)
179
179
180 @pytest.mark.backends("git", "hg")
180 @pytest.mark.backends("git", "hg")
181 def test_api_comment_pull_request_missing_params_error(self, pr_util):
181 def test_api_comment_pull_request_missing_params_error(self, pr_util):
182 pull_request = pr_util.create_pull_request()
182 pull_request = pr_util.create_pull_request()
183 pull_request_id = pull_request.pull_request_id
183 pull_request_id = pull_request.pull_request_id
184 pull_request_repo = pull_request.target_repo.repo_name
184 pull_request_repo = pull_request.target_repo.repo_name
185 id_, params = build_data(
185 id_, params = build_data(
186 self.apikey, 'comment_pull_request',
186 self.apikey, 'comment_pull_request',
187 repoid=pull_request_repo,
187 repoid=pull_request_repo,
188 pullrequestid=pull_request_id)
188 pullrequestid=pull_request_id)
189 response = api_call(self.app, params)
189 response = api_call(self.app, params)
190
190
191 expected = 'Both message and status parameters are missing. At least one is required.'
191 expected = 'Both message and status parameters are missing. At least one is required.'
192 assert_error(id_, expected, given=response.body)
192 assert_error(id_, expected, given=response.body)
193
193
194 @pytest.mark.backends("git", "hg")
194 @pytest.mark.backends("git", "hg")
195 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
195 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
196 pull_request = pr_util.create_pull_request()
196 pull_request = pr_util.create_pull_request()
197 pull_request_id = pull_request.pull_request_id
197 pull_request_id = pull_request.pull_request_id
198 pull_request_repo = pull_request.target_repo.repo_name
198 pull_request_repo = pull_request.target_repo.repo_name
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'comment_pull_request',
200 self.apikey, 'comment_pull_request',
201 repoid=pull_request_repo,
201 repoid=pull_request_repo,
202 pullrequestid=pull_request_id,
202 pullrequestid=pull_request_id,
203 status='42')
203 status='42')
204 response = api_call(self.app, params)
204 response = api_call(self.app, params)
205
205
206 expected = 'Unknown comment status: `42`'
206 expected = 'Unknown comment status: `42`'
207 assert_error(id_, expected, given=response.body)
207 assert_error(id_, expected, given=response.body)
208
208
209 @pytest.mark.backends("git", "hg")
209 @pytest.mark.backends("git", "hg")
210 def test_api_comment_pull_request_repo_error(self, pr_util):
210 def test_api_comment_pull_request_repo_error(self, pr_util):
211 pull_request = pr_util.create_pull_request()
211 pull_request = pr_util.create_pull_request()
212 id_, params = build_data(
212 id_, params = build_data(
213 self.apikey, 'comment_pull_request',
213 self.apikey, 'comment_pull_request',
214 repoid=666, pullrequestid=pull_request.pull_request_id)
214 repoid=666, pullrequestid=pull_request.pull_request_id)
215 response = api_call(self.app, params)
215 response = api_call(self.app, params)
216
216
217 expected = 'repository `666` does not exist'
217 expected = 'repository `666` does not exist'
218 assert_error(id_, expected, given=response.body)
218 assert_error(id_, expected, given=response.body)
219
219
220 @pytest.mark.backends("git", "hg")
220 @pytest.mark.backends("git", "hg")
221 def test_api_comment_pull_request_non_admin_with_userid_error(
221 def test_api_comment_pull_request_non_admin_with_userid_error(self, pr_util):
222 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 pull_request = pr_util.create_pull_request()
235 pull_request = pr_util.create_pull_request()
224 id_, params = build_data(
236 id_, params = build_data(
225 self.apikey_regular, 'comment_pull_request',
237 self.apikey_regular, 'comment_pull_request',
226 repoid=pull_request.target_repo.repo_name,
238 repoid=pull_request.target_repo.repo_name,
227 pullrequestid=pull_request.pull_request_id,
239 pullrequestid=pull_request.pull_request_id,
228 userid=TEST_USER_ADMIN_LOGIN)
240 userid=TEST_USER_ADMIN_LOGIN)
229 response = api_call(self.app, params)
241 response = api_call(self.app, params)
230
242
231 expected = 'userid is not the same as your user'
243 expected = 'userid is not the same as your user'
232 assert_error(id_, expected, given=response.body)
244 assert_error(id_, expected, given=response.body)
233
245
234 @pytest.mark.backends("git", "hg")
246 @pytest.mark.backends("git", "hg")
235 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
247 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
236 pull_request = pr_util.create_pull_request()
248 pull_request = pr_util.create_pull_request()
237 id_, params = build_data(
249 id_, params = build_data(
238 self.apikey_regular, 'comment_pull_request',
250 self.apikey_regular, 'comment_pull_request',
239 repoid=pull_request.target_repo.repo_name,
251 repoid=pull_request.target_repo.repo_name,
240 status='approved',
252 status='approved',
241 pullrequestid=pull_request.pull_request_id,
253 pullrequestid=pull_request.pull_request_id,
242 commit_id='XXX')
254 commit_id='XXX')
243 response = api_call(self.app, params)
255 response = api_call(self.app, params)
244
256
245 expected = 'Invalid commit_id `XXX` for this pull request.'
257 expected = 'Invalid commit_id `XXX` for this pull request.'
246 assert_error(id_, expected, given=response.body)
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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, crash)
29 build_data, api_call, assert_ok, assert_error, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31
31
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 @pytest.mark.usefixtures("testuser_api", "app")
36 @pytest.mark.usefixtures("testuser_api", "app")
37 class TestCreateRepoGroup(object):
37 class TestCreateRepoGroup(object):
38 def test_api_create_repo_group(self):
38 def test_api_create_repo_group(self):
39 repo_group_name = 'api-repo-group'
39 repo_group_name = 'api-repo-group'
40
40
41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
42 assert repo_group is None
42 assert repo_group is None
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey, 'create_repo_group',
45 self.apikey, 'create_repo_group',
46 group_name=repo_group_name,
46 group_name=repo_group_name,
47 owner=TEST_USER_ADMIN_LOGIN,)
47 owner=TEST_USER_ADMIN_LOGIN,)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
51 assert repo_group is not None
51 assert repo_group is not None
52 ret = {
52 ret = {
53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
54 'repo_group': repo_group.get_api_data()
54 'repo_group': repo_group.get_api_data()
55 }
55 }
56 expected = ret
56 expected = ret
57 try:
57 try:
58 assert_ok(id_, expected, given=response.body)
58 assert_ok(id_, expected, given=response.body)
59 finally:
59 finally:
60 fixture.destroy_repo_group(repo_group_name)
60 fixture.destroy_repo_group(repo_group_name)
61
61
62 def test_api_create_repo_group_in_another_group(self):
62 def test_api_create_repo_group_in_another_group(self):
63 repo_group_name = 'api-repo-group'
63 repo_group_name = 'api-repo-group'
64
64
65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
66 assert repo_group is None
66 assert repo_group is None
67 # create the parent
67 # create the parent
68 fixture.create_repo_group(repo_group_name)
68 fixture.create_repo_group(repo_group_name)
69
69
70 full_repo_group_name = repo_group_name+'/'+repo_group_name
70 full_repo_group_name = repo_group_name+'/'+repo_group_name
71 id_, params = build_data(
71 id_, params = build_data(
72 self.apikey, 'create_repo_group',
72 self.apikey, 'create_repo_group',
73 group_name=full_repo_group_name,
73 group_name=full_repo_group_name,
74 owner=TEST_USER_ADMIN_LOGIN,
74 owner=TEST_USER_ADMIN_LOGIN,
75 copy_permissions=True)
75 copy_permissions=True)
76 response = api_call(self.app, params)
76 response = api_call(self.app, params)
77
77
78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
79 assert repo_group is not None
79 assert repo_group is not None
80 ret = {
80 ret = {
81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
82 'repo_group': repo_group.get_api_data()
82 'repo_group': repo_group.get_api_data()
83 }
83 }
84 expected = ret
84 expected = ret
85 try:
85 try:
86 assert_ok(id_, expected, given=response.body)
86 assert_ok(id_, expected, given=response.body)
87 finally:
87 finally:
88 fixture.destroy_repo_group(full_repo_group_name)
88 fixture.destroy_repo_group(full_repo_group_name)
89 fixture.destroy_repo_group(repo_group_name)
89 fixture.destroy_repo_group(repo_group_name)
90
90
91 def test_api_create_repo_group_in_another_group_not_existing(self):
91 def test_api_create_repo_group_in_another_group_not_existing(self):
92 repo_group_name = 'api-repo-group-no'
92 repo_group_name = 'api-repo-group-no'
93
93
94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
95 assert repo_group is None
95 assert repo_group is None
96
96
97 full_repo_group_name = repo_group_name+'/'+repo_group_name
97 full_repo_group_name = repo_group_name+'/'+repo_group_name
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'create_repo_group',
99 self.apikey, 'create_repo_group',
100 group_name=full_repo_group_name,
100 group_name=full_repo_group_name,
101 owner=TEST_USER_ADMIN_LOGIN,
101 owner=TEST_USER_ADMIN_LOGIN,
102 copy_permissions=True)
102 copy_permissions=True)
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104 expected = {
104 expected = {
105 'repo_group':
105 'repo_group':
106 'Parent repository group `{}` does not exist'.format(
106 'Parent repository group `{}` does not exist'.format(
107 repo_group_name)}
107 repo_group_name)}
108 assert_error(id_, expected, given=response.body)
108 assert_error(id_, expected, given=response.body)
109
109
110 def test_api_create_repo_group_that_exists(self):
110 def test_api_create_repo_group_that_exists(self):
111 repo_group_name = 'api-repo-group'
111 repo_group_name = 'api-repo-group'
112
112
113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
114 assert repo_group is None
114 assert repo_group is None
115
115
116 fixture.create_repo_group(repo_group_name)
116 fixture.create_repo_group(repo_group_name)
117 id_, params = build_data(
117 id_, params = build_data(
118 self.apikey, 'create_repo_group',
118 self.apikey, 'create_repo_group',
119 group_name=repo_group_name,
119 group_name=repo_group_name,
120 owner=TEST_USER_ADMIN_LOGIN,)
120 owner=TEST_USER_ADMIN_LOGIN,)
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122 expected = {
122 expected = {
123 'unique_repo_group_name':
123 'unique_repo_group_name':
124 'Repository group with name `{}` already exists'.format(
124 'Repository group with name `{}` already exists'.format(
125 repo_group_name)}
125 repo_group_name)}
126 try:
126 try:
127 assert_error(id_, expected, given=response.body)
127 assert_error(id_, expected, given=response.body)
128 finally:
128 finally:
129 fixture.destroy_repo_group(repo_group_name)
129 fixture.destroy_repo_group(repo_group_name)
130
130
131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
132 self, user_util):
132 self, user_util):
133 regular_user = user_util.create_user()
133 regular_user = user_util.create_user()
134 regular_user_api_key = regular_user.api_key
134 regular_user_api_key = regular_user.api_key
135
135
136 repo_group_name = 'api-repo-group-by-regular-user'
136 repo_group_name = 'api-repo-group-by-regular-user'
137
137
138 usr = UserModel().get_by_username(regular_user.username)
138 usr = UserModel().get_by_username(regular_user.username)
139 usr.inherit_default_permissions = False
139 usr.inherit_default_permissions = False
140 Session().add(usr)
140 Session().add(usr)
141
141
142 UserModel().grant_perm(
142 UserModel().grant_perm(
143 regular_user.username, 'hg.repogroup.create.true')
143 regular_user.username, 'hg.repogroup.create.true')
144 Session().commit()
144 Session().commit()
145
145
146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
147 assert repo_group is None
147 assert repo_group is None
148
148
149 id_, params = build_data(
149 id_, params = build_data(
150 regular_user_api_key, 'create_repo_group',
150 regular_user_api_key, 'create_repo_group',
151 group_name=repo_group_name)
151 group_name=repo_group_name)
152 response = api_call(self.app, params)
152 response = api_call(self.app, params)
153
153
154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
155 assert repo_group is not None
155 assert repo_group is not None
156 expected = {
156 expected = {
157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
158 'repo_group': repo_group.get_api_data()
158 'repo_group': repo_group.get_api_data()
159 }
159 }
160 try:
160 try:
161 assert_ok(id_, expected, given=response.body)
161 assert_ok(id_, expected, given=response.body)
162 finally:
162 finally:
163 fixture.destroy_repo_group(repo_group_name)
163 fixture.destroy_repo_group(repo_group_name)
164
164
165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
166 self, user_util):
166 self, user_util):
167
167
168 repo_group_name = 'api-repo-group-parent'
168 repo_group_name = 'api-repo-group-parent'
169
169
170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
171 assert repo_group is None
171 assert repo_group is None
172 # create the parent
172 # create the parent
173 fixture.create_repo_group(repo_group_name)
173 fixture.create_repo_group(repo_group_name)
174
174
175 # user perms
175 # user perms
176 regular_user = user_util.create_user()
176 regular_user = user_util.create_user()
177 regular_user_api_key = regular_user.api_key
177 regular_user_api_key = regular_user.api_key
178
178
179 usr = UserModel().get_by_username(regular_user.username)
179 usr = UserModel().get_by_username(regular_user.username)
180 usr.inherit_default_permissions = False
180 usr.inherit_default_permissions = False
181 Session().add(usr)
181 Session().add(usr)
182
182
183 RepoGroupModel().grant_user_permission(
183 RepoGroupModel().grant_user_permission(
184 repo_group_name, regular_user.username, 'group.admin')
184 repo_group_name, regular_user.username, 'group.admin')
185 Session().commit()
185 Session().commit()
186
186
187 full_repo_group_name = repo_group_name + '/' + repo_group_name
187 full_repo_group_name = repo_group_name + '/' + repo_group_name
188 id_, params = build_data(
188 id_, params = build_data(
189 regular_user_api_key, 'create_repo_group',
189 regular_user_api_key, 'create_repo_group',
190 group_name=full_repo_group_name)
190 group_name=full_repo_group_name)
191 response = api_call(self.app, params)
191 response = api_call(self.app, params)
192
192
193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
194 assert repo_group is not None
194 assert repo_group is not None
195 expected = {
195 expected = {
196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
197 'repo_group': repo_group.get_api_data()
197 'repo_group': repo_group.get_api_data()
198 }
198 }
199 try:
199 try:
200 assert_ok(id_, expected, given=response.body)
200 assert_ok(id_, expected, given=response.body)
201 finally:
201 finally:
202 fixture.destroy_repo_group(full_repo_group_name)
202 fixture.destroy_repo_group(full_repo_group_name)
203 fixture.destroy_repo_group(repo_group_name)
203 fixture.destroy_repo_group(repo_group_name)
204
204
205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
206 repo_group_name = 'api-repo-group'
206 repo_group_name = 'api-repo-group'
207
207
208 id_, params = build_data(
208 id_, params = build_data(
209 self.apikey_regular, 'create_repo_group',
209 self.apikey_regular, 'create_repo_group',
210 group_name=repo_group_name)
210 group_name=repo_group_name)
211 response = api_call(self.app, params)
211 response = api_call(self.app, params)
212
212
213 expected = {
213 expected = {
214 'repo_group':
214 'repo_group':
215 u'You do not have the permission to store '
215 u'You do not have the permission to store '
216 u'repository groups in the root location.'}
216 u'repository groups in the root location.'}
217 assert_error(id_, expected, given=response.body)
217 assert_error(id_, expected, given=response.body)
218
218
219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
220 repo_group_name = 'api-repo-group-regular-user'
220 repo_group_name = 'api-repo-group-regular-user'
221
221
222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
223 assert repo_group is None
223 assert repo_group is None
224 # create the parent
224 # create the parent
225 fixture.create_repo_group(repo_group_name)
225 fixture.create_repo_group(repo_group_name)
226
226
227 full_repo_group_name = repo_group_name+'/'+repo_group_name
227 full_repo_group_name = repo_group_name+'/'+repo_group_name
228
228
229 id_, params = build_data(
229 id_, params = build_data(
230 self.apikey_regular, 'create_repo_group',
230 self.apikey_regular, 'create_repo_group',
231 group_name=full_repo_group_name)
231 group_name=full_repo_group_name)
232 response = api_call(self.app, params)
232 response = api_call(self.app, params)
233
233
234 expected = {
234 expected = {
235 'repo_group':
235 'repo_group':
236 'Parent repository group `{}` does not exist'.format(
236 u"You do not have the permissions to store "
237 repo_group_name)}
237 u"repository groups inside repository group `{}`".format(repo_group_name)}
238 try:
238 try:
239 assert_error(id_, expected, given=response.body)
239 assert_error(id_, expected, given=response.body)
240 finally:
240 finally:
241 fixture.destroy_repo_group(repo_group_name)
241 fixture.destroy_repo_group(repo_group_name)
242
242
243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
244 self):
244 self):
245 repo_group_name = 'api-repo-group'
245 repo_group_name = 'api-repo-group'
246
246
247 id_, params = build_data(
247 id_, params = build_data(
248 self.apikey_regular, 'create_repo_group',
248 self.apikey_regular, 'create_repo_group',
249 group_name=repo_group_name,
249 group_name=repo_group_name,
250 owner=TEST_USER_ADMIN_LOGIN,)
250 owner=TEST_USER_ADMIN_LOGIN,)
251 response = api_call(self.app, params)
251 response = api_call(self.app, params)
252
252
253 expected = "Only RhodeCode super-admin can specify `owner` param"
253 expected = "Only RhodeCode super-admin can specify `owner` param"
254 assert_error(id_, expected, given=response.body)
254 assert_error(id_, expected, given=response.body)
255
255
256 @mock.patch.object(RepoGroupModel, 'create', crash)
256 @mock.patch.object(RepoGroupModel, 'create', crash)
257 def test_api_create_repo_group_exception_occurred(self):
257 def test_api_create_repo_group_exception_occurred(self):
258 repo_group_name = 'api-repo-group'
258 repo_group_name = 'api-repo-group'
259
259
260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
261 assert repo_group is None
261 assert repo_group is None
262
262
263 id_, params = build_data(
263 id_, params = build_data(
264 self.apikey, 'create_repo_group',
264 self.apikey, 'create_repo_group',
265 group_name=repo_group_name,
265 group_name=repo_group_name,
266 owner=TEST_USER_ADMIN_LOGIN,)
266 owner=TEST_USER_ADMIN_LOGIN,)
267 response = api_call(self.app, params)
267 response = api_call(self.app, params)
268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
269 assert_error(id_, expected, given=response.body)
269 assert_error(id_, expected, given=response.body)
270
270
271 def test_create_group_with_extra_slashes_in_name(self, user_util):
271 def test_create_group_with_extra_slashes_in_name(self, user_util):
272 existing_repo_group = user_util.create_repo_group()
272 existing_repo_group = user_util.create_repo_group()
273 dirty_group_name = '//{}//group2//'.format(
273 dirty_group_name = '//{}//group2//'.format(
274 existing_repo_group.group_name)
274 existing_repo_group.group_name)
275 cleaned_group_name = '{}/group2'.format(
275 cleaned_group_name = '{}/group2'.format(
276 existing_repo_group.group_name)
276 existing_repo_group.group_name)
277
277
278 id_, params = build_data(
278 id_, params = build_data(
279 self.apikey, 'create_repo_group',
279 self.apikey, 'create_repo_group',
280 group_name=dirty_group_name,
280 group_name=dirty_group_name,
281 owner=TEST_USER_ADMIN_LOGIN,)
281 owner=TEST_USER_ADMIN_LOGIN,)
282 response = api_call(self.app, params)
282 response = api_call(self.app, params)
283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
284 expected = {
284 expected = {
285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
286 'repo_group': repo_group.get_api_data()
286 'repo_group': repo_group.get_api_data()
287 }
287 }
288 assert_ok(id_, expected, given=response.body)
288 assert_ok(id_, expected, given=response.body)
289 fixture.destroy_repo_group(cleaned_group_name)
289 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,61 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetMethod(object):
28 class TestGetMethod(object):
29 def test_get_methods_no_matches(self):
29 def test_get_methods_no_matches(self):
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32
32
33 expected = []
33 expected = []
34 assert_ok(id_, expected, given=response.body)
34 assert_ok(id_, expected, given=response.body)
35
35
36 def test_get_methods(self):
36 def test_get_methods(self):
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = ['changeset_comment', 'comment_pull_request',
40 expected = [
41 'get_pull_request_comments', 'comment_commit', 'get_repo_comments']
41 'changeset_comment', 'comment_pull_request', 'get_pull_request_comments',
42 'comment_commit', 'edit_comment', 'get_comment', 'get_repo_comments'
43 ]
42 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
43
45
44 def test_get_methods_on_single_match(self):
46 def test_get_methods_on_single_match(self):
45 id_, params = build_data(self.apikey, 'get_method',
47 id_, params = build_data(self.apikey, 'get_method',
46 pattern='*comment_commit*')
48 pattern='*comment_commit*')
47 response = api_call(self.app, params)
49 response = api_call(self.app, params)
48
50
49 expected = ['comment_commit',
51 expected = ['comment_commit',
50 {'apiuser': '<RequiredType>',
52 {'apiuser': '<RequiredType>',
51 'comment_type': "<Optional:u'note'>",
53 'comment_type': "<Optional:u'note'>",
52 'commit_id': '<RequiredType>',
54 'commit_id': '<RequiredType>',
53 'extra_recipients': '<Optional:[]>',
55 'extra_recipients': '<Optional:[]>',
54 'message': '<RequiredType>',
56 'message': '<RequiredType>',
55 'repoid': '<RequiredType>',
57 'repoid': '<RequiredType>',
56 'request': '<RequiredType>',
58 'request': '<RequiredType>',
57 'resolves_comment_id': '<Optional:None>',
59 'resolves_comment_id': '<Optional:None>',
58 'status': '<Optional:None>',
60 'status': '<Optional:None>',
59 'userid': '<Optional:<OptionalAttr:apiuser>>',
61 'userid': '<Optional:<OptionalAttr:apiuser>>',
60 'send_email': '<Optional:True>'}]
62 'send_email': '<Optional:True>'}]
61 assert_ok(id_, expected, given=response.body)
63 assert_ok(id_, expected, given=response.body)
@@ -1,86 +1,87 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23 import urlobject
23 import urlobject
24
24
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import safe_unicode
28 from rhodecode.lib.utils2 import safe_unicode
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequestComments(object):
34 class TestGetPullRequestComments(object):
35
35
36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38
38
39 pull_request = pr_util.create_pull_request(mergeable=True)
39 pull_request = pr_util.create_pull_request(mergeable=True)
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'get_pull_request_comments',
41 self.apikey, 'get_pull_request_comments',
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43
43
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 assert response.status == '200 OK'
46 assert response.status == '200 OK'
47 resp_date = response.json['result'][0]['comment_created_on']
47 resp_date = response.json['result'][0]['comment_created_on']
48 resp_comment_id = response.json['result'][0]['comment_id']
48 resp_comment_id = response.json['result'][0]['comment_id']
49
49
50 expected = [
50 expected = [
51 {'comment_author': {'active': True,
51 {'comment_author': {'active': True,
52 'full_name_or_username': 'RhodeCode Admin',
52 'full_name_or_username': 'RhodeCode Admin',
53 'username': 'test_admin'},
53 'username': 'test_admin'},
54 'comment_created_on': resp_date,
54 'comment_created_on': resp_date,
55 'comment_f_path': None,
55 'comment_f_path': None,
56 'comment_id': resp_comment_id,
56 'comment_id': resp_comment_id,
57 'comment_lineno': None,
57 'comment_lineno': None,
58 'comment_status': {'status': 'under_review',
58 'comment_status': {'status': 'under_review',
59 'status_lbl': 'Under Review'},
59 'status_lbl': 'Under Review'},
60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
61 'comment_type': 'note',
61 'comment_type': 'note',
62 'comment_resolved_by': None,
62 'comment_resolved_by': None,
63 'pull_request_version': None,
63 'pull_request_version': None,
64 'comment_last_version': 0,
64 'comment_commit_id': None,
65 'comment_commit_id': None,
65 'comment_pull_request_id': pull_request.pull_request_id
66 'comment_pull_request_id': pull_request.pull_request_id
66 }
67 }
67 ]
68 ]
68 assert_ok(id_, expected, response.body)
69 assert_ok(id_, expected, response.body)
69
70
70 def test_api_get_pull_request_comments_repo_error(self, pr_util):
71 def test_api_get_pull_request_comments_repo_error(self, pr_util):
71 pull_request = pr_util.create_pull_request()
72 pull_request = pr_util.create_pull_request()
72 id_, params = build_data(
73 id_, params = build_data(
73 self.apikey, 'get_pull_request_comments',
74 self.apikey, 'get_pull_request_comments',
74 repoid=666, pullrequestid=pull_request.pull_request_id)
75 repoid=666, pullrequestid=pull_request.pull_request_id)
75 response = api_call(self.app, params)
76 response = api_call(self.app, params)
76
77
77 expected = 'repository `666` does not exist'
78 expected = 'repository `666` does not exist'
78 assert_error(id_, expected, given=response.body)
79 assert_error(id_, expected, given=response.body)
79
80
80 def test_api_get_pull_request_comments_pull_request_error(self):
81 def test_api_get_pull_request_comments_pull_request_error(self):
81 id_, params = build_data(
82 id_, params = build_data(
82 self.apikey, 'get_pull_request_comments', pullrequestid=666)
83 self.apikey, 'get_pull_request_comments', pullrequestid=666)
83 response = api_call(self.app, params)
84 response = api_call(self.app, params)
84
85
85 expected = 'pull request `666` does not exist'
86 expected = 'pull request `666` does not exist'
86 assert_error(id_, expected, given=response.body)
87 assert_error(id_, expected, given=response.body)
@@ -1,110 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import User, ChangesetComment
24 from rhodecode.model.db import User, ChangesetComment
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.comment import CommentsModel
26 from rhodecode.model.comment import CommentsModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_call_ok)
28 build_data, api_call, assert_error, assert_call_ok)
29
29
30
30
31 @pytest.fixture()
31 @pytest.fixture()
32 def make_repo_comments_factory(request):
32 def make_repo_comments_factory(request):
33
33
34 class Make(object):
34 class Make(object):
35
35
36 def make_comments(self, repo):
36 def make_comments(self, repo):
37 user = User.get_first_super_admin()
37 user = User.get_first_super_admin()
38 commit = repo.scm_instance()[0]
38 commit = repo.scm_instance()[0]
39
39
40 commit_id = commit.raw_id
40 commit_id = commit.raw_id
41 file_0 = commit.affected_files[0]
41 file_0 = commit.affected_files[0]
42 comments = []
42 comments = []
43
43
44 # general
44 # general
45 CommentsModel().create(
45 comment = CommentsModel().create(
46 text='General Comment', repo=repo, user=user, commit_id=commit_id,
46 text='General Comment', repo=repo, user=user, commit_id=commit_id,
47 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
47 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
48 comments.append(comment)
48
49
49 # inline
50 # inline
50 CommentsModel().create(
51 comment = CommentsModel().create(
51 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
52 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
52 f_path=file_0, line_no='n1',
53 f_path=file_0, line_no='n1',
53 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
54 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
55 comments.append(comment)
54
56
55 # todo
57 # todo
56 CommentsModel().create(
58 comment = CommentsModel().create(
57 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
59 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
58 f_path=file_0, line_no='n1',
60 f_path=file_0, line_no='n1',
59 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
61 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
62 comments.append(comment)
60
63
61 @request.addfinalizer
64 return comments
62 def cleanup():
65
63 for comment in comments:
64 Session().delete(comment)
65 return Make()
66 return Make()
66
67
67
68
68 @pytest.mark.usefixtures("testuser_api", "app")
69 @pytest.mark.usefixtures("testuser_api", "app")
69 class TestGetRepo(object):
70 class TestGetRepo(object):
70
71
71 @pytest.mark.parametrize('filters, expected_count', [
72 @pytest.mark.parametrize('filters, expected_count', [
72 ({}, 3),
73 ({}, 3),
73 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
74 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
74 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
75 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
75 ({'commit_id': 'FILLED DYNAMIC'}, 3),
76 ({'commit_id': 'FILLED DYNAMIC'}, 3),
76 ])
77 ])
77 def test_api_get_repo_comments(self, backend, user_util,
78 def test_api_get_repo_comments(self, backend, user_util,
78 make_repo_comments_factory, filters, expected_count):
79 make_repo_comments_factory, filters, expected_count):
79 commits = [{'message': 'A'}, {'message': 'B'}]
80 commits = [{'message': 'A'}, {'message': 'B'}]
80 repo = backend.create_repo(commits=commits)
81 repo = backend.create_repo(commits=commits)
81 make_repo_comments_factory.make_comments(repo)
82 make_repo_comments_factory.make_comments(repo)
82
83
83 api_call_params = {'repoid': repo.repo_name,}
84 api_call_params = {'repoid': repo.repo_name,}
84 api_call_params.update(filters)
85 api_call_params.update(filters)
85
86
86 if 'commit_id' in api_call_params:
87 if 'commit_id' in api_call_params:
87 commit = repo.scm_instance()[0]
88 commit = repo.scm_instance()[0]
88 commit_id = commit.raw_id
89 commit_id = commit.raw_id
89 api_call_params['commit_id'] = commit_id
90 api_call_params['commit_id'] = commit_id
90
91
91 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
92 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
92 response = api_call(self.app, params)
93 response = api_call(self.app, params)
93 result = assert_call_ok(id_, given=response.body)
94 result = assert_call_ok(id_, given=response.body)
94
95
95 assert len(result) == expected_count
96 assert len(result) == expected_count
96
97
97 def test_api_get_repo_comments_wrong_comment_type(
98 def test_api_get_repo_comments_wrong_comment_type(
98 self, make_repo_comments_factory, backend_hg):
99 self, make_repo_comments_factory, backend_hg):
99 commits = [{'message': 'A'}, {'message': 'B'}]
100 commits = [{'message': 'A'}, {'message': 'B'}]
100 repo = backend_hg.create_repo(commits=commits)
101 repo = backend_hg.create_repo(commits=commits)
101 make_repo_comments_factory.make_comments(repo)
102 make_repo_comments_factory.make_comments(repo)
102
103
103 api_call_params = {'repoid': repo.repo_name}
104 api_call_params = {'repoid': repo.repo_name}
104 api_call_params.update({'comment_type': 'bogus'})
105 api_call_params.update({'comment_type': 'bogus'})
105
106
106 expected = 'comment_type must be one of `{}` got {}'.format(
107 expected = 'comment_type must be one of `{}` got {}'.format(
107 ChangesetComment.COMMENT_TYPES, 'bogus')
108 ChangesetComment.COMMENT_TYPES, 'bogus')
108 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
109 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
109 response = api_call(self.app, params)
110 response = api_call(self.app, params)
110 assert_error(id_, expected, given=response.body)
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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode import events
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
28 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 from rhodecode.lib.base import vcs_operation_context
30 from rhodecode.lib.base import vcs_operation_context
32 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.model.changeset_status import ChangesetStatusModel
32 from rhodecode.model.changeset_status import ChangesetStatusModel
34 from rhodecode.model.comment import CommentsModel
33 from rhodecode.model.comment import CommentsModel
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
34 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
35 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
37 from rhodecode.model.validation_schema import Invalid
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
38 from rhodecode.model.validation_schema.schemas.reviewer_schema import ReviewerListSchema
40 ReviewerListSchema)
41
39
42 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
43
41
44
42
45 @jsonrpc_method()
43 @jsonrpc_method()
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
44 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
47 merge_state=Optional(False)):
45 merge_state=Optional(False)):
48 """
46 """
49 Get a pull request based on the given ID.
47 Get a pull request based on the given ID.
50
48
51 :param apiuser: This is filled automatically from the |authtoken|.
49 :param apiuser: This is filled automatically from the |authtoken|.
52 :type apiuser: AuthUser
50 :type apiuser: AuthUser
53 :param repoid: Optional, repository name or repository ID from where
51 :param repoid: Optional, repository name or repository ID from where
54 the pull request was opened.
52 the pull request was opened.
55 :type repoid: str or int
53 :type repoid: str or int
56 :param pullrequestid: ID of the requested pull request.
54 :param pullrequestid: ID of the requested pull request.
57 :type pullrequestid: int
55 :type pullrequestid: int
58 :param merge_state: Optional calculate merge state for each repository.
56 :param merge_state: Optional calculate merge state for each repository.
59 This could result in longer time to fetch the data
57 This could result in longer time to fetch the data
60 :type merge_state: bool
58 :type merge_state: bool
61
59
62 Example output:
60 Example output:
63
61
64 .. code-block:: bash
62 .. code-block:: bash
65
63
66 "id": <id_given_in_input>,
64 "id": <id_given_in_input>,
67 "result":
65 "result":
68 {
66 {
69 "pull_request_id": "<pull_request_id>",
67 "pull_request_id": "<pull_request_id>",
70 "url": "<url>",
68 "url": "<url>",
71 "title": "<title>",
69 "title": "<title>",
72 "description": "<description>",
70 "description": "<description>",
73 "status" : "<status>",
71 "status" : "<status>",
74 "created_on": "<date_time_created>",
72 "created_on": "<date_time_created>",
75 "updated_on": "<date_time_updated>",
73 "updated_on": "<date_time_updated>",
76 "versions": "<number_or_versions_of_pr>",
74 "versions": "<number_or_versions_of_pr>",
77 "commit_ids": [
75 "commit_ids": [
78 ...
76 ...
79 "<commit_id>",
77 "<commit_id>",
80 "<commit_id>",
78 "<commit_id>",
81 ...
79 ...
82 ],
80 ],
83 "review_status": "<review_status>",
81 "review_status": "<review_status>",
84 "mergeable": {
82 "mergeable": {
85 "status": "<bool>",
83 "status": "<bool>",
86 "message": "<message>",
84 "message": "<message>",
87 },
85 },
88 "source": {
86 "source": {
89 "clone_url": "<clone_url>",
87 "clone_url": "<clone_url>",
90 "repository": "<repository_name>",
88 "repository": "<repository_name>",
91 "reference":
89 "reference":
92 {
90 {
93 "name": "<name>",
91 "name": "<name>",
94 "type": "<type>",
92 "type": "<type>",
95 "commit_id": "<commit_id>",
93 "commit_id": "<commit_id>",
96 }
94 }
97 },
95 },
98 "target": {
96 "target": {
99 "clone_url": "<clone_url>",
97 "clone_url": "<clone_url>",
100 "repository": "<repository_name>",
98 "repository": "<repository_name>",
101 "reference":
99 "reference":
102 {
100 {
103 "name": "<name>",
101 "name": "<name>",
104 "type": "<type>",
102 "type": "<type>",
105 "commit_id": "<commit_id>",
103 "commit_id": "<commit_id>",
106 }
104 }
107 },
105 },
108 "merge": {
106 "merge": {
109 "clone_url": "<clone_url>",
107 "clone_url": "<clone_url>",
110 "reference":
108 "reference":
111 {
109 {
112 "name": "<name>",
110 "name": "<name>",
113 "type": "<type>",
111 "type": "<type>",
114 "commit_id": "<commit_id>",
112 "commit_id": "<commit_id>",
115 }
113 }
116 },
114 },
117 "author": <user_obj>,
115 "author": <user_obj>,
118 "reviewers": [
116 "reviewers": [
119 ...
117 ...
120 {
118 {
121 "user": "<user_obj>",
119 "user": "<user_obj>",
122 "review_status": "<review_status>",
120 "review_status": "<review_status>",
123 }
121 }
124 ...
122 ...
125 ]
123 ]
126 },
124 },
127 "error": null
125 "error": null
128 """
126 """
129
127
130 pull_request = get_pull_request_or_error(pullrequestid)
128 pull_request = get_pull_request_or_error(pullrequestid)
131 if Optional.extract(repoid):
129 if Optional.extract(repoid):
132 repo = get_repo_or_error(repoid)
130 repo = get_repo_or_error(repoid)
133 else:
131 else:
134 repo = pull_request.target_repo
132 repo = pull_request.target_repo
135
133
136 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
134 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
137 raise JSONRPCError('repository `%s` or pull request `%s` '
135 raise JSONRPCError('repository `%s` or pull request `%s` '
138 'does not exist' % (repoid, pullrequestid))
136 'does not exist' % (repoid, pullrequestid))
139
137
140 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
138 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
141 # otherwise we can lock the repo on calculation of merge state while update/merge
139 # otherwise we can lock the repo on calculation of merge state while update/merge
142 # is happening.
140 # is happening.
143 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
141 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
144 merge_state = Optional.extract(merge_state, binary=True) and pr_created
142 merge_state = Optional.extract(merge_state, binary=True) and pr_created
145 data = pull_request.get_api_data(with_merge_state=merge_state)
143 data = pull_request.get_api_data(with_merge_state=merge_state)
146 return data
144 return data
147
145
148
146
149 @jsonrpc_method()
147 @jsonrpc_method()
150 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
148 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
151 merge_state=Optional(False)):
149 merge_state=Optional(False)):
152 """
150 """
153 Get all pull requests from the repository specified in `repoid`.
151 Get all pull requests from the repository specified in `repoid`.
154
152
155 :param apiuser: This is filled automatically from the |authtoken|.
153 :param apiuser: This is filled automatically from the |authtoken|.
156 :type apiuser: AuthUser
154 :type apiuser: AuthUser
157 :param repoid: Optional repository name or repository ID.
155 :param repoid: Optional repository name or repository ID.
158 :type repoid: str or int
156 :type repoid: str or int
159 :param status: Only return pull requests with the specified status.
157 :param status: Only return pull requests with the specified status.
160 Valid options are.
158 Valid options are.
161 * ``new`` (default)
159 * ``new`` (default)
162 * ``open``
160 * ``open``
163 * ``closed``
161 * ``closed``
164 :type status: str
162 :type status: str
165 :param merge_state: Optional calculate merge state for each repository.
163 :param merge_state: Optional calculate merge state for each repository.
166 This could result in longer time to fetch the data
164 This could result in longer time to fetch the data
167 :type merge_state: bool
165 :type merge_state: bool
168
166
169 Example output:
167 Example output:
170
168
171 .. code-block:: bash
169 .. code-block:: bash
172
170
173 "id": <id_given_in_input>,
171 "id": <id_given_in_input>,
174 "result":
172 "result":
175 [
173 [
176 ...
174 ...
177 {
175 {
178 "pull_request_id": "<pull_request_id>",
176 "pull_request_id": "<pull_request_id>",
179 "url": "<url>",
177 "url": "<url>",
180 "title" : "<title>",
178 "title" : "<title>",
181 "description": "<description>",
179 "description": "<description>",
182 "status": "<status>",
180 "status": "<status>",
183 "created_on": "<date_time_created>",
181 "created_on": "<date_time_created>",
184 "updated_on": "<date_time_updated>",
182 "updated_on": "<date_time_updated>",
185 "commit_ids": [
183 "commit_ids": [
186 ...
184 ...
187 "<commit_id>",
185 "<commit_id>",
188 "<commit_id>",
186 "<commit_id>",
189 ...
187 ...
190 ],
188 ],
191 "review_status": "<review_status>",
189 "review_status": "<review_status>",
192 "mergeable": {
190 "mergeable": {
193 "status": "<bool>",
191 "status": "<bool>",
194 "message: "<message>",
192 "message: "<message>",
195 },
193 },
196 "source": {
194 "source": {
197 "clone_url": "<clone_url>",
195 "clone_url": "<clone_url>",
198 "reference":
196 "reference":
199 {
197 {
200 "name": "<name>",
198 "name": "<name>",
201 "type": "<type>",
199 "type": "<type>",
202 "commit_id": "<commit_id>",
200 "commit_id": "<commit_id>",
203 }
201 }
204 },
202 },
205 "target": {
203 "target": {
206 "clone_url": "<clone_url>",
204 "clone_url": "<clone_url>",
207 "reference":
205 "reference":
208 {
206 {
209 "name": "<name>",
207 "name": "<name>",
210 "type": "<type>",
208 "type": "<type>",
211 "commit_id": "<commit_id>",
209 "commit_id": "<commit_id>",
212 }
210 }
213 },
211 },
214 "merge": {
212 "merge": {
215 "clone_url": "<clone_url>",
213 "clone_url": "<clone_url>",
216 "reference":
214 "reference":
217 {
215 {
218 "name": "<name>",
216 "name": "<name>",
219 "type": "<type>",
217 "type": "<type>",
220 "commit_id": "<commit_id>",
218 "commit_id": "<commit_id>",
221 }
219 }
222 },
220 },
223 "author": <user_obj>,
221 "author": <user_obj>,
224 "reviewers": [
222 "reviewers": [
225 ...
223 ...
226 {
224 {
227 "user": "<user_obj>",
225 "user": "<user_obj>",
228 "review_status": "<review_status>",
226 "review_status": "<review_status>",
229 }
227 }
230 ...
228 ...
231 ]
229 ]
232 }
230 }
233 ...
231 ...
234 ],
232 ],
235 "error": null
233 "error": null
236
234
237 """
235 """
238 repo = get_repo_or_error(repoid)
236 repo = get_repo_or_error(repoid)
239 if not has_superadmin_permission(apiuser):
237 if not has_superadmin_permission(apiuser):
240 _perms = (
238 _perms = (
241 'repository.admin', 'repository.write', 'repository.read',)
239 'repository.admin', 'repository.write', 'repository.read',)
242 validate_repo_permissions(apiuser, repoid, repo, _perms)
240 validate_repo_permissions(apiuser, repoid, repo, _perms)
243
241
244 status = Optional.extract(status)
242 status = Optional.extract(status)
245 merge_state = Optional.extract(merge_state, binary=True)
243 merge_state = Optional.extract(merge_state, binary=True)
246 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
244 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
247 order_by='id', order_dir='desc')
245 order_by='id', order_dir='desc')
248 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
246 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
249 return data
247 return data
250
248
251
249
252 @jsonrpc_method()
250 @jsonrpc_method()
253 def merge_pull_request(
251 def merge_pull_request(
254 request, apiuser, pullrequestid, repoid=Optional(None),
252 request, apiuser, pullrequestid, repoid=Optional(None),
255 userid=Optional(OAttr('apiuser'))):
253 userid=Optional(OAttr('apiuser'))):
256 """
254 """
257 Merge the pull request specified by `pullrequestid` into its target
255 Merge the pull request specified by `pullrequestid` into its target
258 repository.
256 repository.
259
257
260 :param apiuser: This is filled automatically from the |authtoken|.
258 :param apiuser: This is filled automatically from the |authtoken|.
261 :type apiuser: AuthUser
259 :type apiuser: AuthUser
262 :param repoid: Optional, repository name or repository ID of the
260 :param repoid: Optional, repository name or repository ID of the
263 target repository to which the |pr| is to be merged.
261 target repository to which the |pr| is to be merged.
264 :type repoid: str or int
262 :type repoid: str or int
265 :param pullrequestid: ID of the pull request which shall be merged.
263 :param pullrequestid: ID of the pull request which shall be merged.
266 :type pullrequestid: int
264 :type pullrequestid: int
267 :param userid: Merge the pull request as this user.
265 :param userid: Merge the pull request as this user.
268 :type userid: Optional(str or int)
266 :type userid: Optional(str or int)
269
267
270 Example output:
268 Example output:
271
269
272 .. code-block:: bash
270 .. code-block:: bash
273
271
274 "id": <id_given_in_input>,
272 "id": <id_given_in_input>,
275 "result": {
273 "result": {
276 "executed": "<bool>",
274 "executed": "<bool>",
277 "failure_reason": "<int>",
275 "failure_reason": "<int>",
278 "merge_status_message": "<str>",
276 "merge_status_message": "<str>",
279 "merge_commit_id": "<merge_commit_id>",
277 "merge_commit_id": "<merge_commit_id>",
280 "possible": "<bool>",
278 "possible": "<bool>",
281 "merge_ref": {
279 "merge_ref": {
282 "commit_id": "<commit_id>",
280 "commit_id": "<commit_id>",
283 "type": "<type>",
281 "type": "<type>",
284 "name": "<name>"
282 "name": "<name>"
285 }
283 }
286 },
284 },
287 "error": null
285 "error": null
288 """
286 """
289 pull_request = get_pull_request_or_error(pullrequestid)
287 pull_request = get_pull_request_or_error(pullrequestid)
290 if Optional.extract(repoid):
288 if Optional.extract(repoid):
291 repo = get_repo_or_error(repoid)
289 repo = get_repo_or_error(repoid)
292 else:
290 else:
293 repo = pull_request.target_repo
291 repo = pull_request.target_repo
294 auth_user = apiuser
292 auth_user = apiuser
293
295 if not isinstance(userid, Optional):
294 if not isinstance(userid, Optional):
296 if (has_superadmin_permission(apiuser) or
295 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
297 HasRepoPermissionAnyApi('repository.admin')(
296 user=apiuser, repo_name=repo.repo_name)
298 user=apiuser, repo_name=repo.repo_name)):
297 if has_superadmin_permission(apiuser) or is_repo_admin:
299 apiuser = get_user_or_error(userid)
298 apiuser = get_user_or_error(userid)
300 auth_user = apiuser.AuthUser()
299 auth_user = apiuser.AuthUser()
301 else:
300 else:
302 raise JSONRPCError('userid is not the same as your user')
301 raise JSONRPCError('userid is not the same as your user')
303
302
304 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
303 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
305 raise JSONRPCError(
304 raise JSONRPCError(
306 'Operation forbidden because pull request is in state {}, '
305 'Operation forbidden because pull request is in state {}, '
307 'only state {} is allowed.'.format(
306 'only state {} is allowed.'.format(
308 pull_request.pull_request_state, PullRequest.STATE_CREATED))
307 pull_request.pull_request_state, PullRequest.STATE_CREATED))
309
308
310 with pull_request.set_state(PullRequest.STATE_UPDATING):
309 with pull_request.set_state(PullRequest.STATE_UPDATING):
311 check = MergeCheck.validate(pull_request, auth_user=auth_user,
310 check = MergeCheck.validate(pull_request, auth_user=auth_user,
312 translator=request.translate)
311 translator=request.translate)
313 merge_possible = not check.failed
312 merge_possible = not check.failed
314
313
315 if not merge_possible:
314 if not merge_possible:
316 error_messages = []
315 error_messages = []
317 for err_type, error_msg in check.errors:
316 for err_type, error_msg in check.errors:
318 error_msg = request.translate(error_msg)
317 error_msg = request.translate(error_msg)
319 error_messages.append(error_msg)
318 error_messages.append(error_msg)
320
319
321 reasons = ','.join(error_messages)
320 reasons = ','.join(error_messages)
322 raise JSONRPCError(
321 raise JSONRPCError(
323 'merge not possible for following reasons: {}'.format(reasons))
322 'merge not possible for following reasons: {}'.format(reasons))
324
323
325 target_repo = pull_request.target_repo
324 target_repo = pull_request.target_repo
326 extras = vcs_operation_context(
325 extras = vcs_operation_context(
327 request.environ, repo_name=target_repo.repo_name,
326 request.environ, repo_name=target_repo.repo_name,
328 username=auth_user.username, action='push',
327 username=auth_user.username, action='push',
329 scm=target_repo.repo_type)
328 scm=target_repo.repo_type)
330 with pull_request.set_state(PullRequest.STATE_UPDATING):
329 with pull_request.set_state(PullRequest.STATE_UPDATING):
331 merge_response = PullRequestModel().merge_repo(
330 merge_response = PullRequestModel().merge_repo(
332 pull_request, apiuser, extras=extras)
331 pull_request, apiuser, extras=extras)
333 if merge_response.executed:
332 if merge_response.executed:
334 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
333 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
335
334
336 Session().commit()
335 Session().commit()
337
336
338 # In previous versions the merge response directly contained the merge
337 # In previous versions the merge response directly contained the merge
339 # commit id. It is now contained in the merge reference object. To be
338 # commit id. It is now contained in the merge reference object. To be
340 # backwards compatible we have to extract it again.
339 # backwards compatible we have to extract it again.
341 merge_response = merge_response.asdict()
340 merge_response = merge_response.asdict()
342 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
341 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
343
342
344 return merge_response
343 return merge_response
345
344
346
345
347 @jsonrpc_method()
346 @jsonrpc_method()
348 def get_pull_request_comments(
347 def get_pull_request_comments(
349 request, apiuser, pullrequestid, repoid=Optional(None)):
348 request, apiuser, pullrequestid, repoid=Optional(None)):
350 """
349 """
351 Get all comments of pull request specified with the `pullrequestid`
350 Get all comments of pull request specified with the `pullrequestid`
352
351
353 :param apiuser: This is filled automatically from the |authtoken|.
352 :param apiuser: This is filled automatically from the |authtoken|.
354 :type apiuser: AuthUser
353 :type apiuser: AuthUser
355 :param repoid: Optional repository name or repository ID.
354 :param repoid: Optional repository name or repository ID.
356 :type repoid: str or int
355 :type repoid: str or int
357 :param pullrequestid: The pull request ID.
356 :param pullrequestid: The pull request ID.
358 :type pullrequestid: int
357 :type pullrequestid: int
359
358
360 Example output:
359 Example output:
361
360
362 .. code-block:: bash
361 .. code-block:: bash
363
362
364 id : <id_given_in_input>
363 id : <id_given_in_input>
365 result : [
364 result : [
366 {
365 {
367 "comment_author": {
366 "comment_author": {
368 "active": true,
367 "active": true,
369 "full_name_or_username": "Tom Gore",
368 "full_name_or_username": "Tom Gore",
370 "username": "admin"
369 "username": "admin"
371 },
370 },
372 "comment_created_on": "2017-01-02T18:43:45.533",
371 "comment_created_on": "2017-01-02T18:43:45.533",
373 "comment_f_path": null,
372 "comment_f_path": null,
374 "comment_id": 25,
373 "comment_id": 25,
375 "comment_lineno": null,
374 "comment_lineno": null,
376 "comment_status": {
375 "comment_status": {
377 "status": "under_review",
376 "status": "under_review",
378 "status_lbl": "Under Review"
377 "status_lbl": "Under Review"
379 },
378 },
380 "comment_text": "Example text",
379 "comment_text": "Example text",
381 "comment_type": null,
380 "comment_type": null,
381 "comment_last_version: 0,
382 "pull_request_version": null,
382 "pull_request_version": null,
383 "comment_commit_id": None,
383 "comment_commit_id": None,
384 "comment_pull_request_id": <pull_request_id>
384 "comment_pull_request_id": <pull_request_id>
385 }
385 }
386 ],
386 ],
387 error : null
387 error : null
388 """
388 """
389
389
390 pull_request = get_pull_request_or_error(pullrequestid)
390 pull_request = get_pull_request_or_error(pullrequestid)
391 if Optional.extract(repoid):
391 if Optional.extract(repoid):
392 repo = get_repo_or_error(repoid)
392 repo = get_repo_or_error(repoid)
393 else:
393 else:
394 repo = pull_request.target_repo
394 repo = pull_request.target_repo
395
395
396 if not PullRequestModel().check_user_read(
396 if not PullRequestModel().check_user_read(
397 pull_request, apiuser, api=True):
397 pull_request, apiuser, api=True):
398 raise JSONRPCError('repository `%s` or pull request `%s` '
398 raise JSONRPCError('repository `%s` or pull request `%s` '
399 'does not exist' % (repoid, pullrequestid))
399 'does not exist' % (repoid, pullrequestid))
400
400
401 (pull_request_latest,
401 (pull_request_latest,
402 pull_request_at_ver,
402 pull_request_at_ver,
403 pull_request_display_obj,
403 pull_request_display_obj,
404 at_version) = PullRequestModel().get_pr_version(
404 at_version) = PullRequestModel().get_pr_version(
405 pull_request.pull_request_id, version=None)
405 pull_request.pull_request_id, version=None)
406
406
407 versions = pull_request_display_obj.versions()
407 versions = pull_request_display_obj.versions()
408 ver_map = {
408 ver_map = {
409 ver.pull_request_version_id: cnt
409 ver.pull_request_version_id: cnt
410 for cnt, ver in enumerate(versions, 1)
410 for cnt, ver in enumerate(versions, 1)
411 }
411 }
412
412
413 # GENERAL COMMENTS with versions #
413 # GENERAL COMMENTS with versions #
414 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
414 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
415 q = q.order_by(ChangesetComment.comment_id.asc())
415 q = q.order_by(ChangesetComment.comment_id.asc())
416 general_comments = q.all()
416 general_comments = q.all()
417
417
418 # INLINE COMMENTS with versions #
418 # INLINE COMMENTS with versions #
419 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
419 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
420 q = q.order_by(ChangesetComment.comment_id.asc())
420 q = q.order_by(ChangesetComment.comment_id.asc())
421 inline_comments = q.all()
421 inline_comments = q.all()
422
422
423 data = []
423 data = []
424 for comment in inline_comments + general_comments:
424 for comment in inline_comments + general_comments:
425 full_data = comment.get_api_data()
425 full_data = comment.get_api_data()
426 pr_version_id = None
426 pr_version_id = None
427 if comment.pull_request_version_id:
427 if comment.pull_request_version_id:
428 pr_version_id = 'v{}'.format(
428 pr_version_id = 'v{}'.format(
429 ver_map[comment.pull_request_version_id])
429 ver_map[comment.pull_request_version_id])
430
430
431 # sanitize some entries
431 # sanitize some entries
432
432
433 full_data['pull_request_version'] = pr_version_id
433 full_data['pull_request_version'] = pr_version_id
434 full_data['comment_author'] = {
434 full_data['comment_author'] = {
435 'username': full_data['comment_author'].username,
435 'username': full_data['comment_author'].username,
436 'full_name_or_username': full_data['comment_author'].full_name_or_username,
436 'full_name_or_username': full_data['comment_author'].full_name_or_username,
437 'active': full_data['comment_author'].active,
437 'active': full_data['comment_author'].active,
438 }
438 }
439
439
440 if full_data['comment_status']:
440 if full_data['comment_status']:
441 full_data['comment_status'] = {
441 full_data['comment_status'] = {
442 'status': full_data['comment_status'][0].status,
442 'status': full_data['comment_status'][0].status,
443 'status_lbl': full_data['comment_status'][0].status_lbl,
443 'status_lbl': full_data['comment_status'][0].status_lbl,
444 }
444 }
445 else:
445 else:
446 full_data['comment_status'] = {}
446 full_data['comment_status'] = {}
447
447
448 data.append(full_data)
448 data.append(full_data)
449 return data
449 return data
450
450
451
451
452 @jsonrpc_method()
452 @jsonrpc_method()
453 def comment_pull_request(
453 def comment_pull_request(
454 request, apiuser, pullrequestid, repoid=Optional(None),
454 request, apiuser, pullrequestid, repoid=Optional(None),
455 message=Optional(None), commit_id=Optional(None), status=Optional(None),
455 message=Optional(None), commit_id=Optional(None), status=Optional(None),
456 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
456 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
457 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
457 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
458 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
458 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
459 """
459 """
460 Comment on the pull request specified with the `pullrequestid`,
460 Comment on the pull request specified with the `pullrequestid`,
461 in the |repo| specified by the `repoid`, and optionally change the
461 in the |repo| specified by the `repoid`, and optionally change the
462 review status.
462 review status.
463
463
464 :param apiuser: This is filled automatically from the |authtoken|.
464 :param apiuser: This is filled automatically from the |authtoken|.
465 :type apiuser: AuthUser
465 :type apiuser: AuthUser
466 :param repoid: Optional repository name or repository ID.
466 :param repoid: Optional repository name or repository ID.
467 :type repoid: str or int
467 :type repoid: str or int
468 :param pullrequestid: The pull request ID.
468 :param pullrequestid: The pull request ID.
469 :type pullrequestid: int
469 :type pullrequestid: int
470 :param commit_id: Specify the commit_id for which to set a comment. If
470 :param commit_id: Specify the commit_id for which to set a comment. If
471 given commit_id is different than latest in the PR status
471 given commit_id is different than latest in the PR status
472 change won't be performed.
472 change won't be performed.
473 :type commit_id: str
473 :type commit_id: str
474 :param message: The text content of the comment.
474 :param message: The text content of the comment.
475 :type message: str
475 :type message: str
476 :param status: (**Optional**) Set the approval status of the pull
476 :param status: (**Optional**) Set the approval status of the pull
477 request. One of: 'not_reviewed', 'approved', 'rejected',
477 request. One of: 'not_reviewed', 'approved', 'rejected',
478 'under_review'
478 'under_review'
479 :type status: str
479 :type status: str
480 :param comment_type: Comment type, one of: 'note', 'todo'
480 :param comment_type: Comment type, one of: 'note', 'todo'
481 :type comment_type: Optional(str), default: 'note'
481 :type comment_type: Optional(str), default: 'note'
482 :param resolves_comment_id: id of comment which this one will resolve
482 :param resolves_comment_id: id of comment which this one will resolve
483 :type resolves_comment_id: Optional(int)
483 :type resolves_comment_id: Optional(int)
484 :param extra_recipients: list of user ids or usernames to add
484 :param extra_recipients: list of user ids or usernames to add
485 notifications for this comment. Acts like a CC for notification
485 notifications for this comment. Acts like a CC for notification
486 :type extra_recipients: Optional(list)
486 :type extra_recipients: Optional(list)
487 :param userid: Comment on the pull request as this user
487 :param userid: Comment on the pull request as this user
488 :type userid: Optional(str or int)
488 :type userid: Optional(str or int)
489 :param send_email: Define if this comment should also send email notification
489 :param send_email: Define if this comment should also send email notification
490 :type send_email: Optional(bool)
490 :type send_email: Optional(bool)
491
491
492 Example output:
492 Example output:
493
493
494 .. code-block:: bash
494 .. code-block:: bash
495
495
496 id : <id_given_in_input>
496 id : <id_given_in_input>
497 result : {
497 result : {
498 "pull_request_id": "<Integer>",
498 "pull_request_id": "<Integer>",
499 "comment_id": "<Integer>",
499 "comment_id": "<Integer>",
500 "status": {"given": <given_status>,
500 "status": {"given": <given_status>,
501 "was_changed": <bool status_was_actually_changed> },
501 "was_changed": <bool status_was_actually_changed> },
502 },
502 },
503 error : null
503 error : null
504 """
504 """
505 pull_request = get_pull_request_or_error(pullrequestid)
505 pull_request = get_pull_request_or_error(pullrequestid)
506 if Optional.extract(repoid):
506 if Optional.extract(repoid):
507 repo = get_repo_or_error(repoid)
507 repo = get_repo_or_error(repoid)
508 else:
508 else:
509 repo = pull_request.target_repo
509 repo = pull_request.target_repo
510
510
511 auth_user = apiuser
511 auth_user = apiuser
512 if not isinstance(userid, Optional):
512 if not isinstance(userid, Optional):
513 if (has_superadmin_permission(apiuser) or
513 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
514 HasRepoPermissionAnyApi('repository.admin')(
514 user=apiuser, repo_name=repo.repo_name)
515 user=apiuser, repo_name=repo.repo_name)):
515 if has_superadmin_permission(apiuser) or is_repo_admin:
516 apiuser = get_user_or_error(userid)
516 apiuser = get_user_or_error(userid)
517 auth_user = apiuser.AuthUser()
517 auth_user = apiuser.AuthUser()
518 else:
518 else:
519 raise JSONRPCError('userid is not the same as your user')
519 raise JSONRPCError('userid is not the same as your user')
520
520
521 if pull_request.is_closed():
521 if pull_request.is_closed():
522 raise JSONRPCError(
522 raise JSONRPCError(
523 'pull request `%s` comment failed, pull request is closed' % (
523 'pull request `%s` comment failed, pull request is closed' % (
524 pullrequestid,))
524 pullrequestid,))
525
525
526 if not PullRequestModel().check_user_read(
526 if not PullRequestModel().check_user_read(
527 pull_request, apiuser, api=True):
527 pull_request, apiuser, api=True):
528 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
528 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
529 message = Optional.extract(message)
529 message = Optional.extract(message)
530 status = Optional.extract(status)
530 status = Optional.extract(status)
531 commit_id = Optional.extract(commit_id)
531 commit_id = Optional.extract(commit_id)
532 comment_type = Optional.extract(comment_type)
532 comment_type = Optional.extract(comment_type)
533 resolves_comment_id = Optional.extract(resolves_comment_id)
533 resolves_comment_id = Optional.extract(resolves_comment_id)
534 extra_recipients = Optional.extract(extra_recipients)
534 extra_recipients = Optional.extract(extra_recipients)
535 send_email = Optional.extract(send_email, binary=True)
535 send_email = Optional.extract(send_email, binary=True)
536
536
537 if not message and not status:
537 if not message and not status:
538 raise JSONRPCError(
538 raise JSONRPCError(
539 'Both message and status parameters are missing. '
539 'Both message and status parameters are missing. '
540 'At least one is required.')
540 'At least one is required.')
541
541
542 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
542 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
543 status is not None):
543 status is not None):
544 raise JSONRPCError('Unknown comment status: `%s`' % status)
544 raise JSONRPCError('Unknown comment status: `%s`' % status)
545
545
546 if commit_id and commit_id not in pull_request.revisions:
546 if commit_id and commit_id not in pull_request.revisions:
547 raise JSONRPCError(
547 raise JSONRPCError(
548 'Invalid commit_id `%s` for this pull request.' % commit_id)
548 'Invalid commit_id `%s` for this pull request.' % commit_id)
549
549
550 allowed_to_change_status = PullRequestModel().check_user_change_status(
550 allowed_to_change_status = PullRequestModel().check_user_change_status(
551 pull_request, apiuser)
551 pull_request, apiuser)
552
552
553 # if commit_id is passed re-validated if user is allowed to change status
553 # if commit_id is passed re-validated if user is allowed to change status
554 # based on latest commit_id from the PR
554 # based on latest commit_id from the PR
555 if commit_id:
555 if commit_id:
556 commit_idx = pull_request.revisions.index(commit_id)
556 commit_idx = pull_request.revisions.index(commit_id)
557 if commit_idx != 0:
557 if commit_idx != 0:
558 allowed_to_change_status = False
558 allowed_to_change_status = False
559
559
560 if resolves_comment_id:
560 if resolves_comment_id:
561 comment = ChangesetComment.get(resolves_comment_id)
561 comment = ChangesetComment.get(resolves_comment_id)
562 if not comment:
562 if not comment:
563 raise JSONRPCError(
563 raise JSONRPCError(
564 'Invalid resolves_comment_id `%s` for this pull request.'
564 'Invalid resolves_comment_id `%s` for this pull request.'
565 % resolves_comment_id)
565 % resolves_comment_id)
566 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
566 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
567 raise JSONRPCError(
567 raise JSONRPCError(
568 'Comment `%s` is wrong type for setting status to resolved.'
568 'Comment `%s` is wrong type for setting status to resolved.'
569 % resolves_comment_id)
569 % resolves_comment_id)
570
570
571 text = message
571 text = message
572 status_label = ChangesetStatus.get_status_lbl(status)
572 status_label = ChangesetStatus.get_status_lbl(status)
573 if status and allowed_to_change_status:
573 if status and allowed_to_change_status:
574 st_message = ('Status change %(transition_icon)s %(status)s'
574 st_message = ('Status change %(transition_icon)s %(status)s'
575 % {'transition_icon': '>', 'status': status_label})
575 % {'transition_icon': '>', 'status': status_label})
576 text = message or st_message
576 text = message or st_message
577
577
578 rc_config = SettingsModel().get_all_settings()
578 rc_config = SettingsModel().get_all_settings()
579 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
579 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
580
580
581 status_change = status and allowed_to_change_status
581 status_change = status and allowed_to_change_status
582 comment = CommentsModel().create(
582 comment = CommentsModel().create(
583 text=text,
583 text=text,
584 repo=pull_request.target_repo.repo_id,
584 repo=pull_request.target_repo.repo_id,
585 user=apiuser.user_id,
585 user=apiuser.user_id,
586 pull_request=pull_request.pull_request_id,
586 pull_request=pull_request.pull_request_id,
587 f_path=None,
587 f_path=None,
588 line_no=None,
588 line_no=None,
589 status_change=(status_label if status_change else None),
589 status_change=(status_label if status_change else None),
590 status_change_type=(status if status_change else None),
590 status_change_type=(status if status_change else None),
591 closing_pr=False,
591 closing_pr=False,
592 renderer=renderer,
592 renderer=renderer,
593 comment_type=comment_type,
593 comment_type=comment_type,
594 resolves_comment_id=resolves_comment_id,
594 resolves_comment_id=resolves_comment_id,
595 auth_user=auth_user,
595 auth_user=auth_user,
596 extra_recipients=extra_recipients,
596 extra_recipients=extra_recipients,
597 send_email=send_email
597 send_email=send_email
598 )
598 )
599
599
600 if allowed_to_change_status and status:
600 if allowed_to_change_status and status:
601 old_calculated_status = pull_request.calculated_review_status()
601 old_calculated_status = pull_request.calculated_review_status()
602 ChangesetStatusModel().set_status(
602 ChangesetStatusModel().set_status(
603 pull_request.target_repo.repo_id,
603 pull_request.target_repo.repo_id,
604 status,
604 status,
605 apiuser.user_id,
605 apiuser.user_id,
606 comment,
606 comment,
607 pull_request=pull_request.pull_request_id
607 pull_request=pull_request.pull_request_id
608 )
608 )
609 Session().flush()
609 Session().flush()
610
610
611 Session().commit()
611 Session().commit()
612
612
613 PullRequestModel().trigger_pull_request_hook(
613 PullRequestModel().trigger_pull_request_hook(
614 pull_request, apiuser, 'comment',
614 pull_request, apiuser, 'comment',
615 data={'comment': comment})
615 data={'comment': comment})
616
616
617 if allowed_to_change_status and status:
617 if allowed_to_change_status and status:
618 # we now calculate the status of pull request, and based on that
618 # we now calculate the status of pull request, and based on that
619 # calculation we set the commits status
619 # calculation we set the commits status
620 calculated_status = pull_request.calculated_review_status()
620 calculated_status = pull_request.calculated_review_status()
621 if old_calculated_status != calculated_status:
621 if old_calculated_status != calculated_status:
622 PullRequestModel().trigger_pull_request_hook(
622 PullRequestModel().trigger_pull_request_hook(
623 pull_request, apiuser, 'review_status_change',
623 pull_request, apiuser, 'review_status_change',
624 data={'status': calculated_status})
624 data={'status': calculated_status})
625
625
626 data = {
626 data = {
627 'pull_request_id': pull_request.pull_request_id,
627 'pull_request_id': pull_request.pull_request_id,
628 'comment_id': comment.comment_id if comment else None,
628 'comment_id': comment.comment_id if comment else None,
629 'status': {'given': status, 'was_changed': status_change},
629 'status': {'given': status, 'was_changed': status_change},
630 }
630 }
631 return data
631 return data
632
632
633
633
634 @jsonrpc_method()
634 @jsonrpc_method()
635 def create_pull_request(
635 def create_pull_request(
636 request, apiuser, source_repo, target_repo, source_ref, target_ref,
636 request, apiuser, source_repo, target_repo, source_ref, target_ref,
637 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
637 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
638 description_renderer=Optional(''), reviewers=Optional(None)):
638 description_renderer=Optional(''), reviewers=Optional(None)):
639 """
639 """
640 Creates a new pull request.
640 Creates a new pull request.
641
641
642 Accepts refs in the following formats:
642 Accepts refs in the following formats:
643
643
644 * branch:<branch_name>:<sha>
644 * branch:<branch_name>:<sha>
645 * branch:<branch_name>
645 * branch:<branch_name>
646 * bookmark:<bookmark_name>:<sha> (Mercurial only)
646 * bookmark:<bookmark_name>:<sha> (Mercurial only)
647 * bookmark:<bookmark_name> (Mercurial only)
647 * bookmark:<bookmark_name> (Mercurial only)
648
648
649 :param apiuser: This is filled automatically from the |authtoken|.
649 :param apiuser: This is filled automatically from the |authtoken|.
650 :type apiuser: AuthUser
650 :type apiuser: AuthUser
651 :param source_repo: Set the source repository name.
651 :param source_repo: Set the source repository name.
652 :type source_repo: str
652 :type source_repo: str
653 :param target_repo: Set the target repository name.
653 :param target_repo: Set the target repository name.
654 :type target_repo: str
654 :type target_repo: str
655 :param source_ref: Set the source ref name.
655 :param source_ref: Set the source ref name.
656 :type source_ref: str
656 :type source_ref: str
657 :param target_ref: Set the target ref name.
657 :param target_ref: Set the target ref name.
658 :type target_ref: str
658 :type target_ref: str
659 :param owner: user_id or username
659 :param owner: user_id or username
660 :type owner: Optional(str)
660 :type owner: Optional(str)
661 :param title: Optionally Set the pull request title, it's generated otherwise
661 :param title: Optionally Set the pull request title, it's generated otherwise
662 :type title: str
662 :type title: str
663 :param description: Set the pull request description.
663 :param description: Set the pull request description.
664 :type description: Optional(str)
664 :type description: Optional(str)
665 :type description_renderer: Optional(str)
665 :type description_renderer: Optional(str)
666 :param description_renderer: Set pull request renderer for the description.
666 :param description_renderer: Set pull request renderer for the description.
667 It should be 'rst', 'markdown' or 'plain'. If not give default
667 It should be 'rst', 'markdown' or 'plain'. If not give default
668 system renderer will be used
668 system renderer will be used
669 :param reviewers: Set the new pull request reviewers list.
669 :param reviewers: Set the new pull request reviewers list.
670 Reviewer defined by review rules will be added automatically to the
670 Reviewer defined by review rules will be added automatically to the
671 defined list.
671 defined list.
672 :type reviewers: Optional(list)
672 :type reviewers: Optional(list)
673 Accepts username strings or objects of the format:
673 Accepts username strings or objects of the format:
674
674
675 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
675 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
676 """
676 """
677
677
678 source_db_repo = get_repo_or_error(source_repo)
678 source_db_repo = get_repo_or_error(source_repo)
679 target_db_repo = get_repo_or_error(target_repo)
679 target_db_repo = get_repo_or_error(target_repo)
680 if not has_superadmin_permission(apiuser):
680 if not has_superadmin_permission(apiuser):
681 _perms = ('repository.admin', 'repository.write', 'repository.read',)
681 _perms = ('repository.admin', 'repository.write', 'repository.read',)
682 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
682 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
683
683
684 owner = validate_set_owner_permissions(apiuser, owner)
684 owner = validate_set_owner_permissions(apiuser, owner)
685
685
686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
688
688
689 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
689 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
690 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
690 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
691
691
692 reviewer_objects = Optional.extract(reviewers) or []
692 reviewer_objects = Optional.extract(reviewers) or []
693
693
694 # serialize and validate passed in given reviewers
694 # serialize and validate passed in given reviewers
695 if reviewer_objects:
695 if reviewer_objects:
696 schema = ReviewerListSchema()
696 schema = ReviewerListSchema()
697 try:
697 try:
698 reviewer_objects = schema.deserialize(reviewer_objects)
698 reviewer_objects = schema.deserialize(reviewer_objects)
699 except Invalid as err:
699 except Invalid as err:
700 raise JSONRPCValidationError(colander_exc=err)
700 raise JSONRPCValidationError(colander_exc=err)
701
701
702 # validate users
702 # validate users
703 for reviewer_object in reviewer_objects:
703 for reviewer_object in reviewer_objects:
704 user = get_user_or_error(reviewer_object['username'])
704 user = get_user_or_error(reviewer_object['username'])
705 reviewer_object['user_id'] = user.user_id
705 reviewer_object['user_id'] = user.user_id
706
706
707 get_default_reviewers_data, validate_default_reviewers = \
707 get_default_reviewers_data, validate_default_reviewers = \
708 PullRequestModel().get_reviewer_functions()
708 PullRequestModel().get_reviewer_functions()
709
709
710 # recalculate reviewers logic, to make sure we can validate this
710 # recalculate reviewers logic, to make sure we can validate this
711 default_reviewers_data = get_default_reviewers_data(
711 default_reviewers_data = get_default_reviewers_data(
712 owner, source_db_repo,
712 owner, source_db_repo,
713 source_commit, target_db_repo, target_commit)
713 source_commit, target_db_repo, target_commit)
714
714
715 # now MERGE our given with the calculated
715 # now MERGE our given with the calculated
716 reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects
716 reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects
717
717
718 try:
718 try:
719 reviewers = validate_default_reviewers(
719 reviewers = validate_default_reviewers(
720 reviewer_objects, default_reviewers_data)
720 reviewer_objects, default_reviewers_data)
721 except ValueError as e:
721 except ValueError as e:
722 raise JSONRPCError('Reviewers Validation: {}'.format(e))
722 raise JSONRPCError('Reviewers Validation: {}'.format(e))
723
723
724 title = Optional.extract(title)
724 title = Optional.extract(title)
725 if not title:
725 if not title:
726 title_source_ref = source_ref.split(':', 2)[1]
726 title_source_ref = source_ref.split(':', 2)[1]
727 title = PullRequestModel().generate_pullrequest_title(
727 title = PullRequestModel().generate_pullrequest_title(
728 source=source_repo,
728 source=source_repo,
729 source_ref=title_source_ref,
729 source_ref=title_source_ref,
730 target=target_repo
730 target=target_repo
731 )
731 )
732
732
733 diff_info = default_reviewers_data['diff_info']
733 diff_info = default_reviewers_data['diff_info']
734 common_ancestor_id = diff_info['ancestor']
734 common_ancestor_id = diff_info['ancestor']
735 commits = diff_info['commits']
735 commits = diff_info['commits']
736
736
737 if not common_ancestor_id:
737 if not common_ancestor_id:
738 raise JSONRPCError('no common ancestor found')
738 raise JSONRPCError('no common ancestor found')
739
739
740 if not commits:
740 if not commits:
741 raise JSONRPCError('no commits found')
741 raise JSONRPCError('no commits found')
742
742
743 # NOTE(marcink): reversed is consistent with how we open it in the WEB interface
743 # NOTE(marcink): reversed is consistent with how we open it in the WEB interface
744 revisions = [commit.raw_id for commit in reversed(commits)]
744 revisions = [commit.raw_id for commit in reversed(commits)]
745
745
746 # recalculate target ref based on ancestor
746 # recalculate target ref based on ancestor
747 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
747 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
748 full_target_ref = ':'.join((target_ref_type, target_ref_name, common_ancestor_id))
748 full_target_ref = ':'.join((target_ref_type, target_ref_name, common_ancestor_id))
749
749
750 # fetch renderer, if set fallback to plain in case of PR
750 # fetch renderer, if set fallback to plain in case of PR
751 rc_config = SettingsModel().get_all_settings()
751 rc_config = SettingsModel().get_all_settings()
752 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
752 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
753 description = Optional.extract(description)
753 description = Optional.extract(description)
754 description_renderer = Optional.extract(description_renderer) or default_system_renderer
754 description_renderer = Optional.extract(description_renderer) or default_system_renderer
755
755
756 pull_request = PullRequestModel().create(
756 pull_request = PullRequestModel().create(
757 created_by=owner.user_id,
757 created_by=owner.user_id,
758 source_repo=source_repo,
758 source_repo=source_repo,
759 source_ref=full_source_ref,
759 source_ref=full_source_ref,
760 target_repo=target_repo,
760 target_repo=target_repo,
761 target_ref=full_target_ref,
761 target_ref=full_target_ref,
762 common_ancestor_id=common_ancestor_id,
762 common_ancestor_id=common_ancestor_id,
763 revisions=revisions,
763 revisions=revisions,
764 reviewers=reviewers,
764 reviewers=reviewers,
765 title=title,
765 title=title,
766 description=description,
766 description=description,
767 description_renderer=description_renderer,
767 description_renderer=description_renderer,
768 reviewer_data=default_reviewers_data,
768 reviewer_data=default_reviewers_data,
769 auth_user=apiuser
769 auth_user=apiuser
770 )
770 )
771
771
772 Session().commit()
772 Session().commit()
773 data = {
773 data = {
774 'msg': 'Created new pull request `{}`'.format(title),
774 'msg': 'Created new pull request `{}`'.format(title),
775 'pull_request_id': pull_request.pull_request_id,
775 'pull_request_id': pull_request.pull_request_id,
776 }
776 }
777 return data
777 return data
778
778
779
779
780 @jsonrpc_method()
780 @jsonrpc_method()
781 def update_pull_request(
781 def update_pull_request(
782 request, apiuser, pullrequestid, repoid=Optional(None),
782 request, apiuser, pullrequestid, repoid=Optional(None),
783 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
783 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
784 reviewers=Optional(None), update_commits=Optional(None)):
784 reviewers=Optional(None), update_commits=Optional(None)):
785 """
785 """
786 Updates a pull request.
786 Updates a pull request.
787
787
788 :param apiuser: This is filled automatically from the |authtoken|.
788 :param apiuser: This is filled automatically from the |authtoken|.
789 :type apiuser: AuthUser
789 :type apiuser: AuthUser
790 :param repoid: Optional repository name or repository ID.
790 :param repoid: Optional repository name or repository ID.
791 :type repoid: str or int
791 :type repoid: str or int
792 :param pullrequestid: The pull request ID.
792 :param pullrequestid: The pull request ID.
793 :type pullrequestid: int
793 :type pullrequestid: int
794 :param title: Set the pull request title.
794 :param title: Set the pull request title.
795 :type title: str
795 :type title: str
796 :param description: Update pull request description.
796 :param description: Update pull request description.
797 :type description: Optional(str)
797 :type description: Optional(str)
798 :type description_renderer: Optional(str)
798 :type description_renderer: Optional(str)
799 :param description_renderer: Update pull request renderer for the description.
799 :param description_renderer: Update pull request renderer for the description.
800 It should be 'rst', 'markdown' or 'plain'
800 It should be 'rst', 'markdown' or 'plain'
801 :param reviewers: Update pull request reviewers list with new value.
801 :param reviewers: Update pull request reviewers list with new value.
802 :type reviewers: Optional(list)
802 :type reviewers: Optional(list)
803 Accepts username strings or objects of the format:
803 Accepts username strings or objects of the format:
804
804
805 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
805 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
806
806
807 :param update_commits: Trigger update of commits for this pull request
807 :param update_commits: Trigger update of commits for this pull request
808 :type: update_commits: Optional(bool)
808 :type: update_commits: Optional(bool)
809
809
810 Example output:
810 Example output:
811
811
812 .. code-block:: bash
812 .. code-block:: bash
813
813
814 id : <id_given_in_input>
814 id : <id_given_in_input>
815 result : {
815 result : {
816 "msg": "Updated pull request `63`",
816 "msg": "Updated pull request `63`",
817 "pull_request": <pull_request_object>,
817 "pull_request": <pull_request_object>,
818 "updated_reviewers": {
818 "updated_reviewers": {
819 "added": [
819 "added": [
820 "username"
820 "username"
821 ],
821 ],
822 "removed": []
822 "removed": []
823 },
823 },
824 "updated_commits": {
824 "updated_commits": {
825 "added": [
825 "added": [
826 "<sha1_hash>"
826 "<sha1_hash>"
827 ],
827 ],
828 "common": [
828 "common": [
829 "<sha1_hash>",
829 "<sha1_hash>",
830 "<sha1_hash>",
830 "<sha1_hash>",
831 ],
831 ],
832 "removed": []
832 "removed": []
833 }
833 }
834 }
834 }
835 error : null
835 error : null
836 """
836 """
837
837
838 pull_request = get_pull_request_or_error(pullrequestid)
838 pull_request = get_pull_request_or_error(pullrequestid)
839 if Optional.extract(repoid):
839 if Optional.extract(repoid):
840 repo = get_repo_or_error(repoid)
840 repo = get_repo_or_error(repoid)
841 else:
841 else:
842 repo = pull_request.target_repo
842 repo = pull_request.target_repo
843
843
844 if not PullRequestModel().check_user_update(
844 if not PullRequestModel().check_user_update(
845 pull_request, apiuser, api=True):
845 pull_request, apiuser, api=True):
846 raise JSONRPCError(
846 raise JSONRPCError(
847 'pull request `%s` update failed, no permission to update.' % (
847 'pull request `%s` update failed, no permission to update.' % (
848 pullrequestid,))
848 pullrequestid,))
849 if pull_request.is_closed():
849 if pull_request.is_closed():
850 raise JSONRPCError(
850 raise JSONRPCError(
851 'pull request `%s` update failed, pull request is closed' % (
851 'pull request `%s` update failed, pull request is closed' % (
852 pullrequestid,))
852 pullrequestid,))
853
853
854 reviewer_objects = Optional.extract(reviewers) or []
854 reviewer_objects = Optional.extract(reviewers) or []
855
855
856 if reviewer_objects:
856 if reviewer_objects:
857 schema = ReviewerListSchema()
857 schema = ReviewerListSchema()
858 try:
858 try:
859 reviewer_objects = schema.deserialize(reviewer_objects)
859 reviewer_objects = schema.deserialize(reviewer_objects)
860 except Invalid as err:
860 except Invalid as err:
861 raise JSONRPCValidationError(colander_exc=err)
861 raise JSONRPCValidationError(colander_exc=err)
862
862
863 # validate users
863 # validate users
864 for reviewer_object in reviewer_objects:
864 for reviewer_object in reviewer_objects:
865 user = get_user_or_error(reviewer_object['username'])
865 user = get_user_or_error(reviewer_object['username'])
866 reviewer_object['user_id'] = user.user_id
866 reviewer_object['user_id'] = user.user_id
867
867
868 get_default_reviewers_data, get_validated_reviewers = \
868 get_default_reviewers_data, get_validated_reviewers = \
869 PullRequestModel().get_reviewer_functions()
869 PullRequestModel().get_reviewer_functions()
870
870
871 # re-use stored rules
871 # re-use stored rules
872 reviewer_rules = pull_request.reviewer_data
872 reviewer_rules = pull_request.reviewer_data
873 try:
873 try:
874 reviewers = get_validated_reviewers(
874 reviewers = get_validated_reviewers(
875 reviewer_objects, reviewer_rules)
875 reviewer_objects, reviewer_rules)
876 except ValueError as e:
876 except ValueError as e:
877 raise JSONRPCError('Reviewers Validation: {}'.format(e))
877 raise JSONRPCError('Reviewers Validation: {}'.format(e))
878 else:
878 else:
879 reviewers = []
879 reviewers = []
880
880
881 title = Optional.extract(title)
881 title = Optional.extract(title)
882 description = Optional.extract(description)
882 description = Optional.extract(description)
883 description_renderer = Optional.extract(description_renderer)
883 description_renderer = Optional.extract(description_renderer)
884
884
885 if title or description:
885 if title or description:
886 PullRequestModel().edit(
886 PullRequestModel().edit(
887 pull_request,
887 pull_request,
888 title or pull_request.title,
888 title or pull_request.title,
889 description or pull_request.description,
889 description or pull_request.description,
890 description_renderer or pull_request.description_renderer,
890 description_renderer or pull_request.description_renderer,
891 apiuser)
891 apiuser)
892 Session().commit()
892 Session().commit()
893
893
894 commit_changes = {"added": [], "common": [], "removed": []}
894 commit_changes = {"added": [], "common": [], "removed": []}
895 if str2bool(Optional.extract(update_commits)):
895 if str2bool(Optional.extract(update_commits)):
896
896
897 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
897 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
898 raise JSONRPCError(
898 raise JSONRPCError(
899 'Operation forbidden because pull request is in state {}, '
899 'Operation forbidden because pull request is in state {}, '
900 'only state {} is allowed.'.format(
900 'only state {} is allowed.'.format(
901 pull_request.pull_request_state, PullRequest.STATE_CREATED))
901 pull_request.pull_request_state, PullRequest.STATE_CREATED))
902
902
903 with pull_request.set_state(PullRequest.STATE_UPDATING):
903 with pull_request.set_state(PullRequest.STATE_UPDATING):
904 if PullRequestModel().has_valid_update_type(pull_request):
904 if PullRequestModel().has_valid_update_type(pull_request):
905 db_user = apiuser.get_instance()
905 db_user = apiuser.get_instance()
906 update_response = PullRequestModel().update_commits(
906 update_response = PullRequestModel().update_commits(
907 pull_request, db_user)
907 pull_request, db_user)
908 commit_changes = update_response.changes or commit_changes
908 commit_changes = update_response.changes or commit_changes
909 Session().commit()
909 Session().commit()
910
910
911 reviewers_changes = {"added": [], "removed": []}
911 reviewers_changes = {"added": [], "removed": []}
912 if reviewers:
912 if reviewers:
913 old_calculated_status = pull_request.calculated_review_status()
913 old_calculated_status = pull_request.calculated_review_status()
914 added_reviewers, removed_reviewers = \
914 added_reviewers, removed_reviewers = \
915 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
915 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
916
916
917 reviewers_changes['added'] = sorted(
917 reviewers_changes['added'] = sorted(
918 [get_user_or_error(n).username for n in added_reviewers])
918 [get_user_or_error(n).username for n in added_reviewers])
919 reviewers_changes['removed'] = sorted(
919 reviewers_changes['removed'] = sorted(
920 [get_user_or_error(n).username for n in removed_reviewers])
920 [get_user_or_error(n).username for n in removed_reviewers])
921 Session().commit()
921 Session().commit()
922
922
923 # trigger status changed if change in reviewers changes the status
923 # trigger status changed if change in reviewers changes the status
924 calculated_status = pull_request.calculated_review_status()
924 calculated_status = pull_request.calculated_review_status()
925 if old_calculated_status != calculated_status:
925 if old_calculated_status != calculated_status:
926 PullRequestModel().trigger_pull_request_hook(
926 PullRequestModel().trigger_pull_request_hook(
927 pull_request, apiuser, 'review_status_change',
927 pull_request, apiuser, 'review_status_change',
928 data={'status': calculated_status})
928 data={'status': calculated_status})
929
929
930 data = {
930 data = {
931 'msg': 'Updated pull request `{}`'.format(
931 'msg': 'Updated pull request `{}`'.format(
932 pull_request.pull_request_id),
932 pull_request.pull_request_id),
933 'pull_request': pull_request.get_api_data(),
933 'pull_request': pull_request.get_api_data(),
934 'updated_commits': commit_changes,
934 'updated_commits': commit_changes,
935 'updated_reviewers': reviewers_changes
935 'updated_reviewers': reviewers_changes
936 }
936 }
937
937
938 return data
938 return data
939
939
940
940
941 @jsonrpc_method()
941 @jsonrpc_method()
942 def close_pull_request(
942 def close_pull_request(
943 request, apiuser, pullrequestid, repoid=Optional(None),
943 request, apiuser, pullrequestid, repoid=Optional(None),
944 userid=Optional(OAttr('apiuser')), message=Optional('')):
944 userid=Optional(OAttr('apiuser')), message=Optional('')):
945 """
945 """
946 Close the pull request specified by `pullrequestid`.
946 Close the pull request specified by `pullrequestid`.
947
947
948 :param apiuser: This is filled automatically from the |authtoken|.
948 :param apiuser: This is filled automatically from the |authtoken|.
949 :type apiuser: AuthUser
949 :type apiuser: AuthUser
950 :param repoid: Repository name or repository ID to which the pull
950 :param repoid: Repository name or repository ID to which the pull
951 request belongs.
951 request belongs.
952 :type repoid: str or int
952 :type repoid: str or int
953 :param pullrequestid: ID of the pull request to be closed.
953 :param pullrequestid: ID of the pull request to be closed.
954 :type pullrequestid: int
954 :type pullrequestid: int
955 :param userid: Close the pull request as this user.
955 :param userid: Close the pull request as this user.
956 :type userid: Optional(str or int)
956 :type userid: Optional(str or int)
957 :param message: Optional message to close the Pull Request with. If not
957 :param message: Optional message to close the Pull Request with. If not
958 specified it will be generated automatically.
958 specified it will be generated automatically.
959 :type message: Optional(str)
959 :type message: Optional(str)
960
960
961 Example output:
961 Example output:
962
962
963 .. code-block:: bash
963 .. code-block:: bash
964
964
965 "id": <id_given_in_input>,
965 "id": <id_given_in_input>,
966 "result": {
966 "result": {
967 "pull_request_id": "<int>",
967 "pull_request_id": "<int>",
968 "close_status": "<str:status_lbl>,
968 "close_status": "<str:status_lbl>,
969 "closed": "<bool>"
969 "closed": "<bool>"
970 },
970 },
971 "error": null
971 "error": null
972
972
973 """
973 """
974 _ = request.translate
974 _ = request.translate
975
975
976 pull_request = get_pull_request_or_error(pullrequestid)
976 pull_request = get_pull_request_or_error(pullrequestid)
977 if Optional.extract(repoid):
977 if Optional.extract(repoid):
978 repo = get_repo_or_error(repoid)
978 repo = get_repo_or_error(repoid)
979 else:
979 else:
980 repo = pull_request.target_repo
980 repo = pull_request.target_repo
981
981
982 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
983 user=apiuser, repo_name=repo.repo_name)
982 if not isinstance(userid, Optional):
984 if not isinstance(userid, Optional):
983 if (has_superadmin_permission(apiuser) or
985 if has_superadmin_permission(apiuser) or is_repo_admin:
984 HasRepoPermissionAnyApi('repository.admin')(
985 user=apiuser, repo_name=repo.repo_name)):
986 apiuser = get_user_or_error(userid)
986 apiuser = get_user_or_error(userid)
987 else:
987 else:
988 raise JSONRPCError('userid is not the same as your user')
988 raise JSONRPCError('userid is not the same as your user')
989
989
990 if pull_request.is_closed():
990 if pull_request.is_closed():
991 raise JSONRPCError(
991 raise JSONRPCError(
992 'pull request `%s` is already closed' % (pullrequestid,))
992 'pull request `%s` is already closed' % (pullrequestid,))
993
993
994 # only owner or admin or person with write permissions
994 # only owner or admin or person with write permissions
995 allowed_to_close = PullRequestModel().check_user_update(
995 allowed_to_close = PullRequestModel().check_user_update(
996 pull_request, apiuser, api=True)
996 pull_request, apiuser, api=True)
997
997
998 if not allowed_to_close:
998 if not allowed_to_close:
999 raise JSONRPCError(
999 raise JSONRPCError(
1000 'pull request `%s` close failed, no permission to close.' % (
1000 'pull request `%s` close failed, no permission to close.' % (
1001 pullrequestid,))
1001 pullrequestid,))
1002
1002
1003 # message we're using to close the PR, else it's automatically generated
1003 # message we're using to close the PR, else it's automatically generated
1004 message = Optional.extract(message)
1004 message = Optional.extract(message)
1005
1005
1006 # finally close the PR, with proper message comment
1006 # finally close the PR, with proper message comment
1007 comment, status = PullRequestModel().close_pull_request_with_comment(
1007 comment, status = PullRequestModel().close_pull_request_with_comment(
1008 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1008 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1009 status_lbl = ChangesetStatus.get_status_lbl(status)
1009 status_lbl = ChangesetStatus.get_status_lbl(status)
1010
1010
1011 Session().commit()
1011 Session().commit()
1012
1012
1013 data = {
1013 data = {
1014 'pull_request_id': pull_request.pull_request_id,
1014 'pull_request_id': pull_request.pull_request_id,
1015 'close_status': status_lbl,
1015 'close_status': status_lbl,
1016 'closed': True,
1016 'closed': True,
1017 }
1017 }
1018 return data
1018 return data
@@ -1,2349 +1,2506 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger, rc_cache
32 from rhodecode.lib import audit_logger, rc_cache
33 from rhodecode.lib import repo_maintenance
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 from rhodecode.lib.celerylib.utils import get_task_id
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 from rhodecode.lib.ext_json import json
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 from rhodecode.lib.vcs import RepositoryError
43 from rhodecode.lib.vcs import RepositoryError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
44 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
41 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
47 from rhodecode.model.db import (
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
48 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 ChangesetComment)
49 ChangesetComment)
46 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.scm import ScmModel, RepoList
53 from rhodecode.model.scm import ScmModel, RepoList
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
54 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
50 from rhodecode.model import validation_schema
55 from rhodecode.model import validation_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
56 from rhodecode.model.validation_schema.schemas import repo_schema
52
57
53 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
54
59
55
60
56 @jsonrpc_method()
61 @jsonrpc_method()
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
62 def get_repo(request, apiuser, repoid, cache=Optional(True)):
58 """
63 """
59 Gets an existing repository by its name or repository_id.
64 Gets an existing repository by its name or repository_id.
60
65
61 The members section so the output returns users groups or users
66 The members section so the output returns users groups or users
62 associated with that repository.
67 associated with that repository.
63
68
64 This command can only be run using an |authtoken| with admin rights,
69 This command can only be run using an |authtoken| with admin rights,
65 or users with at least read rights to the |repo|.
70 or users with at least read rights to the |repo|.
66
71
67 :param apiuser: This is filled automatically from the |authtoken|.
72 :param apiuser: This is filled automatically from the |authtoken|.
68 :type apiuser: AuthUser
73 :type apiuser: AuthUser
69 :param repoid: The repository name or repository id.
74 :param repoid: The repository name or repository id.
70 :type repoid: str or int
75 :type repoid: str or int
71 :param cache: use the cached value for last changeset
76 :param cache: use the cached value for last changeset
72 :type: cache: Optional(bool)
77 :type: cache: Optional(bool)
73
78
74 Example output:
79 Example output:
75
80
76 .. code-block:: bash
81 .. code-block:: bash
77
82
78 {
83 {
79 "error": null,
84 "error": null,
80 "id": <repo_id>,
85 "id": <repo_id>,
81 "result": {
86 "result": {
82 "clone_uri": null,
87 "clone_uri": null,
83 "created_on": "timestamp",
88 "created_on": "timestamp",
84 "description": "repo description",
89 "description": "repo description",
85 "enable_downloads": false,
90 "enable_downloads": false,
86 "enable_locking": false,
91 "enable_locking": false,
87 "enable_statistics": false,
92 "enable_statistics": false,
88 "followers": [
93 "followers": [
89 {
94 {
90 "active": true,
95 "active": true,
91 "admin": false,
96 "admin": false,
92 "api_key": "****************************************",
97 "api_key": "****************************************",
93 "api_keys": [
98 "api_keys": [
94 "****************************************"
99 "****************************************"
95 ],
100 ],
96 "email": "user@example.com",
101 "email": "user@example.com",
97 "emails": [
102 "emails": [
98 "user@example.com"
103 "user@example.com"
99 ],
104 ],
100 "extern_name": "rhodecode",
105 "extern_name": "rhodecode",
101 "extern_type": "rhodecode",
106 "extern_type": "rhodecode",
102 "firstname": "username",
107 "firstname": "username",
103 "ip_addresses": [],
108 "ip_addresses": [],
104 "language": null,
109 "language": null,
105 "last_login": "2015-09-16T17:16:35.854",
110 "last_login": "2015-09-16T17:16:35.854",
106 "lastname": "surname",
111 "lastname": "surname",
107 "user_id": <user_id>,
112 "user_id": <user_id>,
108 "username": "name"
113 "username": "name"
109 }
114 }
110 ],
115 ],
111 "fork_of": "parent-repo",
116 "fork_of": "parent-repo",
112 "landing_rev": [
117 "landing_rev": [
113 "rev",
118 "rev",
114 "tip"
119 "tip"
115 ],
120 ],
116 "last_changeset": {
121 "last_changeset": {
117 "author": "User <user@example.com>",
122 "author": "User <user@example.com>",
118 "branch": "default",
123 "branch": "default",
119 "date": "timestamp",
124 "date": "timestamp",
120 "message": "last commit message",
125 "message": "last commit message",
121 "parents": [
126 "parents": [
122 {
127 {
123 "raw_id": "commit-id"
128 "raw_id": "commit-id"
124 }
129 }
125 ],
130 ],
126 "raw_id": "commit-id",
131 "raw_id": "commit-id",
127 "revision": <revision number>,
132 "revision": <revision number>,
128 "short_id": "short id"
133 "short_id": "short id"
129 },
134 },
130 "lock_reason": null,
135 "lock_reason": null,
131 "locked_by": null,
136 "locked_by": null,
132 "locked_date": null,
137 "locked_date": null,
133 "owner": "owner-name",
138 "owner": "owner-name",
134 "permissions": [
139 "permissions": [
135 {
140 {
136 "name": "super-admin-name",
141 "name": "super-admin-name",
137 "origin": "super-admin",
142 "origin": "super-admin",
138 "permission": "repository.admin",
143 "permission": "repository.admin",
139 "type": "user"
144 "type": "user"
140 },
145 },
141 {
146 {
142 "name": "owner-name",
147 "name": "owner-name",
143 "origin": "owner",
148 "origin": "owner",
144 "permission": "repository.admin",
149 "permission": "repository.admin",
145 "type": "user"
150 "type": "user"
146 },
151 },
147 {
152 {
148 "name": "user-group-name",
153 "name": "user-group-name",
149 "origin": "permission",
154 "origin": "permission",
150 "permission": "repository.write",
155 "permission": "repository.write",
151 "type": "user_group"
156 "type": "user_group"
152 }
157 }
153 ],
158 ],
154 "private": true,
159 "private": true,
155 "repo_id": 676,
160 "repo_id": 676,
156 "repo_name": "user-group/repo-name",
161 "repo_name": "user-group/repo-name",
157 "repo_type": "hg"
162 "repo_type": "hg"
158 }
163 }
159 }
164 }
160 """
165 """
161
166
162 repo = get_repo_or_error(repoid)
167 repo = get_repo_or_error(repoid)
163 cache = Optional.extract(cache)
168 cache = Optional.extract(cache)
164
169
165 include_secrets = False
170 include_secrets = False
166 if has_superadmin_permission(apiuser):
171 if has_superadmin_permission(apiuser):
167 include_secrets = True
172 include_secrets = True
168 else:
173 else:
169 # check if we have at least read permission for this repo !
174 # check if we have at least read permission for this repo !
170 _perms = (
175 _perms = (
171 'repository.admin', 'repository.write', 'repository.read',)
176 'repository.admin', 'repository.write', 'repository.read',)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
177 validate_repo_permissions(apiuser, repoid, repo, _perms)
173
178
174 permissions = []
179 permissions = []
175 for _user in repo.permissions():
180 for _user in repo.permissions():
176 user_data = {
181 user_data = {
177 'name': _user.username,
182 'name': _user.username,
178 'permission': _user.permission,
183 'permission': _user.permission,
179 'origin': get_origin(_user),
184 'origin': get_origin(_user),
180 'type': "user",
185 'type': "user",
181 }
186 }
182 permissions.append(user_data)
187 permissions.append(user_data)
183
188
184 for _user_group in repo.permission_user_groups():
189 for _user_group in repo.permission_user_groups():
185 user_group_data = {
190 user_group_data = {
186 'name': _user_group.users_group_name,
191 'name': _user_group.users_group_name,
187 'permission': _user_group.permission,
192 'permission': _user_group.permission,
188 'origin': get_origin(_user_group),
193 'origin': get_origin(_user_group),
189 'type': "user_group",
194 'type': "user_group",
190 }
195 }
191 permissions.append(user_group_data)
196 permissions.append(user_group_data)
192
197
193 following_users = [
198 following_users = [
194 user.user.get_api_data(include_secrets=include_secrets)
199 user.user.get_api_data(include_secrets=include_secrets)
195 for user in repo.followers]
200 for user in repo.followers]
196
201
197 if not cache:
202 if not cache:
198 repo.update_commit_cache()
203 repo.update_commit_cache()
199 data = repo.get_api_data(include_secrets=include_secrets)
204 data = repo.get_api_data(include_secrets=include_secrets)
200 data['permissions'] = permissions
205 data['permissions'] = permissions
201 data['followers'] = following_users
206 data['followers'] = following_users
202 return data
207 return data
203
208
204
209
205 @jsonrpc_method()
210 @jsonrpc_method()
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
211 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
207 """
212 """
208 Lists all existing repositories.
213 Lists all existing repositories.
209
214
210 This command can only be run using an |authtoken| with admin rights,
215 This command can only be run using an |authtoken| with admin rights,
211 or users with at least read rights to |repos|.
216 or users with at least read rights to |repos|.
212
217
213 :param apiuser: This is filled automatically from the |authtoken|.
218 :param apiuser: This is filled automatically from the |authtoken|.
214 :type apiuser: AuthUser
219 :type apiuser: AuthUser
215 :param root: specify root repository group to fetch repositories.
220 :param root: specify root repository group to fetch repositories.
216 filters the returned repositories to be members of given root group.
221 filters the returned repositories to be members of given root group.
217 :type root: Optional(None)
222 :type root: Optional(None)
218 :param traverse: traverse given root into subrepositories. With this flag
223 :param traverse: traverse given root into subrepositories. With this flag
219 set to False, it will only return top-level repositories from `root`.
224 set to False, it will only return top-level repositories from `root`.
220 if root is empty it will return just top-level repositories.
225 if root is empty it will return just top-level repositories.
221 :type traverse: Optional(True)
226 :type traverse: Optional(True)
222
227
223
228
224 Example output:
229 Example output:
225
230
226 .. code-block:: bash
231 .. code-block:: bash
227
232
228 id : <id_given_in_input>
233 id : <id_given_in_input>
229 result: [
234 result: [
230 {
235 {
231 "repo_id" : "<repo_id>",
236 "repo_id" : "<repo_id>",
232 "repo_name" : "<reponame>"
237 "repo_name" : "<reponame>"
233 "repo_type" : "<repo_type>",
238 "repo_type" : "<repo_type>",
234 "clone_uri" : "<clone_uri>",
239 "clone_uri" : "<clone_uri>",
235 "private": : "<bool>",
240 "private": : "<bool>",
236 "created_on" : "<datetimecreated>",
241 "created_on" : "<datetimecreated>",
237 "description" : "<description>",
242 "description" : "<description>",
238 "landing_rev": "<landing_rev>",
243 "landing_rev": "<landing_rev>",
239 "owner": "<repo_owner>",
244 "owner": "<repo_owner>",
240 "fork_of": "<name_of_fork_parent>",
245 "fork_of": "<name_of_fork_parent>",
241 "enable_downloads": "<bool>",
246 "enable_downloads": "<bool>",
242 "enable_locking": "<bool>",
247 "enable_locking": "<bool>",
243 "enable_statistics": "<bool>",
248 "enable_statistics": "<bool>",
244 },
249 },
245 ...
250 ...
246 ]
251 ]
247 error: null
252 error: null
248 """
253 """
249
254
250 include_secrets = has_superadmin_permission(apiuser)
255 include_secrets = has_superadmin_permission(apiuser)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
256 _perms = ('repository.read', 'repository.write', 'repository.admin',)
252 extras = {'user': apiuser}
257 extras = {'user': apiuser}
253
258
254 root = Optional.extract(root)
259 root = Optional.extract(root)
255 traverse = Optional.extract(traverse, binary=True)
260 traverse = Optional.extract(traverse, binary=True)
256
261
257 if root:
262 if root:
258 # verify parent existance, if it's empty return an error
263 # verify parent existance, if it's empty return an error
259 parent = RepoGroup.get_by_group_name(root)
264 parent = RepoGroup.get_by_group_name(root)
260 if not parent:
265 if not parent:
261 raise JSONRPCError(
266 raise JSONRPCError(
262 'Root repository group `{}` does not exist'.format(root))
267 'Root repository group `{}` does not exist'.format(root))
263
268
264 if traverse:
269 if traverse:
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
270 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
266 else:
271 else:
267 repos = RepoModel().get_repos_for_root(root=parent)
272 repos = RepoModel().get_repos_for_root(root=parent)
268 else:
273 else:
269 if traverse:
274 if traverse:
270 repos = RepoModel().get_all()
275 repos = RepoModel().get_all()
271 else:
276 else:
272 # return just top-level
277 # return just top-level
273 repos = RepoModel().get_repos_for_root(root=None)
278 repos = RepoModel().get_repos_for_root(root=None)
274
279
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
280 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
276 return [repo.get_api_data(include_secrets=include_secrets)
281 return [repo.get_api_data(include_secrets=include_secrets)
277 for repo in repo_list]
282 for repo in repo_list]
278
283
279
284
280 @jsonrpc_method()
285 @jsonrpc_method()
281 def get_repo_changeset(request, apiuser, repoid, revision,
286 def get_repo_changeset(request, apiuser, repoid, revision,
282 details=Optional('basic')):
287 details=Optional('basic')):
283 """
288 """
284 Returns information about a changeset.
289 Returns information about a changeset.
285
290
286 Additionally parameters define the amount of details returned by
291 Additionally parameters define the amount of details returned by
287 this function.
292 this function.
288
293
289 This command can only be run using an |authtoken| with admin rights,
294 This command can only be run using an |authtoken| with admin rights,
290 or users with at least read rights to the |repo|.
295 or users with at least read rights to the |repo|.
291
296
292 :param apiuser: This is filled automatically from the |authtoken|.
297 :param apiuser: This is filled automatically from the |authtoken|.
293 :type apiuser: AuthUser
298 :type apiuser: AuthUser
294 :param repoid: The repository name or repository id
299 :param repoid: The repository name or repository id
295 :type repoid: str or int
300 :type repoid: str or int
296 :param revision: revision for which listing should be done
301 :param revision: revision for which listing should be done
297 :type revision: str
302 :type revision: str
298 :param details: details can be 'basic|extended|full' full gives diff
303 :param details: details can be 'basic|extended|full' full gives diff
299 info details like the diff itself, and number of changed files etc.
304 info details like the diff itself, and number of changed files etc.
300 :type details: Optional(str)
305 :type details: Optional(str)
301
306
302 """
307 """
303 repo = get_repo_or_error(repoid)
308 repo = get_repo_or_error(repoid)
304 if not has_superadmin_permission(apiuser):
309 if not has_superadmin_permission(apiuser):
305 _perms = (
310 _perms = (
306 'repository.admin', 'repository.write', 'repository.read',)
311 'repository.admin', 'repository.write', 'repository.read',)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
312 validate_repo_permissions(apiuser, repoid, repo, _perms)
308
313
309 changes_details = Optional.extract(details)
314 changes_details = Optional.extract(details)
310 _changes_details_types = ['basic', 'extended', 'full']
315 _changes_details_types = ['basic', 'extended', 'full']
311 if changes_details not in _changes_details_types:
316 if changes_details not in _changes_details_types:
312 raise JSONRPCError(
317 raise JSONRPCError(
313 'ret_type must be one of %s' % (
318 'ret_type must be one of %s' % (
314 ','.join(_changes_details_types)))
319 ','.join(_changes_details_types)))
315
320
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
321 pre_load = ['author', 'branch', 'date', 'message', 'parents',
317 'status', '_commit', '_file_paths']
322 'status', '_commit', '_file_paths']
318
323
319 try:
324 try:
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
325 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
321 except TypeError as e:
326 except TypeError as e:
322 raise JSONRPCError(safe_str(e))
327 raise JSONRPCError(safe_str(e))
323 _cs_json = cs.__json__()
328 _cs_json = cs.__json__()
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
329 _cs_json['diff'] = build_commit_data(cs, changes_details)
325 if changes_details == 'full':
330 if changes_details == 'full':
326 _cs_json['refs'] = cs._get_refs()
331 _cs_json['refs'] = cs._get_refs()
327 return _cs_json
332 return _cs_json
328
333
329
334
330 @jsonrpc_method()
335 @jsonrpc_method()
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
336 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
332 details=Optional('basic')):
337 details=Optional('basic')):
333 """
338 """
334 Returns a set of commits limited by the number starting
339 Returns a set of commits limited by the number starting
335 from the `start_rev` option.
340 from the `start_rev` option.
336
341
337 Additional parameters define the amount of details returned by this
342 Additional parameters define the amount of details returned by this
338 function.
343 function.
339
344
340 This command can only be run using an |authtoken| with admin rights,
345 This command can only be run using an |authtoken| with admin rights,
341 or users with at least read rights to |repos|.
346 or users with at least read rights to |repos|.
342
347
343 :param apiuser: This is filled automatically from the |authtoken|.
348 :param apiuser: This is filled automatically from the |authtoken|.
344 :type apiuser: AuthUser
349 :type apiuser: AuthUser
345 :param repoid: The repository name or repository ID.
350 :param repoid: The repository name or repository ID.
346 :type repoid: str or int
351 :type repoid: str or int
347 :param start_rev: The starting revision from where to get changesets.
352 :param start_rev: The starting revision from where to get changesets.
348 :type start_rev: str
353 :type start_rev: str
349 :param limit: Limit the number of commits to this amount
354 :param limit: Limit the number of commits to this amount
350 :type limit: str or int
355 :type limit: str or int
351 :param details: Set the level of detail returned. Valid option are:
356 :param details: Set the level of detail returned. Valid option are:
352 ``basic``, ``extended`` and ``full``.
357 ``basic``, ``extended`` and ``full``.
353 :type details: Optional(str)
358 :type details: Optional(str)
354
359
355 .. note::
360 .. note::
356
361
357 Setting the parameter `details` to the value ``full`` is extensive
362 Setting the parameter `details` to the value ``full`` is extensive
358 and returns details like the diff itself, and the number
363 and returns details like the diff itself, and the number
359 of changed files.
364 of changed files.
360
365
361 """
366 """
362 repo = get_repo_or_error(repoid)
367 repo = get_repo_or_error(repoid)
363 if not has_superadmin_permission(apiuser):
368 if not has_superadmin_permission(apiuser):
364 _perms = (
369 _perms = (
365 'repository.admin', 'repository.write', 'repository.read',)
370 'repository.admin', 'repository.write', 'repository.read',)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
371 validate_repo_permissions(apiuser, repoid, repo, _perms)
367
372
368 changes_details = Optional.extract(details)
373 changes_details = Optional.extract(details)
369 _changes_details_types = ['basic', 'extended', 'full']
374 _changes_details_types = ['basic', 'extended', 'full']
370 if changes_details not in _changes_details_types:
375 if changes_details not in _changes_details_types:
371 raise JSONRPCError(
376 raise JSONRPCError(
372 'ret_type must be one of %s' % (
377 'ret_type must be one of %s' % (
373 ','.join(_changes_details_types)))
378 ','.join(_changes_details_types)))
374
379
375 limit = int(limit)
380 limit = int(limit)
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
381 pre_load = ['author', 'branch', 'date', 'message', 'parents',
377 'status', '_commit', '_file_paths']
382 'status', '_commit', '_file_paths']
378
383
379 vcs_repo = repo.scm_instance()
384 vcs_repo = repo.scm_instance()
380 # SVN needs a special case to distinguish its index and commit id
385 # SVN needs a special case to distinguish its index and commit id
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
386 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
382 start_rev = vcs_repo.commit_ids[0]
387 start_rev = vcs_repo.commit_ids[0]
383
388
384 try:
389 try:
385 commits = vcs_repo.get_commits(
390 commits = vcs_repo.get_commits(
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
391 start_id=start_rev, pre_load=pre_load, translate_tags=False)
387 except TypeError as e:
392 except TypeError as e:
388 raise JSONRPCError(safe_str(e))
393 raise JSONRPCError(safe_str(e))
389 except Exception:
394 except Exception:
390 log.exception('Fetching of commits failed')
395 log.exception('Fetching of commits failed')
391 raise JSONRPCError('Error occurred during commit fetching')
396 raise JSONRPCError('Error occurred during commit fetching')
392
397
393 ret = []
398 ret = []
394 for cnt, commit in enumerate(commits):
399 for cnt, commit in enumerate(commits):
395 if cnt >= limit != -1:
400 if cnt >= limit != -1:
396 break
401 break
397 _cs_json = commit.__json__()
402 _cs_json = commit.__json__()
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
403 _cs_json['diff'] = build_commit_data(commit, changes_details)
399 if changes_details == 'full':
404 if changes_details == 'full':
400 _cs_json['refs'] = {
405 _cs_json['refs'] = {
401 'branches': [commit.branch],
406 'branches': [commit.branch],
402 'bookmarks': getattr(commit, 'bookmarks', []),
407 'bookmarks': getattr(commit, 'bookmarks', []),
403 'tags': commit.tags
408 'tags': commit.tags
404 }
409 }
405 ret.append(_cs_json)
410 ret.append(_cs_json)
406 return ret
411 return ret
407
412
408
413
409 @jsonrpc_method()
414 @jsonrpc_method()
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
415 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
411 ret_type=Optional('all'), details=Optional('basic'),
416 ret_type=Optional('all'), details=Optional('basic'),
412 max_file_bytes=Optional(None)):
417 max_file_bytes=Optional(None)):
413 """
418 """
414 Returns a list of nodes and children in a flat list for a given
419 Returns a list of nodes and children in a flat list for a given
415 path at given revision.
420 path at given revision.
416
421
417 It's possible to specify ret_type to show only `files` or `dirs`.
422 It's possible to specify ret_type to show only `files` or `dirs`.
418
423
419 This command can only be run using an |authtoken| with admin rights,
424 This command can only be run using an |authtoken| with admin rights,
420 or users with at least read rights to |repos|.
425 or users with at least read rights to |repos|.
421
426
422 :param apiuser: This is filled automatically from the |authtoken|.
427 :param apiuser: This is filled automatically from the |authtoken|.
423 :type apiuser: AuthUser
428 :type apiuser: AuthUser
424 :param repoid: The repository name or repository ID.
429 :param repoid: The repository name or repository ID.
425 :type repoid: str or int
430 :type repoid: str or int
426 :param revision: The revision for which listing should be done.
431 :param revision: The revision for which listing should be done.
427 :type revision: str
432 :type revision: str
428 :param root_path: The path from which to start displaying.
433 :param root_path: The path from which to start displaying.
429 :type root_path: str
434 :type root_path: str
430 :param ret_type: Set the return type. Valid options are
435 :param ret_type: Set the return type. Valid options are
431 ``all`` (default), ``files`` and ``dirs``.
436 ``all`` (default), ``files`` and ``dirs``.
432 :type ret_type: Optional(str)
437 :type ret_type: Optional(str)
433 :param details: Returns extended information about nodes, such as
438 :param details: Returns extended information about nodes, such as
434 md5, binary, and or content.
439 md5, binary, and or content.
435 The valid options are ``basic`` and ``full``.
440 The valid options are ``basic`` and ``full``.
436 :type details: Optional(str)
441 :type details: Optional(str)
437 :param max_file_bytes: Only return file content under this file size bytes
442 :param max_file_bytes: Only return file content under this file size bytes
438 :type details: Optional(int)
443 :type details: Optional(int)
439
444
440 Example output:
445 Example output:
441
446
442 .. code-block:: bash
447 .. code-block:: bash
443
448
444 id : <id_given_in_input>
449 id : <id_given_in_input>
445 result: [
450 result: [
446 {
451 {
447 "binary": false,
452 "binary": false,
448 "content": "File line",
453 "content": "File line",
449 "extension": "md",
454 "extension": "md",
450 "lines": 2,
455 "lines": 2,
451 "md5": "059fa5d29b19c0657e384749480f6422",
456 "md5": "059fa5d29b19c0657e384749480f6422",
452 "mimetype": "text/x-minidsrc",
457 "mimetype": "text/x-minidsrc",
453 "name": "file.md",
458 "name": "file.md",
454 "size": 580,
459 "size": 580,
455 "type": "file"
460 "type": "file"
456 },
461 },
457 ...
462 ...
458 ]
463 ]
459 error: null
464 error: null
460 """
465 """
461
466
462 repo = get_repo_or_error(repoid)
467 repo = get_repo_or_error(repoid)
463 if not has_superadmin_permission(apiuser):
468 if not has_superadmin_permission(apiuser):
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
469 _perms = ('repository.admin', 'repository.write', 'repository.read',)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
470 validate_repo_permissions(apiuser, repoid, repo, _perms)
466
471
467 ret_type = Optional.extract(ret_type)
472 ret_type = Optional.extract(ret_type)
468 details = Optional.extract(details)
473 details = Optional.extract(details)
469 _extended_types = ['basic', 'full']
474 _extended_types = ['basic', 'full']
470 if details not in _extended_types:
475 if details not in _extended_types:
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
476 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
472 extended_info = False
477 extended_info = False
473 content = False
478 content = False
474 if details == 'basic':
479 if details == 'basic':
475 extended_info = True
480 extended_info = True
476
481
477 if details == 'full':
482 if details == 'full':
478 extended_info = content = True
483 extended_info = content = True
479
484
480 _map = {}
485 _map = {}
481 try:
486 try:
482 # check if repo is not empty by any chance, skip quicker if it is.
487 # check if repo is not empty by any chance, skip quicker if it is.
483 _scm = repo.scm_instance()
488 _scm = repo.scm_instance()
484 if _scm.is_empty():
489 if _scm.is_empty():
485 return []
490 return []
486
491
487 _d, _f = ScmModel().get_nodes(
492 _d, _f = ScmModel().get_nodes(
488 repo, revision, root_path, flat=False,
493 repo, revision, root_path, flat=False,
489 extended_info=extended_info, content=content,
494 extended_info=extended_info, content=content,
490 max_file_bytes=max_file_bytes)
495 max_file_bytes=max_file_bytes)
491 _map = {
496 _map = {
492 'all': _d + _f,
497 'all': _d + _f,
493 'files': _f,
498 'files': _f,
494 'dirs': _d,
499 'dirs': _d,
495 }
500 }
496 return _map[ret_type]
501 return _map[ret_type]
497 except KeyError:
502 except KeyError:
498 raise JSONRPCError(
503 raise JSONRPCError(
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
504 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
500 except Exception:
505 except Exception:
501 log.exception("Exception occurred while trying to get repo nodes")
506 log.exception("Exception occurred while trying to get repo nodes")
502 raise JSONRPCError(
507 raise JSONRPCError(
503 'failed to get repo: `%s` nodes' % repo.repo_name
508 'failed to get repo: `%s` nodes' % repo.repo_name
504 )
509 )
505
510
506
511
507 @jsonrpc_method()
512 @jsonrpc_method()
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
513 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
509 max_file_bytes=Optional(None), details=Optional('basic'),
514 max_file_bytes=Optional(None), details=Optional('basic'),
510 cache=Optional(True)):
515 cache=Optional(True)):
511 """
516 """
512 Returns a single file from repository at given revision.
517 Returns a single file from repository at given revision.
513
518
514 This command can only be run using an |authtoken| with admin rights,
519 This command can only be run using an |authtoken| with admin rights,
515 or users with at least read rights to |repos|.
520 or users with at least read rights to |repos|.
516
521
517 :param apiuser: This is filled automatically from the |authtoken|.
522 :param apiuser: This is filled automatically from the |authtoken|.
518 :type apiuser: AuthUser
523 :type apiuser: AuthUser
519 :param repoid: The repository name or repository ID.
524 :param repoid: The repository name or repository ID.
520 :type repoid: str or int
525 :type repoid: str or int
521 :param commit_id: The revision for which listing should be done.
526 :param commit_id: The revision for which listing should be done.
522 :type commit_id: str
527 :type commit_id: str
523 :param file_path: The path from which to start displaying.
528 :param file_path: The path from which to start displaying.
524 :type file_path: str
529 :type file_path: str
525 :param details: Returns different set of information about nodes.
530 :param details: Returns different set of information about nodes.
526 The valid options are ``minimal`` ``basic`` and ``full``.
531 The valid options are ``minimal`` ``basic`` and ``full``.
527 :type details: Optional(str)
532 :type details: Optional(str)
528 :param max_file_bytes: Only return file content under this file size bytes
533 :param max_file_bytes: Only return file content under this file size bytes
529 :type max_file_bytes: Optional(int)
534 :type max_file_bytes: Optional(int)
530 :param cache: Use internal caches for fetching files. If disabled fetching
535 :param cache: Use internal caches for fetching files. If disabled fetching
531 files is slower but more memory efficient
536 files is slower but more memory efficient
532 :type cache: Optional(bool)
537 :type cache: Optional(bool)
533
538
534 Example output:
539 Example output:
535
540
536 .. code-block:: bash
541 .. code-block:: bash
537
542
538 id : <id_given_in_input>
543 id : <id_given_in_input>
539 result: {
544 result: {
540 "binary": false,
545 "binary": false,
541 "extension": "py",
546 "extension": "py",
542 "lines": 35,
547 "lines": 35,
543 "content": "....",
548 "content": "....",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
549 "md5": "76318336366b0f17ee249e11b0c99c41",
545 "mimetype": "text/x-python",
550 "mimetype": "text/x-python",
546 "name": "python.py",
551 "name": "python.py",
547 "size": 817,
552 "size": 817,
548 "type": "file",
553 "type": "file",
549 }
554 }
550 error: null
555 error: null
551 """
556 """
552
557
553 repo = get_repo_or_error(repoid)
558 repo = get_repo_or_error(repoid)
554 if not has_superadmin_permission(apiuser):
559 if not has_superadmin_permission(apiuser):
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
560 _perms = ('repository.admin', 'repository.write', 'repository.read',)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
561 validate_repo_permissions(apiuser, repoid, repo, _perms)
557
562
558 cache = Optional.extract(cache, binary=True)
563 cache = Optional.extract(cache, binary=True)
559 details = Optional.extract(details)
564 details = Optional.extract(details)
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
565 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
561 if details not in _extended_types:
566 if details not in _extended_types:
562 raise JSONRPCError(
567 raise JSONRPCError(
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
568 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
564 extended_info = False
569 extended_info = False
565 content = False
570 content = False
566
571
567 if details == 'minimal':
572 if details == 'minimal':
568 extended_info = False
573 extended_info = False
569
574
570 elif details == 'basic':
575 elif details == 'basic':
571 extended_info = True
576 extended_info = True
572
577
573 elif details == 'full':
578 elif details == 'full':
574 extended_info = content = True
579 extended_info = content = True
575
580
576 file_path = safe_unicode(file_path)
581 file_path = safe_unicode(file_path)
577 try:
582 try:
578 # check if repo is not empty by any chance, skip quicker if it is.
583 # check if repo is not empty by any chance, skip quicker if it is.
579 _scm = repo.scm_instance()
584 _scm = repo.scm_instance()
580 if _scm.is_empty():
585 if _scm.is_empty():
581 return None
586 return None
582
587
583 node = ScmModel().get_node(
588 node = ScmModel().get_node(
584 repo, commit_id, file_path, extended_info=extended_info,
589 repo, commit_id, file_path, extended_info=extended_info,
585 content=content, max_file_bytes=max_file_bytes, cache=cache)
590 content=content, max_file_bytes=max_file_bytes, cache=cache)
586 except NodeDoesNotExistError:
591 except NodeDoesNotExistError:
587 raise JSONRPCError(u'There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
592 raise JSONRPCError(u'There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
588 repo.repo_name, file_path, commit_id))
593 repo.repo_name, file_path, commit_id))
589 except Exception:
594 except Exception:
590 log.exception(u"Exception occurred while trying to get repo %s file",
595 log.exception(u"Exception occurred while trying to get repo %s file",
591 repo.repo_name)
596 repo.repo_name)
592 raise JSONRPCError(u'failed to get repo: `{}` file at path {}'.format(
597 raise JSONRPCError(u'failed to get repo: `{}` file at path {}'.format(
593 repo.repo_name, file_path))
598 repo.repo_name, file_path))
594
599
595 return node
600 return node
596
601
597
602
598 @jsonrpc_method()
603 @jsonrpc_method()
599 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
604 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
600 """
605 """
601 Returns a list of tree nodes for path at given revision. This api is built
606 Returns a list of tree nodes for path at given revision. This api is built
602 strictly for usage in full text search building, and shouldn't be consumed
607 strictly for usage in full text search building, and shouldn't be consumed
603
608
604 This command can only be run using an |authtoken| with admin rights,
609 This command can only be run using an |authtoken| with admin rights,
605 or users with at least read rights to |repos|.
610 or users with at least read rights to |repos|.
606
611
607 """
612 """
608
613
609 repo = get_repo_or_error(repoid)
614 repo = get_repo_or_error(repoid)
610 if not has_superadmin_permission(apiuser):
615 if not has_superadmin_permission(apiuser):
611 _perms = ('repository.admin', 'repository.write', 'repository.read',)
616 _perms = ('repository.admin', 'repository.write', 'repository.read',)
612 validate_repo_permissions(apiuser, repoid, repo, _perms)
617 validate_repo_permissions(apiuser, repoid, repo, _perms)
613
618
614 repo_id = repo.repo_id
619 repo_id = repo.repo_id
615 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
620 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
616 cache_on = cache_seconds > 0
621 cache_on = cache_seconds > 0
617
622
618 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
623 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
619 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
624 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
620
625
621 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
626 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
622 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
627 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
623
628
624 try:
629 try:
625 # check if repo is not empty by any chance, skip quicker if it is.
630 # check if repo is not empty by any chance, skip quicker if it is.
626 _scm = repo.scm_instance()
631 _scm = repo.scm_instance()
627 if _scm.is_empty():
632 if _scm.is_empty():
628 return []
633 return []
629 except RepositoryError:
634 except RepositoryError:
630 log.exception("Exception occurred while trying to get repo nodes")
635 log.exception("Exception occurred while trying to get repo nodes")
631 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
636 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
632
637
633 try:
638 try:
634 # we need to resolve commit_id to a FULL sha for cache to work correctly.
639 # we need to resolve commit_id to a FULL sha for cache to work correctly.
635 # sending 'master' is a pointer that needs to be translated to current commit.
640 # sending 'master' is a pointer that needs to be translated to current commit.
636 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
641 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
637 log.debug(
642 log.debug(
638 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
643 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
639 'with caching: %s[TTL: %ss]' % (
644 'with caching: %s[TTL: %ss]' % (
640 repo_id, commit_id, cache_on, cache_seconds or 0))
645 repo_id, commit_id, cache_on, cache_seconds or 0))
641
646
642 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
647 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
643 return tree_files
648 return tree_files
644
649
645 except Exception:
650 except Exception:
646 log.exception("Exception occurred while trying to get repo nodes")
651 log.exception("Exception occurred while trying to get repo nodes")
647 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
652 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
648
653
649
654
650 @jsonrpc_method()
655 @jsonrpc_method()
651 def get_repo_refs(request, apiuser, repoid):
656 def get_repo_refs(request, apiuser, repoid):
652 """
657 """
653 Returns a dictionary of current references. It returns
658 Returns a dictionary of current references. It returns
654 bookmarks, branches, closed_branches, and tags for given repository
659 bookmarks, branches, closed_branches, and tags for given repository
655
660
656 It's possible to specify ret_type to show only `files` or `dirs`.
661 It's possible to specify ret_type to show only `files` or `dirs`.
657
662
658 This command can only be run using an |authtoken| with admin rights,
663 This command can only be run using an |authtoken| with admin rights,
659 or users with at least read rights to |repos|.
664 or users with at least read rights to |repos|.
660
665
661 :param apiuser: This is filled automatically from the |authtoken|.
666 :param apiuser: This is filled automatically from the |authtoken|.
662 :type apiuser: AuthUser
667 :type apiuser: AuthUser
663 :param repoid: The repository name or repository ID.
668 :param repoid: The repository name or repository ID.
664 :type repoid: str or int
669 :type repoid: str or int
665
670
666 Example output:
671 Example output:
667
672
668 .. code-block:: bash
673 .. code-block:: bash
669
674
670 id : <id_given_in_input>
675 id : <id_given_in_input>
671 "result": {
676 "result": {
672 "bookmarks": {
677 "bookmarks": {
673 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
678 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
674 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
679 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
675 },
680 },
676 "branches": {
681 "branches": {
677 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
682 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
678 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
683 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
679 },
684 },
680 "branches_closed": {},
685 "branches_closed": {},
681 "tags": {
686 "tags": {
682 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
687 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
683 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
688 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
684 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
689 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
685 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
690 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
686 }
691 }
687 }
692 }
688 error: null
693 error: null
689 """
694 """
690
695
691 repo = get_repo_or_error(repoid)
696 repo = get_repo_or_error(repoid)
692 if not has_superadmin_permission(apiuser):
697 if not has_superadmin_permission(apiuser):
693 _perms = ('repository.admin', 'repository.write', 'repository.read',)
698 _perms = ('repository.admin', 'repository.write', 'repository.read',)
694 validate_repo_permissions(apiuser, repoid, repo, _perms)
699 validate_repo_permissions(apiuser, repoid, repo, _perms)
695
700
696 try:
701 try:
697 # check if repo is not empty by any chance, skip quicker if it is.
702 # check if repo is not empty by any chance, skip quicker if it is.
698 vcs_instance = repo.scm_instance()
703 vcs_instance = repo.scm_instance()
699 refs = vcs_instance.refs()
704 refs = vcs_instance.refs()
700 return refs
705 return refs
701 except Exception:
706 except Exception:
702 log.exception("Exception occurred while trying to get repo refs")
707 log.exception("Exception occurred while trying to get repo refs")
703 raise JSONRPCError(
708 raise JSONRPCError(
704 'failed to get repo: `%s` references' % repo.repo_name
709 'failed to get repo: `%s` references' % repo.repo_name
705 )
710 )
706
711
707
712
708 @jsonrpc_method()
713 @jsonrpc_method()
709 def create_repo(
714 def create_repo(
710 request, apiuser, repo_name, repo_type,
715 request, apiuser, repo_name, repo_type,
711 owner=Optional(OAttr('apiuser')),
716 owner=Optional(OAttr('apiuser')),
712 description=Optional(''),
717 description=Optional(''),
713 private=Optional(False),
718 private=Optional(False),
714 clone_uri=Optional(None),
719 clone_uri=Optional(None),
715 push_uri=Optional(None),
720 push_uri=Optional(None),
716 landing_rev=Optional(None),
721 landing_rev=Optional(None),
717 enable_statistics=Optional(False),
722 enable_statistics=Optional(False),
718 enable_locking=Optional(False),
723 enable_locking=Optional(False),
719 enable_downloads=Optional(False),
724 enable_downloads=Optional(False),
720 copy_permissions=Optional(False)):
725 copy_permissions=Optional(False)):
721 """
726 """
722 Creates a repository.
727 Creates a repository.
723
728
724 * If the repository name contains "/", repository will be created inside
729 * If the repository name contains "/", repository will be created inside
725 a repository group or nested repository groups
730 a repository group or nested repository groups
726
731
727 For example "foo/bar/repo1" will create |repo| called "repo1" inside
732 For example "foo/bar/repo1" will create |repo| called "repo1" inside
728 group "foo/bar". You have to have permissions to access and write to
733 group "foo/bar". You have to have permissions to access and write to
729 the last repository group ("bar" in this example)
734 the last repository group ("bar" in this example)
730
735
731 This command can only be run using an |authtoken| with at least
736 This command can only be run using an |authtoken| with at least
732 permissions to create repositories, or write permissions to
737 permissions to create repositories, or write permissions to
733 parent repository groups.
738 parent repository groups.
734
739
735 :param apiuser: This is filled automatically from the |authtoken|.
740 :param apiuser: This is filled automatically from the |authtoken|.
736 :type apiuser: AuthUser
741 :type apiuser: AuthUser
737 :param repo_name: Set the repository name.
742 :param repo_name: Set the repository name.
738 :type repo_name: str
743 :type repo_name: str
739 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
744 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
740 :type repo_type: str
745 :type repo_type: str
741 :param owner: user_id or username
746 :param owner: user_id or username
742 :type owner: Optional(str)
747 :type owner: Optional(str)
743 :param description: Set the repository description.
748 :param description: Set the repository description.
744 :type description: Optional(str)
749 :type description: Optional(str)
745 :param private: set repository as private
750 :param private: set repository as private
746 :type private: bool
751 :type private: bool
747 :param clone_uri: set clone_uri
752 :param clone_uri: set clone_uri
748 :type clone_uri: str
753 :type clone_uri: str
749 :param push_uri: set push_uri
754 :param push_uri: set push_uri
750 :type push_uri: str
755 :type push_uri: str
751 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
756 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
752 :type landing_rev: str
757 :type landing_rev: str
753 :param enable_locking:
758 :param enable_locking:
754 :type enable_locking: bool
759 :type enable_locking: bool
755 :param enable_downloads:
760 :param enable_downloads:
756 :type enable_downloads: bool
761 :type enable_downloads: bool
757 :param enable_statistics:
762 :param enable_statistics:
758 :type enable_statistics: bool
763 :type enable_statistics: bool
759 :param copy_permissions: Copy permission from group in which the
764 :param copy_permissions: Copy permission from group in which the
760 repository is being created.
765 repository is being created.
761 :type copy_permissions: bool
766 :type copy_permissions: bool
762
767
763
768
764 Example output:
769 Example output:
765
770
766 .. code-block:: bash
771 .. code-block:: bash
767
772
768 id : <id_given_in_input>
773 id : <id_given_in_input>
769 result: {
774 result: {
770 "msg": "Created new repository `<reponame>`",
775 "msg": "Created new repository `<reponame>`",
771 "success": true,
776 "success": true,
772 "task": "<celery task id or None if done sync>"
777 "task": "<celery task id or None if done sync>"
773 }
778 }
774 error: null
779 error: null
775
780
776
781
777 Example error output:
782 Example error output:
778
783
779 .. code-block:: bash
784 .. code-block:: bash
780
785
781 id : <id_given_in_input>
786 id : <id_given_in_input>
782 result : null
787 result : null
783 error : {
788 error : {
784 'failed to create repository `<repo_name>`'
789 'failed to create repository `<repo_name>`'
785 }
790 }
786
791
787 """
792 """
788
793
789 owner = validate_set_owner_permissions(apiuser, owner)
794 owner = validate_set_owner_permissions(apiuser, owner)
790
795
791 description = Optional.extract(description)
796 description = Optional.extract(description)
792 copy_permissions = Optional.extract(copy_permissions)
797 copy_permissions = Optional.extract(copy_permissions)
793 clone_uri = Optional.extract(clone_uri)
798 clone_uri = Optional.extract(clone_uri)
794 push_uri = Optional.extract(push_uri)
799 push_uri = Optional.extract(push_uri)
795
800
796 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
801 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
797 if isinstance(private, Optional):
802 if isinstance(private, Optional):
798 private = defs.get('repo_private') or Optional.extract(private)
803 private = defs.get('repo_private') or Optional.extract(private)
799 if isinstance(repo_type, Optional):
804 if isinstance(repo_type, Optional):
800 repo_type = defs.get('repo_type')
805 repo_type = defs.get('repo_type')
801 if isinstance(enable_statistics, Optional):
806 if isinstance(enable_statistics, Optional):
802 enable_statistics = defs.get('repo_enable_statistics')
807 enable_statistics = defs.get('repo_enable_statistics')
803 if isinstance(enable_locking, Optional):
808 if isinstance(enable_locking, Optional):
804 enable_locking = defs.get('repo_enable_locking')
809 enable_locking = defs.get('repo_enable_locking')
805 if isinstance(enable_downloads, Optional):
810 if isinstance(enable_downloads, Optional):
806 enable_downloads = defs.get('repo_enable_downloads')
811 enable_downloads = defs.get('repo_enable_downloads')
807
812
808 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
813 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
809 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
814 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
810 ref_choices = list(set(ref_choices + [landing_ref]))
815 ref_choices = list(set(ref_choices + [landing_ref]))
811
816
812 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
817 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
813
818
814 schema = repo_schema.RepoSchema().bind(
819 schema = repo_schema.RepoSchema().bind(
815 repo_type_options=rhodecode.BACKENDS.keys(),
820 repo_type_options=rhodecode.BACKENDS.keys(),
816 repo_ref_options=ref_choices,
821 repo_ref_options=ref_choices,
817 repo_type=repo_type,
822 repo_type=repo_type,
818 # user caller
823 # user caller
819 user=apiuser)
824 user=apiuser)
820
825
821 try:
826 try:
822 schema_data = schema.deserialize(dict(
827 schema_data = schema.deserialize(dict(
823 repo_name=repo_name,
828 repo_name=repo_name,
824 repo_type=repo_type,
829 repo_type=repo_type,
825 repo_owner=owner.username,
830 repo_owner=owner.username,
826 repo_description=description,
831 repo_description=description,
827 repo_landing_commit_ref=landing_commit_ref,
832 repo_landing_commit_ref=landing_commit_ref,
828 repo_clone_uri=clone_uri,
833 repo_clone_uri=clone_uri,
829 repo_push_uri=push_uri,
834 repo_push_uri=push_uri,
830 repo_private=private,
835 repo_private=private,
831 repo_copy_permissions=copy_permissions,
836 repo_copy_permissions=copy_permissions,
832 repo_enable_statistics=enable_statistics,
837 repo_enable_statistics=enable_statistics,
833 repo_enable_downloads=enable_downloads,
838 repo_enable_downloads=enable_downloads,
834 repo_enable_locking=enable_locking))
839 repo_enable_locking=enable_locking))
835 except validation_schema.Invalid as err:
840 except validation_schema.Invalid as err:
836 raise JSONRPCValidationError(colander_exc=err)
841 raise JSONRPCValidationError(colander_exc=err)
837
842
838 try:
843 try:
839 data = {
844 data = {
840 'owner': owner,
845 'owner': owner,
841 'repo_name': schema_data['repo_group']['repo_name_without_group'],
846 'repo_name': schema_data['repo_group']['repo_name_without_group'],
842 'repo_name_full': schema_data['repo_name'],
847 'repo_name_full': schema_data['repo_name'],
843 'repo_group': schema_data['repo_group']['repo_group_id'],
848 'repo_group': schema_data['repo_group']['repo_group_id'],
844 'repo_type': schema_data['repo_type'],
849 'repo_type': schema_data['repo_type'],
845 'repo_description': schema_data['repo_description'],
850 'repo_description': schema_data['repo_description'],
846 'repo_private': schema_data['repo_private'],
851 'repo_private': schema_data['repo_private'],
847 'clone_uri': schema_data['repo_clone_uri'],
852 'clone_uri': schema_data['repo_clone_uri'],
848 'push_uri': schema_data['repo_push_uri'],
853 'push_uri': schema_data['repo_push_uri'],
849 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
854 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
850 'enable_statistics': schema_data['repo_enable_statistics'],
855 'enable_statistics': schema_data['repo_enable_statistics'],
851 'enable_locking': schema_data['repo_enable_locking'],
856 'enable_locking': schema_data['repo_enable_locking'],
852 'enable_downloads': schema_data['repo_enable_downloads'],
857 'enable_downloads': schema_data['repo_enable_downloads'],
853 'repo_copy_permissions': schema_data['repo_copy_permissions'],
858 'repo_copy_permissions': schema_data['repo_copy_permissions'],
854 }
859 }
855
860
856 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
861 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
857 task_id = get_task_id(task)
862 task_id = get_task_id(task)
858 # no commit, it's done in RepoModel, or async via celery
863 # no commit, it's done in RepoModel, or async via celery
859 return {
864 return {
860 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
865 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
861 'success': True, # cannot return the repo data here since fork
866 'success': True, # cannot return the repo data here since fork
862 # can be done async
867 # can be done async
863 'task': task_id
868 'task': task_id
864 }
869 }
865 except Exception:
870 except Exception:
866 log.exception(
871 log.exception(
867 u"Exception while trying to create the repository %s",
872 u"Exception while trying to create the repository %s",
868 schema_data['repo_name'])
873 schema_data['repo_name'])
869 raise JSONRPCError(
874 raise JSONRPCError(
870 'failed to create repository `%s`' % (schema_data['repo_name'],))
875 'failed to create repository `%s`' % (schema_data['repo_name'],))
871
876
872
877
873 @jsonrpc_method()
878 @jsonrpc_method()
874 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
879 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
875 description=Optional('')):
880 description=Optional('')):
876 """
881 """
877 Adds an extra field to a repository.
882 Adds an extra field to a repository.
878
883
879 This command can only be run using an |authtoken| with at least
884 This command can only be run using an |authtoken| with at least
880 write permissions to the |repo|.
885 write permissions to the |repo|.
881
886
882 :param apiuser: This is filled automatically from the |authtoken|.
887 :param apiuser: This is filled automatically from the |authtoken|.
883 :type apiuser: AuthUser
888 :type apiuser: AuthUser
884 :param repoid: Set the repository name or repository id.
889 :param repoid: Set the repository name or repository id.
885 :type repoid: str or int
890 :type repoid: str or int
886 :param key: Create a unique field key for this repository.
891 :param key: Create a unique field key for this repository.
887 :type key: str
892 :type key: str
888 :param label:
893 :param label:
889 :type label: Optional(str)
894 :type label: Optional(str)
890 :param description:
895 :param description:
891 :type description: Optional(str)
896 :type description: Optional(str)
892 """
897 """
893 repo = get_repo_or_error(repoid)
898 repo = get_repo_or_error(repoid)
894 if not has_superadmin_permission(apiuser):
899 if not has_superadmin_permission(apiuser):
895 _perms = ('repository.admin',)
900 _perms = ('repository.admin',)
896 validate_repo_permissions(apiuser, repoid, repo, _perms)
901 validate_repo_permissions(apiuser, repoid, repo, _perms)
897
902
898 label = Optional.extract(label) or key
903 label = Optional.extract(label) or key
899 description = Optional.extract(description)
904 description = Optional.extract(description)
900
905
901 field = RepositoryField.get_by_key_name(key, repo)
906 field = RepositoryField.get_by_key_name(key, repo)
902 if field:
907 if field:
903 raise JSONRPCError('Field with key '
908 raise JSONRPCError('Field with key '
904 '`%s` exists for repo `%s`' % (key, repoid))
909 '`%s` exists for repo `%s`' % (key, repoid))
905
910
906 try:
911 try:
907 RepoModel().add_repo_field(repo, key, field_label=label,
912 RepoModel().add_repo_field(repo, key, field_label=label,
908 field_desc=description)
913 field_desc=description)
909 Session().commit()
914 Session().commit()
910 return {
915 return {
911 'msg': "Added new repository field `%s`" % (key,),
916 'msg': "Added new repository field `%s`" % (key,),
912 'success': True,
917 'success': True,
913 }
918 }
914 except Exception:
919 except Exception:
915 log.exception("Exception occurred while trying to add field to repo")
920 log.exception("Exception occurred while trying to add field to repo")
916 raise JSONRPCError(
921 raise JSONRPCError(
917 'failed to create new field for repository `%s`' % (repoid,))
922 'failed to create new field for repository `%s`' % (repoid,))
918
923
919
924
920 @jsonrpc_method()
925 @jsonrpc_method()
921 def remove_field_from_repo(request, apiuser, repoid, key):
926 def remove_field_from_repo(request, apiuser, repoid, key):
922 """
927 """
923 Removes an extra field from a repository.
928 Removes an extra field from a repository.
924
929
925 This command can only be run using an |authtoken| with at least
930 This command can only be run using an |authtoken| with at least
926 write permissions to the |repo|.
931 write permissions to the |repo|.
927
932
928 :param apiuser: This is filled automatically from the |authtoken|.
933 :param apiuser: This is filled automatically from the |authtoken|.
929 :type apiuser: AuthUser
934 :type apiuser: AuthUser
930 :param repoid: Set the repository name or repository ID.
935 :param repoid: Set the repository name or repository ID.
931 :type repoid: str or int
936 :type repoid: str or int
932 :param key: Set the unique field key for this repository.
937 :param key: Set the unique field key for this repository.
933 :type key: str
938 :type key: str
934 """
939 """
935
940
936 repo = get_repo_or_error(repoid)
941 repo = get_repo_or_error(repoid)
937 if not has_superadmin_permission(apiuser):
942 if not has_superadmin_permission(apiuser):
938 _perms = ('repository.admin',)
943 _perms = ('repository.admin',)
939 validate_repo_permissions(apiuser, repoid, repo, _perms)
944 validate_repo_permissions(apiuser, repoid, repo, _perms)
940
945
941 field = RepositoryField.get_by_key_name(key, repo)
946 field = RepositoryField.get_by_key_name(key, repo)
942 if not field:
947 if not field:
943 raise JSONRPCError('Field with key `%s` does not '
948 raise JSONRPCError('Field with key `%s` does not '
944 'exists for repo `%s`' % (key, repoid))
949 'exists for repo `%s`' % (key, repoid))
945
950
946 try:
951 try:
947 RepoModel().delete_repo_field(repo, field_key=key)
952 RepoModel().delete_repo_field(repo, field_key=key)
948 Session().commit()
953 Session().commit()
949 return {
954 return {
950 'msg': "Deleted repository field `%s`" % (key,),
955 'msg': "Deleted repository field `%s`" % (key,),
951 'success': True,
956 'success': True,
952 }
957 }
953 except Exception:
958 except Exception:
954 log.exception(
959 log.exception(
955 "Exception occurred while trying to delete field from repo")
960 "Exception occurred while trying to delete field from repo")
956 raise JSONRPCError(
961 raise JSONRPCError(
957 'failed to delete field for repository `%s`' % (repoid,))
962 'failed to delete field for repository `%s`' % (repoid,))
958
963
959
964
960 @jsonrpc_method()
965 @jsonrpc_method()
961 def update_repo(
966 def update_repo(
962 request, apiuser, repoid, repo_name=Optional(None),
967 request, apiuser, repoid, repo_name=Optional(None),
963 owner=Optional(OAttr('apiuser')), description=Optional(''),
968 owner=Optional(OAttr('apiuser')), description=Optional(''),
964 private=Optional(False),
969 private=Optional(False),
965 clone_uri=Optional(None), push_uri=Optional(None),
970 clone_uri=Optional(None), push_uri=Optional(None),
966 landing_rev=Optional(None), fork_of=Optional(None),
971 landing_rev=Optional(None), fork_of=Optional(None),
967 enable_statistics=Optional(False),
972 enable_statistics=Optional(False),
968 enable_locking=Optional(False),
973 enable_locking=Optional(False),
969 enable_downloads=Optional(False), fields=Optional('')):
974 enable_downloads=Optional(False), fields=Optional('')):
970 """
975 """
971 Updates a repository with the given information.
976 Updates a repository with the given information.
972
977
973 This command can only be run using an |authtoken| with at least
978 This command can only be run using an |authtoken| with at least
974 admin permissions to the |repo|.
979 admin permissions to the |repo|.
975
980
976 * If the repository name contains "/", repository will be updated
981 * If the repository name contains "/", repository will be updated
977 accordingly with a repository group or nested repository groups
982 accordingly with a repository group or nested repository groups
978
983
979 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
984 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
980 called "repo-test" and place it inside group "foo/bar".
985 called "repo-test" and place it inside group "foo/bar".
981 You have to have permissions to access and write to the last repository
986 You have to have permissions to access and write to the last repository
982 group ("bar" in this example)
987 group ("bar" in this example)
983
988
984 :param apiuser: This is filled automatically from the |authtoken|.
989 :param apiuser: This is filled automatically from the |authtoken|.
985 :type apiuser: AuthUser
990 :type apiuser: AuthUser
986 :param repoid: repository name or repository ID.
991 :param repoid: repository name or repository ID.
987 :type repoid: str or int
992 :type repoid: str or int
988 :param repo_name: Update the |repo| name, including the
993 :param repo_name: Update the |repo| name, including the
989 repository group it's in.
994 repository group it's in.
990 :type repo_name: str
995 :type repo_name: str
991 :param owner: Set the |repo| owner.
996 :param owner: Set the |repo| owner.
992 :type owner: str
997 :type owner: str
993 :param fork_of: Set the |repo| as fork of another |repo|.
998 :param fork_of: Set the |repo| as fork of another |repo|.
994 :type fork_of: str
999 :type fork_of: str
995 :param description: Update the |repo| description.
1000 :param description: Update the |repo| description.
996 :type description: str
1001 :type description: str
997 :param private: Set the |repo| as private. (True | False)
1002 :param private: Set the |repo| as private. (True | False)
998 :type private: bool
1003 :type private: bool
999 :param clone_uri: Update the |repo| clone URI.
1004 :param clone_uri: Update the |repo| clone URI.
1000 :type clone_uri: str
1005 :type clone_uri: str
1001 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1006 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1002 :type landing_rev: str
1007 :type landing_rev: str
1003 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1008 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1004 :type enable_statistics: bool
1009 :type enable_statistics: bool
1005 :param enable_locking: Enable |repo| locking.
1010 :param enable_locking: Enable |repo| locking.
1006 :type enable_locking: bool
1011 :type enable_locking: bool
1007 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1012 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1008 :type enable_downloads: bool
1013 :type enable_downloads: bool
1009 :param fields: Add extra fields to the |repo|. Use the following
1014 :param fields: Add extra fields to the |repo|. Use the following
1010 example format: ``field_key=field_val,field_key2=fieldval2``.
1015 example format: ``field_key=field_val,field_key2=fieldval2``.
1011 Escape ', ' with \,
1016 Escape ', ' with \,
1012 :type fields: str
1017 :type fields: str
1013 """
1018 """
1014
1019
1015 repo = get_repo_or_error(repoid)
1020 repo = get_repo_or_error(repoid)
1016
1021
1017 include_secrets = False
1022 include_secrets = False
1018 if not has_superadmin_permission(apiuser):
1023 if not has_superadmin_permission(apiuser):
1019 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1024 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1020 else:
1025 else:
1021 include_secrets = True
1026 include_secrets = True
1022
1027
1023 updates = dict(
1028 updates = dict(
1024 repo_name=repo_name
1029 repo_name=repo_name
1025 if not isinstance(repo_name, Optional) else repo.repo_name,
1030 if not isinstance(repo_name, Optional) else repo.repo_name,
1026
1031
1027 fork_id=fork_of
1032 fork_id=fork_of
1028 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1033 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1029
1034
1030 user=owner
1035 user=owner
1031 if not isinstance(owner, Optional) else repo.user.username,
1036 if not isinstance(owner, Optional) else repo.user.username,
1032
1037
1033 repo_description=description
1038 repo_description=description
1034 if not isinstance(description, Optional) else repo.description,
1039 if not isinstance(description, Optional) else repo.description,
1035
1040
1036 repo_private=private
1041 repo_private=private
1037 if not isinstance(private, Optional) else repo.private,
1042 if not isinstance(private, Optional) else repo.private,
1038
1043
1039 clone_uri=clone_uri
1044 clone_uri=clone_uri
1040 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1045 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1041
1046
1042 push_uri=push_uri
1047 push_uri=push_uri
1043 if not isinstance(push_uri, Optional) else repo.push_uri,
1048 if not isinstance(push_uri, Optional) else repo.push_uri,
1044
1049
1045 repo_landing_rev=landing_rev
1050 repo_landing_rev=landing_rev
1046 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1051 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1047
1052
1048 repo_enable_statistics=enable_statistics
1053 repo_enable_statistics=enable_statistics
1049 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1054 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1050
1055
1051 repo_enable_locking=enable_locking
1056 repo_enable_locking=enable_locking
1052 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1057 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1053
1058
1054 repo_enable_downloads=enable_downloads
1059 repo_enable_downloads=enable_downloads
1055 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1060 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1056
1061
1057 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1062 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1058 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1063 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1059 request.translate, repo=repo)
1064 request.translate, repo=repo)
1060 ref_choices = list(set(ref_choices + [landing_ref]))
1065 ref_choices = list(set(ref_choices + [landing_ref]))
1061
1066
1062 old_values = repo.get_api_data()
1067 old_values = repo.get_api_data()
1063 repo_type = repo.repo_type
1068 repo_type = repo.repo_type
1064 schema = repo_schema.RepoSchema().bind(
1069 schema = repo_schema.RepoSchema().bind(
1065 repo_type_options=rhodecode.BACKENDS.keys(),
1070 repo_type_options=rhodecode.BACKENDS.keys(),
1066 repo_ref_options=ref_choices,
1071 repo_ref_options=ref_choices,
1067 repo_type=repo_type,
1072 repo_type=repo_type,
1068 # user caller
1073 # user caller
1069 user=apiuser,
1074 user=apiuser,
1070 old_values=old_values)
1075 old_values=old_values)
1071 try:
1076 try:
1072 schema_data = schema.deserialize(dict(
1077 schema_data = schema.deserialize(dict(
1073 # we save old value, users cannot change type
1078 # we save old value, users cannot change type
1074 repo_type=repo_type,
1079 repo_type=repo_type,
1075
1080
1076 repo_name=updates['repo_name'],
1081 repo_name=updates['repo_name'],
1077 repo_owner=updates['user'],
1082 repo_owner=updates['user'],
1078 repo_description=updates['repo_description'],
1083 repo_description=updates['repo_description'],
1079 repo_clone_uri=updates['clone_uri'],
1084 repo_clone_uri=updates['clone_uri'],
1080 repo_push_uri=updates['push_uri'],
1085 repo_push_uri=updates['push_uri'],
1081 repo_fork_of=updates['fork_id'],
1086 repo_fork_of=updates['fork_id'],
1082 repo_private=updates['repo_private'],
1087 repo_private=updates['repo_private'],
1083 repo_landing_commit_ref=updates['repo_landing_rev'],
1088 repo_landing_commit_ref=updates['repo_landing_rev'],
1084 repo_enable_statistics=updates['repo_enable_statistics'],
1089 repo_enable_statistics=updates['repo_enable_statistics'],
1085 repo_enable_downloads=updates['repo_enable_downloads'],
1090 repo_enable_downloads=updates['repo_enable_downloads'],
1086 repo_enable_locking=updates['repo_enable_locking']))
1091 repo_enable_locking=updates['repo_enable_locking']))
1087 except validation_schema.Invalid as err:
1092 except validation_schema.Invalid as err:
1088 raise JSONRPCValidationError(colander_exc=err)
1093 raise JSONRPCValidationError(colander_exc=err)
1089
1094
1090 # save validated data back into the updates dict
1095 # save validated data back into the updates dict
1091 validated_updates = dict(
1096 validated_updates = dict(
1092 repo_name=schema_data['repo_group']['repo_name_without_group'],
1097 repo_name=schema_data['repo_group']['repo_name_without_group'],
1093 repo_group=schema_data['repo_group']['repo_group_id'],
1098 repo_group=schema_data['repo_group']['repo_group_id'],
1094
1099
1095 user=schema_data['repo_owner'],
1100 user=schema_data['repo_owner'],
1096 repo_description=schema_data['repo_description'],
1101 repo_description=schema_data['repo_description'],
1097 repo_private=schema_data['repo_private'],
1102 repo_private=schema_data['repo_private'],
1098 clone_uri=schema_data['repo_clone_uri'],
1103 clone_uri=schema_data['repo_clone_uri'],
1099 push_uri=schema_data['repo_push_uri'],
1104 push_uri=schema_data['repo_push_uri'],
1100 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1105 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1101 repo_enable_statistics=schema_data['repo_enable_statistics'],
1106 repo_enable_statistics=schema_data['repo_enable_statistics'],
1102 repo_enable_locking=schema_data['repo_enable_locking'],
1107 repo_enable_locking=schema_data['repo_enable_locking'],
1103 repo_enable_downloads=schema_data['repo_enable_downloads'],
1108 repo_enable_downloads=schema_data['repo_enable_downloads'],
1104 )
1109 )
1105
1110
1106 if schema_data['repo_fork_of']:
1111 if schema_data['repo_fork_of']:
1107 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1112 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1108 validated_updates['fork_id'] = fork_repo.repo_id
1113 validated_updates['fork_id'] = fork_repo.repo_id
1109
1114
1110 # extra fields
1115 # extra fields
1111 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1116 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1112 if fields:
1117 if fields:
1113 validated_updates.update(fields)
1118 validated_updates.update(fields)
1114
1119
1115 try:
1120 try:
1116 RepoModel().update(repo, **validated_updates)
1121 RepoModel().update(repo, **validated_updates)
1117 audit_logger.store_api(
1122 audit_logger.store_api(
1118 'repo.edit', action_data={'old_data': old_values},
1123 'repo.edit', action_data={'old_data': old_values},
1119 user=apiuser, repo=repo)
1124 user=apiuser, repo=repo)
1120 Session().commit()
1125 Session().commit()
1121 return {
1126 return {
1122 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1127 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1123 'repository': repo.get_api_data(include_secrets=include_secrets)
1128 'repository': repo.get_api_data(include_secrets=include_secrets)
1124 }
1129 }
1125 except Exception:
1130 except Exception:
1126 log.exception(
1131 log.exception(
1127 u"Exception while trying to update the repository %s",
1132 u"Exception while trying to update the repository %s",
1128 repoid)
1133 repoid)
1129 raise JSONRPCError('failed to update repo `%s`' % repoid)
1134 raise JSONRPCError('failed to update repo `%s`' % repoid)
1130
1135
1131
1136
1132 @jsonrpc_method()
1137 @jsonrpc_method()
1133 def fork_repo(request, apiuser, repoid, fork_name,
1138 def fork_repo(request, apiuser, repoid, fork_name,
1134 owner=Optional(OAttr('apiuser')),
1139 owner=Optional(OAttr('apiuser')),
1135 description=Optional(''),
1140 description=Optional(''),
1136 private=Optional(False),
1141 private=Optional(False),
1137 clone_uri=Optional(None),
1142 clone_uri=Optional(None),
1138 landing_rev=Optional(None),
1143 landing_rev=Optional(None),
1139 copy_permissions=Optional(False)):
1144 copy_permissions=Optional(False)):
1140 """
1145 """
1141 Creates a fork of the specified |repo|.
1146 Creates a fork of the specified |repo|.
1142
1147
1143 * If the fork_name contains "/", fork will be created inside
1148 * If the fork_name contains "/", fork will be created inside
1144 a repository group or nested repository groups
1149 a repository group or nested repository groups
1145
1150
1146 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1151 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1147 inside group "foo/bar". You have to have permissions to access and
1152 inside group "foo/bar". You have to have permissions to access and
1148 write to the last repository group ("bar" in this example)
1153 write to the last repository group ("bar" in this example)
1149
1154
1150 This command can only be run using an |authtoken| with minimum
1155 This command can only be run using an |authtoken| with minimum
1151 read permissions of the forked repo, create fork permissions for an user.
1156 read permissions of the forked repo, create fork permissions for an user.
1152
1157
1153 :param apiuser: This is filled automatically from the |authtoken|.
1158 :param apiuser: This is filled automatically from the |authtoken|.
1154 :type apiuser: AuthUser
1159 :type apiuser: AuthUser
1155 :param repoid: Set repository name or repository ID.
1160 :param repoid: Set repository name or repository ID.
1156 :type repoid: str or int
1161 :type repoid: str or int
1157 :param fork_name: Set the fork name, including it's repository group membership.
1162 :param fork_name: Set the fork name, including it's repository group membership.
1158 :type fork_name: str
1163 :type fork_name: str
1159 :param owner: Set the fork owner.
1164 :param owner: Set the fork owner.
1160 :type owner: str
1165 :type owner: str
1161 :param description: Set the fork description.
1166 :param description: Set the fork description.
1162 :type description: str
1167 :type description: str
1163 :param copy_permissions: Copy permissions from parent |repo|. The
1168 :param copy_permissions: Copy permissions from parent |repo|. The
1164 default is False.
1169 default is False.
1165 :type copy_permissions: bool
1170 :type copy_permissions: bool
1166 :param private: Make the fork private. The default is False.
1171 :param private: Make the fork private. The default is False.
1167 :type private: bool
1172 :type private: bool
1168 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1173 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1169
1174
1170 Example output:
1175 Example output:
1171
1176
1172 .. code-block:: bash
1177 .. code-block:: bash
1173
1178
1174 id : <id_for_response>
1179 id : <id_for_response>
1175 api_key : "<api_key>"
1180 api_key : "<api_key>"
1176 args: {
1181 args: {
1177 "repoid" : "<reponame or repo_id>",
1182 "repoid" : "<reponame or repo_id>",
1178 "fork_name": "<forkname>",
1183 "fork_name": "<forkname>",
1179 "owner": "<username or user_id = Optional(=apiuser)>",
1184 "owner": "<username or user_id = Optional(=apiuser)>",
1180 "description": "<description>",
1185 "description": "<description>",
1181 "copy_permissions": "<bool>",
1186 "copy_permissions": "<bool>",
1182 "private": "<bool>",
1187 "private": "<bool>",
1183 "landing_rev": "<landing_rev>"
1188 "landing_rev": "<landing_rev>"
1184 }
1189 }
1185
1190
1186 Example error output:
1191 Example error output:
1187
1192
1188 .. code-block:: bash
1193 .. code-block:: bash
1189
1194
1190 id : <id_given_in_input>
1195 id : <id_given_in_input>
1191 result: {
1196 result: {
1192 "msg": "Created fork of `<reponame>` as `<forkname>`",
1197 "msg": "Created fork of `<reponame>` as `<forkname>`",
1193 "success": true,
1198 "success": true,
1194 "task": "<celery task id or None if done sync>"
1199 "task": "<celery task id or None if done sync>"
1195 }
1200 }
1196 error: null
1201 error: null
1197
1202
1198 """
1203 """
1199
1204
1200 repo = get_repo_or_error(repoid)
1205 repo = get_repo_or_error(repoid)
1201 repo_name = repo.repo_name
1206 repo_name = repo.repo_name
1202
1207
1203 if not has_superadmin_permission(apiuser):
1208 if not has_superadmin_permission(apiuser):
1204 # check if we have at least read permission for
1209 # check if we have at least read permission for
1205 # this repo that we fork !
1210 # this repo that we fork !
1206 _perms = (
1211 _perms = (
1207 'repository.admin', 'repository.write', 'repository.read')
1212 'repository.admin', 'repository.write', 'repository.read')
1208 validate_repo_permissions(apiuser, repoid, repo, _perms)
1213 validate_repo_permissions(apiuser, repoid, repo, _perms)
1209
1214
1210 # check if the regular user has at least fork permissions as well
1215 # check if the regular user has at least fork permissions as well
1211 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1216 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1212 raise JSONRPCForbidden()
1217 raise JSONRPCForbidden()
1213
1218
1214 # check if user can set owner parameter
1219 # check if user can set owner parameter
1215 owner = validate_set_owner_permissions(apiuser, owner)
1220 owner = validate_set_owner_permissions(apiuser, owner)
1216
1221
1217 description = Optional.extract(description)
1222 description = Optional.extract(description)
1218 copy_permissions = Optional.extract(copy_permissions)
1223 copy_permissions = Optional.extract(copy_permissions)
1219 clone_uri = Optional.extract(clone_uri)
1224 clone_uri = Optional.extract(clone_uri)
1220
1225
1221 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1226 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1222 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1227 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1223 ref_choices = list(set(ref_choices + [landing_ref]))
1228 ref_choices = list(set(ref_choices + [landing_ref]))
1224 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1229 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1225
1230
1226 private = Optional.extract(private)
1231 private = Optional.extract(private)
1227
1232
1228 schema = repo_schema.RepoSchema().bind(
1233 schema = repo_schema.RepoSchema().bind(
1229 repo_type_options=rhodecode.BACKENDS.keys(),
1234 repo_type_options=rhodecode.BACKENDS.keys(),
1230 repo_ref_options=ref_choices,
1235 repo_ref_options=ref_choices,
1231 repo_type=repo.repo_type,
1236 repo_type=repo.repo_type,
1232 # user caller
1237 # user caller
1233 user=apiuser)
1238 user=apiuser)
1234
1239
1235 try:
1240 try:
1236 schema_data = schema.deserialize(dict(
1241 schema_data = schema.deserialize(dict(
1237 repo_name=fork_name,
1242 repo_name=fork_name,
1238 repo_type=repo.repo_type,
1243 repo_type=repo.repo_type,
1239 repo_owner=owner.username,
1244 repo_owner=owner.username,
1240 repo_description=description,
1245 repo_description=description,
1241 repo_landing_commit_ref=landing_commit_ref,
1246 repo_landing_commit_ref=landing_commit_ref,
1242 repo_clone_uri=clone_uri,
1247 repo_clone_uri=clone_uri,
1243 repo_private=private,
1248 repo_private=private,
1244 repo_copy_permissions=copy_permissions))
1249 repo_copy_permissions=copy_permissions))
1245 except validation_schema.Invalid as err:
1250 except validation_schema.Invalid as err:
1246 raise JSONRPCValidationError(colander_exc=err)
1251 raise JSONRPCValidationError(colander_exc=err)
1247
1252
1248 try:
1253 try:
1249 data = {
1254 data = {
1250 'fork_parent_id': repo.repo_id,
1255 'fork_parent_id': repo.repo_id,
1251
1256
1252 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1257 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1253 'repo_name_full': schema_data['repo_name'],
1258 'repo_name_full': schema_data['repo_name'],
1254 'repo_group': schema_data['repo_group']['repo_group_id'],
1259 'repo_group': schema_data['repo_group']['repo_group_id'],
1255 'repo_type': schema_data['repo_type'],
1260 'repo_type': schema_data['repo_type'],
1256 'description': schema_data['repo_description'],
1261 'description': schema_data['repo_description'],
1257 'private': schema_data['repo_private'],
1262 'private': schema_data['repo_private'],
1258 'copy_permissions': schema_data['repo_copy_permissions'],
1263 'copy_permissions': schema_data['repo_copy_permissions'],
1259 'landing_rev': schema_data['repo_landing_commit_ref'],
1264 'landing_rev': schema_data['repo_landing_commit_ref'],
1260 }
1265 }
1261
1266
1262 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1267 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1263 # no commit, it's done in RepoModel, or async via celery
1268 # no commit, it's done in RepoModel, or async via celery
1264 task_id = get_task_id(task)
1269 task_id = get_task_id(task)
1265
1270
1266 return {
1271 return {
1267 'msg': 'Created fork of `%s` as `%s`' % (
1272 'msg': 'Created fork of `%s` as `%s`' % (
1268 repo.repo_name, schema_data['repo_name']),
1273 repo.repo_name, schema_data['repo_name']),
1269 'success': True, # cannot return the repo data here since fork
1274 'success': True, # cannot return the repo data here since fork
1270 # can be done async
1275 # can be done async
1271 'task': task_id
1276 'task': task_id
1272 }
1277 }
1273 except Exception:
1278 except Exception:
1274 log.exception(
1279 log.exception(
1275 u"Exception while trying to create fork %s",
1280 u"Exception while trying to create fork %s",
1276 schema_data['repo_name'])
1281 schema_data['repo_name'])
1277 raise JSONRPCError(
1282 raise JSONRPCError(
1278 'failed to fork repository `%s` as `%s`' % (
1283 'failed to fork repository `%s` as `%s`' % (
1279 repo_name, schema_data['repo_name']))
1284 repo_name, schema_data['repo_name']))
1280
1285
1281
1286
1282 @jsonrpc_method()
1287 @jsonrpc_method()
1283 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1288 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1284 """
1289 """
1285 Deletes a repository.
1290 Deletes a repository.
1286
1291
1287 * When the `forks` parameter is set it's possible to detach or delete
1292 * When the `forks` parameter is set it's possible to detach or delete
1288 forks of deleted repository.
1293 forks of deleted repository.
1289
1294
1290 This command can only be run using an |authtoken| with admin
1295 This command can only be run using an |authtoken| with admin
1291 permissions on the |repo|.
1296 permissions on the |repo|.
1292
1297
1293 :param apiuser: This is filled automatically from the |authtoken|.
1298 :param apiuser: This is filled automatically from the |authtoken|.
1294 :type apiuser: AuthUser
1299 :type apiuser: AuthUser
1295 :param repoid: Set the repository name or repository ID.
1300 :param repoid: Set the repository name or repository ID.
1296 :type repoid: str or int
1301 :type repoid: str or int
1297 :param forks: Set to `detach` or `delete` forks from the |repo|.
1302 :param forks: Set to `detach` or `delete` forks from the |repo|.
1298 :type forks: Optional(str)
1303 :type forks: Optional(str)
1299
1304
1300 Example error output:
1305 Example error output:
1301
1306
1302 .. code-block:: bash
1307 .. code-block:: bash
1303
1308
1304 id : <id_given_in_input>
1309 id : <id_given_in_input>
1305 result: {
1310 result: {
1306 "msg": "Deleted repository `<reponame>`",
1311 "msg": "Deleted repository `<reponame>`",
1307 "success": true
1312 "success": true
1308 }
1313 }
1309 error: null
1314 error: null
1310 """
1315 """
1311
1316
1312 repo = get_repo_or_error(repoid)
1317 repo = get_repo_or_error(repoid)
1313 repo_name = repo.repo_name
1318 repo_name = repo.repo_name
1314 if not has_superadmin_permission(apiuser):
1319 if not has_superadmin_permission(apiuser):
1315 _perms = ('repository.admin',)
1320 _perms = ('repository.admin',)
1316 validate_repo_permissions(apiuser, repoid, repo, _perms)
1321 validate_repo_permissions(apiuser, repoid, repo, _perms)
1317
1322
1318 try:
1323 try:
1319 handle_forks = Optional.extract(forks)
1324 handle_forks = Optional.extract(forks)
1320 _forks_msg = ''
1325 _forks_msg = ''
1321 _forks = [f for f in repo.forks]
1326 _forks = [f for f in repo.forks]
1322 if handle_forks == 'detach':
1327 if handle_forks == 'detach':
1323 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1328 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1324 elif handle_forks == 'delete':
1329 elif handle_forks == 'delete':
1325 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1330 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1326 elif _forks:
1331 elif _forks:
1327 raise JSONRPCError(
1332 raise JSONRPCError(
1328 'Cannot delete `%s` it still contains attached forks' %
1333 'Cannot delete `%s` it still contains attached forks' %
1329 (repo.repo_name,)
1334 (repo.repo_name,)
1330 )
1335 )
1331 old_data = repo.get_api_data()
1336 old_data = repo.get_api_data()
1332 RepoModel().delete(repo, forks=forks)
1337 RepoModel().delete(repo, forks=forks)
1333
1338
1334 repo = audit_logger.RepoWrap(repo_id=None,
1339 repo = audit_logger.RepoWrap(repo_id=None,
1335 repo_name=repo.repo_name)
1340 repo_name=repo.repo_name)
1336
1341
1337 audit_logger.store_api(
1342 audit_logger.store_api(
1338 'repo.delete', action_data={'old_data': old_data},
1343 'repo.delete', action_data={'old_data': old_data},
1339 user=apiuser, repo=repo)
1344 user=apiuser, repo=repo)
1340
1345
1341 ScmModel().mark_for_invalidation(repo_name, delete=True)
1346 ScmModel().mark_for_invalidation(repo_name, delete=True)
1342 Session().commit()
1347 Session().commit()
1343 return {
1348 return {
1344 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1349 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1345 'success': True
1350 'success': True
1346 }
1351 }
1347 except Exception:
1352 except Exception:
1348 log.exception("Exception occurred while trying to delete repo")
1353 log.exception("Exception occurred while trying to delete repo")
1349 raise JSONRPCError(
1354 raise JSONRPCError(
1350 'failed to delete repository `%s`' % (repo_name,)
1355 'failed to delete repository `%s`' % (repo_name,)
1351 )
1356 )
1352
1357
1353
1358
1354 #TODO: marcink, change name ?
1359 #TODO: marcink, change name ?
1355 @jsonrpc_method()
1360 @jsonrpc_method()
1356 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1361 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1357 """
1362 """
1358 Invalidates the cache for the specified repository.
1363 Invalidates the cache for the specified repository.
1359
1364
1360 This command can only be run using an |authtoken| with admin rights to
1365 This command can only be run using an |authtoken| with admin rights to
1361 the specified repository.
1366 the specified repository.
1362
1367
1363 This command takes the following options:
1368 This command takes the following options:
1364
1369
1365 :param apiuser: This is filled automatically from |authtoken|.
1370 :param apiuser: This is filled automatically from |authtoken|.
1366 :type apiuser: AuthUser
1371 :type apiuser: AuthUser
1367 :param repoid: Sets the repository name or repository ID.
1372 :param repoid: Sets the repository name or repository ID.
1368 :type repoid: str or int
1373 :type repoid: str or int
1369 :param delete_keys: This deletes the invalidated keys instead of
1374 :param delete_keys: This deletes the invalidated keys instead of
1370 just flagging them.
1375 just flagging them.
1371 :type delete_keys: Optional(``True`` | ``False``)
1376 :type delete_keys: Optional(``True`` | ``False``)
1372
1377
1373 Example output:
1378 Example output:
1374
1379
1375 .. code-block:: bash
1380 .. code-block:: bash
1376
1381
1377 id : <id_given_in_input>
1382 id : <id_given_in_input>
1378 result : {
1383 result : {
1379 'msg': Cache for repository `<repository name>` was invalidated,
1384 'msg': Cache for repository `<repository name>` was invalidated,
1380 'repository': <repository name>
1385 'repository': <repository name>
1381 }
1386 }
1382 error : null
1387 error : null
1383
1388
1384 Example error output:
1389 Example error output:
1385
1390
1386 .. code-block:: bash
1391 .. code-block:: bash
1387
1392
1388 id : <id_given_in_input>
1393 id : <id_given_in_input>
1389 result : null
1394 result : null
1390 error : {
1395 error : {
1391 'Error occurred during cache invalidation action'
1396 'Error occurred during cache invalidation action'
1392 }
1397 }
1393
1398
1394 """
1399 """
1395
1400
1396 repo = get_repo_or_error(repoid)
1401 repo = get_repo_or_error(repoid)
1397 if not has_superadmin_permission(apiuser):
1402 if not has_superadmin_permission(apiuser):
1398 _perms = ('repository.admin', 'repository.write',)
1403 _perms = ('repository.admin', 'repository.write',)
1399 validate_repo_permissions(apiuser, repoid, repo, _perms)
1404 validate_repo_permissions(apiuser, repoid, repo, _perms)
1400
1405
1401 delete = Optional.extract(delete_keys)
1406 delete = Optional.extract(delete_keys)
1402 try:
1407 try:
1403 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1408 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1404 return {
1409 return {
1405 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1410 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1406 'repository': repo.repo_name
1411 'repository': repo.repo_name
1407 }
1412 }
1408 except Exception:
1413 except Exception:
1409 log.exception(
1414 log.exception(
1410 "Exception occurred while trying to invalidate repo cache")
1415 "Exception occurred while trying to invalidate repo cache")
1411 raise JSONRPCError(
1416 raise JSONRPCError(
1412 'Error occurred during cache invalidation action'
1417 'Error occurred during cache invalidation action'
1413 )
1418 )
1414
1419
1415
1420
1416 #TODO: marcink, change name ?
1421 #TODO: marcink, change name ?
1417 @jsonrpc_method()
1422 @jsonrpc_method()
1418 def lock(request, apiuser, repoid, locked=Optional(None),
1423 def lock(request, apiuser, repoid, locked=Optional(None),
1419 userid=Optional(OAttr('apiuser'))):
1424 userid=Optional(OAttr('apiuser'))):
1420 """
1425 """
1421 Sets the lock state of the specified |repo| by the given user.
1426 Sets the lock state of the specified |repo| by the given user.
1422 From more information, see :ref:`repo-locking`.
1427 From more information, see :ref:`repo-locking`.
1423
1428
1424 * If the ``userid`` option is not set, the repository is locked to the
1429 * If the ``userid`` option is not set, the repository is locked to the
1425 user who called the method.
1430 user who called the method.
1426 * If the ``locked`` parameter is not set, the current lock state of the
1431 * If the ``locked`` parameter is not set, the current lock state of the
1427 repository is displayed.
1432 repository is displayed.
1428
1433
1429 This command can only be run using an |authtoken| with admin rights to
1434 This command can only be run using an |authtoken| with admin rights to
1430 the specified repository.
1435 the specified repository.
1431
1436
1432 This command takes the following options:
1437 This command takes the following options:
1433
1438
1434 :param apiuser: This is filled automatically from the |authtoken|.
1439 :param apiuser: This is filled automatically from the |authtoken|.
1435 :type apiuser: AuthUser
1440 :type apiuser: AuthUser
1436 :param repoid: Sets the repository name or repository ID.
1441 :param repoid: Sets the repository name or repository ID.
1437 :type repoid: str or int
1442 :type repoid: str or int
1438 :param locked: Sets the lock state.
1443 :param locked: Sets the lock state.
1439 :type locked: Optional(``True`` | ``False``)
1444 :type locked: Optional(``True`` | ``False``)
1440 :param userid: Set the repository lock to this user.
1445 :param userid: Set the repository lock to this user.
1441 :type userid: Optional(str or int)
1446 :type userid: Optional(str or int)
1442
1447
1443 Example error output:
1448 Example error output:
1444
1449
1445 .. code-block:: bash
1450 .. code-block:: bash
1446
1451
1447 id : <id_given_in_input>
1452 id : <id_given_in_input>
1448 result : {
1453 result : {
1449 'repo': '<reponame>',
1454 'repo': '<reponame>',
1450 'locked': <bool: lock state>,
1455 'locked': <bool: lock state>,
1451 'locked_since': <int: lock timestamp>,
1456 'locked_since': <int: lock timestamp>,
1452 'locked_by': <username of person who made the lock>,
1457 'locked_by': <username of person who made the lock>,
1453 'lock_reason': <str: reason for locking>,
1458 'lock_reason': <str: reason for locking>,
1454 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1459 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1455 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1460 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1456 or
1461 or
1457 'msg': 'Repo `<repository name>` not locked.'
1462 'msg': 'Repo `<repository name>` not locked.'
1458 or
1463 or
1459 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1464 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1460 }
1465 }
1461 error : null
1466 error : null
1462
1467
1463 Example error output:
1468 Example error output:
1464
1469
1465 .. code-block:: bash
1470 .. code-block:: bash
1466
1471
1467 id : <id_given_in_input>
1472 id : <id_given_in_input>
1468 result : null
1473 result : null
1469 error : {
1474 error : {
1470 'Error occurred locking repository `<reponame>`'
1475 'Error occurred locking repository `<reponame>`'
1471 }
1476 }
1472 """
1477 """
1473
1478
1474 repo = get_repo_or_error(repoid)
1479 repo = get_repo_or_error(repoid)
1475 if not has_superadmin_permission(apiuser):
1480 if not has_superadmin_permission(apiuser):
1476 # check if we have at least write permission for this repo !
1481 # check if we have at least write permission for this repo !
1477 _perms = ('repository.admin', 'repository.write',)
1482 _perms = ('repository.admin', 'repository.write',)
1478 validate_repo_permissions(apiuser, repoid, repo, _perms)
1483 validate_repo_permissions(apiuser, repoid, repo, _perms)
1479
1484
1480 # make sure normal user does not pass someone else userid,
1485 # make sure normal user does not pass someone else userid,
1481 # he is not allowed to do that
1486 # he is not allowed to do that
1482 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1487 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1483 raise JSONRPCError('userid is not the same as your user')
1488 raise JSONRPCError('userid is not the same as your user')
1484
1489
1485 if isinstance(userid, Optional):
1490 if isinstance(userid, Optional):
1486 userid = apiuser.user_id
1491 userid = apiuser.user_id
1487
1492
1488 user = get_user_or_error(userid)
1493 user = get_user_or_error(userid)
1489
1494
1490 if isinstance(locked, Optional):
1495 if isinstance(locked, Optional):
1491 lockobj = repo.locked
1496 lockobj = repo.locked
1492
1497
1493 if lockobj[0] is None:
1498 if lockobj[0] is None:
1494 _d = {
1499 _d = {
1495 'repo': repo.repo_name,
1500 'repo': repo.repo_name,
1496 'locked': False,
1501 'locked': False,
1497 'locked_since': None,
1502 'locked_since': None,
1498 'locked_by': None,
1503 'locked_by': None,
1499 'lock_reason': None,
1504 'lock_reason': None,
1500 'lock_state_changed': False,
1505 'lock_state_changed': False,
1501 'msg': 'Repo `%s` not locked.' % repo.repo_name
1506 'msg': 'Repo `%s` not locked.' % repo.repo_name
1502 }
1507 }
1503 return _d
1508 return _d
1504 else:
1509 else:
1505 _user_id, _time, _reason = lockobj
1510 _user_id, _time, _reason = lockobj
1506 lock_user = get_user_or_error(userid)
1511 lock_user = get_user_or_error(userid)
1507 _d = {
1512 _d = {
1508 'repo': repo.repo_name,
1513 'repo': repo.repo_name,
1509 'locked': True,
1514 'locked': True,
1510 'locked_since': _time,
1515 'locked_since': _time,
1511 'locked_by': lock_user.username,
1516 'locked_by': lock_user.username,
1512 'lock_reason': _reason,
1517 'lock_reason': _reason,
1513 'lock_state_changed': False,
1518 'lock_state_changed': False,
1514 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1519 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1515 % (repo.repo_name, lock_user.username,
1520 % (repo.repo_name, lock_user.username,
1516 json.dumps(time_to_datetime(_time))))
1521 json.dumps(time_to_datetime(_time))))
1517 }
1522 }
1518 return _d
1523 return _d
1519
1524
1520 # force locked state through a flag
1525 # force locked state through a flag
1521 else:
1526 else:
1522 locked = str2bool(locked)
1527 locked = str2bool(locked)
1523 lock_reason = Repository.LOCK_API
1528 lock_reason = Repository.LOCK_API
1524 try:
1529 try:
1525 if locked:
1530 if locked:
1526 lock_time = time.time()
1531 lock_time = time.time()
1527 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1532 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1528 else:
1533 else:
1529 lock_time = None
1534 lock_time = None
1530 Repository.unlock(repo)
1535 Repository.unlock(repo)
1531 _d = {
1536 _d = {
1532 'repo': repo.repo_name,
1537 'repo': repo.repo_name,
1533 'locked': locked,
1538 'locked': locked,
1534 'locked_since': lock_time,
1539 'locked_since': lock_time,
1535 'locked_by': user.username,
1540 'locked_by': user.username,
1536 'lock_reason': lock_reason,
1541 'lock_reason': lock_reason,
1537 'lock_state_changed': True,
1542 'lock_state_changed': True,
1538 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1543 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1539 % (user.username, repo.repo_name, locked))
1544 % (user.username, repo.repo_name, locked))
1540 }
1545 }
1541 return _d
1546 return _d
1542 except Exception:
1547 except Exception:
1543 log.exception(
1548 log.exception(
1544 "Exception occurred while trying to lock repository")
1549 "Exception occurred while trying to lock repository")
1545 raise JSONRPCError(
1550 raise JSONRPCError(
1546 'Error occurred locking repository `%s`' % repo.repo_name
1551 'Error occurred locking repository `%s`' % repo.repo_name
1547 )
1552 )
1548
1553
1549
1554
1550 @jsonrpc_method()
1555 @jsonrpc_method()
1551 def comment_commit(
1556 def comment_commit(
1552 request, apiuser, repoid, commit_id, message, status=Optional(None),
1557 request, apiuser, repoid, commit_id, message, status=Optional(None),
1553 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1558 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1554 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1559 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1555 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1560 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1556 """
1561 """
1557 Set a commit comment, and optionally change the status of the commit.
1562 Set a commit comment, and optionally change the status of the commit.
1558
1563
1559 :param apiuser: This is filled automatically from the |authtoken|.
1564 :param apiuser: This is filled automatically from the |authtoken|.
1560 :type apiuser: AuthUser
1565 :type apiuser: AuthUser
1561 :param repoid: Set the repository name or repository ID.
1566 :param repoid: Set the repository name or repository ID.
1562 :type repoid: str or int
1567 :type repoid: str or int
1563 :param commit_id: Specify the commit_id for which to set a comment.
1568 :param commit_id: Specify the commit_id for which to set a comment.
1564 :type commit_id: str
1569 :type commit_id: str
1565 :param message: The comment text.
1570 :param message: The comment text.
1566 :type message: str
1571 :type message: str
1567 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1572 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1568 'approved', 'rejected', 'under_review'
1573 'approved', 'rejected', 'under_review'
1569 :type status: str
1574 :type status: str
1570 :param comment_type: Comment type, one of: 'note', 'todo'
1575 :param comment_type: Comment type, one of: 'note', 'todo'
1571 :type comment_type: Optional(str), default: 'note'
1576 :type comment_type: Optional(str), default: 'note'
1572 :param resolves_comment_id: id of comment which this one will resolve
1577 :param resolves_comment_id: id of comment which this one will resolve
1573 :type resolves_comment_id: Optional(int)
1578 :type resolves_comment_id: Optional(int)
1574 :param extra_recipients: list of user ids or usernames to add
1579 :param extra_recipients: list of user ids or usernames to add
1575 notifications for this comment. Acts like a CC for notification
1580 notifications for this comment. Acts like a CC for notification
1576 :type extra_recipients: Optional(list)
1581 :type extra_recipients: Optional(list)
1577 :param userid: Set the user name of the comment creator.
1582 :param userid: Set the user name of the comment creator.
1578 :type userid: Optional(str or int)
1583 :type userid: Optional(str or int)
1579 :param send_email: Define if this comment should also send email notification
1584 :param send_email: Define if this comment should also send email notification
1580 :type send_email: Optional(bool)
1585 :type send_email: Optional(bool)
1581
1586
1582 Example error output:
1587 Example error output:
1583
1588
1584 .. code-block:: bash
1589 .. code-block:: bash
1585
1590
1586 {
1591 {
1587 "id" : <id_given_in_input>,
1592 "id" : <id_given_in_input>,
1588 "result" : {
1593 "result" : {
1589 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1594 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1590 "status_change": null or <status>,
1595 "status_change": null or <status>,
1591 "success": true
1596 "success": true
1592 },
1597 },
1593 "error" : null
1598 "error" : null
1594 }
1599 }
1595
1600
1596 """
1601 """
1597 repo = get_repo_or_error(repoid)
1602 repo = get_repo_or_error(repoid)
1598 if not has_superadmin_permission(apiuser):
1603 if not has_superadmin_permission(apiuser):
1599 _perms = ('repository.read', 'repository.write', 'repository.admin')
1604 _perms = ('repository.read', 'repository.write', 'repository.admin')
1600 validate_repo_permissions(apiuser, repoid, repo, _perms)
1605 validate_repo_permissions(apiuser, repoid, repo, _perms)
1601
1606
1602 try:
1607 try:
1603 commit = repo.scm_instance().get_commit(commit_id=commit_id)
1608 commit = repo.scm_instance().get_commit(commit_id=commit_id)
1604 commit_id = commit.raw_id
1609 commit_id = commit.raw_id
1605 except Exception as e:
1610 except Exception as e:
1606 log.exception('Failed to fetch commit')
1611 log.exception('Failed to fetch commit')
1607 raise JSONRPCError(safe_str(e))
1612 raise JSONRPCError(safe_str(e))
1608
1613
1609 if isinstance(userid, Optional):
1614 if isinstance(userid, Optional):
1610 userid = apiuser.user_id
1615 userid = apiuser.user_id
1611
1616
1612 user = get_user_or_error(userid)
1617 user = get_user_or_error(userid)
1613 status = Optional.extract(status)
1618 status = Optional.extract(status)
1614 comment_type = Optional.extract(comment_type)
1619 comment_type = Optional.extract(comment_type)
1615 resolves_comment_id = Optional.extract(resolves_comment_id)
1620 resolves_comment_id = Optional.extract(resolves_comment_id)
1616 extra_recipients = Optional.extract(extra_recipients)
1621 extra_recipients = Optional.extract(extra_recipients)
1617 send_email = Optional.extract(send_email, binary=True)
1622 send_email = Optional.extract(send_email, binary=True)
1618
1623
1619 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1624 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1620 if status and status not in allowed_statuses:
1625 if status and status not in allowed_statuses:
1621 raise JSONRPCError('Bad status, must be on '
1626 raise JSONRPCError('Bad status, must be on '
1622 'of %s got %s' % (allowed_statuses, status,))
1627 'of %s got %s' % (allowed_statuses, status,))
1623
1628
1624 if resolves_comment_id:
1629 if resolves_comment_id:
1625 comment = ChangesetComment.get(resolves_comment_id)
1630 comment = ChangesetComment.get(resolves_comment_id)
1626 if not comment:
1631 if not comment:
1627 raise JSONRPCError(
1632 raise JSONRPCError(
1628 'Invalid resolves_comment_id `%s` for this commit.'
1633 'Invalid resolves_comment_id `%s` for this commit.'
1629 % resolves_comment_id)
1634 % resolves_comment_id)
1630 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1635 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1631 raise JSONRPCError(
1636 raise JSONRPCError(
1632 'Comment `%s` is wrong type for setting status to resolved.'
1637 'Comment `%s` is wrong type for setting status to resolved.'
1633 % resolves_comment_id)
1638 % resolves_comment_id)
1634
1639
1635 try:
1640 try:
1636 rc_config = SettingsModel().get_all_settings()
1641 rc_config = SettingsModel().get_all_settings()
1637 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1642 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1638 status_change_label = ChangesetStatus.get_status_lbl(status)
1643 status_change_label = ChangesetStatus.get_status_lbl(status)
1639 comment = CommentsModel().create(
1644 comment = CommentsModel().create(
1640 message, repo, user, commit_id=commit_id,
1645 message, repo, user, commit_id=commit_id,
1641 status_change=status_change_label,
1646 status_change=status_change_label,
1642 status_change_type=status,
1647 status_change_type=status,
1643 renderer=renderer,
1648 renderer=renderer,
1644 comment_type=comment_type,
1649 comment_type=comment_type,
1645 resolves_comment_id=resolves_comment_id,
1650 resolves_comment_id=resolves_comment_id,
1646 auth_user=apiuser,
1651 auth_user=apiuser,
1647 extra_recipients=extra_recipients,
1652 extra_recipients=extra_recipients,
1648 send_email=send_email
1653 send_email=send_email
1649 )
1654 )
1650 if status:
1655 if status:
1651 # also do a status change
1656 # also do a status change
1652 try:
1657 try:
1653 ChangesetStatusModel().set_status(
1658 ChangesetStatusModel().set_status(
1654 repo, status, user, comment, revision=commit_id,
1659 repo, status, user, comment, revision=commit_id,
1655 dont_allow_on_closed_pull_request=True
1660 dont_allow_on_closed_pull_request=True
1656 )
1661 )
1657 except StatusChangeOnClosedPullRequestError:
1662 except StatusChangeOnClosedPullRequestError:
1658 log.exception(
1663 log.exception(
1659 "Exception occurred while trying to change repo commit status")
1664 "Exception occurred while trying to change repo commit status")
1660 msg = ('Changing status on a commit associated with '
1665 msg = ('Changing status on a commit associated with '
1661 'a closed pull request is not allowed')
1666 'a closed pull request is not allowed')
1662 raise JSONRPCError(msg)
1667 raise JSONRPCError(msg)
1663
1668
1664 CommentsModel().trigger_commit_comment_hook(
1669 CommentsModel().trigger_commit_comment_hook(
1665 repo, apiuser, 'create',
1670 repo, apiuser, 'create',
1666 data={'comment': comment, 'commit': commit})
1671 data={'comment': comment, 'commit': commit})
1667
1672
1668 Session().commit()
1673 Session().commit()
1669 return {
1674 return {
1670 'msg': (
1675 'msg': (
1671 'Commented on commit `%s` for repository `%s`' % (
1676 'Commented on commit `%s` for repository `%s`' % (
1672 comment.revision, repo.repo_name)),
1677 comment.revision, repo.repo_name)),
1673 'status_change': status,
1678 'status_change': status,
1674 'success': True,
1679 'success': True,
1675 }
1680 }
1676 except JSONRPCError:
1681 except JSONRPCError:
1677 # catch any inside errors, and re-raise them to prevent from
1682 # catch any inside errors, and re-raise them to prevent from
1678 # below global catch to silence them
1683 # below global catch to silence them
1679 raise
1684 raise
1680 except Exception:
1685 except Exception:
1681 log.exception("Exception occurred while trying to comment on commit")
1686 log.exception("Exception occurred while trying to comment on commit")
1682 raise JSONRPCError(
1687 raise JSONRPCError(
1683 'failed to set comment on repository `%s`' % (repo.repo_name,)
1688 'failed to set comment on repository `%s`' % (repo.repo_name,)
1684 )
1689 )
1685
1690
1686
1691
1687 @jsonrpc_method()
1692 @jsonrpc_method()
1688 def get_repo_comments(request, apiuser, repoid,
1693 def get_repo_comments(request, apiuser, repoid,
1689 commit_id=Optional(None), comment_type=Optional(None),
1694 commit_id=Optional(None), comment_type=Optional(None),
1690 userid=Optional(None)):
1695 userid=Optional(None)):
1691 """
1696 """
1692 Get all comments for a repository
1697 Get all comments for a repository
1693
1698
1694 :param apiuser: This is filled automatically from the |authtoken|.
1699 :param apiuser: This is filled automatically from the |authtoken|.
1695 :type apiuser: AuthUser
1700 :type apiuser: AuthUser
1696 :param repoid: Set the repository name or repository ID.
1701 :param repoid: Set the repository name or repository ID.
1697 :type repoid: str or int
1702 :type repoid: str or int
1698 :param commit_id: Optionally filter the comments by the commit_id
1703 :param commit_id: Optionally filter the comments by the commit_id
1699 :type commit_id: Optional(str), default: None
1704 :type commit_id: Optional(str), default: None
1700 :param comment_type: Optionally filter the comments by the comment_type
1705 :param comment_type: Optionally filter the comments by the comment_type
1701 one of: 'note', 'todo'
1706 one of: 'note', 'todo'
1702 :type comment_type: Optional(str), default: None
1707 :type comment_type: Optional(str), default: None
1703 :param userid: Optionally filter the comments by the author of comment
1708 :param userid: Optionally filter the comments by the author of comment
1704 :type userid: Optional(str or int), Default: None
1709 :type userid: Optional(str or int), Default: None
1705
1710
1706 Example error output:
1711 Example error output:
1707
1712
1708 .. code-block:: bash
1713 .. code-block:: bash
1709
1714
1710 {
1715 {
1711 "id" : <id_given_in_input>,
1716 "id" : <id_given_in_input>,
1712 "result" : [
1717 "result" : [
1713 {
1718 {
1714 "comment_author": <USER_DETAILS>,
1719 "comment_author": <USER_DETAILS>,
1715 "comment_created_on": "2017-02-01T14:38:16.309",
1720 "comment_created_on": "2017-02-01T14:38:16.309",
1716 "comment_f_path": "file.txt",
1721 "comment_f_path": "file.txt",
1717 "comment_id": 282,
1722 "comment_id": 282,
1718 "comment_lineno": "n1",
1723 "comment_lineno": "n1",
1719 "comment_resolved_by": null,
1724 "comment_resolved_by": null,
1720 "comment_status": [],
1725 "comment_status": [],
1721 "comment_text": "This file needs a header",
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 "error" : null
1731 "error" : null
1726 }
1732 }
1727
1733
1728 """
1734 """
1729 repo = get_repo_or_error(repoid)
1735 repo = get_repo_or_error(repoid)
1730 if not has_superadmin_permission(apiuser):
1736 if not has_superadmin_permission(apiuser):
1731 _perms = ('repository.read', 'repository.write', 'repository.admin')
1737 _perms = ('repository.read', 'repository.write', 'repository.admin')
1732 validate_repo_permissions(apiuser, repoid, repo, _perms)
1738 validate_repo_permissions(apiuser, repoid, repo, _perms)
1733
1739
1734 commit_id = Optional.extract(commit_id)
1740 commit_id = Optional.extract(commit_id)
1735
1741
1736 userid = Optional.extract(userid)
1742 userid = Optional.extract(userid)
1737 if userid:
1743 if userid:
1738 user = get_user_or_error(userid)
1744 user = get_user_or_error(userid)
1739 else:
1745 else:
1740 user = None
1746 user = None
1741
1747
1742 comment_type = Optional.extract(comment_type)
1748 comment_type = Optional.extract(comment_type)
1743 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1749 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1744 raise JSONRPCError(
1750 raise JSONRPCError(
1745 'comment_type must be one of `{}` got {}'.format(
1751 'comment_type must be one of `{}` got {}'.format(
1746 ChangesetComment.COMMENT_TYPES, comment_type)
1752 ChangesetComment.COMMENT_TYPES, comment_type)
1747 )
1753 )
1748
1754
1749 comments = CommentsModel().get_repository_comments(
1755 comments = CommentsModel().get_repository_comments(
1750 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1756 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1751 return comments
1757 return comments
1752
1758
1753
1759
1754 @jsonrpc_method()
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 def grant_user_permission(request, apiuser, repoid, userid, perm):
1912 def grant_user_permission(request, apiuser, repoid, userid, perm):
1756 """
1913 """
1757 Grant permissions for the specified user on the given repository,
1914 Grant permissions for the specified user on the given repository,
1758 or update existing permissions if found.
1915 or update existing permissions if found.
1759
1916
1760 This command can only be run using an |authtoken| with admin
1917 This command can only be run using an |authtoken| with admin
1761 permissions on the |repo|.
1918 permissions on the |repo|.
1762
1919
1763 :param apiuser: This is filled automatically from the |authtoken|.
1920 :param apiuser: This is filled automatically from the |authtoken|.
1764 :type apiuser: AuthUser
1921 :type apiuser: AuthUser
1765 :param repoid: Set the repository name or repository ID.
1922 :param repoid: Set the repository name or repository ID.
1766 :type repoid: str or int
1923 :type repoid: str or int
1767 :param userid: Set the user name.
1924 :param userid: Set the user name.
1768 :type userid: str
1925 :type userid: str
1769 :param perm: Set the user permissions, using the following format
1926 :param perm: Set the user permissions, using the following format
1770 ``(repository.(none|read|write|admin))``
1927 ``(repository.(none|read|write|admin))``
1771 :type perm: str
1928 :type perm: str
1772
1929
1773 Example output:
1930 Example output:
1774
1931
1775 .. code-block:: bash
1932 .. code-block:: bash
1776
1933
1777 id : <id_given_in_input>
1934 id : <id_given_in_input>
1778 result: {
1935 result: {
1779 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1936 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1780 "success": true
1937 "success": true
1781 }
1938 }
1782 error: null
1939 error: null
1783 """
1940 """
1784
1941
1785 repo = get_repo_or_error(repoid)
1942 repo = get_repo_or_error(repoid)
1786 user = get_user_or_error(userid)
1943 user = get_user_or_error(userid)
1787 perm = get_perm_or_error(perm)
1944 perm = get_perm_or_error(perm)
1788 if not has_superadmin_permission(apiuser):
1945 if not has_superadmin_permission(apiuser):
1789 _perms = ('repository.admin',)
1946 _perms = ('repository.admin',)
1790 validate_repo_permissions(apiuser, repoid, repo, _perms)
1947 validate_repo_permissions(apiuser, repoid, repo, _perms)
1791
1948
1792 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1949 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1793 try:
1950 try:
1794 changes = RepoModel().update_permissions(
1951 changes = RepoModel().update_permissions(
1795 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1952 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1796
1953
1797 action_data = {
1954 action_data = {
1798 'added': changes['added'],
1955 'added': changes['added'],
1799 'updated': changes['updated'],
1956 'updated': changes['updated'],
1800 'deleted': changes['deleted'],
1957 'deleted': changes['deleted'],
1801 }
1958 }
1802 audit_logger.store_api(
1959 audit_logger.store_api(
1803 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1960 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1804 Session().commit()
1961 Session().commit()
1805 PermissionModel().flush_user_permission_caches(changes)
1962 PermissionModel().flush_user_permission_caches(changes)
1806
1963
1807 return {
1964 return {
1808 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1965 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1809 perm.permission_name, user.username, repo.repo_name
1966 perm.permission_name, user.username, repo.repo_name
1810 ),
1967 ),
1811 'success': True
1968 'success': True
1812 }
1969 }
1813 except Exception:
1970 except Exception:
1814 log.exception("Exception occurred while trying edit permissions for repo")
1971 log.exception("Exception occurred while trying edit permissions for repo")
1815 raise JSONRPCError(
1972 raise JSONRPCError(
1816 'failed to edit permission for user: `%s` in repo: `%s`' % (
1973 'failed to edit permission for user: `%s` in repo: `%s`' % (
1817 userid, repoid
1974 userid, repoid
1818 )
1975 )
1819 )
1976 )
1820
1977
1821
1978
1822 @jsonrpc_method()
1979 @jsonrpc_method()
1823 def revoke_user_permission(request, apiuser, repoid, userid):
1980 def revoke_user_permission(request, apiuser, repoid, userid):
1824 """
1981 """
1825 Revoke permission for a user on the specified repository.
1982 Revoke permission for a user on the specified repository.
1826
1983
1827 This command can only be run using an |authtoken| with admin
1984 This command can only be run using an |authtoken| with admin
1828 permissions on the |repo|.
1985 permissions on the |repo|.
1829
1986
1830 :param apiuser: This is filled automatically from the |authtoken|.
1987 :param apiuser: This is filled automatically from the |authtoken|.
1831 :type apiuser: AuthUser
1988 :type apiuser: AuthUser
1832 :param repoid: Set the repository name or repository ID.
1989 :param repoid: Set the repository name or repository ID.
1833 :type repoid: str or int
1990 :type repoid: str or int
1834 :param userid: Set the user name of revoked user.
1991 :param userid: Set the user name of revoked user.
1835 :type userid: str or int
1992 :type userid: str or int
1836
1993
1837 Example error output:
1994 Example error output:
1838
1995
1839 .. code-block:: bash
1996 .. code-block:: bash
1840
1997
1841 id : <id_given_in_input>
1998 id : <id_given_in_input>
1842 result: {
1999 result: {
1843 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
2000 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1844 "success": true
2001 "success": true
1845 }
2002 }
1846 error: null
2003 error: null
1847 """
2004 """
1848
2005
1849 repo = get_repo_or_error(repoid)
2006 repo = get_repo_or_error(repoid)
1850 user = get_user_or_error(userid)
2007 user = get_user_or_error(userid)
1851 if not has_superadmin_permission(apiuser):
2008 if not has_superadmin_permission(apiuser):
1852 _perms = ('repository.admin',)
2009 _perms = ('repository.admin',)
1853 validate_repo_permissions(apiuser, repoid, repo, _perms)
2010 validate_repo_permissions(apiuser, repoid, repo, _perms)
1854
2011
1855 perm_deletions = [[user.user_id, None, "user"]]
2012 perm_deletions = [[user.user_id, None, "user"]]
1856 try:
2013 try:
1857 changes = RepoModel().update_permissions(
2014 changes = RepoModel().update_permissions(
1858 repo=repo, perm_deletions=perm_deletions, cur_user=user)
2015 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1859
2016
1860 action_data = {
2017 action_data = {
1861 'added': changes['added'],
2018 'added': changes['added'],
1862 'updated': changes['updated'],
2019 'updated': changes['updated'],
1863 'deleted': changes['deleted'],
2020 'deleted': changes['deleted'],
1864 }
2021 }
1865 audit_logger.store_api(
2022 audit_logger.store_api(
1866 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2023 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1867 Session().commit()
2024 Session().commit()
1868 PermissionModel().flush_user_permission_caches(changes)
2025 PermissionModel().flush_user_permission_caches(changes)
1869
2026
1870 return {
2027 return {
1871 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
2028 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1872 user.username, repo.repo_name
2029 user.username, repo.repo_name
1873 ),
2030 ),
1874 'success': True
2031 'success': True
1875 }
2032 }
1876 except Exception:
2033 except Exception:
1877 log.exception("Exception occurred while trying revoke permissions to repo")
2034 log.exception("Exception occurred while trying revoke permissions to repo")
1878 raise JSONRPCError(
2035 raise JSONRPCError(
1879 'failed to edit permission for user: `%s` in repo: `%s`' % (
2036 'failed to edit permission for user: `%s` in repo: `%s`' % (
1880 userid, repoid
2037 userid, repoid
1881 )
2038 )
1882 )
2039 )
1883
2040
1884
2041
1885 @jsonrpc_method()
2042 @jsonrpc_method()
1886 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
2043 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1887 """
2044 """
1888 Grant permission for a user group on the specified repository,
2045 Grant permission for a user group on the specified repository,
1889 or update existing permissions.
2046 or update existing permissions.
1890
2047
1891 This command can only be run using an |authtoken| with admin
2048 This command can only be run using an |authtoken| with admin
1892 permissions on the |repo|.
2049 permissions on the |repo|.
1893
2050
1894 :param apiuser: This is filled automatically from the |authtoken|.
2051 :param apiuser: This is filled automatically from the |authtoken|.
1895 :type apiuser: AuthUser
2052 :type apiuser: AuthUser
1896 :param repoid: Set the repository name or repository ID.
2053 :param repoid: Set the repository name or repository ID.
1897 :type repoid: str or int
2054 :type repoid: str or int
1898 :param usergroupid: Specify the ID of the user group.
2055 :param usergroupid: Specify the ID of the user group.
1899 :type usergroupid: str or int
2056 :type usergroupid: str or int
1900 :param perm: Set the user group permissions using the following
2057 :param perm: Set the user group permissions using the following
1901 format: (repository.(none|read|write|admin))
2058 format: (repository.(none|read|write|admin))
1902 :type perm: str
2059 :type perm: str
1903
2060
1904 Example output:
2061 Example output:
1905
2062
1906 .. code-block:: bash
2063 .. code-block:: bash
1907
2064
1908 id : <id_given_in_input>
2065 id : <id_given_in_input>
1909 result : {
2066 result : {
1910 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
2067 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1911 "success": true
2068 "success": true
1912
2069
1913 }
2070 }
1914 error : null
2071 error : null
1915
2072
1916 Example error output:
2073 Example error output:
1917
2074
1918 .. code-block:: bash
2075 .. code-block:: bash
1919
2076
1920 id : <id_given_in_input>
2077 id : <id_given_in_input>
1921 result : null
2078 result : null
1922 error : {
2079 error : {
1923 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
2080 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1924 }
2081 }
1925
2082
1926 """
2083 """
1927
2084
1928 repo = get_repo_or_error(repoid)
2085 repo = get_repo_or_error(repoid)
1929 perm = get_perm_or_error(perm)
2086 perm = get_perm_or_error(perm)
1930 if not has_superadmin_permission(apiuser):
2087 if not has_superadmin_permission(apiuser):
1931 _perms = ('repository.admin',)
2088 _perms = ('repository.admin',)
1932 validate_repo_permissions(apiuser, repoid, repo, _perms)
2089 validate_repo_permissions(apiuser, repoid, repo, _perms)
1933
2090
1934 user_group = get_user_group_or_error(usergroupid)
2091 user_group = get_user_group_or_error(usergroupid)
1935 if not has_superadmin_permission(apiuser):
2092 if not has_superadmin_permission(apiuser):
1936 # check if we have at least read permission for this user group !
2093 # check if we have at least read permission for this user group !
1937 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2094 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1938 if not HasUserGroupPermissionAnyApi(*_perms)(
2095 if not HasUserGroupPermissionAnyApi(*_perms)(
1939 user=apiuser, user_group_name=user_group.users_group_name):
2096 user=apiuser, user_group_name=user_group.users_group_name):
1940 raise JSONRPCError(
2097 raise JSONRPCError(
1941 'user group `%s` does not exist' % (usergroupid,))
2098 'user group `%s` does not exist' % (usergroupid,))
1942
2099
1943 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
2100 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1944 try:
2101 try:
1945 changes = RepoModel().update_permissions(
2102 changes = RepoModel().update_permissions(
1946 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
2103 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1947 action_data = {
2104 action_data = {
1948 'added': changes['added'],
2105 'added': changes['added'],
1949 'updated': changes['updated'],
2106 'updated': changes['updated'],
1950 'deleted': changes['deleted'],
2107 'deleted': changes['deleted'],
1951 }
2108 }
1952 audit_logger.store_api(
2109 audit_logger.store_api(
1953 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2110 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1954 Session().commit()
2111 Session().commit()
1955 PermissionModel().flush_user_permission_caches(changes)
2112 PermissionModel().flush_user_permission_caches(changes)
1956
2113
1957 return {
2114 return {
1958 'msg': 'Granted perm: `%s` for user group: `%s` in '
2115 'msg': 'Granted perm: `%s` for user group: `%s` in '
1959 'repo: `%s`' % (
2116 'repo: `%s`' % (
1960 perm.permission_name, user_group.users_group_name,
2117 perm.permission_name, user_group.users_group_name,
1961 repo.repo_name
2118 repo.repo_name
1962 ),
2119 ),
1963 'success': True
2120 'success': True
1964 }
2121 }
1965 except Exception:
2122 except Exception:
1966 log.exception(
2123 log.exception(
1967 "Exception occurred while trying change permission on repo")
2124 "Exception occurred while trying change permission on repo")
1968 raise JSONRPCError(
2125 raise JSONRPCError(
1969 'failed to edit permission for user group: `%s` in '
2126 'failed to edit permission for user group: `%s` in '
1970 'repo: `%s`' % (
2127 'repo: `%s`' % (
1971 usergroupid, repo.repo_name
2128 usergroupid, repo.repo_name
1972 )
2129 )
1973 )
2130 )
1974
2131
1975
2132
1976 @jsonrpc_method()
2133 @jsonrpc_method()
1977 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
2134 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1978 """
2135 """
1979 Revoke the permissions of a user group on a given repository.
2136 Revoke the permissions of a user group on a given repository.
1980
2137
1981 This command can only be run using an |authtoken| with admin
2138 This command can only be run using an |authtoken| with admin
1982 permissions on the |repo|.
2139 permissions on the |repo|.
1983
2140
1984 :param apiuser: This is filled automatically from the |authtoken|.
2141 :param apiuser: This is filled automatically from the |authtoken|.
1985 :type apiuser: AuthUser
2142 :type apiuser: AuthUser
1986 :param repoid: Set the repository name or repository ID.
2143 :param repoid: Set the repository name or repository ID.
1987 :type repoid: str or int
2144 :type repoid: str or int
1988 :param usergroupid: Specify the user group ID.
2145 :param usergroupid: Specify the user group ID.
1989 :type usergroupid: str or int
2146 :type usergroupid: str or int
1990
2147
1991 Example output:
2148 Example output:
1992
2149
1993 .. code-block:: bash
2150 .. code-block:: bash
1994
2151
1995 id : <id_given_in_input>
2152 id : <id_given_in_input>
1996 result: {
2153 result: {
1997 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
2154 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1998 "success": true
2155 "success": true
1999 }
2156 }
2000 error: null
2157 error: null
2001 """
2158 """
2002
2159
2003 repo = get_repo_or_error(repoid)
2160 repo = get_repo_or_error(repoid)
2004 if not has_superadmin_permission(apiuser):
2161 if not has_superadmin_permission(apiuser):
2005 _perms = ('repository.admin',)
2162 _perms = ('repository.admin',)
2006 validate_repo_permissions(apiuser, repoid, repo, _perms)
2163 validate_repo_permissions(apiuser, repoid, repo, _perms)
2007
2164
2008 user_group = get_user_group_or_error(usergroupid)
2165 user_group = get_user_group_or_error(usergroupid)
2009 if not has_superadmin_permission(apiuser):
2166 if not has_superadmin_permission(apiuser):
2010 # check if we have at least read permission for this user group !
2167 # check if we have at least read permission for this user group !
2011 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2168 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2012 if not HasUserGroupPermissionAnyApi(*_perms)(
2169 if not HasUserGroupPermissionAnyApi(*_perms)(
2013 user=apiuser, user_group_name=user_group.users_group_name):
2170 user=apiuser, user_group_name=user_group.users_group_name):
2014 raise JSONRPCError(
2171 raise JSONRPCError(
2015 'user group `%s` does not exist' % (usergroupid,))
2172 'user group `%s` does not exist' % (usergroupid,))
2016
2173
2017 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2174 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2018 try:
2175 try:
2019 changes = RepoModel().update_permissions(
2176 changes = RepoModel().update_permissions(
2020 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2177 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2021 action_data = {
2178 action_data = {
2022 'added': changes['added'],
2179 'added': changes['added'],
2023 'updated': changes['updated'],
2180 'updated': changes['updated'],
2024 'deleted': changes['deleted'],
2181 'deleted': changes['deleted'],
2025 }
2182 }
2026 audit_logger.store_api(
2183 audit_logger.store_api(
2027 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2184 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2028 Session().commit()
2185 Session().commit()
2029 PermissionModel().flush_user_permission_caches(changes)
2186 PermissionModel().flush_user_permission_caches(changes)
2030
2187
2031 return {
2188 return {
2032 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2189 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2033 user_group.users_group_name, repo.repo_name
2190 user_group.users_group_name, repo.repo_name
2034 ),
2191 ),
2035 'success': True
2192 'success': True
2036 }
2193 }
2037 except Exception:
2194 except Exception:
2038 log.exception("Exception occurred while trying revoke "
2195 log.exception("Exception occurred while trying revoke "
2039 "user group permission on repo")
2196 "user group permission on repo")
2040 raise JSONRPCError(
2197 raise JSONRPCError(
2041 'failed to edit permission for user group: `%s` in '
2198 'failed to edit permission for user group: `%s` in '
2042 'repo: `%s`' % (
2199 'repo: `%s`' % (
2043 user_group.users_group_name, repo.repo_name
2200 user_group.users_group_name, repo.repo_name
2044 )
2201 )
2045 )
2202 )
2046
2203
2047
2204
2048 @jsonrpc_method()
2205 @jsonrpc_method()
2049 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2206 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2050 """
2207 """
2051 Triggers a pull on the given repository from a remote location. You
2208 Triggers a pull on the given repository from a remote location. You
2052 can use this to keep remote repositories up-to-date.
2209 can use this to keep remote repositories up-to-date.
2053
2210
2054 This command can only be run using an |authtoken| with admin
2211 This command can only be run using an |authtoken| with admin
2055 rights to the specified repository. For more information,
2212 rights to the specified repository. For more information,
2056 see :ref:`config-token-ref`.
2213 see :ref:`config-token-ref`.
2057
2214
2058 This command takes the following options:
2215 This command takes the following options:
2059
2216
2060 :param apiuser: This is filled automatically from the |authtoken|.
2217 :param apiuser: This is filled automatically from the |authtoken|.
2061 :type apiuser: AuthUser
2218 :type apiuser: AuthUser
2062 :param repoid: The repository name or repository ID.
2219 :param repoid: The repository name or repository ID.
2063 :type repoid: str or int
2220 :type repoid: str or int
2064 :param remote_uri: Optional remote URI to pass in for pull
2221 :param remote_uri: Optional remote URI to pass in for pull
2065 :type remote_uri: str
2222 :type remote_uri: str
2066
2223
2067 Example output:
2224 Example output:
2068
2225
2069 .. code-block:: bash
2226 .. code-block:: bash
2070
2227
2071 id : <id_given_in_input>
2228 id : <id_given_in_input>
2072 result : {
2229 result : {
2073 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2230 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2074 "repository": "<repository name>"
2231 "repository": "<repository name>"
2075 }
2232 }
2076 error : null
2233 error : null
2077
2234
2078 Example error output:
2235 Example error output:
2079
2236
2080 .. code-block:: bash
2237 .. code-block:: bash
2081
2238
2082 id : <id_given_in_input>
2239 id : <id_given_in_input>
2083 result : null
2240 result : null
2084 error : {
2241 error : {
2085 "Unable to push changes from `<remote_url>`"
2242 "Unable to push changes from `<remote_url>`"
2086 }
2243 }
2087
2244
2088 """
2245 """
2089
2246
2090 repo = get_repo_or_error(repoid)
2247 repo = get_repo_or_error(repoid)
2091 remote_uri = Optional.extract(remote_uri)
2248 remote_uri = Optional.extract(remote_uri)
2092 remote_uri_display = remote_uri or repo.clone_uri_hidden
2249 remote_uri_display = remote_uri or repo.clone_uri_hidden
2093 if not has_superadmin_permission(apiuser):
2250 if not has_superadmin_permission(apiuser):
2094 _perms = ('repository.admin',)
2251 _perms = ('repository.admin',)
2095 validate_repo_permissions(apiuser, repoid, repo, _perms)
2252 validate_repo_permissions(apiuser, repoid, repo, _perms)
2096
2253
2097 try:
2254 try:
2098 ScmModel().pull_changes(
2255 ScmModel().pull_changes(
2099 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2256 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2100 return {
2257 return {
2101 'msg': 'Pulled from url `%s` on repo `%s`' % (
2258 'msg': 'Pulled from url `%s` on repo `%s`' % (
2102 remote_uri_display, repo.repo_name),
2259 remote_uri_display, repo.repo_name),
2103 'repository': repo.repo_name
2260 'repository': repo.repo_name
2104 }
2261 }
2105 except Exception:
2262 except Exception:
2106 log.exception("Exception occurred while trying to "
2263 log.exception("Exception occurred while trying to "
2107 "pull changes from remote location")
2264 "pull changes from remote location")
2108 raise JSONRPCError(
2265 raise JSONRPCError(
2109 'Unable to pull changes from `%s`' % remote_uri_display
2266 'Unable to pull changes from `%s`' % remote_uri_display
2110 )
2267 )
2111
2268
2112
2269
2113 @jsonrpc_method()
2270 @jsonrpc_method()
2114 def strip(request, apiuser, repoid, revision, branch):
2271 def strip(request, apiuser, repoid, revision, branch):
2115 """
2272 """
2116 Strips the given revision from the specified repository.
2273 Strips the given revision from the specified repository.
2117
2274
2118 * This will remove the revision and all of its decendants.
2275 * This will remove the revision and all of its decendants.
2119
2276
2120 This command can only be run using an |authtoken| with admin rights to
2277 This command can only be run using an |authtoken| with admin rights to
2121 the specified repository.
2278 the specified repository.
2122
2279
2123 This command takes the following options:
2280 This command takes the following options:
2124
2281
2125 :param apiuser: This is filled automatically from the |authtoken|.
2282 :param apiuser: This is filled automatically from the |authtoken|.
2126 :type apiuser: AuthUser
2283 :type apiuser: AuthUser
2127 :param repoid: The repository name or repository ID.
2284 :param repoid: The repository name or repository ID.
2128 :type repoid: str or int
2285 :type repoid: str or int
2129 :param revision: The revision you wish to strip.
2286 :param revision: The revision you wish to strip.
2130 :type revision: str
2287 :type revision: str
2131 :param branch: The branch from which to strip the revision.
2288 :param branch: The branch from which to strip the revision.
2132 :type branch: str
2289 :type branch: str
2133
2290
2134 Example output:
2291 Example output:
2135
2292
2136 .. code-block:: bash
2293 .. code-block:: bash
2137
2294
2138 id : <id_given_in_input>
2295 id : <id_given_in_input>
2139 result : {
2296 result : {
2140 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2297 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2141 "repository": "<repository name>"
2298 "repository": "<repository name>"
2142 }
2299 }
2143 error : null
2300 error : null
2144
2301
2145 Example error output:
2302 Example error output:
2146
2303
2147 .. code-block:: bash
2304 .. code-block:: bash
2148
2305
2149 id : <id_given_in_input>
2306 id : <id_given_in_input>
2150 result : null
2307 result : null
2151 error : {
2308 error : {
2152 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2309 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2153 }
2310 }
2154
2311
2155 """
2312 """
2156
2313
2157 repo = get_repo_or_error(repoid)
2314 repo = get_repo_or_error(repoid)
2158 if not has_superadmin_permission(apiuser):
2315 if not has_superadmin_permission(apiuser):
2159 _perms = ('repository.admin',)
2316 _perms = ('repository.admin',)
2160 validate_repo_permissions(apiuser, repoid, repo, _perms)
2317 validate_repo_permissions(apiuser, repoid, repo, _perms)
2161
2318
2162 try:
2319 try:
2163 ScmModel().strip(repo, revision, branch)
2320 ScmModel().strip(repo, revision, branch)
2164 audit_logger.store_api(
2321 audit_logger.store_api(
2165 'repo.commit.strip', action_data={'commit_id': revision},
2322 'repo.commit.strip', action_data={'commit_id': revision},
2166 repo=repo,
2323 repo=repo,
2167 user=apiuser, commit=True)
2324 user=apiuser, commit=True)
2168
2325
2169 return {
2326 return {
2170 'msg': 'Stripped commit %s from repo `%s`' % (
2327 'msg': 'Stripped commit %s from repo `%s`' % (
2171 revision, repo.repo_name),
2328 revision, repo.repo_name),
2172 'repository': repo.repo_name
2329 'repository': repo.repo_name
2173 }
2330 }
2174 except Exception:
2331 except Exception:
2175 log.exception("Exception while trying to strip")
2332 log.exception("Exception while trying to strip")
2176 raise JSONRPCError(
2333 raise JSONRPCError(
2177 'Unable to strip commit %s from repo `%s`' % (
2334 'Unable to strip commit %s from repo `%s`' % (
2178 revision, repo.repo_name)
2335 revision, repo.repo_name)
2179 )
2336 )
2180
2337
2181
2338
2182 @jsonrpc_method()
2339 @jsonrpc_method()
2183 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2340 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2184 """
2341 """
2185 Returns all settings for a repository. If key is given it only returns the
2342 Returns all settings for a repository. If key is given it only returns the
2186 setting identified by the key or null.
2343 setting identified by the key or null.
2187
2344
2188 :param apiuser: This is filled automatically from the |authtoken|.
2345 :param apiuser: This is filled automatically from the |authtoken|.
2189 :type apiuser: AuthUser
2346 :type apiuser: AuthUser
2190 :param repoid: The repository name or repository id.
2347 :param repoid: The repository name or repository id.
2191 :type repoid: str or int
2348 :type repoid: str or int
2192 :param key: Key of the setting to return.
2349 :param key: Key of the setting to return.
2193 :type: key: Optional(str)
2350 :type: key: Optional(str)
2194
2351
2195 Example output:
2352 Example output:
2196
2353
2197 .. code-block:: bash
2354 .. code-block:: bash
2198
2355
2199 {
2356 {
2200 "error": null,
2357 "error": null,
2201 "id": 237,
2358 "id": 237,
2202 "result": {
2359 "result": {
2203 "extensions_largefiles": true,
2360 "extensions_largefiles": true,
2204 "extensions_evolve": true,
2361 "extensions_evolve": true,
2205 "hooks_changegroup_push_logger": true,
2362 "hooks_changegroup_push_logger": true,
2206 "hooks_changegroup_repo_size": false,
2363 "hooks_changegroup_repo_size": false,
2207 "hooks_outgoing_pull_logger": true,
2364 "hooks_outgoing_pull_logger": true,
2208 "phases_publish": "True",
2365 "phases_publish": "True",
2209 "rhodecode_hg_use_rebase_for_merging": true,
2366 "rhodecode_hg_use_rebase_for_merging": true,
2210 "rhodecode_pr_merge_enabled": true,
2367 "rhodecode_pr_merge_enabled": true,
2211 "rhodecode_use_outdated_comments": true
2368 "rhodecode_use_outdated_comments": true
2212 }
2369 }
2213 }
2370 }
2214 """
2371 """
2215
2372
2216 # Restrict access to this api method to admins only.
2373 # Restrict access to this api method to admins only.
2217 if not has_superadmin_permission(apiuser):
2374 if not has_superadmin_permission(apiuser):
2218 raise JSONRPCForbidden()
2375 raise JSONRPCForbidden()
2219
2376
2220 try:
2377 try:
2221 repo = get_repo_or_error(repoid)
2378 repo = get_repo_or_error(repoid)
2222 settings_model = VcsSettingsModel(repo=repo)
2379 settings_model = VcsSettingsModel(repo=repo)
2223 settings = settings_model.get_global_settings()
2380 settings = settings_model.get_global_settings()
2224 settings.update(settings_model.get_repo_settings())
2381 settings.update(settings_model.get_repo_settings())
2225
2382
2226 # If only a single setting is requested fetch it from all settings.
2383 # If only a single setting is requested fetch it from all settings.
2227 key = Optional.extract(key)
2384 key = Optional.extract(key)
2228 if key is not None:
2385 if key is not None:
2229 settings = settings.get(key, None)
2386 settings = settings.get(key, None)
2230 except Exception:
2387 except Exception:
2231 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2388 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2232 log.exception(msg)
2389 log.exception(msg)
2233 raise JSONRPCError(msg)
2390 raise JSONRPCError(msg)
2234
2391
2235 return settings
2392 return settings
2236
2393
2237
2394
2238 @jsonrpc_method()
2395 @jsonrpc_method()
2239 def set_repo_settings(request, apiuser, repoid, settings):
2396 def set_repo_settings(request, apiuser, repoid, settings):
2240 """
2397 """
2241 Update repository settings. Returns true on success.
2398 Update repository settings. Returns true on success.
2242
2399
2243 :param apiuser: This is filled automatically from the |authtoken|.
2400 :param apiuser: This is filled automatically from the |authtoken|.
2244 :type apiuser: AuthUser
2401 :type apiuser: AuthUser
2245 :param repoid: The repository name or repository id.
2402 :param repoid: The repository name or repository id.
2246 :type repoid: str or int
2403 :type repoid: str or int
2247 :param settings: The new settings for the repository.
2404 :param settings: The new settings for the repository.
2248 :type: settings: dict
2405 :type: settings: dict
2249
2406
2250 Example output:
2407 Example output:
2251
2408
2252 .. code-block:: bash
2409 .. code-block:: bash
2253
2410
2254 {
2411 {
2255 "error": null,
2412 "error": null,
2256 "id": 237,
2413 "id": 237,
2257 "result": true
2414 "result": true
2258 }
2415 }
2259 """
2416 """
2260 # Restrict access to this api method to admins only.
2417 # Restrict access to this api method to admins only.
2261 if not has_superadmin_permission(apiuser):
2418 if not has_superadmin_permission(apiuser):
2262 raise JSONRPCForbidden()
2419 raise JSONRPCForbidden()
2263
2420
2264 if type(settings) is not dict:
2421 if type(settings) is not dict:
2265 raise JSONRPCError('Settings have to be a JSON Object.')
2422 raise JSONRPCError('Settings have to be a JSON Object.')
2266
2423
2267 try:
2424 try:
2268 settings_model = VcsSettingsModel(repo=repoid)
2425 settings_model = VcsSettingsModel(repo=repoid)
2269
2426
2270 # Merge global, repo and incoming settings.
2427 # Merge global, repo and incoming settings.
2271 new_settings = settings_model.get_global_settings()
2428 new_settings = settings_model.get_global_settings()
2272 new_settings.update(settings_model.get_repo_settings())
2429 new_settings.update(settings_model.get_repo_settings())
2273 new_settings.update(settings)
2430 new_settings.update(settings)
2274
2431
2275 # Update the settings.
2432 # Update the settings.
2276 inherit_global_settings = new_settings.get(
2433 inherit_global_settings = new_settings.get(
2277 'inherit_global_settings', False)
2434 'inherit_global_settings', False)
2278 settings_model.create_or_update_repo_settings(
2435 settings_model.create_or_update_repo_settings(
2279 new_settings, inherit_global_settings=inherit_global_settings)
2436 new_settings, inherit_global_settings=inherit_global_settings)
2280 Session().commit()
2437 Session().commit()
2281 except Exception:
2438 except Exception:
2282 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2439 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2283 log.exception(msg)
2440 log.exception(msg)
2284 raise JSONRPCError(msg)
2441 raise JSONRPCError(msg)
2285
2442
2286 # Indicate success.
2443 # Indicate success.
2287 return True
2444 return True
2288
2445
2289
2446
2290 @jsonrpc_method()
2447 @jsonrpc_method()
2291 def maintenance(request, apiuser, repoid):
2448 def maintenance(request, apiuser, repoid):
2292 """
2449 """
2293 Triggers a maintenance on the given repository.
2450 Triggers a maintenance on the given repository.
2294
2451
2295 This command can only be run using an |authtoken| with admin
2452 This command can only be run using an |authtoken| with admin
2296 rights to the specified repository. For more information,
2453 rights to the specified repository. For more information,
2297 see :ref:`config-token-ref`.
2454 see :ref:`config-token-ref`.
2298
2455
2299 This command takes the following options:
2456 This command takes the following options:
2300
2457
2301 :param apiuser: This is filled automatically from the |authtoken|.
2458 :param apiuser: This is filled automatically from the |authtoken|.
2302 :type apiuser: AuthUser
2459 :type apiuser: AuthUser
2303 :param repoid: The repository name or repository ID.
2460 :param repoid: The repository name or repository ID.
2304 :type repoid: str or int
2461 :type repoid: str or int
2305
2462
2306 Example output:
2463 Example output:
2307
2464
2308 .. code-block:: bash
2465 .. code-block:: bash
2309
2466
2310 id : <id_given_in_input>
2467 id : <id_given_in_input>
2311 result : {
2468 result : {
2312 "msg": "executed maintenance command",
2469 "msg": "executed maintenance command",
2313 "executed_actions": [
2470 "executed_actions": [
2314 <action_message>, <action_message2>...
2471 <action_message>, <action_message2>...
2315 ],
2472 ],
2316 "repository": "<repository name>"
2473 "repository": "<repository name>"
2317 }
2474 }
2318 error : null
2475 error : null
2319
2476
2320 Example error output:
2477 Example error output:
2321
2478
2322 .. code-block:: bash
2479 .. code-block:: bash
2323
2480
2324 id : <id_given_in_input>
2481 id : <id_given_in_input>
2325 result : null
2482 result : null
2326 error : {
2483 error : {
2327 "Unable to execute maintenance on `<reponame>`"
2484 "Unable to execute maintenance on `<reponame>`"
2328 }
2485 }
2329
2486
2330 """
2487 """
2331
2488
2332 repo = get_repo_or_error(repoid)
2489 repo = get_repo_or_error(repoid)
2333 if not has_superadmin_permission(apiuser):
2490 if not has_superadmin_permission(apiuser):
2334 _perms = ('repository.admin',)
2491 _perms = ('repository.admin',)
2335 validate_repo_permissions(apiuser, repoid, repo, _perms)
2492 validate_repo_permissions(apiuser, repoid, repo, _perms)
2336
2493
2337 try:
2494 try:
2338 maintenance = repo_maintenance.RepoMaintenance()
2495 maintenance = repo_maintenance.RepoMaintenance()
2339 executed_actions = maintenance.execute(repo)
2496 executed_actions = maintenance.execute(repo)
2340
2497
2341 return {
2498 return {
2342 'msg': 'executed maintenance command',
2499 'msg': 'executed maintenance command',
2343 'executed_actions': executed_actions,
2500 'executed_actions': executed_actions,
2344 'repository': repo.repo_name
2501 'repository': repo.repo_name
2345 }
2502 }
2346 except Exception:
2503 except Exception:
2347 log.exception("Exception occurred while trying to run maintenance")
2504 log.exception("Exception occurred while trying to run maintenance")
2348 raise JSONRPCError(
2505 raise JSONRPCError(
2349 'Unable to execute maintenance on `%s`' % repo.repo_name)
2506 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,360 +1,374 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import datetime
20 import datetime
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34
34
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, CSRFRequired, NotAnonymous,
36 LoginRequired, CSRFRequired, NotAnonymous,
37 HasPermissionAny, HasRepoGroupPermissionAny)
37 HasPermissionAny, HasRepoGroupPermissionAny)
38 from rhodecode.lib import helpers as h, audit_logger
38 from rhodecode.lib import helpers as h, audit_logger
39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 from rhodecode.model.forms import RepoGroupForm
40 from rhodecode.model.forms import RepoGroupForm
41 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo_group import RepoGroupModel
42 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.scm import RepoGroupList
43 from rhodecode.model.scm import RepoGroupList
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51
51
52 def load_default_context(self):
52 def load_default_context(self):
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54
54
55 return c
55 return c
56
56
57 def _load_form_data(self, c):
57 def _load_form_data(self, c):
58 allow_empty_group = False
58 allow_empty_group = False
59
59
60 if self._can_create_repo_group():
60 if self._can_create_repo_group():
61 # we're global admin, we're ok and we can create TOP level groups
61 # we're global admin, we're ok and we can create TOP level groups
62 allow_empty_group = True
62 allow_empty_group = True
63
63
64 # override the choices for this form, we need to filter choices
64 # override the choices for this form, we need to filter choices
65 # and display only those we have ADMIN right
65 # and display only those we have ADMIN right
66 groups_with_admin_rights = RepoGroupList(
66 groups_with_admin_rights = RepoGroupList(
67 RepoGroup.query().all(),
67 RepoGroup.query().all(),
68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 c.repo_groups = RepoGroup.groups_choices(
69 c.repo_groups = RepoGroup.groups_choices(
70 groups=groups_with_admin_rights,
70 groups=groups_with_admin_rights,
71 show_empty_group=allow_empty_group)
71 show_empty_group=allow_empty_group)
72 c.personal_repo_group = self._rhodecode_user.personal_repo_group
72
73
73 def _can_create_repo_group(self, parent_group_id=None):
74 def _can_create_repo_group(self, parent_group_id=None):
74 is_admin = HasPermissionAny('hg.admin')('group create controller')
75 is_admin = HasPermissionAny('hg.admin')('group create controller')
75 create_repo_group = HasPermissionAny(
76 create_repo_group = HasPermissionAny(
76 'hg.repogroup.create.true')('group create controller')
77 'hg.repogroup.create.true')('group create controller')
77 if is_admin or (create_repo_group and not parent_group_id):
78 if is_admin or (create_repo_group and not parent_group_id):
78 # we're global admin, or we have global repo group create
79 # we're global admin, or we have global repo group create
79 # permission
80 # permission
80 # we're ok and we can create TOP level groups
81 # we're ok and we can create TOP level groups
81 return True
82 return True
82 elif parent_group_id:
83 elif parent_group_id:
83 # we check the permission if we can write to parent group
84 # we check the permission if we can write to parent group
84 group = RepoGroup.get(parent_group_id)
85 group = RepoGroup.get(parent_group_id)
85 group_name = group.group_name if group else None
86 group_name = group.group_name if group else None
86 if HasRepoGroupPermissionAny('group.admin')(
87 if HasRepoGroupPermissionAny('group.admin')(
87 group_name, 'check if user is an admin of group'):
88 group_name, 'check if user is an admin of group'):
88 # we're an admin of passed in group, we're ok.
89 # we're an admin of passed in group, we're ok.
89 return True
90 return True
90 else:
91 else:
91 return False
92 return False
92 return False
93 return False
93
94
94 # permission check in data loading of
95 # permission check in data loading of
95 # `repo_group_list_data` via RepoGroupList
96 # `repo_group_list_data` via RepoGroupList
96 @LoginRequired()
97 @LoginRequired()
97 @NotAnonymous()
98 @NotAnonymous()
98 @view_config(
99 @view_config(
99 route_name='repo_groups', request_method='GET',
100 route_name='repo_groups', request_method='GET',
100 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
101 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
101 def repo_group_list(self):
102 def repo_group_list(self):
102 c = self.load_default_context()
103 c = self.load_default_context()
103 return self._get_template_context(c)
104 return self._get_template_context(c)
104
105
105 # permission check inside
106 # permission check inside
106 @LoginRequired()
107 @LoginRequired()
107 @NotAnonymous()
108 @NotAnonymous()
108 @view_config(
109 @view_config(
109 route_name='repo_groups_data', request_method='GET',
110 route_name='repo_groups_data', request_method='GET',
110 renderer='json_ext', xhr=True)
111 renderer='json_ext', xhr=True)
111 def repo_group_list_data(self):
112 def repo_group_list_data(self):
112 self.load_default_context()
113 self.load_default_context()
113 column_map = {
114 column_map = {
114 'name': 'group_name_hash',
115 'name': 'group_name_hash',
115 'desc': 'group_description',
116 'desc': 'group_description',
116 'last_change': 'updated_on',
117 'last_change': 'updated_on',
117 'top_level_repos': 'repos_total',
118 'top_level_repos': 'repos_total',
118 'owner': 'user_username',
119 'owner': 'user_username',
119 }
120 }
120 draw, start, limit = self._extract_chunk(self.request)
121 draw, start, limit = self._extract_chunk(self.request)
121 search_q, order_by, order_dir = self._extract_ordering(
122 search_q, order_by, order_dir = self._extract_ordering(
122 self.request, column_map=column_map)
123 self.request, column_map=column_map)
123
124
124 _render = self.request.get_partial_renderer(
125 _render = self.request.get_partial_renderer(
125 'rhodecode:templates/data_table/_dt_elements.mako')
126 'rhodecode:templates/data_table/_dt_elements.mako')
126 c = _render.get_call_context()
127 c = _render.get_call_context()
127
128
128 def quick_menu(repo_group_name):
129 def quick_menu(repo_group_name):
129 return _render('quick_repo_group_menu', repo_group_name)
130 return _render('quick_repo_group_menu', repo_group_name)
130
131
131 def repo_group_lnk(repo_group_name):
132 def repo_group_lnk(repo_group_name):
132 return _render('repo_group_name', repo_group_name)
133 return _render('repo_group_name', repo_group_name)
133
134
134 def last_change(last_change):
135 def last_change(last_change):
135 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
136 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
136 ts = time.time()
137 ts = time.time()
137 utc_offset = (datetime.datetime.fromtimestamp(ts)
138 utc_offset = (datetime.datetime.fromtimestamp(ts)
138 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
139 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
139 last_change = last_change + datetime.timedelta(seconds=utc_offset)
140 last_change = last_change + datetime.timedelta(seconds=utc_offset)
140 return _render("last_change", last_change)
141 return _render("last_change", last_change)
141
142
142 def desc(desc, personal):
143 def desc(desc, personal):
143 return _render(
144 return _render(
144 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
145 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
145
146
146 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
147 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
147 return _render(
148 return _render(
148 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
149 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
149
150
150 def user_profile(username):
151 def user_profile(username):
151 return _render('user_profile', username)
152 return _render('user_profile', username)
152
153
153 _perms = ['group.admin']
154 _perms = ['group.admin']
154 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
155 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
155
156
156 repo_groups_data_total_count = RepoGroup.query()\
157 repo_groups_data_total_count = RepoGroup.query()\
157 .filter(or_(
158 .filter(or_(
158 # generate multiple IN to fix limitation problems
159 # generate multiple IN to fix limitation problems
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 )) \
161 )) \
161 .count()
162 .count()
162
163
163 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 .count()
166 .count()
166
167
167 repo_count = count(Repository.repo_id)
168 repo_count = count(Repository.repo_id)
168 base_q = Session.query(
169 base_q = Session.query(
169 RepoGroup.group_name,
170 RepoGroup.group_name,
170 RepoGroup.group_name_hash,
171 RepoGroup.group_name_hash,
171 RepoGroup.group_description,
172 RepoGroup.group_description,
172 RepoGroup.group_id,
173 RepoGroup.group_id,
173 RepoGroup.personal,
174 RepoGroup.personal,
174 RepoGroup.updated_on,
175 RepoGroup.updated_on,
175 User,
176 User,
176 repo_count.label('repos_count')
177 repo_count.label('repos_count')
177 ) \
178 ) \
178 .filter(or_(
179 .filter(or_(
179 # generate multiple IN to fix limitation problems
180 # generate multiple IN to fix limitation problems
180 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 )) \
182 )) \
182 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
183 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
183 .join(User, User.user_id == RepoGroup.user_id) \
184 .join(User, User.user_id == RepoGroup.user_id) \
184 .group_by(RepoGroup, User)
185 .group_by(RepoGroup, User)
185
186
186 if search_q:
187 if search_q:
187 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 base_q = base_q.filter(or_(
189 base_q = base_q.filter(or_(
189 RepoGroup.group_name.ilike(like_expression),
190 RepoGroup.group_name.ilike(like_expression),
190 ))
191 ))
191
192
192 repo_groups_data_total_filtered_count = base_q.count()
193 repo_groups_data_total_filtered_count = base_q.count()
193 # the inactive isn't really used, but we still make it same as other data grids
194 # the inactive isn't really used, but we still make it same as other data grids
194 # which use inactive (users,user groups)
195 # which use inactive (users,user groups)
195 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196
197
197 sort_defined = False
198 sort_defined = False
198 if order_by == 'group_name':
199 if order_by == 'group_name':
199 sort_col = func.lower(RepoGroup.group_name)
200 sort_col = func.lower(RepoGroup.group_name)
200 sort_defined = True
201 sort_defined = True
201 elif order_by == 'repos_total':
202 elif order_by == 'repos_total':
202 sort_col = repo_count
203 sort_col = repo_count
203 sort_defined = True
204 sort_defined = True
204 elif order_by == 'user_username':
205 elif order_by == 'user_username':
205 sort_col = User.username
206 sort_col = User.username
206 else:
207 else:
207 sort_col = getattr(RepoGroup, order_by, None)
208 sort_col = getattr(RepoGroup, order_by, None)
208
209
209 if sort_defined or sort_col:
210 if sort_defined or sort_col:
210 if order_dir == 'asc':
211 if order_dir == 'asc':
211 sort_col = sort_col.asc()
212 sort_col = sort_col.asc()
212 else:
213 else:
213 sort_col = sort_col.desc()
214 sort_col = sort_col.desc()
214
215
215 base_q = base_q.order_by(sort_col)
216 base_q = base_q.order_by(sort_col)
216 base_q = base_q.offset(start).limit(limit)
217 base_q = base_q.offset(start).limit(limit)
217
218
218 # authenticated access to user groups
219 # authenticated access to user groups
219 auth_repo_group_list = base_q.all()
220 auth_repo_group_list = base_q.all()
220
221
221 repo_groups_data = []
222 repo_groups_data = []
222 for repo_gr in auth_repo_group_list:
223 for repo_gr in auth_repo_group_list:
223 row = {
224 row = {
224 "menu": quick_menu(repo_gr.group_name),
225 "menu": quick_menu(repo_gr.group_name),
225 "name": repo_group_lnk(repo_gr.group_name),
226 "name": repo_group_lnk(repo_gr.group_name),
226
227
227 "last_change": last_change(repo_gr.updated_on),
228 "last_change": last_change(repo_gr.updated_on),
228
229
229 "last_changeset": "",
230 "last_changeset": "",
230 "last_changeset_raw": "",
231 "last_changeset_raw": "",
231
232
232 "desc": desc(repo_gr.group_description, repo_gr.personal),
233 "desc": desc(repo_gr.group_description, repo_gr.personal),
233 "owner": user_profile(repo_gr.User.username),
234 "owner": user_profile(repo_gr.User.username),
234 "top_level_repos": repo_gr.repos_count,
235 "top_level_repos": repo_gr.repos_count,
235 "action": repo_group_actions(
236 "action": repo_group_actions(
236 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
237 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
237
238
238 }
239 }
239
240
240 repo_groups_data.append(row)
241 repo_groups_data.append(row)
241
242
242 data = ({
243 data = ({
243 'draw': draw,
244 'draw': draw,
244 'data': repo_groups_data,
245 'data': repo_groups_data,
245 'recordsTotal': repo_groups_data_total_count,
246 'recordsTotal': repo_groups_data_total_count,
246 'recordsTotalInactive': repo_groups_data_total_inactive_count,
247 'recordsTotalInactive': repo_groups_data_total_inactive_count,
247 'recordsFiltered': repo_groups_data_total_filtered_count,
248 'recordsFiltered': repo_groups_data_total_filtered_count,
248 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
249 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
249 })
250 })
250
251
251 return data
252 return data
252
253
253 @LoginRequired()
254 @LoginRequired()
254 @NotAnonymous()
255 @NotAnonymous()
255 # perm checks inside
256 # perm checks inside
256 @view_config(
257 @view_config(
257 route_name='repo_group_new', request_method='GET',
258 route_name='repo_group_new', request_method='GET',
258 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
259 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
259 def repo_group_new(self):
260 def repo_group_new(self):
260 c = self.load_default_context()
261 c = self.load_default_context()
261
262
262 # perm check for admin, create_group perm or admin of parent_group
263 # perm check for admin, create_group perm or admin of parent_group
263 parent_group_id = safe_int(self.request.GET.get('parent_group'))
264 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 _gr = RepoGroup.get(parent_group_id)
264 if not self._can_create_repo_group(parent_group_id):
266 if not self._can_create_repo_group(parent_group_id):
265 raise HTTPForbidden()
267 raise HTTPForbidden()
266
268
267 self._load_form_data(c)
269 self._load_form_data(c)
268
270
269 defaults = {} # Future proof for default of repo group
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 data = render(
283 data = render(
271 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
284 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
272 self._get_template_context(c), self.request)
285 self._get_template_context(c), self.request)
286
273 html = formencode.htmlfill.render(
287 html = formencode.htmlfill.render(
274 data,
288 data,
275 defaults=defaults,
289 defaults=defaults,
276 encoding="UTF-8",
290 encoding="UTF-8",
277 force_defaults=False
291 force_defaults=False
278 )
292 )
279 return Response(html)
293 return Response(html)
280
294
281 @LoginRequired()
295 @LoginRequired()
282 @NotAnonymous()
296 @NotAnonymous()
283 @CSRFRequired()
297 @CSRFRequired()
284 # perm checks inside
298 # perm checks inside
285 @view_config(
299 @view_config(
286 route_name='repo_group_create', request_method='POST',
300 route_name='repo_group_create', request_method='POST',
287 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
301 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
288 def repo_group_create(self):
302 def repo_group_create(self):
289 c = self.load_default_context()
303 c = self.load_default_context()
290 _ = self.request.translate
304 _ = self.request.translate
291
305
292 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
306 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
293 can_create = self._can_create_repo_group(parent_group_id)
307 can_create = self._can_create_repo_group(parent_group_id)
294
308
295 self._load_form_data(c)
309 self._load_form_data(c)
296 # permissions for can create group based on parent_id are checked
310 # permissions for can create group based on parent_id are checked
297 # here in the Form
311 # here in the Form
298 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
312 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
299 repo_group_form = RepoGroupForm(
313 repo_group_form = RepoGroupForm(
300 self.request.translate, available_groups=available_groups,
314 self.request.translate, available_groups=available_groups,
301 can_create_in_root=can_create)()
315 can_create_in_root=can_create)()
302
316
303 repo_group_name = self.request.POST.get('group_name')
317 repo_group_name = self.request.POST.get('group_name')
304 try:
318 try:
305 owner = self._rhodecode_user
319 owner = self._rhodecode_user
306 form_result = repo_group_form.to_python(dict(self.request.POST))
320 form_result = repo_group_form.to_python(dict(self.request.POST))
307 copy_permissions = form_result.get('group_copy_permissions')
321 copy_permissions = form_result.get('group_copy_permissions')
308 repo_group = RepoGroupModel().create(
322 repo_group = RepoGroupModel().create(
309 group_name=form_result['group_name_full'],
323 group_name=form_result['group_name_full'],
310 group_description=form_result['group_description'],
324 group_description=form_result['group_description'],
311 owner=owner.user_id,
325 owner=owner.user_id,
312 copy_permissions=form_result['group_copy_permissions']
326 copy_permissions=form_result['group_copy_permissions']
313 )
327 )
314 Session().flush()
328 Session().flush()
315
329
316 repo_group_data = repo_group.get_api_data()
330 repo_group_data = repo_group.get_api_data()
317 audit_logger.store_web(
331 audit_logger.store_web(
318 'repo_group.create', action_data={'data': repo_group_data},
332 'repo_group.create', action_data={'data': repo_group_data},
319 user=self._rhodecode_user)
333 user=self._rhodecode_user)
320
334
321 Session().commit()
335 Session().commit()
322
336
323 _new_group_name = form_result['group_name_full']
337 _new_group_name = form_result['group_name_full']
324
338
325 repo_group_url = h.link_to(
339 repo_group_url = h.link_to(
326 _new_group_name,
340 _new_group_name,
327 h.route_path('repo_group_home', repo_group_name=_new_group_name))
341 h.route_path('repo_group_home', repo_group_name=_new_group_name))
328 h.flash(h.literal(_('Created repository group %s')
342 h.flash(h.literal(_('Created repository group %s')
329 % repo_group_url), category='success')
343 % repo_group_url), category='success')
330
344
331 except formencode.Invalid as errors:
345 except formencode.Invalid as errors:
332 data = render(
346 data = render(
333 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
347 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
334 self._get_template_context(c), self.request)
348 self._get_template_context(c), self.request)
335 html = formencode.htmlfill.render(
349 html = formencode.htmlfill.render(
336 data,
350 data,
337 defaults=errors.value,
351 defaults=errors.value,
338 errors=errors.error_dict or {},
352 errors=errors.error_dict or {},
339 prefix_error=False,
353 prefix_error=False,
340 encoding="UTF-8",
354 encoding="UTF-8",
341 force_defaults=False
355 force_defaults=False
342 )
356 )
343 return Response(html)
357 return Response(html)
344 except Exception:
358 except Exception:
345 log.exception("Exception during creation of repository group")
359 log.exception("Exception during creation of repository group")
346 h.flash(_('Error occurred during creation of repository group %s')
360 h.flash(_('Error occurred during creation of repository group %s')
347 % repo_group_name, category='error')
361 % repo_group_name, category='error')
348 raise HTTPFound(h.route_path('home'))
362 raise HTTPFound(h.route_path('home'))
349
363
350 affected_user_ids = [self._rhodecode_user.user_id]
364 affected_user_ids = [self._rhodecode_user.user_id]
351 if copy_permissions:
365 if copy_permissions:
352 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
366 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
353 copy_perms = [perm['user_id'] for perm in user_group_perms]
367 copy_perms = [perm['user_id'] for perm in user_group_perms]
354 # also include those newly created by copy
368 # also include those newly created by copy
355 affected_user_ids.extend(copy_perms)
369 affected_user_ids.extend(copy_perms)
356 PermissionModel().trigger_permission_flush(affected_user_ids)
370 PermissionModel().trigger_permission_flush(affected_user_ids)
357
371
358 raise HTTPFound(
372 raise HTTPFound(
359 h.route_path('repo_group_home',
373 h.route_path('repo_group_home',
360 repo_group_name=form_result['group_name_full']))
374 repo_group_name=form_result['group_name_full']))
@@ -1,266 +1,266 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.celerylib.utils import get_task_id
32 from rhodecode.lib.celerylib.utils import get_task_id
33
33
34 from rhodecode.lib.auth import (
34 from rhodecode.lib.auth import (
35 LoginRequired, CSRFRequired, NotAnonymous,
35 LoginRequired, CSRFRequired, NotAnonymous,
36 HasPermissionAny, HasRepoGroupPermissionAny)
36 HasPermissionAny, HasRepoGroupPermissionAny)
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils2 import safe_int, safe_unicode
39 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 from rhodecode.model.forms import RepoForm
40 from rhodecode.model.forms import RepoForm
41 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.db import (
45 from rhodecode.model.db import (
46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class AdminReposView(BaseAppView, DataGridAppView):
51 class AdminReposView(BaseAppView, DataGridAppView):
52
52
53 def load_default_context(self):
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
55
55
56 return c
56 return c
57
57
58 def _load_form_data(self, c):
58 def _load_form_data(self, c):
59 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 perm_set=['group.write', 'group.admin'])
60 perm_set=['group.write', 'group.admin'])
61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
64
64
65 @LoginRequired()
65 @LoginRequired()
66 @NotAnonymous()
66 @NotAnonymous()
67 # perms check inside
67 # perms check inside
68 @view_config(
68 @view_config(
69 route_name='repos', request_method='GET',
69 route_name='repos', request_method='GET',
70 renderer='rhodecode:templates/admin/repos/repos.mako')
70 renderer='rhodecode:templates/admin/repos/repos.mako')
71 def repository_list(self):
71 def repository_list(self):
72 c = self.load_default_context()
72 c = self.load_default_context()
73 return self._get_template_context(c)
73 return self._get_template_context(c)
74
74
75 @LoginRequired()
75 @LoginRequired()
76 @NotAnonymous()
76 @NotAnonymous()
77 # perms check inside
77 # perms check inside
78 @view_config(
78 @view_config(
79 route_name='repos_data', request_method='GET',
79 route_name='repos_data', request_method='GET',
80 renderer='json_ext', xhr=True)
80 renderer='json_ext', xhr=True)
81 def repository_list_data(self):
81 def repository_list_data(self):
82 self.load_default_context()
82 self.load_default_context()
83 column_map = {
83 column_map = {
84 'name': 'repo_name',
84 'name': 'repo_name',
85 'desc': 'description',
85 'desc': 'description',
86 'last_change': 'updated_on',
86 'last_change': 'updated_on',
87 'owner': 'user_username',
87 'owner': 'user_username',
88 }
88 }
89 draw, start, limit = self._extract_chunk(self.request)
89 draw, start, limit = self._extract_chunk(self.request)
90 search_q, order_by, order_dir = self._extract_ordering(
90 search_q, order_by, order_dir = self._extract_ordering(
91 self.request, column_map=column_map)
91 self.request, column_map=column_map)
92
92
93 _perms = ['repository.admin']
93 _perms = ['repository.admin']
94 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
94 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
95
95
96 repos_data_total_count = Repository.query() \
96 repos_data_total_count = Repository.query() \
97 .filter(or_(
97 .filter(or_(
98 # generate multiple IN to fix limitation problems
98 # generate multiple IN to fix limitation problems
99 *in_filter_generator(Repository.repo_id, allowed_ids))
99 *in_filter_generator(Repository.repo_id, allowed_ids))
100 ) \
100 ) \
101 .count()
101 .count()
102
102
103 base_q = Session.query(
103 base_q = Session.query(
104 Repository.repo_id,
104 Repository.repo_id,
105 Repository.repo_name,
105 Repository.repo_name,
106 Repository.description,
106 Repository.description,
107 Repository.repo_type,
107 Repository.repo_type,
108 Repository.repo_state,
108 Repository.repo_state,
109 Repository.private,
109 Repository.private,
110 Repository.archived,
110 Repository.archived,
111 Repository.fork,
111 Repository.fork,
112 Repository.updated_on,
112 Repository.updated_on,
113 Repository._changeset_cache,
113 Repository._changeset_cache,
114 User,
114 User,
115 ) \
115 ) \
116 .filter(or_(
116 .filter(or_(
117 # generate multiple IN to fix limitation problems
117 # generate multiple IN to fix limitation problems
118 *in_filter_generator(Repository.repo_id, allowed_ids))
118 *in_filter_generator(Repository.repo_id, allowed_ids))
119 ) \
119 ) \
120 .join(User, User.user_id == Repository.user_id) \
120 .join(User, User.user_id == Repository.user_id) \
121 .group_by(Repository, User)
121 .group_by(Repository, User)
122
122
123 if search_q:
123 if search_q:
124 like_expression = u'%{}%'.format(safe_unicode(search_q))
124 like_expression = u'%{}%'.format(safe_unicode(search_q))
125 base_q = base_q.filter(or_(
125 base_q = base_q.filter(or_(
126 Repository.repo_name.ilike(like_expression),
126 Repository.repo_name.ilike(like_expression),
127 ))
127 ))
128
128
129 repos_data_total_filtered_count = base_q.count()
129 repos_data_total_filtered_count = base_q.count()
130
130
131 sort_defined = False
131 sort_defined = False
132 if order_by == 'repo_name':
132 if order_by == 'repo_name':
133 sort_col = func.lower(Repository.repo_name)
133 sort_col = func.lower(Repository.repo_name)
134 sort_defined = True
134 sort_defined = True
135 elif order_by == 'user_username':
135 elif order_by == 'user_username':
136 sort_col = User.username
136 sort_col = User.username
137 else:
137 else:
138 sort_col = getattr(Repository, order_by, None)
138 sort_col = getattr(Repository, order_by, None)
139
139
140 if sort_defined or sort_col:
140 if sort_defined or sort_col:
141 if order_dir == 'asc':
141 if order_dir == 'asc':
142 sort_col = sort_col.asc()
142 sort_col = sort_col.asc()
143 else:
143 else:
144 sort_col = sort_col.desc()
144 sort_col = sort_col.desc()
145
145
146 base_q = base_q.order_by(sort_col)
146 base_q = base_q.order_by(sort_col)
147 base_q = base_q.offset(start).limit(limit)
147 base_q = base_q.offset(start).limit(limit)
148
148
149 repos_list = base_q.all()
149 repos_list = base_q.all()
150
150
151 repos_data = RepoModel().get_repos_as_dict(
151 repos_data = RepoModel().get_repos_as_dict(
152 repo_list=repos_list, admin=True, super_user_actions=True)
152 repo_list=repos_list, admin=True, super_user_actions=True)
153
153
154 data = ({
154 data = ({
155 'draw': draw,
155 'draw': draw,
156 'data': repos_data,
156 'data': repos_data,
157 'recordsTotal': repos_data_total_count,
157 'recordsTotal': repos_data_total_count,
158 'recordsFiltered': repos_data_total_filtered_count,
158 'recordsFiltered': repos_data_total_filtered_count,
159 })
159 })
160 return data
160 return data
161
161
162 @LoginRequired()
162 @LoginRequired()
163 @NotAnonymous()
163 @NotAnonymous()
164 # perms check inside
164 # perms check inside
165 @view_config(
165 @view_config(
166 route_name='repo_new', request_method='GET',
166 route_name='repo_new', request_method='GET',
167 renderer='rhodecode:templates/admin/repos/repo_add.mako')
167 renderer='rhodecode:templates/admin/repos/repo_add.mako')
168 def repository_new(self):
168 def repository_new(self):
169 c = self.load_default_context()
169 c = self.load_default_context()
170
170
171 new_repo = self.request.GET.get('repo', '')
171 new_repo = self.request.GET.get('repo', '')
172 parent_group = safe_int(self.request.GET.get('parent_group'))
172 parent_group_id = safe_int(self.request.GET.get('parent_group'))
173 _gr = RepoGroup.get(parent_group)
173 _gr = RepoGroup.get(parent_group_id)
174
174
175 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
175 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
176 # you're not super admin nor have global create permissions,
176 # you're not super admin nor have global create permissions,
177 # but maybe you have at least write permission to a parent group ?
177 # but maybe you have at least write permission to a parent group ?
178
178
179 gr_name = _gr.group_name if _gr else None
179 gr_name = _gr.group_name if _gr else None
180 # create repositories with write permission on group is set to true
180 # create repositories with write permission on group is set to true
181 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
181 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
182 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
182 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
183 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
183 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
184 if not (group_admin or (group_write and create_on_write)):
184 if not (group_admin or (group_write and create_on_write)):
185 raise HTTPForbidden()
185 raise HTTPForbidden()
186
186
187 self._load_form_data(c)
187 self._load_form_data(c)
188 c.new_repo = repo_name_slug(new_repo)
188 c.new_repo = repo_name_slug(new_repo)
189
189
190 # apply the defaults from defaults page
190 # apply the defaults from defaults page
191 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
191 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
192 # set checkbox to autochecked
192 # set checkbox to autochecked
193 defaults['repo_copy_permissions'] = True
193 defaults['repo_copy_permissions'] = True
194
194
195 parent_group_choice = '-1'
195 parent_group_choice = '-1'
196 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
196 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
197 parent_group_choice = self._rhodecode_user.personal_repo_group
197 parent_group_choice = self._rhodecode_user.personal_repo_group
198
198
199 if parent_group and _gr:
199 if parent_group_id and _gr:
200 if parent_group in [x[0] for x in c.repo_groups]:
200 if parent_group_id in [x[0] for x in c.repo_groups]:
201 parent_group_choice = safe_unicode(parent_group)
201 parent_group_choice = safe_unicode(parent_group_id)
202
202
203 defaults.update({'repo_group': parent_group_choice})
203 defaults.update({'repo_group': parent_group_choice})
204
204
205 data = render('rhodecode:templates/admin/repos/repo_add.mako',
205 data = render('rhodecode:templates/admin/repos/repo_add.mako',
206 self._get_template_context(c), self.request)
206 self._get_template_context(c), self.request)
207 html = formencode.htmlfill.render(
207 html = formencode.htmlfill.render(
208 data,
208 data,
209 defaults=defaults,
209 defaults=defaults,
210 encoding="UTF-8",
210 encoding="UTF-8",
211 force_defaults=False
211 force_defaults=False
212 )
212 )
213 return Response(html)
213 return Response(html)
214
214
215 @LoginRequired()
215 @LoginRequired()
216 @NotAnonymous()
216 @NotAnonymous()
217 @CSRFRequired()
217 @CSRFRequired()
218 # perms check inside
218 # perms check inside
219 @view_config(
219 @view_config(
220 route_name='repo_create', request_method='POST',
220 route_name='repo_create', request_method='POST',
221 renderer='rhodecode:templates/admin/repos/repos.mako')
221 renderer='rhodecode:templates/admin/repos/repos.mako')
222 def repository_create(self):
222 def repository_create(self):
223 c = self.load_default_context()
223 c = self.load_default_context()
224
224
225 form_result = {}
225 form_result = {}
226 self._load_form_data(c)
226 self._load_form_data(c)
227
227
228 try:
228 try:
229 # CanWriteToGroup validators checks permissions of this POST
229 # CanWriteToGroup validators checks permissions of this POST
230 form = RepoForm(
230 form = RepoForm(
231 self.request.translate, repo_groups=c.repo_groups_choices)()
231 self.request.translate, repo_groups=c.repo_groups_choices)()
232 form_result = form.to_python(dict(self.request.POST))
232 form_result = form.to_python(dict(self.request.POST))
233 copy_permissions = form_result.get('repo_copy_permissions')
233 copy_permissions = form_result.get('repo_copy_permissions')
234 # create is done sometimes async on celery, db transaction
234 # create is done sometimes async on celery, db transaction
235 # management is handled there.
235 # management is handled there.
236 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
236 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
237 task_id = get_task_id(task)
237 task_id = get_task_id(task)
238 except formencode.Invalid as errors:
238 except formencode.Invalid as errors:
239 data = render('rhodecode:templates/admin/repos/repo_add.mako',
239 data = render('rhodecode:templates/admin/repos/repo_add.mako',
240 self._get_template_context(c), self.request)
240 self._get_template_context(c), self.request)
241 html = formencode.htmlfill.render(
241 html = formencode.htmlfill.render(
242 data,
242 data,
243 defaults=errors.value,
243 defaults=errors.value,
244 errors=errors.error_dict or {},
244 errors=errors.error_dict or {},
245 prefix_error=False,
245 prefix_error=False,
246 encoding="UTF-8",
246 encoding="UTF-8",
247 force_defaults=False
247 force_defaults=False
248 )
248 )
249 return Response(html)
249 return Response(html)
250
250
251 except Exception as e:
251 except Exception as e:
252 msg = self._log_creation_exception(e, form_result.get('repo_name'))
252 msg = self._log_creation_exception(e, form_result.get('repo_name'))
253 h.flash(msg, category='error')
253 h.flash(msg, category='error')
254 raise HTTPFound(h.route_path('home'))
254 raise HTTPFound(h.route_path('home'))
255
255
256 repo_name = form_result.get('repo_name_full')
256 repo_name = form_result.get('repo_name_full')
257
257
258 affected_user_ids = [self._rhodecode_user.user_id]
258 affected_user_ids = [self._rhodecode_user.user_id]
259 if copy_permissions:
259 if copy_permissions:
260 # permission flush is done in repo creating
260 # permission flush is done in repo creating
261 pass
261 pass
262 PermissionModel().trigger_permission_flush(affected_user_ids)
262 PermissionModel().trigger_permission_flush(affected_user_ids)
263
263
264 raise HTTPFound(
264 raise HTTPFound(
265 h.route_path('repo_creating', repo_name=repo_name,
265 h.route_path('repo_creating', repo_name=repo_name,
266 _query=dict(task_id=task_id)))
266 _query=dict(task_id=task_id)))
@@ -1,782 +1,783 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import formencode.htmlfill
27 import formencode.htmlfill
28
28
29 import rhodecode
29 import rhodecode
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 from rhodecode.apps._base import BaseAppView
35 from rhodecode.apps._base import BaseAppView
36 from rhodecode.apps._base.navigation import navigation_list
36 from rhodecode.apps._base.navigation import navigation_list
37 from rhodecode.apps.svn_support.config_keys import generate_config
37 from rhodecode.apps.svn_support.config_keys import generate_config
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils import repo2db_mapper
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 from rhodecode.lib.index import searcher_from_config
44 from rhodecode.lib.index import searcher_from_config
45
45
46 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.db import RhodeCodeUi, Repository
47 from rhodecode.model.forms import (ApplicationSettingsForm,
47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 LabsSettingsForm, IssueTrackerPatternsForm)
49 LabsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.repo_group import RepoGroupModel
51 from rhodecode.model.repo_group import RepoGroupModel
51
52
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
56 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
58 SettingsModel)
58
59
59
60
60 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
61
62
62
63
63 class AdminSettingsView(BaseAppView):
64 class AdminSettingsView(BaseAppView):
64
65
65 def load_default_context(self):
66 def load_default_context(self):
66 c = self._get_local_tmpl_context()
67 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
68 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
70 c.navlist = navigation_list(self.request)
70
71
71 return c
72 return c
72
73
73 @classmethod
74 @classmethod
74 def _get_ui_settings(cls):
75 def _get_ui_settings(cls):
75 ret = RhodeCodeUi.query().all()
76 ret = RhodeCodeUi.query().all()
76
77
77 if not ret:
78 if not ret:
78 raise Exception('Could not get application ui settings !')
79 raise Exception('Could not get application ui settings !')
79 settings = {}
80 settings = {}
80 for each in ret:
81 for each in ret:
81 k = each.ui_key
82 k = each.ui_key
82 v = each.ui_value
83 v = each.ui_value
83 if k == '/':
84 if k == '/':
84 k = 'root_path'
85 k = 'root_path'
85
86
86 if k in ['push_ssl', 'publish', 'enabled']:
87 if k in ['push_ssl', 'publish', 'enabled']:
87 v = str2bool(v)
88 v = str2bool(v)
88
89
89 if k.find('.') != -1:
90 if k.find('.') != -1:
90 k = k.replace('.', '_')
91 k = k.replace('.', '_')
91
92
92 if each.ui_section in ['hooks', 'extensions']:
93 if each.ui_section in ['hooks', 'extensions']:
93 v = each.ui_active
94 v = each.ui_active
94
95
95 settings[each.ui_section + '_' + k] = v
96 settings[each.ui_section + '_' + k] = v
96 return settings
97 return settings
97
98
98 @classmethod
99 @classmethod
99 def _form_defaults(cls):
100 def _form_defaults(cls):
100 defaults = SettingsModel().get_all_settings()
101 defaults = SettingsModel().get_all_settings()
101 defaults.update(cls._get_ui_settings())
102 defaults.update(cls._get_ui_settings())
102
103
103 defaults.update({
104 defaults.update({
104 'new_svn_branch': '',
105 'new_svn_branch': '',
105 'new_svn_tag': '',
106 'new_svn_tag': '',
106 })
107 })
107 return defaults
108 return defaults
108
109
109 @LoginRequired()
110 @LoginRequired()
110 @HasPermissionAllDecorator('hg.admin')
111 @HasPermissionAllDecorator('hg.admin')
111 @view_config(
112 @view_config(
112 route_name='admin_settings_vcs', request_method='GET',
113 route_name='admin_settings_vcs', request_method='GET',
113 renderer='rhodecode:templates/admin/settings/settings.mako')
114 renderer='rhodecode:templates/admin/settings/settings.mako')
114 def settings_vcs(self):
115 def settings_vcs(self):
115 c = self.load_default_context()
116 c = self.load_default_context()
116 c.active = 'vcs'
117 c.active = 'vcs'
117 model = VcsSettingsModel()
118 model = VcsSettingsModel()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120
121
121 settings = self.request.registry.settings
122 settings = self.request.registry.settings
122 c.svn_proxy_generate_config = settings[generate_config]
123 c.svn_proxy_generate_config = settings[generate_config]
123
124
124 defaults = self._form_defaults()
125 defaults = self._form_defaults()
125
126
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127
128
128 data = render('rhodecode:templates/admin/settings/settings.mako',
129 data = render('rhodecode:templates/admin/settings/settings.mako',
129 self._get_template_context(c), self.request)
130 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
131 html = formencode.htmlfill.render(
131 data,
132 data,
132 defaults=defaults,
133 defaults=defaults,
133 encoding="UTF-8",
134 encoding="UTF-8",
134 force_defaults=False
135 force_defaults=False
135 )
136 )
136 return Response(html)
137 return Response(html)
137
138
138 @LoginRequired()
139 @LoginRequired()
139 @HasPermissionAllDecorator('hg.admin')
140 @HasPermissionAllDecorator('hg.admin')
140 @CSRFRequired()
141 @CSRFRequired()
141 @view_config(
142 @view_config(
142 route_name='admin_settings_vcs_update', request_method='POST',
143 route_name='admin_settings_vcs_update', request_method='POST',
143 renderer='rhodecode:templates/admin/settings/settings.mako')
144 renderer='rhodecode:templates/admin/settings/settings.mako')
144 def settings_vcs_update(self):
145 def settings_vcs_update(self):
145 _ = self.request.translate
146 _ = self.request.translate
146 c = self.load_default_context()
147 c = self.load_default_context()
147 c.active = 'vcs'
148 c.active = 'vcs'
148
149
149 model = VcsSettingsModel()
150 model = VcsSettingsModel()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152
153
153 settings = self.request.registry.settings
154 settings = self.request.registry.settings
154 c.svn_proxy_generate_config = settings[generate_config]
155 c.svn_proxy_generate_config = settings[generate_config]
155
156
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
157 application_form = ApplicationUiSettingsForm(self.request.translate)()
157
158
158 try:
159 try:
159 form_result = application_form.to_python(dict(self.request.POST))
160 form_result = application_form.to_python(dict(self.request.POST))
160 except formencode.Invalid as errors:
161 except formencode.Invalid as errors:
161 h.flash(
162 h.flash(
162 _("Some form inputs contain invalid data."),
163 _("Some form inputs contain invalid data."),
163 category='error')
164 category='error')
164 data = render('rhodecode:templates/admin/settings/settings.mako',
165 data = render('rhodecode:templates/admin/settings/settings.mako',
165 self._get_template_context(c), self.request)
166 self._get_template_context(c), self.request)
166 html = formencode.htmlfill.render(
167 html = formencode.htmlfill.render(
167 data,
168 data,
168 defaults=errors.value,
169 defaults=errors.value,
169 errors=errors.error_dict or {},
170 errors=errors.error_dict or {},
170 prefix_error=False,
171 prefix_error=False,
171 encoding="UTF-8",
172 encoding="UTF-8",
172 force_defaults=False
173 force_defaults=False
173 )
174 )
174 return Response(html)
175 return Response(html)
175
176
176 try:
177 try:
177 if c.visual.allow_repo_location_change:
178 if c.visual.allow_repo_location_change:
178 model.update_global_path_setting(form_result['paths_root_path'])
179 model.update_global_path_setting(form_result['paths_root_path'])
179
180
180 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 model.update_global_hook_settings(form_result)
182 model.update_global_hook_settings(form_result)
182
183
183 model.create_or_update_global_svn_settings(form_result)
184 model.create_or_update_global_svn_settings(form_result)
184 model.create_or_update_global_hg_settings(form_result)
185 model.create_or_update_global_hg_settings(form_result)
185 model.create_or_update_global_git_settings(form_result)
186 model.create_or_update_global_git_settings(form_result)
186 model.create_or_update_global_pr_settings(form_result)
187 model.create_or_update_global_pr_settings(form_result)
187 except Exception:
188 except Exception:
188 log.exception("Exception while updating settings")
189 log.exception("Exception while updating settings")
189 h.flash(_('Error occurred during updating '
190 h.flash(_('Error occurred during updating '
190 'application settings'), category='error')
191 'application settings'), category='error')
191 else:
192 else:
192 Session().commit()
193 Session().commit()
193 h.flash(_('Updated VCS settings'), category='success')
194 h.flash(_('Updated VCS settings'), category='success')
194 raise HTTPFound(h.route_path('admin_settings_vcs'))
195 raise HTTPFound(h.route_path('admin_settings_vcs'))
195
196
196 data = render('rhodecode:templates/admin/settings/settings.mako',
197 data = render('rhodecode:templates/admin/settings/settings.mako',
197 self._get_template_context(c), self.request)
198 self._get_template_context(c), self.request)
198 html = formencode.htmlfill.render(
199 html = formencode.htmlfill.render(
199 data,
200 data,
200 defaults=self._form_defaults(),
201 defaults=self._form_defaults(),
201 encoding="UTF-8",
202 encoding="UTF-8",
202 force_defaults=False
203 force_defaults=False
203 )
204 )
204 return Response(html)
205 return Response(html)
205
206
206 @LoginRequired()
207 @LoginRequired()
207 @HasPermissionAllDecorator('hg.admin')
208 @HasPermissionAllDecorator('hg.admin')
208 @CSRFRequired()
209 @CSRFRequired()
209 @view_config(
210 @view_config(
210 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 renderer='json_ext', xhr=True)
212 renderer='json_ext', xhr=True)
212 def settings_vcs_delete_svn_pattern(self):
213 def settings_vcs_delete_svn_pattern(self):
213 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 model = VcsSettingsModel()
215 model = VcsSettingsModel()
215 try:
216 try:
216 model.delete_global_svn_pattern(delete_pattern_id)
217 model.delete_global_svn_pattern(delete_pattern_id)
217 except SettingNotFound:
218 except SettingNotFound:
218 log.exception(
219 log.exception(
219 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 raise HTTPNotFound()
221 raise HTTPNotFound()
221
222
222 Session().commit()
223 Session().commit()
223 return True
224 return True
224
225
225 @LoginRequired()
226 @LoginRequired()
226 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
227 @view_config(
228 @view_config(
228 route_name='admin_settings_mapping', request_method='GET',
229 route_name='admin_settings_mapping', request_method='GET',
229 renderer='rhodecode:templates/admin/settings/settings.mako')
230 renderer='rhodecode:templates/admin/settings/settings.mako')
230 def settings_mapping(self):
231 def settings_mapping(self):
231 c = self.load_default_context()
232 c = self.load_default_context()
232 c.active = 'mapping'
233 c.active = 'mapping'
233
234
234 data = render('rhodecode:templates/admin/settings/settings.mako',
235 data = render('rhodecode:templates/admin/settings/settings.mako',
235 self._get_template_context(c), self.request)
236 self._get_template_context(c), self.request)
236 html = formencode.htmlfill.render(
237 html = formencode.htmlfill.render(
237 data,
238 data,
238 defaults=self._form_defaults(),
239 defaults=self._form_defaults(),
239 encoding="UTF-8",
240 encoding="UTF-8",
240 force_defaults=False
241 force_defaults=False
241 )
242 )
242 return Response(html)
243 return Response(html)
243
244
244 @LoginRequired()
245 @LoginRequired()
245 @HasPermissionAllDecorator('hg.admin')
246 @HasPermissionAllDecorator('hg.admin')
246 @CSRFRequired()
247 @CSRFRequired()
247 @view_config(
248 @view_config(
248 route_name='admin_settings_mapping_update', request_method='POST',
249 route_name='admin_settings_mapping_update', request_method='POST',
249 renderer='rhodecode:templates/admin/settings/settings.mako')
250 renderer='rhodecode:templates/admin/settings/settings.mako')
250 def settings_mapping_update(self):
251 def settings_mapping_update(self):
251 _ = self.request.translate
252 _ = self.request.translate
252 c = self.load_default_context()
253 c = self.load_default_context()
253 c.active = 'mapping'
254 c.active = 'mapping'
254 rm_obsolete = self.request.POST.get('destroy', False)
255 rm_obsolete = self.request.POST.get('destroy', False)
255 invalidate_cache = self.request.POST.get('invalidate', False)
256 invalidate_cache = self.request.POST.get('invalidate', False)
256 log.debug(
257 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
257 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
258
258
259 if invalidate_cache:
259 if invalidate_cache:
260 log.debug('invalidating all repositories cache')
260 log.debug('invalidating all repositories cache')
261 for repo in Repository.get_all():
261 for repo in Repository.get_all():
262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
262 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263
263
264 filesystem_repos = ScmModel().repo_scan()
264 filesystem_repos = ScmModel().repo_scan()
265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
265 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 PermissionModel().trigger_permission_flush()
267
266 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
268 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 h.flash(_('Repositories successfully '
269 h.flash(_('Repositories successfully '
268 'rescanned added: %s ; removed: %s') %
270 'rescanned added: %s ; removed: %s') %
269 (_repr(added), _repr(removed)),
271 (_repr(added), _repr(removed)),
270 category='success')
272 category='success')
271 raise HTTPFound(h.route_path('admin_settings_mapping'))
273 raise HTTPFound(h.route_path('admin_settings_mapping'))
272
274
273 @LoginRequired()
275 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
276 @HasPermissionAllDecorator('hg.admin')
275 @view_config(
277 @view_config(
276 route_name='admin_settings', request_method='GET',
278 route_name='admin_settings', request_method='GET',
277 renderer='rhodecode:templates/admin/settings/settings.mako')
279 renderer='rhodecode:templates/admin/settings/settings.mako')
278 @view_config(
280 @view_config(
279 route_name='admin_settings_global', request_method='GET',
281 route_name='admin_settings_global', request_method='GET',
280 renderer='rhodecode:templates/admin/settings/settings.mako')
282 renderer='rhodecode:templates/admin/settings/settings.mako')
281 def settings_global(self):
283 def settings_global(self):
282 c = self.load_default_context()
284 c = self.load_default_context()
283 c.active = 'global'
285 c.active = 'global'
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
286 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 .get_personal_group_name_pattern()
287 .get_personal_group_name_pattern()
286
288
287 data = render('rhodecode:templates/admin/settings/settings.mako',
289 data = render('rhodecode:templates/admin/settings/settings.mako',
288 self._get_template_context(c), self.request)
290 self._get_template_context(c), self.request)
289 html = formencode.htmlfill.render(
291 html = formencode.htmlfill.render(
290 data,
292 data,
291 defaults=self._form_defaults(),
293 defaults=self._form_defaults(),
292 encoding="UTF-8",
294 encoding="UTF-8",
293 force_defaults=False
295 force_defaults=False
294 )
296 )
295 return Response(html)
297 return Response(html)
296
298
297 @LoginRequired()
299 @LoginRequired()
298 @HasPermissionAllDecorator('hg.admin')
300 @HasPermissionAllDecorator('hg.admin')
299 @CSRFRequired()
301 @CSRFRequired()
300 @view_config(
302 @view_config(
301 route_name='admin_settings_update', request_method='POST',
303 route_name='admin_settings_update', request_method='POST',
302 renderer='rhodecode:templates/admin/settings/settings.mako')
304 renderer='rhodecode:templates/admin/settings/settings.mako')
303 @view_config(
305 @view_config(
304 route_name='admin_settings_global_update', request_method='POST',
306 route_name='admin_settings_global_update', request_method='POST',
305 renderer='rhodecode:templates/admin/settings/settings.mako')
307 renderer='rhodecode:templates/admin/settings/settings.mako')
306 def settings_global_update(self):
308 def settings_global_update(self):
307 _ = self.request.translate
309 _ = self.request.translate
308 c = self.load_default_context()
310 c = self.load_default_context()
309 c.active = 'global'
311 c.active = 'global'
310 c.personal_repo_group_default_pattern = RepoGroupModel()\
312 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 .get_personal_group_name_pattern()
313 .get_personal_group_name_pattern()
312 application_form = ApplicationSettingsForm(self.request.translate)()
314 application_form = ApplicationSettingsForm(self.request.translate)()
313 try:
315 try:
314 form_result = application_form.to_python(dict(self.request.POST))
316 form_result = application_form.to_python(dict(self.request.POST))
315 except formencode.Invalid as errors:
317 except formencode.Invalid as errors:
316 h.flash(
318 h.flash(
317 _("Some form inputs contain invalid data."),
319 _("Some form inputs contain invalid data."),
318 category='error')
320 category='error')
319 data = render('rhodecode:templates/admin/settings/settings.mako',
321 data = render('rhodecode:templates/admin/settings/settings.mako',
320 self._get_template_context(c), self.request)
322 self._get_template_context(c), self.request)
321 html = formencode.htmlfill.render(
323 html = formencode.htmlfill.render(
322 data,
324 data,
323 defaults=errors.value,
325 defaults=errors.value,
324 errors=errors.error_dict or {},
326 errors=errors.error_dict or {},
325 prefix_error=False,
327 prefix_error=False,
326 encoding="UTF-8",
328 encoding="UTF-8",
327 force_defaults=False
329 force_defaults=False
328 )
330 )
329 return Response(html)
331 return Response(html)
330
332
331 settings = [
333 settings = [
332 ('title', 'rhodecode_title', 'unicode'),
334 ('title', 'rhodecode_title', 'unicode'),
333 ('realm', 'rhodecode_realm', 'unicode'),
335 ('realm', 'rhodecode_realm', 'unicode'),
334 ('pre_code', 'rhodecode_pre_code', 'unicode'),
336 ('pre_code', 'rhodecode_pre_code', 'unicode'),
335 ('post_code', 'rhodecode_post_code', 'unicode'),
337 ('post_code', 'rhodecode_post_code', 'unicode'),
336 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
338 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
337 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
339 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
338 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
340 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
339 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
341 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
340 ]
342 ]
341 try:
343 try:
342 for setting, form_key, type_ in settings:
344 for setting, form_key, type_ in settings:
343 sett = SettingsModel().create_or_update_setting(
345 sett = SettingsModel().create_or_update_setting(
344 setting, form_result[form_key], type_)
346 setting, form_result[form_key], type_)
345 Session().add(sett)
347 Session().add(sett)
346
348
347 Session().commit()
349 Session().commit()
348 SettingsModel().invalidate_settings_cache()
350 SettingsModel().invalidate_settings_cache()
349 h.flash(_('Updated application settings'), category='success')
351 h.flash(_('Updated application settings'), category='success')
350 except Exception:
352 except Exception:
351 log.exception("Exception while updating application settings")
353 log.exception("Exception while updating application settings")
352 h.flash(
354 h.flash(
353 _('Error occurred during updating application settings'),
355 _('Error occurred during updating application settings'),
354 category='error')
356 category='error')
355
357
356 raise HTTPFound(h.route_path('admin_settings_global'))
358 raise HTTPFound(h.route_path('admin_settings_global'))
357
359
358 @LoginRequired()
360 @LoginRequired()
359 @HasPermissionAllDecorator('hg.admin')
361 @HasPermissionAllDecorator('hg.admin')
360 @view_config(
362 @view_config(
361 route_name='admin_settings_visual', request_method='GET',
363 route_name='admin_settings_visual', request_method='GET',
362 renderer='rhodecode:templates/admin/settings/settings.mako')
364 renderer='rhodecode:templates/admin/settings/settings.mako')
363 def settings_visual(self):
365 def settings_visual(self):
364 c = self.load_default_context()
366 c = self.load_default_context()
365 c.active = 'visual'
367 c.active = 'visual'
366
368
367 data = render('rhodecode:templates/admin/settings/settings.mako',
369 data = render('rhodecode:templates/admin/settings/settings.mako',
368 self._get_template_context(c), self.request)
370 self._get_template_context(c), self.request)
369 html = formencode.htmlfill.render(
371 html = formencode.htmlfill.render(
370 data,
372 data,
371 defaults=self._form_defaults(),
373 defaults=self._form_defaults(),
372 encoding="UTF-8",
374 encoding="UTF-8",
373 force_defaults=False
375 force_defaults=False
374 )
376 )
375 return Response(html)
377 return Response(html)
376
378
377 @LoginRequired()
379 @LoginRequired()
378 @HasPermissionAllDecorator('hg.admin')
380 @HasPermissionAllDecorator('hg.admin')
379 @CSRFRequired()
381 @CSRFRequired()
380 @view_config(
382 @view_config(
381 route_name='admin_settings_visual_update', request_method='POST',
383 route_name='admin_settings_visual_update', request_method='POST',
382 renderer='rhodecode:templates/admin/settings/settings.mako')
384 renderer='rhodecode:templates/admin/settings/settings.mako')
383 def settings_visual_update(self):
385 def settings_visual_update(self):
384 _ = self.request.translate
386 _ = self.request.translate
385 c = self.load_default_context()
387 c = self.load_default_context()
386 c.active = 'visual'
388 c.active = 'visual'
387 application_form = ApplicationVisualisationForm(self.request.translate)()
389 application_form = ApplicationVisualisationForm(self.request.translate)()
388 try:
390 try:
389 form_result = application_form.to_python(dict(self.request.POST))
391 form_result = application_form.to_python(dict(self.request.POST))
390 except formencode.Invalid as errors:
392 except formencode.Invalid as errors:
391 h.flash(
393 h.flash(
392 _("Some form inputs contain invalid data."),
394 _("Some form inputs contain invalid data."),
393 category='error')
395 category='error')
394 data = render('rhodecode:templates/admin/settings/settings.mako',
396 data = render('rhodecode:templates/admin/settings/settings.mako',
395 self._get_template_context(c), self.request)
397 self._get_template_context(c), self.request)
396 html = formencode.htmlfill.render(
398 html = formencode.htmlfill.render(
397 data,
399 data,
398 defaults=errors.value,
400 defaults=errors.value,
399 errors=errors.error_dict or {},
401 errors=errors.error_dict or {},
400 prefix_error=False,
402 prefix_error=False,
401 encoding="UTF-8",
403 encoding="UTF-8",
402 force_defaults=False
404 force_defaults=False
403 )
405 )
404 return Response(html)
406 return Response(html)
405
407
406 try:
408 try:
407 settings = [
409 settings = [
408 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
410 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
409 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
411 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
410 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
412 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
411 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
413 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
412 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
414 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
413 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
415 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
414 ('show_version', 'rhodecode_show_version', 'bool'),
416 ('show_version', 'rhodecode_show_version', 'bool'),
415 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
417 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
416 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
418 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
417 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
419 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
418 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
420 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
419 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
421 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
420 ('support_url', 'rhodecode_support_url', 'unicode'),
422 ('support_url', 'rhodecode_support_url', 'unicode'),
421 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
423 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
422 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
424 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
423 ]
425 ]
424 for setting, form_key, type_ in settings:
426 for setting, form_key, type_ in settings:
425 sett = SettingsModel().create_or_update_setting(
427 sett = SettingsModel().create_or_update_setting(
426 setting, form_result[form_key], type_)
428 setting, form_result[form_key], type_)
427 Session().add(sett)
429 Session().add(sett)
428
430
429 Session().commit()
431 Session().commit()
430 SettingsModel().invalidate_settings_cache()
432 SettingsModel().invalidate_settings_cache()
431 h.flash(_('Updated visualisation settings'), category='success')
433 h.flash(_('Updated visualisation settings'), category='success')
432 except Exception:
434 except Exception:
433 log.exception("Exception updating visualization settings")
435 log.exception("Exception updating visualization settings")
434 h.flash(_('Error occurred during updating '
436 h.flash(_('Error occurred during updating '
435 'visualisation settings'),
437 'visualisation settings'),
436 category='error')
438 category='error')
437
439
438 raise HTTPFound(h.route_path('admin_settings_visual'))
440 raise HTTPFound(h.route_path('admin_settings_visual'))
439
441
440 @LoginRequired()
442 @LoginRequired()
441 @HasPermissionAllDecorator('hg.admin')
443 @HasPermissionAllDecorator('hg.admin')
442 @view_config(
444 @view_config(
443 route_name='admin_settings_issuetracker', request_method='GET',
445 route_name='admin_settings_issuetracker', request_method='GET',
444 renderer='rhodecode:templates/admin/settings/settings.mako')
446 renderer='rhodecode:templates/admin/settings/settings.mako')
445 def settings_issuetracker(self):
447 def settings_issuetracker(self):
446 c = self.load_default_context()
448 c = self.load_default_context()
447 c.active = 'issuetracker'
449 c.active = 'issuetracker'
448 defaults = c.rc_config
450 defaults = c.rc_config
449
451
450 entry_key = 'rhodecode_issuetracker_pat_'
452 entry_key = 'rhodecode_issuetracker_pat_'
451
453
452 c.issuetracker_entries = {}
454 c.issuetracker_entries = {}
453 for k, v in defaults.items():
455 for k, v in defaults.items():
454 if k.startswith(entry_key):
456 if k.startswith(entry_key):
455 uid = k[len(entry_key):]
457 uid = k[len(entry_key):]
456 c.issuetracker_entries[uid] = None
458 c.issuetracker_entries[uid] = None
457
459
458 for uid in c.issuetracker_entries:
460 for uid in c.issuetracker_entries:
459 c.issuetracker_entries[uid] = AttributeDict({
461 c.issuetracker_entries[uid] = AttributeDict({
460 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
462 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
461 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
463 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
462 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
464 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
463 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
465 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
464 })
466 })
465
467
466 return self._get_template_context(c)
468 return self._get_template_context(c)
467
469
468 @LoginRequired()
470 @LoginRequired()
469 @HasPermissionAllDecorator('hg.admin')
471 @HasPermissionAllDecorator('hg.admin')
470 @CSRFRequired()
472 @CSRFRequired()
471 @view_config(
473 @view_config(
472 route_name='admin_settings_issuetracker_test', request_method='POST',
474 route_name='admin_settings_issuetracker_test', request_method='POST',
473 renderer='string', xhr=True)
475 renderer='string', xhr=True)
474 def settings_issuetracker_test(self):
476 def settings_issuetracker_test(self):
475 return h.urlify_commit_message(
477 return h.urlify_commit_message(
476 self.request.POST.get('test_text', ''),
478 self.request.POST.get('test_text', ''),
477 'repo_group/test_repo1')
479 'repo_group/test_repo1')
478
480
479 @LoginRequired()
481 @LoginRequired()
480 @HasPermissionAllDecorator('hg.admin')
482 @HasPermissionAllDecorator('hg.admin')
481 @CSRFRequired()
483 @CSRFRequired()
482 @view_config(
484 @view_config(
483 route_name='admin_settings_issuetracker_update', request_method='POST',
485 route_name='admin_settings_issuetracker_update', request_method='POST',
484 renderer='rhodecode:templates/admin/settings/settings.mako')
486 renderer='rhodecode:templates/admin/settings/settings.mako')
485 def settings_issuetracker_update(self):
487 def settings_issuetracker_update(self):
486 _ = self.request.translate
488 _ = self.request.translate
487 self.load_default_context()
489 self.load_default_context()
488 settings_model = IssueTrackerSettingsModel()
490 settings_model = IssueTrackerSettingsModel()
489
491
490 try:
492 try:
491 form = IssueTrackerPatternsForm(self.request.translate)()
493 form = IssueTrackerPatternsForm(self.request.translate)()
492 data = form.to_python(self.request.POST)
494 data = form.to_python(self.request.POST)
493 except formencode.Invalid as errors:
495 except formencode.Invalid as errors:
494 log.exception('Failed to add new pattern')
496 log.exception('Failed to add new pattern')
495 error = errors
497 error = errors
496 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
498 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
497 category='error')
499 category='error')
498 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
500 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
499
501
500 if data:
502 if data:
501 for uid in data.get('delete_patterns', []):
503 for uid in data.get('delete_patterns', []):
502 settings_model.delete_entries(uid)
504 settings_model.delete_entries(uid)
503
505
504 for pattern in data.get('patterns', []):
506 for pattern in data.get('patterns', []):
505 for setting, value, type_ in pattern:
507 for setting, value, type_ in pattern:
506 sett = settings_model.create_or_update_setting(
508 sett = settings_model.create_or_update_setting(
507 setting, value, type_)
509 setting, value, type_)
508 Session().add(sett)
510 Session().add(sett)
509
511
510 Session().commit()
512 Session().commit()
511
513
512 SettingsModel().invalidate_settings_cache()
514 SettingsModel().invalidate_settings_cache()
513 h.flash(_('Updated issue tracker entries'), category='success')
515 h.flash(_('Updated issue tracker entries'), category='success')
514 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
516 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
515
517
516 @LoginRequired()
518 @LoginRequired()
517 @HasPermissionAllDecorator('hg.admin')
519 @HasPermissionAllDecorator('hg.admin')
518 @CSRFRequired()
520 @CSRFRequired()
519 @view_config(
521 @view_config(
520 route_name='admin_settings_issuetracker_delete', request_method='POST',
522 route_name='admin_settings_issuetracker_delete', request_method='POST',
521 renderer='json_ext', xhr=True)
523 renderer='json_ext', xhr=True)
522 def settings_issuetracker_delete(self):
524 def settings_issuetracker_delete(self):
523 _ = self.request.translate
525 _ = self.request.translate
524 self.load_default_context()
526 self.load_default_context()
525 uid = self.request.POST.get('uid')
527 uid = self.request.POST.get('uid')
526 try:
528 try:
527 IssueTrackerSettingsModel().delete_entries(uid)
529 IssueTrackerSettingsModel().delete_entries(uid)
528 except Exception:
530 except Exception:
529 log.exception('Failed to delete issue tracker setting %s', uid)
531 log.exception('Failed to delete issue tracker setting %s', uid)
530 raise HTTPNotFound()
532 raise HTTPNotFound()
531
533
532 SettingsModel().invalidate_settings_cache()
534 SettingsModel().invalidate_settings_cache()
533 h.flash(_('Removed issue tracker entry.'), category='success')
535 h.flash(_('Removed issue tracker entry.'), category='success')
534
536
535 return {'deleted': uid}
537 return {'deleted': uid}
536
538
537 @LoginRequired()
539 @LoginRequired()
538 @HasPermissionAllDecorator('hg.admin')
540 @HasPermissionAllDecorator('hg.admin')
539 @view_config(
541 @view_config(
540 route_name='admin_settings_email', request_method='GET',
542 route_name='admin_settings_email', request_method='GET',
541 renderer='rhodecode:templates/admin/settings/settings.mako')
543 renderer='rhodecode:templates/admin/settings/settings.mako')
542 def settings_email(self):
544 def settings_email(self):
543 c = self.load_default_context()
545 c = self.load_default_context()
544 c.active = 'email'
546 c.active = 'email'
545 c.rhodecode_ini = rhodecode.CONFIG
547 c.rhodecode_ini = rhodecode.CONFIG
546
548
547 data = render('rhodecode:templates/admin/settings/settings.mako',
549 data = render('rhodecode:templates/admin/settings/settings.mako',
548 self._get_template_context(c), self.request)
550 self._get_template_context(c), self.request)
549 html = formencode.htmlfill.render(
551 html = formencode.htmlfill.render(
550 data,
552 data,
551 defaults=self._form_defaults(),
553 defaults=self._form_defaults(),
552 encoding="UTF-8",
554 encoding="UTF-8",
553 force_defaults=False
555 force_defaults=False
554 )
556 )
555 return Response(html)
557 return Response(html)
556
558
557 @LoginRequired()
559 @LoginRequired()
558 @HasPermissionAllDecorator('hg.admin')
560 @HasPermissionAllDecorator('hg.admin')
559 @CSRFRequired()
561 @CSRFRequired()
560 @view_config(
562 @view_config(
561 route_name='admin_settings_email_update', request_method='POST',
563 route_name='admin_settings_email_update', request_method='POST',
562 renderer='rhodecode:templates/admin/settings/settings.mako')
564 renderer='rhodecode:templates/admin/settings/settings.mako')
563 def settings_email_update(self):
565 def settings_email_update(self):
564 _ = self.request.translate
566 _ = self.request.translate
565 c = self.load_default_context()
567 c = self.load_default_context()
566 c.active = 'email'
568 c.active = 'email'
567
569
568 test_email = self.request.POST.get('test_email')
570 test_email = self.request.POST.get('test_email')
569
571
570 if not test_email:
572 if not test_email:
571 h.flash(_('Please enter email address'), category='error')
573 h.flash(_('Please enter email address'), category='error')
572 raise HTTPFound(h.route_path('admin_settings_email'))
574 raise HTTPFound(h.route_path('admin_settings_email'))
573
575
574 email_kwargs = {
576 email_kwargs = {
575 'date': datetime.datetime.now(),
577 'date': datetime.datetime.now(),
576 'user': self._rhodecode_db_user
578 'user': self._rhodecode_db_user
577 }
579 }
578
580
579 (subject, headers, email_body,
581 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
580 email_body_plaintext) = EmailNotificationModel().render_email(
581 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
582 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
582
583
583 recipients = [test_email] if test_email else None
584 recipients = [test_email] if test_email else None
584
585
585 run_task(tasks.send_email, recipients, subject,
586 run_task(tasks.send_email, recipients, subject,
586 email_body_plaintext, email_body)
587 email_body_plaintext, email_body)
587
588
588 h.flash(_('Send email task created'), category='success')
589 h.flash(_('Send email task created'), category='success')
589 raise HTTPFound(h.route_path('admin_settings_email'))
590 raise HTTPFound(h.route_path('admin_settings_email'))
590
591
591 @LoginRequired()
592 @LoginRequired()
592 @HasPermissionAllDecorator('hg.admin')
593 @HasPermissionAllDecorator('hg.admin')
593 @view_config(
594 @view_config(
594 route_name='admin_settings_hooks', request_method='GET',
595 route_name='admin_settings_hooks', request_method='GET',
595 renderer='rhodecode:templates/admin/settings/settings.mako')
596 renderer='rhodecode:templates/admin/settings/settings.mako')
596 def settings_hooks(self):
597 def settings_hooks(self):
597 c = self.load_default_context()
598 c = self.load_default_context()
598 c.active = 'hooks'
599 c.active = 'hooks'
599
600
600 model = SettingsModel()
601 model = SettingsModel()
601 c.hooks = model.get_builtin_hooks()
602 c.hooks = model.get_builtin_hooks()
602 c.custom_hooks = model.get_custom_hooks()
603 c.custom_hooks = model.get_custom_hooks()
603
604
604 data = render('rhodecode:templates/admin/settings/settings.mako',
605 data = render('rhodecode:templates/admin/settings/settings.mako',
605 self._get_template_context(c), self.request)
606 self._get_template_context(c), self.request)
606 html = formencode.htmlfill.render(
607 html = formencode.htmlfill.render(
607 data,
608 data,
608 defaults=self._form_defaults(),
609 defaults=self._form_defaults(),
609 encoding="UTF-8",
610 encoding="UTF-8",
610 force_defaults=False
611 force_defaults=False
611 )
612 )
612 return Response(html)
613 return Response(html)
613
614
614 @LoginRequired()
615 @LoginRequired()
615 @HasPermissionAllDecorator('hg.admin')
616 @HasPermissionAllDecorator('hg.admin')
616 @CSRFRequired()
617 @CSRFRequired()
617 @view_config(
618 @view_config(
618 route_name='admin_settings_hooks_update', request_method='POST',
619 route_name='admin_settings_hooks_update', request_method='POST',
619 renderer='rhodecode:templates/admin/settings/settings.mako')
620 renderer='rhodecode:templates/admin/settings/settings.mako')
620 @view_config(
621 @view_config(
621 route_name='admin_settings_hooks_delete', request_method='POST',
622 route_name='admin_settings_hooks_delete', request_method='POST',
622 renderer='rhodecode:templates/admin/settings/settings.mako')
623 renderer='rhodecode:templates/admin/settings/settings.mako')
623 def settings_hooks_update(self):
624 def settings_hooks_update(self):
624 _ = self.request.translate
625 _ = self.request.translate
625 c = self.load_default_context()
626 c = self.load_default_context()
626 c.active = 'hooks'
627 c.active = 'hooks'
627 if c.visual.allow_custom_hooks_settings:
628 if c.visual.allow_custom_hooks_settings:
628 ui_key = self.request.POST.get('new_hook_ui_key')
629 ui_key = self.request.POST.get('new_hook_ui_key')
629 ui_value = self.request.POST.get('new_hook_ui_value')
630 ui_value = self.request.POST.get('new_hook_ui_value')
630
631
631 hook_id = self.request.POST.get('hook_id')
632 hook_id = self.request.POST.get('hook_id')
632 new_hook = False
633 new_hook = False
633
634
634 model = SettingsModel()
635 model = SettingsModel()
635 try:
636 try:
636 if ui_value and ui_key:
637 if ui_value and ui_key:
637 model.create_or_update_hook(ui_key, ui_value)
638 model.create_or_update_hook(ui_key, ui_value)
638 h.flash(_('Added new hook'), category='success')
639 h.flash(_('Added new hook'), category='success')
639 new_hook = True
640 new_hook = True
640 elif hook_id:
641 elif hook_id:
641 RhodeCodeUi.delete(hook_id)
642 RhodeCodeUi.delete(hook_id)
642 Session().commit()
643 Session().commit()
643
644
644 # check for edits
645 # check for edits
645 update = False
646 update = False
646 _d = self.request.POST.dict_of_lists()
647 _d = self.request.POST.dict_of_lists()
647 for k, v in zip(_d.get('hook_ui_key', []),
648 for k, v in zip(_d.get('hook_ui_key', []),
648 _d.get('hook_ui_value_new', [])):
649 _d.get('hook_ui_value_new', [])):
649 model.create_or_update_hook(k, v)
650 model.create_or_update_hook(k, v)
650 update = True
651 update = True
651
652
652 if update and not new_hook:
653 if update and not new_hook:
653 h.flash(_('Updated hooks'), category='success')
654 h.flash(_('Updated hooks'), category='success')
654 Session().commit()
655 Session().commit()
655 except Exception:
656 except Exception:
656 log.exception("Exception during hook creation")
657 log.exception("Exception during hook creation")
657 h.flash(_('Error occurred during hook creation'),
658 h.flash(_('Error occurred during hook creation'),
658 category='error')
659 category='error')
659
660
660 raise HTTPFound(h.route_path('admin_settings_hooks'))
661 raise HTTPFound(h.route_path('admin_settings_hooks'))
661
662
662 @LoginRequired()
663 @LoginRequired()
663 @HasPermissionAllDecorator('hg.admin')
664 @HasPermissionAllDecorator('hg.admin')
664 @view_config(
665 @view_config(
665 route_name='admin_settings_search', request_method='GET',
666 route_name='admin_settings_search', request_method='GET',
666 renderer='rhodecode:templates/admin/settings/settings.mako')
667 renderer='rhodecode:templates/admin/settings/settings.mako')
667 def settings_search(self):
668 def settings_search(self):
668 c = self.load_default_context()
669 c = self.load_default_context()
669 c.active = 'search'
670 c.active = 'search'
670
671
671 c.searcher = searcher_from_config(self.request.registry.settings)
672 c.searcher = searcher_from_config(self.request.registry.settings)
672 c.statistics = c.searcher.statistics(self.request.translate)
673 c.statistics = c.searcher.statistics(self.request.translate)
673
674
674 return self._get_template_context(c)
675 return self._get_template_context(c)
675
676
676 @LoginRequired()
677 @LoginRequired()
677 @HasPermissionAllDecorator('hg.admin')
678 @HasPermissionAllDecorator('hg.admin')
678 @view_config(
679 @view_config(
679 route_name='admin_settings_automation', request_method='GET',
680 route_name='admin_settings_automation', request_method='GET',
680 renderer='rhodecode:templates/admin/settings/settings.mako')
681 renderer='rhodecode:templates/admin/settings/settings.mako')
681 def settings_automation(self):
682 def settings_automation(self):
682 c = self.load_default_context()
683 c = self.load_default_context()
683 c.active = 'automation'
684 c.active = 'automation'
684
685
685 return self._get_template_context(c)
686 return self._get_template_context(c)
686
687
687 @LoginRequired()
688 @LoginRequired()
688 @HasPermissionAllDecorator('hg.admin')
689 @HasPermissionAllDecorator('hg.admin')
689 @view_config(
690 @view_config(
690 route_name='admin_settings_labs', request_method='GET',
691 route_name='admin_settings_labs', request_method='GET',
691 renderer='rhodecode:templates/admin/settings/settings.mako')
692 renderer='rhodecode:templates/admin/settings/settings.mako')
692 def settings_labs(self):
693 def settings_labs(self):
693 c = self.load_default_context()
694 c = self.load_default_context()
694 if not c.labs_active:
695 if not c.labs_active:
695 raise HTTPFound(h.route_path('admin_settings'))
696 raise HTTPFound(h.route_path('admin_settings'))
696
697
697 c.active = 'labs'
698 c.active = 'labs'
698 c.lab_settings = _LAB_SETTINGS
699 c.lab_settings = _LAB_SETTINGS
699
700
700 data = render('rhodecode:templates/admin/settings/settings.mako',
701 data = render('rhodecode:templates/admin/settings/settings.mako',
701 self._get_template_context(c), self.request)
702 self._get_template_context(c), self.request)
702 html = formencode.htmlfill.render(
703 html = formencode.htmlfill.render(
703 data,
704 data,
704 defaults=self._form_defaults(),
705 defaults=self._form_defaults(),
705 encoding="UTF-8",
706 encoding="UTF-8",
706 force_defaults=False
707 force_defaults=False
707 )
708 )
708 return Response(html)
709 return Response(html)
709
710
710 @LoginRequired()
711 @LoginRequired()
711 @HasPermissionAllDecorator('hg.admin')
712 @HasPermissionAllDecorator('hg.admin')
712 @CSRFRequired()
713 @CSRFRequired()
713 @view_config(
714 @view_config(
714 route_name='admin_settings_labs_update', request_method='POST',
715 route_name='admin_settings_labs_update', request_method='POST',
715 renderer='rhodecode:templates/admin/settings/settings.mako')
716 renderer='rhodecode:templates/admin/settings/settings.mako')
716 def settings_labs_update(self):
717 def settings_labs_update(self):
717 _ = self.request.translate
718 _ = self.request.translate
718 c = self.load_default_context()
719 c = self.load_default_context()
719 c.active = 'labs'
720 c.active = 'labs'
720
721
721 application_form = LabsSettingsForm(self.request.translate)()
722 application_form = LabsSettingsForm(self.request.translate)()
722 try:
723 try:
723 form_result = application_form.to_python(dict(self.request.POST))
724 form_result = application_form.to_python(dict(self.request.POST))
724 except formencode.Invalid as errors:
725 except formencode.Invalid as errors:
725 h.flash(
726 h.flash(
726 _("Some form inputs contain invalid data."),
727 _("Some form inputs contain invalid data."),
727 category='error')
728 category='error')
728 data = render('rhodecode:templates/admin/settings/settings.mako',
729 data = render('rhodecode:templates/admin/settings/settings.mako',
729 self._get_template_context(c), self.request)
730 self._get_template_context(c), self.request)
730 html = formencode.htmlfill.render(
731 html = formencode.htmlfill.render(
731 data,
732 data,
732 defaults=errors.value,
733 defaults=errors.value,
733 errors=errors.error_dict or {},
734 errors=errors.error_dict or {},
734 prefix_error=False,
735 prefix_error=False,
735 encoding="UTF-8",
736 encoding="UTF-8",
736 force_defaults=False
737 force_defaults=False
737 )
738 )
738 return Response(html)
739 return Response(html)
739
740
740 try:
741 try:
741 session = Session()
742 session = Session()
742 for setting in _LAB_SETTINGS:
743 for setting in _LAB_SETTINGS:
743 setting_name = setting.key[len('rhodecode_'):]
744 setting_name = setting.key[len('rhodecode_'):]
744 sett = SettingsModel().create_or_update_setting(
745 sett = SettingsModel().create_or_update_setting(
745 setting_name, form_result[setting.key], setting.type)
746 setting_name, form_result[setting.key], setting.type)
746 session.add(sett)
747 session.add(sett)
747
748
748 except Exception:
749 except Exception:
749 log.exception('Exception while updating lab settings')
750 log.exception('Exception while updating lab settings')
750 h.flash(_('Error occurred during updating labs settings'),
751 h.flash(_('Error occurred during updating labs settings'),
751 category='error')
752 category='error')
752 else:
753 else:
753 Session().commit()
754 Session().commit()
754 SettingsModel().invalidate_settings_cache()
755 SettingsModel().invalidate_settings_cache()
755 h.flash(_('Updated Labs settings'), category='success')
756 h.flash(_('Updated Labs settings'), category='success')
756 raise HTTPFound(h.route_path('admin_settings_labs'))
757 raise HTTPFound(h.route_path('admin_settings_labs'))
757
758
758 data = render('rhodecode:templates/admin/settings/settings.mako',
759 data = render('rhodecode:templates/admin/settings/settings.mako',
759 self._get_template_context(c), self.request)
760 self._get_template_context(c), self.request)
760 html = formencode.htmlfill.render(
761 html = formencode.htmlfill.render(
761 data,
762 data,
762 defaults=self._form_defaults(),
763 defaults=self._form_defaults(),
763 encoding="UTF-8",
764 encoding="UTF-8",
764 force_defaults=False
765 force_defaults=False
765 )
766 )
766 return Response(html)
767 return Response(html)
767
768
768
769
769 # :param key: name of the setting including the 'rhodecode_' prefix
770 # :param key: name of the setting including the 'rhodecode_' prefix
770 # :param type: the RhodeCodeSetting type to use.
771 # :param type: the RhodeCodeSetting type to use.
771 # :param group: the i18ned group in which we should dispaly this setting
772 # :param group: the i18ned group in which we should dispaly this setting
772 # :param label: the i18ned label we should display for this setting
773 # :param label: the i18ned label we should display for this setting
773 # :param help: the i18ned help we should dispaly for this setting
774 # :param help: the i18ned help we should dispaly for this setting
774 LabSetting = collections.namedtuple(
775 LabSetting = collections.namedtuple(
775 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
776 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
776
777
777
778
778 # This list has to be kept in sync with the form
779 # This list has to be kept in sync with the form
779 # rhodecode.model.forms.LabsSettingsForm.
780 # rhodecode.model.forms.LabsSettingsForm.
780 _LAB_SETTINGS = [
781 _LAB_SETTINGS = [
781
782
782 ]
783 ]
@@ -1,419 +1,418 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23 import datetime
23 import datetime
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.renderers import render_to_response
26 from pyramid.renderers import render_to_response
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib.celerylib import run_task, tasks
28 from rhodecode.lib.celerylib import run_task, tasks
29 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.lib.utils2 import AttributeDict
30 from rhodecode.model.db import User
30 from rhodecode.model.db import User
31 from rhodecode.model.notification import EmailNotificationModel
31 from rhodecode.model.notification import EmailNotificationModel
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class DebugStyleView(BaseAppView):
36 class DebugStyleView(BaseAppView):
37 def load_default_context(self):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
38 c = self._get_local_tmpl_context()
39
39
40 return c
40 return c
41
41
42 @view_config(
42 @view_config(
43 route_name='debug_style_home', request_method='GET',
43 route_name='debug_style_home', request_method='GET',
44 renderer=None)
44 renderer=None)
45 def index(self):
45 def index(self):
46 c = self.load_default_context()
46 c = self.load_default_context()
47 c.active = 'index'
47 c.active = 'index'
48
48
49 return render_to_response(
49 return render_to_response(
50 'debug_style/index.html', self._get_template_context(c),
50 'debug_style/index.html', self._get_template_context(c),
51 request=self.request)
51 request=self.request)
52
52
53 @view_config(
53 @view_config(
54 route_name='debug_style_email', request_method='GET',
54 route_name='debug_style_email', request_method='GET',
55 renderer=None)
55 renderer=None)
56 @view_config(
56 @view_config(
57 route_name='debug_style_email_plain_rendered', request_method='GET',
57 route_name='debug_style_email_plain_rendered', request_method='GET',
58 renderer=None)
58 renderer=None)
59 def render_email(self):
59 def render_email(self):
60 c = self.load_default_context()
60 c = self.load_default_context()
61 email_id = self.request.matchdict['email_id']
61 email_id = self.request.matchdict['email_id']
62 c.active = 'emails'
62 c.active = 'emails'
63
63
64 pr = AttributeDict(
64 pr = AttributeDict(
65 pull_request_id=123,
65 pull_request_id=123,
66 title='digital_ocean: fix redis, elastic search start on boot, '
66 title='digital_ocean: fix redis, elastic search start on boot, '
67 'fix fd limits on supervisor, set postgres 11 version',
67 'fix fd limits on supervisor, set postgres 11 version',
68 description='''
68 description='''
69 Check if we should use full-topic or mini-topic.
69 Check if we should use full-topic or mini-topic.
70
70
71 - full topic produces some problems with merge states etc
71 - full topic produces some problems with merge states etc
72 - server-mini-topic needs probably tweeks.
72 - server-mini-topic needs probably tweeks.
73 ''',
73 ''',
74 repo_name='foobar',
74 repo_name='foobar',
75 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
75 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
76 target_ref_parts=AttributeDict(type='branch', name='master'),
76 target_ref_parts=AttributeDict(type='branch', name='master'),
77 )
77 )
78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
81 # file/commit changes for PR update
81 # file/commit changes for PR update
82 commit_changes = AttributeDict({
82 commit_changes = AttributeDict({
83 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
83 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
84 'removed': ['eeeeeeeeeee'],
84 'removed': ['eeeeeeeeeee'],
85 })
85 })
86 file_changes = AttributeDict({
86 file_changes = AttributeDict({
87 'added': ['a/file1.md', 'file2.py'],
87 'added': ['a/file1.md', 'file2.py'],
88 'modified': ['b/modified_file.rst'],
88 'modified': ['b/modified_file.rst'],
89 'removed': ['.idea'],
89 'removed': ['.idea'],
90 })
90 })
91
91
92 exc_traceback = {
92 exc_traceback = {
93 'exc_utc_date': '2020-03-26T12:54:50.683281',
93 'exc_utc_date': '2020-03-26T12:54:50.683281',
94 'exc_id': 139638856342656,
94 'exc_id': 139638856342656,
95 'exc_timestamp': '1585227290.683288',
95 'exc_timestamp': '1585227290.683288',
96 'version': 'v1',
96 'version': 'v1',
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',
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 'exc_type': 'AttributeError'
98 'exc_type': 'AttributeError'
99 }
99 }
100 email_kwargs = {
100 email_kwargs = {
101 'test': {},
101 'test': {},
102 'message': {
102 'message': {
103 'body': 'message body !'
103 'body': 'message body !'
104 },
104 },
105 'email_test': {
105 'email_test': {
106 'user': user,
106 'user': user,
107 'date': datetime.datetime.now(),
107 'date': datetime.datetime.now(),
108 },
108 },
109 'exception': {
109 'exception': {
110 'email_prefix': '[RHODECODE ERROR]',
110 'email_prefix': '[RHODECODE ERROR]',
111 'exc_id': exc_traceback['exc_id'],
111 'exc_id': exc_traceback['exc_id'],
112 'exc_url': 'http://server-url/{}'.format(exc_traceback['exc_id']),
112 'exc_url': 'http://server-url/{}'.format(exc_traceback['exc_id']),
113 'exc_type_name': 'NameError',
113 'exc_type_name': 'NameError',
114 'exc_traceback': exc_traceback,
114 'exc_traceback': exc_traceback,
115 },
115 },
116 'password_reset': {
116 'password_reset': {
117 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
117 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
118
118
119 'user': user,
119 'user': user,
120 'date': datetime.datetime.now(),
120 'date': datetime.datetime.now(),
121 'email': 'test@rhodecode.com',
121 'email': 'test@rhodecode.com',
122 'first_admin_email': User.get_first_super_admin().email
122 'first_admin_email': User.get_first_super_admin().email
123 },
123 },
124 'password_reset_confirmation': {
124 'password_reset_confirmation': {
125 'new_password': 'new-password-example',
125 'new_password': 'new-password-example',
126 'user': user,
126 'user': user,
127 'date': datetime.datetime.now(),
127 'date': datetime.datetime.now(),
128 'email': 'test@rhodecode.com',
128 'email': 'test@rhodecode.com',
129 'first_admin_email': User.get_first_super_admin().email
129 'first_admin_email': User.get_first_super_admin().email
130 },
130 },
131 'registration': {
131 'registration': {
132 'user': user,
132 'user': user,
133 'date': datetime.datetime.now(),
133 'date': datetime.datetime.now(),
134 },
134 },
135
135
136 'pull_request_comment': {
136 'pull_request_comment': {
137 'user': user,
137 'user': user,
138
138
139 'status_change': None,
139 'status_change': None,
140 'status_change_type': None,
140 'status_change_type': None,
141
141
142 'pull_request': pr,
142 'pull_request': pr,
143 'pull_request_commits': [],
143 'pull_request_commits': [],
144
144
145 'pull_request_target_repo': target_repo,
145 'pull_request_target_repo': target_repo,
146 'pull_request_target_repo_url': 'http://target-repo/url',
146 'pull_request_target_repo_url': 'http://target-repo/url',
147
147
148 'pull_request_source_repo': source_repo,
148 'pull_request_source_repo': source_repo,
149 'pull_request_source_repo_url': 'http://source-repo/url',
149 'pull_request_source_repo_url': 'http://source-repo/url',
150
150
151 'pull_request_url': 'http://localhost/pr1',
151 'pull_request_url': 'http://localhost/pr1',
152 'pr_comment_url': 'http://comment-url',
152 'pr_comment_url': 'http://comment-url',
153 'pr_comment_reply_url': 'http://comment-url#reply',
153 'pr_comment_reply_url': 'http://comment-url#reply',
154
154
155 'comment_file': None,
155 'comment_file': None,
156 'comment_line': None,
156 'comment_line': None,
157 'comment_type': 'note',
157 'comment_type': 'note',
158 'comment_body': 'This is my comment body. *I like !*',
158 'comment_body': 'This is my comment body. *I like !*',
159 'comment_id': 2048,
159 'comment_id': 2048,
160 'renderer_type': 'markdown',
160 'renderer_type': 'markdown',
161 'mention': True,
161 'mention': True,
162
162
163 },
163 },
164 'pull_request_comment+status': {
164 'pull_request_comment+status': {
165 'user': user,
165 'user': user,
166
166
167 'status_change': 'approved',
167 'status_change': 'approved',
168 'status_change_type': 'approved',
168 'status_change_type': 'approved',
169
169
170 'pull_request': pr,
170 'pull_request': pr,
171 'pull_request_commits': [],
171 'pull_request_commits': [],
172
172
173 'pull_request_target_repo': target_repo,
173 'pull_request_target_repo': target_repo,
174 'pull_request_target_repo_url': 'http://target-repo/url',
174 'pull_request_target_repo_url': 'http://target-repo/url',
175
175
176 'pull_request_source_repo': source_repo,
176 'pull_request_source_repo': source_repo,
177 'pull_request_source_repo_url': 'http://source-repo/url',
177 'pull_request_source_repo_url': 'http://source-repo/url',
178
178
179 'pull_request_url': 'http://localhost/pr1',
179 'pull_request_url': 'http://localhost/pr1',
180 'pr_comment_url': 'http://comment-url',
180 'pr_comment_url': 'http://comment-url',
181 'pr_comment_reply_url': 'http://comment-url#reply',
181 'pr_comment_reply_url': 'http://comment-url#reply',
182
182
183 'comment_type': 'todo',
183 'comment_type': 'todo',
184 'comment_file': None,
184 'comment_file': None,
185 'comment_line': None,
185 'comment_line': None,
186 'comment_body': '''
186 'comment_body': '''
187 I think something like this would be better
187 I think something like this would be better
188
188
189 ```py
189 ```py
190 // markdown renderer
190 // markdown renderer
191
191
192 def db():
192 def db():
193 global connection
193 global connection
194 return connection
194 return connection
195
195
196 ```
196 ```
197
197
198 ''',
198 ''',
199 'comment_id': 2048,
199 'comment_id': 2048,
200 'renderer_type': 'markdown',
200 'renderer_type': 'markdown',
201 'mention': True,
201 'mention': True,
202
202
203 },
203 },
204 'pull_request_comment+file': {
204 'pull_request_comment+file': {
205 'user': user,
205 'user': user,
206
206
207 'status_change': None,
207 'status_change': None,
208 'status_change_type': None,
208 'status_change_type': None,
209
209
210 'pull_request': pr,
210 'pull_request': pr,
211 'pull_request_commits': [],
211 'pull_request_commits': [],
212
212
213 'pull_request_target_repo': target_repo,
213 'pull_request_target_repo': target_repo,
214 'pull_request_target_repo_url': 'http://target-repo/url',
214 'pull_request_target_repo_url': 'http://target-repo/url',
215
215
216 'pull_request_source_repo': source_repo,
216 'pull_request_source_repo': source_repo,
217 'pull_request_source_repo_url': 'http://source-repo/url',
217 'pull_request_source_repo_url': 'http://source-repo/url',
218
218
219 'pull_request_url': 'http://localhost/pr1',
219 'pull_request_url': 'http://localhost/pr1',
220
220
221 'pr_comment_url': 'http://comment-url',
221 'pr_comment_url': 'http://comment-url',
222 'pr_comment_reply_url': 'http://comment-url#reply',
222 'pr_comment_reply_url': 'http://comment-url#reply',
223
223
224 'comment_file': 'rhodecode/model/get_flow_commits',
224 'comment_file': 'rhodecode/model/get_flow_commits',
225 'comment_line': 'o1210',
225 'comment_line': 'o1210',
226 'comment_type': 'todo',
226 'comment_type': 'todo',
227 'comment_body': '''
227 'comment_body': '''
228 I like this !
228 I like this !
229
229
230 But please check this code
230 But please check this code
231
231
232 .. code-block:: javascript
232 .. code-block:: javascript
233
233
234 // THIS IS RST CODE
234 // THIS IS RST CODE
235
235
236 this.createResolutionComment = function(commentId) {
236 this.createResolutionComment = function(commentId) {
237 // hide the trigger text
237 // hide the trigger text
238 $('#resolve-comment-{0}'.format(commentId)).hide();
238 $('#resolve-comment-{0}'.format(commentId)).hide();
239
239
240 var comment = $('#comment-'+commentId);
240 var comment = $('#comment-'+commentId);
241 var commentData = comment.data();
241 var commentData = comment.data();
242 if (commentData.commentInline) {
242 if (commentData.commentInline) {
243 this.createComment(comment, commentId)
243 this.createComment(comment, commentId)
244 } else {
244 } else {
245 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
245 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
246 }
246 }
247
247
248 return false;
248 return false;
249 };
249 };
250
250
251 This should work better !
251 This should work better !
252 ''',
252 ''',
253 'comment_id': 2048,
253 'comment_id': 2048,
254 'renderer_type': 'rst',
254 'renderer_type': 'rst',
255 'mention': True,
255 'mention': True,
256
256
257 },
257 },
258
258
259 'pull_request_update': {
259 'pull_request_update': {
260 'updating_user': user,
260 'updating_user': user,
261
261
262 'status_change': None,
262 'status_change': None,
263 'status_change_type': None,
263 'status_change_type': None,
264
264
265 'pull_request': pr,
265 'pull_request': pr,
266 'pull_request_commits': [],
266 'pull_request_commits': [],
267
267
268 'pull_request_target_repo': target_repo,
268 'pull_request_target_repo': target_repo,
269 'pull_request_target_repo_url': 'http://target-repo/url',
269 'pull_request_target_repo_url': 'http://target-repo/url',
270
270
271 'pull_request_source_repo': source_repo,
271 'pull_request_source_repo': source_repo,
272 'pull_request_source_repo_url': 'http://source-repo/url',
272 'pull_request_source_repo_url': 'http://source-repo/url',
273
273
274 'pull_request_url': 'http://localhost/pr1',
274 'pull_request_url': 'http://localhost/pr1',
275
275
276 # update comment links
276 # update comment links
277 'pr_comment_url': 'http://comment-url',
277 'pr_comment_url': 'http://comment-url',
278 'pr_comment_reply_url': 'http://comment-url#reply',
278 'pr_comment_reply_url': 'http://comment-url#reply',
279 'ancestor_commit_id': 'f39bd443',
279 'ancestor_commit_id': 'f39bd443',
280 'added_commits': commit_changes.added,
280 'added_commits': commit_changes.added,
281 'removed_commits': commit_changes.removed,
281 'removed_commits': commit_changes.removed,
282 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
282 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
283 'added_files': file_changes.added,
283 'added_files': file_changes.added,
284 'modified_files': file_changes.modified,
284 'modified_files': file_changes.modified,
285 'removed_files': file_changes.removed,
285 'removed_files': file_changes.removed,
286 },
286 },
287
287
288 'cs_comment': {
288 'cs_comment': {
289 'user': user,
289 'user': user,
290 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
290 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
291 'status_change': None,
291 'status_change': None,
292 'status_change_type': None,
292 'status_change_type': None,
293
293
294 'commit_target_repo_url': 'http://foo.example.com/#comment1',
294 'commit_target_repo_url': 'http://foo.example.com/#comment1',
295 'repo_name': 'test-repo',
295 'repo_name': 'test-repo',
296 'comment_type': 'note',
296 'comment_type': 'note',
297 'comment_file': None,
297 'comment_file': None,
298 'comment_line': None,
298 'comment_line': None,
299 'commit_comment_url': 'http://comment-url',
299 'commit_comment_url': 'http://comment-url',
300 'commit_comment_reply_url': 'http://comment-url#reply',
300 'commit_comment_reply_url': 'http://comment-url#reply',
301 'comment_body': 'This is my comment body. *I like !*',
301 'comment_body': 'This is my comment body. *I like !*',
302 'comment_id': 2048,
302 'comment_id': 2048,
303 'renderer_type': 'markdown',
303 'renderer_type': 'markdown',
304 'mention': True,
304 'mention': True,
305 },
305 },
306 'cs_comment+status': {
306 'cs_comment+status': {
307 'user': user,
307 'user': user,
308 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
308 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
309 'status_change': 'approved',
309 'status_change': 'approved',
310 'status_change_type': 'approved',
310 'status_change_type': 'approved',
311
311
312 'commit_target_repo_url': 'http://foo.example.com/#comment1',
312 'commit_target_repo_url': 'http://foo.example.com/#comment1',
313 'repo_name': 'test-repo',
313 'repo_name': 'test-repo',
314 'comment_type': 'note',
314 'comment_type': 'note',
315 'comment_file': None,
315 'comment_file': None,
316 'comment_line': None,
316 'comment_line': None,
317 'commit_comment_url': 'http://comment-url',
317 'commit_comment_url': 'http://comment-url',
318 'commit_comment_reply_url': 'http://comment-url#reply',
318 'commit_comment_reply_url': 'http://comment-url#reply',
319 'comment_body': '''
319 'comment_body': '''
320 Hello **world**
320 Hello **world**
321
321
322 This is a multiline comment :)
322 This is a multiline comment :)
323
323
324 - list
324 - list
325 - list2
325 - list2
326 ''',
326 ''',
327 'comment_id': 2048,
327 'comment_id': 2048,
328 'renderer_type': 'markdown',
328 'renderer_type': 'markdown',
329 'mention': True,
329 'mention': True,
330 },
330 },
331 'cs_comment+file': {
331 'cs_comment+file': {
332 'user': user,
332 'user': user,
333 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
333 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
334 'status_change': None,
334 'status_change': None,
335 'status_change_type': None,
335 'status_change_type': None,
336
336
337 'commit_target_repo_url': 'http://foo.example.com/#comment1',
337 'commit_target_repo_url': 'http://foo.example.com/#comment1',
338 'repo_name': 'test-repo',
338 'repo_name': 'test-repo',
339
339
340 'comment_type': 'note',
340 'comment_type': 'note',
341 'comment_file': 'test-file.py',
341 'comment_file': 'test-file.py',
342 'comment_line': 'n100',
342 'comment_line': 'n100',
343
343
344 'commit_comment_url': 'http://comment-url',
344 'commit_comment_url': 'http://comment-url',
345 'commit_comment_reply_url': 'http://comment-url#reply',
345 'commit_comment_reply_url': 'http://comment-url#reply',
346 'comment_body': 'This is my comment body. *I like !*',
346 'comment_body': 'This is my comment body. *I like !*',
347 'comment_id': 2048,
347 'comment_id': 2048,
348 'renderer_type': 'markdown',
348 'renderer_type': 'markdown',
349 'mention': True,
349 'mention': True,
350 },
350 },
351
351
352 'pull_request': {
352 'pull_request': {
353 'user': user,
353 'user': user,
354 'pull_request': pr,
354 'pull_request': pr,
355 'pull_request_commits': [
355 'pull_request_commits': [
356 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
356 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
357 my-account: moved email closer to profile as it's similar data just moved outside.
357 my-account: moved email closer to profile as it's similar data just moved outside.
358 '''),
358 '''),
359 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
359 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
360 users: description edit fixes
360 users: description edit fixes
361
361
362 - tests
362 - tests
363 - added metatags info
363 - added metatags info
364 '''),
364 '''),
365 ],
365 ],
366
366
367 'pull_request_target_repo': target_repo,
367 'pull_request_target_repo': target_repo,
368 'pull_request_target_repo_url': 'http://target-repo/url',
368 'pull_request_target_repo_url': 'http://target-repo/url',
369
369
370 'pull_request_source_repo': source_repo,
370 'pull_request_source_repo': source_repo,
371 'pull_request_source_repo_url': 'http://source-repo/url',
371 'pull_request_source_repo_url': 'http://source-repo/url',
372
372
373 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
373 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
374 }
374 }
375
375
376 }
376 }
377
377
378 template_type = email_id.split('+')[0]
378 template_type = email_id.split('+')[0]
379 (c.subject, c.headers, c.email_body,
379 (c.subject, c.email_body, c.email_body_plaintext) = EmailNotificationModel().render_email(
380 c.email_body_plaintext) = EmailNotificationModel().render_email(
381 template_type, **email_kwargs.get(email_id, {}))
380 template_type, **email_kwargs.get(email_id, {}))
382
381
383 test_email = self.request.GET.get('email')
382 test_email = self.request.GET.get('email')
384 if test_email:
383 if test_email:
385 recipients = [test_email]
384 recipients = [test_email]
386 run_task(tasks.send_email, recipients, c.subject,
385 run_task(tasks.send_email, recipients, c.subject,
387 c.email_body_plaintext, c.email_body)
386 c.email_body_plaintext, c.email_body)
388
387
389 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
388 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
390 template = 'debug_style/email_plain_rendered.mako'
389 template = 'debug_style/email_plain_rendered.mako'
391 else:
390 else:
392 template = 'debug_style/email.mako'
391 template = 'debug_style/email.mako'
393 return render_to_response(
392 return render_to_response(
394 template, self._get_template_context(c),
393 template, self._get_template_context(c),
395 request=self.request)
394 request=self.request)
396
395
397 @view_config(
396 @view_config(
398 route_name='debug_style_template', request_method='GET',
397 route_name='debug_style_template', request_method='GET',
399 renderer=None)
398 renderer=None)
400 def template(self):
399 def template(self):
401 t_path = self.request.matchdict['t_path']
400 t_path = self.request.matchdict['t_path']
402 c = self.load_default_context()
401 c = self.load_default_context()
403 c.active = os.path.splitext(t_path)[0]
402 c.active = os.path.splitext(t_path)[0]
404 c.came_from = ''
403 c.came_from = ''
405 c.email_types = {
404 c.email_types = {
406 'cs_comment+file': {},
405 'cs_comment+file': {},
407 'cs_comment+status': {},
406 'cs_comment+status': {},
408
407
409 'pull_request_comment+file': {},
408 'pull_request_comment+file': {},
410 'pull_request_comment+status': {},
409 'pull_request_comment+status': {},
411
410
412 'pull_request_update': {},
411 'pull_request_update': {},
413 }
412 }
414 c.email_types.update(EmailNotificationModel.email_types)
413 c.email_types.update(EmailNotificationModel.email_types)
415
414
416 return render_to_response(
415 return render_to_response(
417 'debug_style/' + t_path, self._get_template_context(c),
416 'debug_style/' + t_path, self._get_template_context(c),
418 request=self.request)
417 request=self.request)
419
418
@@ -1,391 +1,391 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib import helpers as h
24 from rhodecode.lib import helpers as h
25 from rhodecode.model.db import User, Gist
25 from rhodecode.model.db import User, Gist
26 from rhodecode.model.gist import GistModel
26 from rhodecode.model.gist import GistModel
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import (
28 from rhodecode.tests import (
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
30 TestController, assert_session_flash)
30 TestController, assert_session_flash)
31
31
32
32
33 def route_path(name, params=None, **kwargs):
33 def route_path(name, params=None, **kwargs):
34 import urllib
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
35 from rhodecode.apps._base import ADMIN_PREFIX
36
36
37 base_url = {
37 base_url = {
38 'gists_show': ADMIN_PREFIX + '/gists',
38 'gists_show': ADMIN_PREFIX + '/gists',
39 'gists_new': ADMIN_PREFIX + '/gists/new',
39 'gists_new': ADMIN_PREFIX + '/gists/new',
40 'gists_create': ADMIN_PREFIX + '/gists/create',
40 'gists_create': ADMIN_PREFIX + '/gists/create',
41 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}',
41 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}',
42 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete',
42 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete',
43 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit',
43 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit',
44 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision',
44 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision',
45 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update',
45 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update',
46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/{revision}',
46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/{revision}',
47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
49
49
50 }[name].format(**kwargs)
50 }[name].format(**kwargs)
51
51
52 if params:
52 if params:
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 return base_url
54 return base_url
55
55
56
56
57 class GistUtility(object):
57 class GistUtility(object):
58
58
59 def __init__(self):
59 def __init__(self):
60 self._gist_ids = []
60 self._gist_ids = []
61
61
62 def __call__(
62 def __call__(
63 self, f_name, content='some gist', lifetime=-1,
63 self, f_name, content='some gist', lifetime=-1,
64 description='gist-desc', gist_type='public',
64 description='gist-desc', gist_type='public',
65 acl_level=Gist.GIST_PUBLIC, owner=TEST_USER_ADMIN_LOGIN):
65 acl_level=Gist.GIST_PUBLIC, owner=TEST_USER_ADMIN_LOGIN):
66 gist_mapping = {
66 gist_mapping = {
67 f_name: {'content': content}
67 f_name: {'content': content}
68 }
68 }
69 user = User.get_by_username(owner)
69 user = User.get_by_username(owner)
70 gist = GistModel().create(
70 gist = GistModel().create(
71 description, owner=user, gist_mapping=gist_mapping,
71 description, owner=user, gist_mapping=gist_mapping,
72 gist_type=gist_type, lifetime=lifetime, gist_acl_level=acl_level)
72 gist_type=gist_type, lifetime=lifetime, gist_acl_level=acl_level)
73 Session().commit()
73 Session().commit()
74 self._gist_ids.append(gist.gist_id)
74 self._gist_ids.append(gist.gist_id)
75 return gist
75 return gist
76
76
77 def cleanup(self):
77 def cleanup(self):
78 for gist_id in self._gist_ids:
78 for gist_id in self._gist_ids:
79 gist = Gist.get(gist_id)
79 gist = Gist.get(gist_id)
80 if gist:
80 if gist:
81 Session().delete(gist)
81 Session().delete(gist)
82
82
83 Session().commit()
83 Session().commit()
84
84
85
85
86 @pytest.fixture()
86 @pytest.fixture()
87 def create_gist(request):
87 def create_gist(request):
88 gist_utility = GistUtility()
88 gist_utility = GistUtility()
89 request.addfinalizer(gist_utility.cleanup)
89 request.addfinalizer(gist_utility.cleanup)
90 return gist_utility
90 return gist_utility
91
91
92
92
93 class TestGistsController(TestController):
93 class TestGistsController(TestController):
94
94
95 def test_index_empty(self, create_gist):
95 def test_index_empty(self, create_gist):
96 self.log_user()
96 self.log_user()
97 response = self.app.get(route_path('gists_show'))
97 response = self.app.get(route_path('gists_show'))
98 response.mustcontain('data: [],')
98 response.mustcontain('data: [],')
99
99
100 def test_index(self, create_gist):
100 def test_index(self, create_gist):
101 self.log_user()
101 self.log_user()
102 g1 = create_gist('gist1')
102 g1 = create_gist('gist1')
103 g2 = create_gist('gist2', lifetime=1400)
103 g2 = create_gist('gist2', lifetime=1400)
104 g3 = create_gist('gist3', description='gist3-desc')
104 g3 = create_gist('gist3', description='gist3-desc')
105 g4 = create_gist('gist4', gist_type='private').gist_access_id
105 g4 = create_gist('gist4', gist_type='private').gist_access_id
106 response = self.app.get(route_path('gists_show'))
106 response = self.app.get(route_path('gists_show'))
107
107
108 response.mustcontain(g1.gist_access_id)
108 response.mustcontain(g1.gist_access_id)
109 response.mustcontain(g2.gist_access_id)
109 response.mustcontain(g2.gist_access_id)
110 response.mustcontain(g3.gist_access_id)
110 response.mustcontain(g3.gist_access_id)
111 response.mustcontain('gist3-desc')
111 response.mustcontain('gist3-desc')
112 response.mustcontain(no=[g4])
112 response.mustcontain(no=[g4])
113
113
114 # Expiration information should be visible
114 # Expiration information should be visible
115 expires_tag = '%s' % h.age_component(
115 expires_tag = '%s' % h.age_component(
116 h.time_to_utcdatetime(g2.gist_expires))
116 h.time_to_utcdatetime(g2.gist_expires))
117 response.mustcontain(expires_tag.replace('"', '\\"'))
117 response.mustcontain(expires_tag.replace('"', '\\"'))
118
118
119 def test_index_private_gists(self, create_gist):
119 def test_index_private_gists(self, create_gist):
120 self.log_user()
120 self.log_user()
121 gist = create_gist('gist5', gist_type='private')
121 gist = create_gist('gist5', gist_type='private')
122 response = self.app.get(route_path('gists_show', params=dict(private=1)))
122 response = self.app.get(route_path('gists_show', params=dict(private=1)))
123
123
124 # and privates
124 # and privates
125 response.mustcontain(gist.gist_access_id)
125 response.mustcontain(gist.gist_access_id)
126
126
127 def test_index_show_all(self, create_gist):
127 def test_index_show_all(self, create_gist):
128 self.log_user()
128 self.log_user()
129 create_gist('gist1')
129 create_gist('gist1')
130 create_gist('gist2', lifetime=1400)
130 create_gist('gist2', lifetime=1400)
131 create_gist('gist3', description='gist3-desc')
131 create_gist('gist3', description='gist3-desc')
132 create_gist('gist4', gist_type='private')
132 create_gist('gist4', gist_type='private')
133
133
134 response = self.app.get(route_path('gists_show', params=dict(all=1)))
134 response = self.app.get(route_path('gists_show', params=dict(all=1)))
135
135
136 assert len(GistModel.get_all()) == 4
136 assert len(GistModel.get_all()) == 4
137 # and privates
137 # and privates
138 for gist in GistModel.get_all():
138 for gist in GistModel.get_all():
139 response.mustcontain(gist.gist_access_id)
139 response.mustcontain(gist.gist_access_id)
140
140
141 def test_index_show_all_hidden_from_regular(self, create_gist):
141 def test_index_show_all_hidden_from_regular(self, create_gist):
142 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
142 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
143 create_gist('gist2', gist_type='private')
143 create_gist('gist2', gist_type='private')
144 create_gist('gist3', gist_type='private')
144 create_gist('gist3', gist_type='private')
145 create_gist('gist4', gist_type='private')
145 create_gist('gist4', gist_type='private')
146
146
147 response = self.app.get(route_path('gists_show', params=dict(all=1)))
147 response = self.app.get(route_path('gists_show', params=dict(all=1)))
148
148
149 assert len(GistModel.get_all()) == 3
149 assert len(GistModel.get_all()) == 3
150 # since we don't have access to private in this view, we
150 # since we don't have access to private in this view, we
151 # should see nothing
151 # should see nothing
152 for gist in GistModel.get_all():
152 for gist in GistModel.get_all():
153 response.mustcontain(no=[gist.gist_access_id])
153 response.mustcontain(no=[gist.gist_access_id])
154
154
155 def test_create(self):
155 def test_create(self):
156 self.log_user()
156 self.log_user()
157 response = self.app.post(
157 response = self.app.post(
158 route_path('gists_create'),
158 route_path('gists_create'),
159 params={'lifetime': -1,
159 params={'lifetime': -1,
160 'content': 'gist test',
160 'content': 'gist test',
161 'filename': 'foo',
161 'filename': 'foo',
162 'gist_type': 'public',
162 'gist_type': 'public',
163 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
163 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
164 'csrf_token': self.csrf_token},
164 'csrf_token': self.csrf_token},
165 status=302)
165 status=302)
166 response = response.follow()
166 response = response.follow()
167 response.mustcontain('added file: foo')
167 response.mustcontain('added file: foo')
168 response.mustcontain('gist test')
168 response.mustcontain('gist test')
169
169
170 def test_create_with_path_with_dirs(self):
170 def test_create_with_path_with_dirs(self):
171 self.log_user()
171 self.log_user()
172 response = self.app.post(
172 response = self.app.post(
173 route_path('gists_create'),
173 route_path('gists_create'),
174 params={'lifetime': -1,
174 params={'lifetime': -1,
175 'content': 'gist test',
175 'content': 'gist test',
176 'filename': '/home/foo',
176 'filename': '/home/foo',
177 'gist_type': 'public',
177 'gist_type': 'public',
178 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
178 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
179 'csrf_token': self.csrf_token},
179 'csrf_token': self.csrf_token},
180 status=200)
180 status=200)
181 response.mustcontain('Filename /home/foo cannot be inside a directory')
181 response.mustcontain('Filename /home/foo cannot be inside a directory')
182
182
183 def test_access_expired_gist(self, create_gist):
183 def test_access_expired_gist(self, create_gist):
184 self.log_user()
184 self.log_user()
185 gist = create_gist('never-see-me')
185 gist = create_gist('never-see-me')
186 gist.gist_expires = 0 # 1970
186 gist.gist_expires = 0 # 1970
187 Session().add(gist)
187 Session().add(gist)
188 Session().commit()
188 Session().commit()
189
189
190 self.app.get(route_path('gist_show', gist_id=gist.gist_access_id),
190 self.app.get(route_path('gist_show', gist_id=gist.gist_access_id),
191 status=404)
191 status=404)
192
192
193 def test_create_private(self):
193 def test_create_private(self):
194 self.log_user()
194 self.log_user()
195 response = self.app.post(
195 response = self.app.post(
196 route_path('gists_create'),
196 route_path('gists_create'),
197 params={'lifetime': -1,
197 params={'lifetime': -1,
198 'content': 'private gist test',
198 'content': 'private gist test',
199 'filename': 'private-foo',
199 'filename': 'private-foo',
200 'gist_type': 'private',
200 'gist_type': 'private',
201 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
201 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
202 'csrf_token': self.csrf_token},
202 'csrf_token': self.csrf_token},
203 status=302)
203 status=302)
204 response = response.follow()
204 response = response.follow()
205 response.mustcontain('added file: private-foo<')
205 response.mustcontain('added file: private-foo<')
206 response.mustcontain('private gist test')
206 response.mustcontain('private gist test')
207 response.mustcontain('Private Gist')
207 response.mustcontain('Private Gist')
208 # Make sure private gists are not indexed by robots
208 # Make sure private gists are not indexed by robots
209 response.mustcontain(
209 response.mustcontain(
210 '<meta name="robots" content="noindex, nofollow">')
210 '<meta name="robots" content="noindex, nofollow">')
211
211
212 def test_create_private_acl_private(self):
212 def test_create_private_acl_private(self):
213 self.log_user()
213 self.log_user()
214 response = self.app.post(
214 response = self.app.post(
215 route_path('gists_create'),
215 route_path('gists_create'),
216 params={'lifetime': -1,
216 params={'lifetime': -1,
217 'content': 'private gist test',
217 'content': 'private gist test',
218 'filename': 'private-foo',
218 'filename': 'private-foo',
219 'gist_type': 'private',
219 'gist_type': 'private',
220 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
220 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
221 'csrf_token': self.csrf_token},
221 'csrf_token': self.csrf_token},
222 status=302)
222 status=302)
223 response = response.follow()
223 response = response.follow()
224 response.mustcontain('added file: private-foo<')
224 response.mustcontain('added file: private-foo<')
225 response.mustcontain('private gist test')
225 response.mustcontain('private gist test')
226 response.mustcontain('Private Gist')
226 response.mustcontain('Private Gist')
227 # Make sure private gists are not indexed by robots
227 # Make sure private gists are not indexed by robots
228 response.mustcontain(
228 response.mustcontain(
229 '<meta name="robots" content="noindex, nofollow">')
229 '<meta name="robots" content="noindex, nofollow">')
230
230
231 def test_create_with_description(self):
231 def test_create_with_description(self):
232 self.log_user()
232 self.log_user()
233 response = self.app.post(
233 response = self.app.post(
234 route_path('gists_create'),
234 route_path('gists_create'),
235 params={'lifetime': -1,
235 params={'lifetime': -1,
236 'content': 'gist test',
236 'content': 'gist test',
237 'filename': 'foo-desc',
237 'filename': 'foo-desc',
238 'description': 'gist-desc',
238 'description': 'gist-desc',
239 'gist_type': 'public',
239 'gist_type': 'public',
240 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
240 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
241 'csrf_token': self.csrf_token},
241 'csrf_token': self.csrf_token},
242 status=302)
242 status=302)
243 response = response.follow()
243 response = response.follow()
244 response.mustcontain('added file: foo-desc')
244 response.mustcontain('added file: foo-desc')
245 response.mustcontain('gist test')
245 response.mustcontain('gist test')
246 response.mustcontain('gist-desc')
246 response.mustcontain('gist-desc')
247
247
248 def test_create_public_with_anonymous_access(self):
248 def test_create_public_with_anonymous_access(self):
249 self.log_user()
249 self.log_user()
250 params = {
250 params = {
251 'lifetime': -1,
251 'lifetime': -1,
252 'content': 'gist test',
252 'content': 'gist test',
253 'filename': 'foo-desc',
253 'filename': 'foo-desc',
254 'description': 'gist-desc',
254 'description': 'gist-desc',
255 'gist_type': 'public',
255 'gist_type': 'public',
256 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
256 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
257 'csrf_token': self.csrf_token
257 'csrf_token': self.csrf_token
258 }
258 }
259 response = self.app.post(
259 response = self.app.post(
260 route_path('gists_create'), params=params, status=302)
260 route_path('gists_create'), params=params, status=302)
261 self.logout_user()
261 self.logout_user()
262 response = response.follow()
262 response = response.follow()
263 response.mustcontain('added file: foo-desc')
263 response.mustcontain('added file: foo-desc')
264 response.mustcontain('gist test')
264 response.mustcontain('gist test')
265 response.mustcontain('gist-desc')
265 response.mustcontain('gist-desc')
266
266
267 def test_new(self):
267 def test_new(self):
268 self.log_user()
268 self.log_user()
269 self.app.get(route_path('gists_new'))
269 self.app.get(route_path('gists_new'))
270
270
271 def test_delete(self, create_gist):
271 def test_delete(self, create_gist):
272 self.log_user()
272 self.log_user()
273 gist = create_gist('delete-me')
273 gist = create_gist('delete-me')
274 response = self.app.post(
274 response = self.app.post(
275 route_path('gist_delete', gist_id=gist.gist_id),
275 route_path('gist_delete', gist_id=gist.gist_id),
276 params={'csrf_token': self.csrf_token})
276 params={'csrf_token': self.csrf_token})
277 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
277 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
278
278
279 def test_delete_normal_user_his_gist(self, create_gist):
279 def test_delete_normal_user_his_gist(self, create_gist):
280 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
280 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
281 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
281 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
282
282
283 response = self.app.post(
283 response = self.app.post(
284 route_path('gist_delete', gist_id=gist.gist_id),
284 route_path('gist_delete', gist_id=gist.gist_id),
285 params={'csrf_token': self.csrf_token})
285 params={'csrf_token': self.csrf_token})
286 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
286 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
287
287
288 def test_delete_normal_user_not_his_own_gist(self, create_gist):
288 def test_delete_normal_user_not_his_own_gist(self, create_gist):
289 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
289 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
290 gist = create_gist('delete-me-2')
290 gist = create_gist('delete-me-2')
291
291
292 self.app.post(
292 self.app.post(
293 route_path('gist_delete', gist_id=gist.gist_id),
293 route_path('gist_delete', gist_id=gist.gist_id),
294 params={'csrf_token': self.csrf_token}, status=404)
294 params={'csrf_token': self.csrf_token}, status=404)
295
295
296 def test_show(self, create_gist):
296 def test_show(self, create_gist):
297 gist = create_gist('gist-show-me')
297 gist = create_gist('gist-show-me')
298 response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
298 response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
299
299
300 response.mustcontain('added file: gist-show-me<')
300 response.mustcontain('added file: gist-show-me<')
301
301
302 assert_response = response.assert_response()
302 assert_response = response.assert_response()
303 assert_response.element_equals_to(
303 assert_response.element_equals_to(
304 'div.rc-user span.user',
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 response.mustcontain('gist-desc')
307 response.mustcontain('gist-desc')
308
308
309 def test_show_without_hg(self, create_gist):
309 def test_show_without_hg(self, create_gist):
310 with mock.patch(
310 with mock.patch(
311 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
311 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
312 gist = create_gist('gist-show-me-again')
312 gist = create_gist('gist-show-me-again')
313 self.app.get(
313 self.app.get(
314 route_path('gist_show', gist_id=gist.gist_access_id), status=200)
314 route_path('gist_show', gist_id=gist.gist_access_id), status=200)
315
315
316 def test_show_acl_private(self, create_gist):
316 def test_show_acl_private(self, create_gist):
317 gist = create_gist('gist-show-me-only-when-im-logged-in',
317 gist = create_gist('gist-show-me-only-when-im-logged-in',
318 acl_level=Gist.ACL_LEVEL_PRIVATE)
318 acl_level=Gist.ACL_LEVEL_PRIVATE)
319 self.app.get(
319 self.app.get(
320 route_path('gist_show', gist_id=gist.gist_access_id), status=404)
320 route_path('gist_show', gist_id=gist.gist_access_id), status=404)
321
321
322 # now we log-in we should see thi gist
322 # now we log-in we should see thi gist
323 self.log_user()
323 self.log_user()
324 response = self.app.get(
324 response = self.app.get(
325 route_path('gist_show', gist_id=gist.gist_access_id))
325 route_path('gist_show', gist_id=gist.gist_access_id))
326 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
326 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
327
327
328 assert_response = response.assert_response()
328 assert_response = response.assert_response()
329 assert_response.element_equals_to(
329 assert_response.element_equals_to(
330 'div.rc-user span.user',
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 response.mustcontain('gist-desc')
332 response.mustcontain('gist-desc')
333
333
334 def test_show_as_raw(self, create_gist):
334 def test_show_as_raw(self, create_gist):
335 gist = create_gist('gist-show-me', content='GIST CONTENT')
335 gist = create_gist('gist-show-me', content='GIST CONTENT')
336 response = self.app.get(
336 response = self.app.get(
337 route_path('gist_show_formatted',
337 route_path('gist_show_formatted',
338 gist_id=gist.gist_access_id, revision='tip',
338 gist_id=gist.gist_access_id, revision='tip',
339 format='raw'))
339 format='raw'))
340 assert response.body == 'GIST CONTENT'
340 assert response.body == 'GIST CONTENT'
341
341
342 def test_show_as_raw_individual_file(self, create_gist):
342 def test_show_as_raw_individual_file(self, create_gist):
343 gist = create_gist('gist-show-me-raw', content='GIST BODY')
343 gist = create_gist('gist-show-me-raw', content='GIST BODY')
344 response = self.app.get(
344 response = self.app.get(
345 route_path('gist_show_formatted_path',
345 route_path('gist_show_formatted_path',
346 gist_id=gist.gist_access_id, format='raw',
346 gist_id=gist.gist_access_id, format='raw',
347 revision='tip', f_path='gist-show-me-raw'))
347 revision='tip', f_path='gist-show-me-raw'))
348 assert response.body == 'GIST BODY'
348 assert response.body == 'GIST BODY'
349
349
350 def test_edit_page(self, create_gist):
350 def test_edit_page(self, create_gist):
351 self.log_user()
351 self.log_user()
352 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
352 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
353 response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id))
353 response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id))
354 response.mustcontain('GIST EDIT BODY')
354 response.mustcontain('GIST EDIT BODY')
355
355
356 def test_edit_page_non_logged_user(self, create_gist):
356 def test_edit_page_non_logged_user(self, create_gist):
357 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
357 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
358 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
358 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
359 status=302)
359 status=302)
360
360
361 def test_edit_normal_user_his_gist(self, create_gist):
361 def test_edit_normal_user_his_gist(self, create_gist):
362 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
362 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
363 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
363 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
364 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id,
364 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id,
365 status=200))
365 status=200))
366
366
367 def test_edit_normal_user_not_his_own_gist(self, create_gist):
367 def test_edit_normal_user_not_his_own_gist(self, create_gist):
368 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
368 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
369 gist = create_gist('delete-me')
369 gist = create_gist('delete-me')
370 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
370 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
371 status=404)
371 status=404)
372
372
373 def test_user_first_name_is_escaped(self, user_util, create_gist):
373 def test_user_first_name_is_escaped(self, user_util, create_gist):
374 xss_atack_string = '"><script>alert(\'First Name\')</script>'
374 xss_atack_string = '"><script>alert(\'First Name\')</script>'
375 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
375 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
376 password = 'test'
376 password = 'test'
377 user = user_util.create_user(
377 user = user_util.create_user(
378 firstname=xss_atack_string, password=password)
378 firstname=xss_atack_string, password=password)
379 create_gist('gist', gist_type='public', owner=user.username)
379 create_gist('gist', gist_type='public', owner=user.username)
380 response = self.app.get(route_path('gists_show'))
380 response = self.app.get(route_path('gists_show'))
381 response.mustcontain(xss_escaped_string)
381 response.mustcontain(xss_escaped_string)
382
382
383 def test_user_last_name_is_escaped(self, user_util, create_gist):
383 def test_user_last_name_is_escaped(self, user_util, create_gist):
384 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
384 xss_atack_string = '"><script>alert(\'Last Name\')</script>'
385 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
385 xss_escaped_string = h.html_escape(h.escape(xss_atack_string))
386 password = 'test'
386 password = 'test'
387 user = user_util.create_user(
387 user = user_util.create_user(
388 lastname=xss_atack_string, password=password)
388 lastname=xss_atack_string, password=password)
389 create_gist('gist', gist_type='public', owner=user.username)
389 create_gist('gist', gist_type='public', owner=user.username)
390 response = self.app.get(route_path('gists_show'))
390 response = self.app.get(route_path('gists_show'))
391 response.mustcontain(xss_escaped_string)
391 response.mustcontain(xss_escaped_string)
@@ -1,520 +1,533 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # repo creating checks, special cases that aren't repo routes
25 # repo creating checks, special cases that aren't repo routes
26 config.add_route(
26 config.add_route(
27 name='repo_creating',
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
29
30 config.add_route(
30 config.add_route(
31 name='repo_creating_check',
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
33
34 # Summary
34 # Summary
35 # NOTE(marcink): one additional route is defined in very bottom, catch
35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 # all pattern
36 # all pattern
37 config.add_route(
37 config.add_route(
38 name='repo_summary_explicit',
38 name='repo_summary_explicit',
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 config.add_route(
40 config.add_route(
41 name='repo_summary_commits',
41 name='repo_summary_commits',
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43
43
44 # Commits
44 # Commits
45 config.add_route(
45 config.add_route(
46 name='repo_commit',
46 name='repo_commit',
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48
48
49 config.add_route(
49 config.add_route(
50 name='repo_commit_children',
50 name='repo_commit_children',
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52
52
53 config.add_route(
53 config.add_route(
54 name='repo_commit_parents',
54 name='repo_commit_parents',
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56
56
57 config.add_route(
57 config.add_route(
58 name='repo_commit_raw',
58 name='repo_commit_raw',
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60
60
61 config.add_route(
61 config.add_route(
62 name='repo_commit_patch',
62 name='repo_commit_patch',
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64
64
65 config.add_route(
65 config.add_route(
66 name='repo_commit_download',
66 name='repo_commit_download',
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68
68
69 config.add_route(
69 config.add_route(
70 name='repo_commit_data',
70 name='repo_commit_data',
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72
72
73 config.add_route(
73 config.add_route(
74 name='repo_commit_comment_create',
74 name='repo_commit_comment_create',
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76
76
77 config.add_route(
77 config.add_route(
78 name='repo_commit_comment_preview',
78 name='repo_commit_comment_preview',
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80
80
81 config.add_route(
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 name='repo_commit_comment_attachment_upload',
86 name='repo_commit_comment_attachment_upload',
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
87 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
84
88
85 config.add_route(
89 config.add_route(
86 name='repo_commit_comment_delete',
90 name='repo_commit_comment_delete',
87 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
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 # still working url for backward compat.
97 # still working url for backward compat.
90 config.add_route(
98 config.add_route(
91 name='repo_commit_raw_deprecated',
99 name='repo_commit_raw_deprecated',
92 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
100 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
93
101
94 # Files
102 # Files
95 config.add_route(
103 config.add_route(
96 name='repo_archivefile',
104 name='repo_archivefile',
97 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
105 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
98
106
99 config.add_route(
107 config.add_route(
100 name='repo_files_diff',
108 name='repo_files_diff',
101 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
109 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
102 config.add_route( # legacy route to make old links work
110 config.add_route( # legacy route to make old links work
103 name='repo_files_diff_2way_redirect',
111 name='repo_files_diff_2way_redirect',
104 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
112 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
105
113
106 config.add_route(
114 config.add_route(
107 name='repo_files',
115 name='repo_files',
108 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
116 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
109 config.add_route(
117 config.add_route(
110 name='repo_files:default_path',
118 name='repo_files:default_path',
111 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
119 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
112 config.add_route(
120 config.add_route(
113 name='repo_files:default_commit',
121 name='repo_files:default_commit',
114 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
122 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
115
123
116 config.add_route(
124 config.add_route(
117 name='repo_files:rendered',
125 name='repo_files:rendered',
118 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
126 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
119
127
120 config.add_route(
128 config.add_route(
121 name='repo_files:annotated',
129 name='repo_files:annotated',
122 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
130 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
123 config.add_route(
131 config.add_route(
124 name='repo_files:annotated_previous',
132 name='repo_files:annotated_previous',
125 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
133 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
126
134
127 config.add_route(
135 config.add_route(
128 name='repo_nodetree_full',
136 name='repo_nodetree_full',
129 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
137 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
130 config.add_route(
138 config.add_route(
131 name='repo_nodetree_full:default_path',
139 name='repo_nodetree_full:default_path',
132 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
133
141
134 config.add_route(
142 config.add_route(
135 name='repo_files_nodelist',
143 name='repo_files_nodelist',
136 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
144 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
137
145
138 config.add_route(
146 config.add_route(
139 name='repo_file_raw',
147 name='repo_file_raw',
140 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
148 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
141
149
142 config.add_route(
150 config.add_route(
143 name='repo_file_download',
151 name='repo_file_download',
144 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
152 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
145 config.add_route( # backward compat to keep old links working
153 config.add_route( # backward compat to keep old links working
146 name='repo_file_download:legacy',
154 name='repo_file_download:legacy',
147 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
155 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
148 repo_route=True)
156 repo_route=True)
149
157
150 config.add_route(
158 config.add_route(
151 name='repo_file_history',
159 name='repo_file_history',
152 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
160 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
153
161
154 config.add_route(
162 config.add_route(
155 name='repo_file_authors',
163 name='repo_file_authors',
156 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
164 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
157
165
158 config.add_route(
166 config.add_route(
159 name='repo_files_check_head',
167 name='repo_files_check_head',
160 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
168 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
161 repo_route=True)
169 repo_route=True)
162 config.add_route(
170 config.add_route(
163 name='repo_files_remove_file',
171 name='repo_files_remove_file',
164 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
172 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
165 repo_route=True)
173 repo_route=True)
166 config.add_route(
174 config.add_route(
167 name='repo_files_delete_file',
175 name='repo_files_delete_file',
168 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
176 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
169 repo_route=True)
177 repo_route=True)
170 config.add_route(
178 config.add_route(
171 name='repo_files_edit_file',
179 name='repo_files_edit_file',
172 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
180 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
173 repo_route=True)
181 repo_route=True)
174 config.add_route(
182 config.add_route(
175 name='repo_files_update_file',
183 name='repo_files_update_file',
176 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
184 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
177 repo_route=True)
185 repo_route=True)
178 config.add_route(
186 config.add_route(
179 name='repo_files_add_file',
187 name='repo_files_add_file',
180 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
188 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
181 repo_route=True)
189 repo_route=True)
182 config.add_route(
190 config.add_route(
183 name='repo_files_upload_file',
191 name='repo_files_upload_file',
184 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
192 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
185 repo_route=True)
193 repo_route=True)
186 config.add_route(
194 config.add_route(
187 name='repo_files_create_file',
195 name='repo_files_create_file',
188 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
196 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
189 repo_route=True)
197 repo_route=True)
190
198
191 # Refs data
199 # Refs data
192 config.add_route(
200 config.add_route(
193 name='repo_refs_data',
201 name='repo_refs_data',
194 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
202 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
195
203
196 config.add_route(
204 config.add_route(
197 name='repo_refs_changelog_data',
205 name='repo_refs_changelog_data',
198 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
206 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
199
207
200 config.add_route(
208 config.add_route(
201 name='repo_stats',
209 name='repo_stats',
202 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
210 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
203
211
204 # Commits
212 # Commits
205 config.add_route(
213 config.add_route(
206 name='repo_commits',
214 name='repo_commits',
207 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
215 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
208 config.add_route(
216 config.add_route(
209 name='repo_commits_file',
217 name='repo_commits_file',
210 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
218 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
211 config.add_route(
219 config.add_route(
212 name='repo_commits_elements',
220 name='repo_commits_elements',
213 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
221 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
214 config.add_route(
222 config.add_route(
215 name='repo_commits_elements_file',
223 name='repo_commits_elements_file',
216 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
224 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
217
225
218 # Changelog (old deprecated name for commits page)
226 # Changelog (old deprecated name for commits page)
219 config.add_route(
227 config.add_route(
220 name='repo_changelog',
228 name='repo_changelog',
221 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
229 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
222 config.add_route(
230 config.add_route(
223 name='repo_changelog_file',
231 name='repo_changelog_file',
224 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
232 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
225
233
226 # Compare
234 # Compare
227 config.add_route(
235 config.add_route(
228 name='repo_compare_select',
236 name='repo_compare_select',
229 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
237 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
230
238
231 config.add_route(
239 config.add_route(
232 name='repo_compare',
240 name='repo_compare',
233 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
241 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
234
242
235 # Tags
243 # Tags
236 config.add_route(
244 config.add_route(
237 name='tags_home',
245 name='tags_home',
238 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
246 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
239
247
240 # Branches
248 # Branches
241 config.add_route(
249 config.add_route(
242 name='branches_home',
250 name='branches_home',
243 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
251 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
244
252
245 # Bookmarks
253 # Bookmarks
246 config.add_route(
254 config.add_route(
247 name='bookmarks_home',
255 name='bookmarks_home',
248 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
256 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
249
257
250 # Forks
258 # Forks
251 config.add_route(
259 config.add_route(
252 name='repo_fork_new',
260 name='repo_fork_new',
253 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
261 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
254 repo_forbid_when_archived=True,
262 repo_forbid_when_archived=True,
255 repo_accepted_types=['hg', 'git'])
263 repo_accepted_types=['hg', 'git'])
256
264
257 config.add_route(
265 config.add_route(
258 name='repo_fork_create',
266 name='repo_fork_create',
259 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
267 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
260 repo_forbid_when_archived=True,
268 repo_forbid_when_archived=True,
261 repo_accepted_types=['hg', 'git'])
269 repo_accepted_types=['hg', 'git'])
262
270
263 config.add_route(
271 config.add_route(
264 name='repo_forks_show_all',
272 name='repo_forks_show_all',
265 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
273 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
266 repo_accepted_types=['hg', 'git'])
274 repo_accepted_types=['hg', 'git'])
267 config.add_route(
275 config.add_route(
268 name='repo_forks_data',
276 name='repo_forks_data',
269 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
277 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
270 repo_accepted_types=['hg', 'git'])
278 repo_accepted_types=['hg', 'git'])
271
279
272 # Pull Requests
280 # Pull Requests
273 config.add_route(
281 config.add_route(
274 name='pullrequest_show',
282 name='pullrequest_show',
275 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
283 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
276 repo_route=True)
284 repo_route=True)
277
285
278 config.add_route(
286 config.add_route(
279 name='pullrequest_show_all',
287 name='pullrequest_show_all',
280 pattern='/{repo_name:.*?[^/]}/pull-request',
288 pattern='/{repo_name:.*?[^/]}/pull-request',
281 repo_route=True, repo_accepted_types=['hg', 'git'])
289 repo_route=True, repo_accepted_types=['hg', 'git'])
282
290
283 config.add_route(
291 config.add_route(
284 name='pullrequest_show_all_data',
292 name='pullrequest_show_all_data',
285 pattern='/{repo_name:.*?[^/]}/pull-request-data',
293 pattern='/{repo_name:.*?[^/]}/pull-request-data',
286 repo_route=True, repo_accepted_types=['hg', 'git'])
294 repo_route=True, repo_accepted_types=['hg', 'git'])
287
295
288 config.add_route(
296 config.add_route(
289 name='pullrequest_repo_refs',
297 name='pullrequest_repo_refs',
290 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
298 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
291 repo_route=True)
299 repo_route=True)
292
300
293 config.add_route(
301 config.add_route(
294 name='pullrequest_repo_targets',
302 name='pullrequest_repo_targets',
295 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
303 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
296 repo_route=True)
304 repo_route=True)
297
305
298 config.add_route(
306 config.add_route(
299 name='pullrequest_new',
307 name='pullrequest_new',
300 pattern='/{repo_name:.*?[^/]}/pull-request/new',
308 pattern='/{repo_name:.*?[^/]}/pull-request/new',
301 repo_route=True, repo_accepted_types=['hg', 'git'],
309 repo_route=True, repo_accepted_types=['hg', 'git'],
302 repo_forbid_when_archived=True)
310 repo_forbid_when_archived=True)
303
311
304 config.add_route(
312 config.add_route(
305 name='pullrequest_create',
313 name='pullrequest_create',
306 pattern='/{repo_name:.*?[^/]}/pull-request/create',
314 pattern='/{repo_name:.*?[^/]}/pull-request/create',
307 repo_route=True, repo_accepted_types=['hg', 'git'],
315 repo_route=True, repo_accepted_types=['hg', 'git'],
308 repo_forbid_when_archived=True)
316 repo_forbid_when_archived=True)
309
317
310 config.add_route(
318 config.add_route(
311 name='pullrequest_update',
319 name='pullrequest_update',
312 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
320 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
313 repo_route=True, repo_forbid_when_archived=True)
321 repo_route=True, repo_forbid_when_archived=True)
314
322
315 config.add_route(
323 config.add_route(
316 name='pullrequest_merge',
324 name='pullrequest_merge',
317 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
325 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
318 repo_route=True, repo_forbid_when_archived=True)
326 repo_route=True, repo_forbid_when_archived=True)
319
327
320 config.add_route(
328 config.add_route(
321 name='pullrequest_delete',
329 name='pullrequest_delete',
322 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
330 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
323 repo_route=True, repo_forbid_when_archived=True)
331 repo_route=True, repo_forbid_when_archived=True)
324
332
325 config.add_route(
333 config.add_route(
326 name='pullrequest_comment_create',
334 name='pullrequest_comment_create',
327 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
335 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
328 repo_route=True)
336 repo_route=True)
329
337
330 config.add_route(
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 name='pullrequest_comment_delete',
344 name='pullrequest_comment_delete',
332 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
345 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
333 repo_route=True, repo_accepted_types=['hg', 'git'])
346 repo_route=True, repo_accepted_types=['hg', 'git'])
334
347
335 # Artifacts, (EE feature)
348 # Artifacts, (EE feature)
336 config.add_route(
349 config.add_route(
337 name='repo_artifacts_list',
350 name='repo_artifacts_list',
338 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
351 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
339
352
340 # Settings
353 # Settings
341 config.add_route(
354 config.add_route(
342 name='edit_repo',
355 name='edit_repo',
343 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
356 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
344 # update is POST on edit_repo
357 # update is POST on edit_repo
345
358
346 # Settings advanced
359 # Settings advanced
347 config.add_route(
360 config.add_route(
348 name='edit_repo_advanced',
361 name='edit_repo_advanced',
349 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
362 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
350 config.add_route(
363 config.add_route(
351 name='edit_repo_advanced_archive',
364 name='edit_repo_advanced_archive',
352 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
365 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
353 config.add_route(
366 config.add_route(
354 name='edit_repo_advanced_delete',
367 name='edit_repo_advanced_delete',
355 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
368 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
356 config.add_route(
369 config.add_route(
357 name='edit_repo_advanced_locking',
370 name='edit_repo_advanced_locking',
358 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
371 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
359 config.add_route(
372 config.add_route(
360 name='edit_repo_advanced_journal',
373 name='edit_repo_advanced_journal',
361 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
374 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
362 config.add_route(
375 config.add_route(
363 name='edit_repo_advanced_fork',
376 name='edit_repo_advanced_fork',
364 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
377 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
365
378
366 config.add_route(
379 config.add_route(
367 name='edit_repo_advanced_hooks',
380 name='edit_repo_advanced_hooks',
368 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
381 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
369
382
370 # Caches
383 # Caches
371 config.add_route(
384 config.add_route(
372 name='edit_repo_caches',
385 name='edit_repo_caches',
373 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
386 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
374
387
375 # Permissions
388 # Permissions
376 config.add_route(
389 config.add_route(
377 name='edit_repo_perms',
390 name='edit_repo_perms',
378 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
391 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
379
392
380 config.add_route(
393 config.add_route(
381 name='edit_repo_perms_set_private',
394 name='edit_repo_perms_set_private',
382 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
395 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
383
396
384 # Permissions Branch (EE feature)
397 # Permissions Branch (EE feature)
385 config.add_route(
398 config.add_route(
386 name='edit_repo_perms_branch',
399 name='edit_repo_perms_branch',
387 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
400 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
388 config.add_route(
401 config.add_route(
389 name='edit_repo_perms_branch_delete',
402 name='edit_repo_perms_branch_delete',
390 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
403 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
391 repo_route=True)
404 repo_route=True)
392
405
393 # Maintenance
406 # Maintenance
394 config.add_route(
407 config.add_route(
395 name='edit_repo_maintenance',
408 name='edit_repo_maintenance',
396 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
409 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
397
410
398 config.add_route(
411 config.add_route(
399 name='edit_repo_maintenance_execute',
412 name='edit_repo_maintenance_execute',
400 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
413 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
401
414
402 # Fields
415 # Fields
403 config.add_route(
416 config.add_route(
404 name='edit_repo_fields',
417 name='edit_repo_fields',
405 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
418 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
406 config.add_route(
419 config.add_route(
407 name='edit_repo_fields_create',
420 name='edit_repo_fields_create',
408 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
421 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
409 config.add_route(
422 config.add_route(
410 name='edit_repo_fields_delete',
423 name='edit_repo_fields_delete',
411 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
424 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
412
425
413 # Locking
426 # Locking
414 config.add_route(
427 config.add_route(
415 name='repo_edit_toggle_locking',
428 name='repo_edit_toggle_locking',
416 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
429 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
417
430
418 # Remote
431 # Remote
419 config.add_route(
432 config.add_route(
420 name='edit_repo_remote',
433 name='edit_repo_remote',
421 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
434 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
422 config.add_route(
435 config.add_route(
423 name='edit_repo_remote_pull',
436 name='edit_repo_remote_pull',
424 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
437 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
425 config.add_route(
438 config.add_route(
426 name='edit_repo_remote_push',
439 name='edit_repo_remote_push',
427 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
440 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
428
441
429 # Statistics
442 # Statistics
430 config.add_route(
443 config.add_route(
431 name='edit_repo_statistics',
444 name='edit_repo_statistics',
432 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
445 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
433 config.add_route(
446 config.add_route(
434 name='edit_repo_statistics_reset',
447 name='edit_repo_statistics_reset',
435 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
448 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
436
449
437 # Issue trackers
450 # Issue trackers
438 config.add_route(
451 config.add_route(
439 name='edit_repo_issuetracker',
452 name='edit_repo_issuetracker',
440 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
453 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
441 config.add_route(
454 config.add_route(
442 name='edit_repo_issuetracker_test',
455 name='edit_repo_issuetracker_test',
443 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
456 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
444 config.add_route(
457 config.add_route(
445 name='edit_repo_issuetracker_delete',
458 name='edit_repo_issuetracker_delete',
446 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
459 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
447 config.add_route(
460 config.add_route(
448 name='edit_repo_issuetracker_update',
461 name='edit_repo_issuetracker_update',
449 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
462 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
450
463
451 # VCS Settings
464 # VCS Settings
452 config.add_route(
465 config.add_route(
453 name='edit_repo_vcs',
466 name='edit_repo_vcs',
454 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
467 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
455 config.add_route(
468 config.add_route(
456 name='edit_repo_vcs_update',
469 name='edit_repo_vcs_update',
457 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
470 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
458
471
459 # svn pattern
472 # svn pattern
460 config.add_route(
473 config.add_route(
461 name='edit_repo_vcs_svn_pattern_delete',
474 name='edit_repo_vcs_svn_pattern_delete',
462 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
475 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
463
476
464 # Repo Review Rules (EE feature)
477 # Repo Review Rules (EE feature)
465 config.add_route(
478 config.add_route(
466 name='repo_reviewers',
479 name='repo_reviewers',
467 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
480 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
468
481
469 config.add_route(
482 config.add_route(
470 name='repo_default_reviewers_data',
483 name='repo_default_reviewers_data',
471 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
484 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
472
485
473 # Repo Automation (EE feature)
486 # Repo Automation (EE feature)
474 config.add_route(
487 config.add_route(
475 name='repo_automation',
488 name='repo_automation',
476 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
489 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
477
490
478 # Strip
491 # Strip
479 config.add_route(
492 config.add_route(
480 name='edit_repo_strip',
493 name='edit_repo_strip',
481 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
494 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
482
495
483 config.add_route(
496 config.add_route(
484 name='strip_check',
497 name='strip_check',
485 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
498 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
486
499
487 config.add_route(
500 config.add_route(
488 name='strip_execute',
501 name='strip_execute',
489 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
502 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
490
503
491 # Audit logs
504 # Audit logs
492 config.add_route(
505 config.add_route(
493 name='edit_repo_audit_logs',
506 name='edit_repo_audit_logs',
494 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
507 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
495
508
496 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
509 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
497 config.add_route(
510 config.add_route(
498 name='rss_feed_home',
511 name='rss_feed_home',
499 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
512 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
500
513
501 config.add_route(
514 config.add_route(
502 name='atom_feed_home',
515 name='atom_feed_home',
503 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
516 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
504
517
505 config.add_route(
518 config.add_route(
506 name='rss_feed_home_old',
519 name='rss_feed_home_old',
507 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
520 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
508
521
509 config.add_route(
522 config.add_route(
510 name='atom_feed_home_old',
523 name='atom_feed_home_old',
511 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
524 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
512
525
513 # NOTE(marcink): needs to be at the end for catch-all
526 # NOTE(marcink): needs to be at the end for catch-all
514 add_route_with_slash(
527 add_route_with_slash(
515 config,
528 config,
516 name='repo_summary',
529 name='repo_summary',
517 pattern='/{repo_name:.*?[^/]}', repo_route=True)
530 pattern='/{repo_name:.*?[^/]}', repo_route=True)
518
531
519 # Scan module for configuration decorators.
532 # Scan module for configuration decorators.
520 config.scan('.views', ignore='.tests')
533 config.scan('.views', ignore='.tests')
@@ -1,348 +1,507 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import TestController
23 from rhodecode.tests import TestController
24
24
25 from rhodecode.model.db import ChangesetComment, Notification
25 from rhodecode.model.db import ChangesetComment, Notification
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28
28
29
29
30 def route_path(name, params=None, **kwargs):
30 def route_path(name, params=None, **kwargs):
31 import urllib
31 import urllib
32
32
33 base_url = {
33 base_url = {
34 'repo_commit': '/{repo_name}/changeset/{commit_id}',
34 'repo_commit': '/{repo_name}/changeset/{commit_id}',
35 'repo_commit_comment_create': '/{repo_name}/changeset/{commit_id}/comment/create',
35 'repo_commit_comment_create': '/{repo_name}/changeset/{commit_id}/comment/create',
36 'repo_commit_comment_preview': '/{repo_name}/changeset/{commit_id}/comment/preview',
36 'repo_commit_comment_preview': '/{repo_name}/changeset/{commit_id}/comment/preview',
37 'repo_commit_comment_delete': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete',
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 }[name].format(**kwargs)
39 }[name].format(**kwargs)
39
40
40 if params:
41 if params:
41 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 return base_url
43 return base_url
43
44
44
45
45 @pytest.mark.backends("git", "hg", "svn")
46 @pytest.mark.backends("git", "hg", "svn")
46 class TestRepoCommitCommentsView(TestController):
47 class TestRepoCommitCommentsView(TestController):
47
48
48 @pytest.fixture(autouse=True)
49 @pytest.fixture(autouse=True)
49 def prepare(self, request, baseapp):
50 def prepare(self, request, baseapp):
50 for x in ChangesetComment.query().all():
51 for x in ChangesetComment.query().all():
51 Session().delete(x)
52 Session().delete(x)
52 Session().commit()
53 Session().commit()
53
54
54 for x in Notification.query().all():
55 for x in Notification.query().all():
55 Session().delete(x)
56 Session().delete(x)
56 Session().commit()
57 Session().commit()
57
58
58 request.addfinalizer(self.cleanup)
59 request.addfinalizer(self.cleanup)
59
60
60 def cleanup(self):
61 def cleanup(self):
61 for x in ChangesetComment.query().all():
62 for x in ChangesetComment.query().all():
62 Session().delete(x)
63 Session().delete(x)
63 Session().commit()
64 Session().commit()
64
65
65 for x in Notification.query().all():
66 for x in Notification.query().all():
66 Session().delete(x)
67 Session().delete(x)
67 Session().commit()
68 Session().commit()
68
69
69 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
70 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
70 def test_create(self, comment_type, backend):
71 def test_create(self, comment_type, backend):
71 self.log_user()
72 self.log_user()
72 commit = backend.repo.get_commit('300')
73 commit = backend.repo.get_commit('300')
73 commit_id = commit.raw_id
74 commit_id = commit.raw_id
74 text = u'CommentOnCommit'
75 text = u'CommentOnCommit'
75
76
76 params = {'text': text, 'csrf_token': self.csrf_token,
77 params = {'text': text, 'csrf_token': self.csrf_token,
77 'comment_type': comment_type}
78 'comment_type': comment_type}
78 self.app.post(
79 self.app.post(
79 route_path('repo_commit_comment_create',
80 route_path('repo_commit_comment_create',
80 repo_name=backend.repo_name, commit_id=commit_id),
81 repo_name=backend.repo_name, commit_id=commit_id),
81 params=params)
82 params=params)
82
83
83 response = self.app.get(
84 response = self.app.get(
84 route_path('repo_commit',
85 route_path('repo_commit',
85 repo_name=backend.repo_name, commit_id=commit_id))
86 repo_name=backend.repo_name, commit_id=commit_id))
86
87
87 # test DB
88 # test DB
88 assert ChangesetComment.query().count() == 1
89 assert ChangesetComment.query().count() == 1
89 assert_comment_links(response, ChangesetComment.query().count(), 0)
90 assert_comment_links(response, ChangesetComment.query().count(), 0)
90
91
91 assert Notification.query().count() == 1
92 assert Notification.query().count() == 1
92 assert ChangesetComment.query().count() == 1
93 assert ChangesetComment.query().count() == 1
93
94
94 notification = Notification.query().all()[0]
95 notification = Notification.query().all()[0]
95
96
96 comment_id = ChangesetComment.query().first().comment_id
97 comment_id = ChangesetComment.query().first().comment_id
97 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
98 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
98
99
99 author = notification.created_by_user.username_and_name
100 author = notification.created_by_user.username_and_name
100 sbj = '@{0} left a {1} on commit `{2}` in the `{3}` repository'.format(
101 sbj = '@{0} left a {1} on commit `{2}` in the `{3}` repository'.format(
101 author, comment_type, h.show_id(commit), backend.repo_name)
102 author, comment_type, h.show_id(commit), backend.repo_name)
102 assert sbj == notification.subject
103 assert sbj == notification.subject
103
104
104 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
105 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
105 backend.repo_name, commit_id, comment_id))
106 backend.repo_name, commit_id, comment_id))
106 assert lnk in notification.body
107 assert lnk in notification.body
107
108
108 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
109 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
109 def test_create_inline(self, comment_type, backend):
110 def test_create_inline(self, comment_type, backend):
110 self.log_user()
111 self.log_user()
111 commit = backend.repo.get_commit('300')
112 commit = backend.repo.get_commit('300')
112 commit_id = commit.raw_id
113 commit_id = commit.raw_id
113 text = u'CommentOnCommit'
114 text = u'CommentOnCommit'
114 f_path = 'vcs/web/simplevcs/views/repository.py'
115 f_path = 'vcs/web/simplevcs/views/repository.py'
115 line = 'n1'
116 line = 'n1'
116
117
117 params = {'text': text, 'f_path': f_path, 'line': line,
118 params = {'text': text, 'f_path': f_path, 'line': line,
118 'comment_type': comment_type,
119 'comment_type': comment_type,
119 'csrf_token': self.csrf_token}
120 'csrf_token': self.csrf_token}
120
121
121 self.app.post(
122 self.app.post(
122 route_path('repo_commit_comment_create',
123 route_path('repo_commit_comment_create',
123 repo_name=backend.repo_name, commit_id=commit_id),
124 repo_name=backend.repo_name, commit_id=commit_id),
124 params=params)
125 params=params)
125
126
126 response = self.app.get(
127 response = self.app.get(
127 route_path('repo_commit',
128 route_path('repo_commit',
128 repo_name=backend.repo_name, commit_id=commit_id))
129 repo_name=backend.repo_name, commit_id=commit_id))
129
130
130 # test DB
131 # test DB
131 assert ChangesetComment.query().count() == 1
132 assert ChangesetComment.query().count() == 1
132 assert_comment_links(response, 0, ChangesetComment.query().count())
133 assert_comment_links(response, 0, ChangesetComment.query().count())
133
134
134 if backend.alias == 'svn':
135 if backend.alias == 'svn':
135 response.mustcontain(
136 response.mustcontain(
136 '''data-f-path="vcs/commands/summary.py" '''
137 '''data-f-path="vcs/commands/summary.py" '''
137 '''data-anchor-id="c-300-ad05457a43f8"'''
138 '''data-anchor-id="c-300-ad05457a43f8"'''
138 )
139 )
139 if backend.alias == 'git':
140 if backend.alias == 'git':
140 response.mustcontain(
141 response.mustcontain(
141 '''data-f-path="vcs/backends/hg.py" '''
142 '''data-f-path="vcs/backends/hg.py" '''
142 '''data-anchor-id="c-883e775e89ea-9c390eb52cd6"'''
143 '''data-anchor-id="c-883e775e89ea-9c390eb52cd6"'''
143 )
144 )
144
145
145 if backend.alias == 'hg':
146 if backend.alias == 'hg':
146 response.mustcontain(
147 response.mustcontain(
147 '''data-f-path="vcs/backends/hg.py" '''
148 '''data-f-path="vcs/backends/hg.py" '''
148 '''data-anchor-id="c-e58d85a3973b-9c390eb52cd6"'''
149 '''data-anchor-id="c-e58d85a3973b-9c390eb52cd6"'''
149 )
150 )
150
151
151 assert Notification.query().count() == 1
152 assert Notification.query().count() == 1
152 assert ChangesetComment.query().count() == 1
153 assert ChangesetComment.query().count() == 1
153
154
154 notification = Notification.query().all()[0]
155 notification = Notification.query().all()[0]
155 comment = ChangesetComment.query().first()
156 comment = ChangesetComment.query().first()
156 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
157 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
157
158
158 assert comment.revision == commit_id
159 assert comment.revision == commit_id
159
160
160 author = notification.created_by_user.username_and_name
161 author = notification.created_by_user.username_and_name
161 sbj = '@{0} left a {1} on file `{2}` in commit `{3}` in the `{4}` repository'.format(
162 sbj = '@{0} left a {1} on file `{2}` in commit `{3}` in the `{4}` repository'.format(
162 author, comment_type, f_path, h.show_id(commit), backend.repo_name)
163 author, comment_type, f_path, h.show_id(commit), backend.repo_name)
163
164
164 assert sbj == notification.subject
165 assert sbj == notification.subject
165
166
166 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
167 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
167 backend.repo_name, commit_id, comment.comment_id))
168 backend.repo_name, commit_id, comment.comment_id))
168 assert lnk in notification.body
169 assert lnk in notification.body
169 assert 'on line n1' in notification.body
170 assert 'on line n1' in notification.body
170
171
171 def test_create_with_mention(self, backend):
172 def test_create_with_mention(self, backend):
172 self.log_user()
173 self.log_user()
173
174
174 commit_id = backend.repo.get_commit('300').raw_id
175 commit_id = backend.repo.get_commit('300').raw_id
175 text = u'@test_regular check CommentOnCommit'
176 text = u'@test_regular check CommentOnCommit'
176
177
177 params = {'text': text, 'csrf_token': self.csrf_token}
178 params = {'text': text, 'csrf_token': self.csrf_token}
178 self.app.post(
179 self.app.post(
179 route_path('repo_commit_comment_create',
180 route_path('repo_commit_comment_create',
180 repo_name=backend.repo_name, commit_id=commit_id),
181 repo_name=backend.repo_name, commit_id=commit_id),
181 params=params)
182 params=params)
182
183
183 response = self.app.get(
184 response = self.app.get(
184 route_path('repo_commit',
185 route_path('repo_commit',
185 repo_name=backend.repo_name, commit_id=commit_id))
186 repo_name=backend.repo_name, commit_id=commit_id))
186 # test DB
187 # test DB
187 assert ChangesetComment.query().count() == 1
188 assert ChangesetComment.query().count() == 1
188 assert_comment_links(response, ChangesetComment.query().count(), 0)
189 assert_comment_links(response, ChangesetComment.query().count(), 0)
189
190
190 notification = Notification.query().one()
191 notification = Notification.query().one()
191
192
192 assert len(notification.recipients) == 2
193 assert len(notification.recipients) == 2
193 users = [x.username for x in notification.recipients]
194 users = [x.username for x in notification.recipients]
194
195
195 # test_regular gets notification by @mention
196 # test_regular gets notification by @mention
196 assert sorted(users) == [u'test_admin', u'test_regular']
197 assert sorted(users) == [u'test_admin', u'test_regular']
197
198
198 def test_create_with_status_change(self, backend):
199 def test_create_with_status_change(self, backend):
199 self.log_user()
200 self.log_user()
200 commit = backend.repo.get_commit('300')
201 commit = backend.repo.get_commit('300')
201 commit_id = commit.raw_id
202 commit_id = commit.raw_id
202 text = u'CommentOnCommit'
203 text = u'CommentOnCommit'
203 f_path = 'vcs/web/simplevcs/views/repository.py'
204 f_path = 'vcs/web/simplevcs/views/repository.py'
204 line = 'n1'
205 line = 'n1'
205
206
206 params = {'text': text, 'changeset_status': 'approved',
207 params = {'text': text, 'changeset_status': 'approved',
207 'csrf_token': self.csrf_token}
208 'csrf_token': self.csrf_token}
208
209
209 self.app.post(
210 self.app.post(
210 route_path(
211 route_path(
211 'repo_commit_comment_create',
212 'repo_commit_comment_create',
212 repo_name=backend.repo_name, commit_id=commit_id),
213 repo_name=backend.repo_name, commit_id=commit_id),
213 params=params)
214 params=params)
214
215
215 response = self.app.get(
216 response = self.app.get(
216 route_path('repo_commit',
217 route_path('repo_commit',
217 repo_name=backend.repo_name, commit_id=commit_id))
218 repo_name=backend.repo_name, commit_id=commit_id))
218
219
219 # test DB
220 # test DB
220 assert ChangesetComment.query().count() == 1
221 assert ChangesetComment.query().count() == 1
221 assert_comment_links(response, ChangesetComment.query().count(), 0)
222 assert_comment_links(response, ChangesetComment.query().count(), 0)
222
223
223 assert Notification.query().count() == 1
224 assert Notification.query().count() == 1
224 assert ChangesetComment.query().count() == 1
225 assert ChangesetComment.query().count() == 1
225
226
226 notification = Notification.query().all()[0]
227 notification = Notification.query().all()[0]
227
228
228 comment_id = ChangesetComment.query().first().comment_id
229 comment_id = ChangesetComment.query().first().comment_id
229 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
230 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
230
231
231 author = notification.created_by_user.username_and_name
232 author = notification.created_by_user.username_and_name
232 sbj = '[status: Approved] @{0} left a note on commit `{1}` in the `{2}` repository'.format(
233 sbj = '[status: Approved] @{0} left a note on commit `{1}` in the `{2}` repository'.format(
233 author, h.show_id(commit), backend.repo_name)
234 author, h.show_id(commit), backend.repo_name)
234 assert sbj == notification.subject
235 assert sbj == notification.subject
235
236
236 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
237 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
237 backend.repo_name, commit_id, comment_id))
238 backend.repo_name, commit_id, comment_id))
238 assert lnk in notification.body
239 assert lnk in notification.body
239
240
240 def test_delete(self, backend):
241 def test_delete(self, backend):
241 self.log_user()
242 self.log_user()
242 commit_id = backend.repo.get_commit('300').raw_id
243 commit_id = backend.repo.get_commit('300').raw_id
243 text = u'CommentOnCommit'
244 text = u'CommentOnCommit'
244
245
245 params = {'text': text, 'csrf_token': self.csrf_token}
246 params = {'text': text, 'csrf_token': self.csrf_token}
246 self.app.post(
247 self.app.post(
247 route_path(
248 route_path(
248 'repo_commit_comment_create',
249 'repo_commit_comment_create',
249 repo_name=backend.repo_name, commit_id=commit_id),
250 repo_name=backend.repo_name, commit_id=commit_id),
250 params=params)
251 params=params)
251
252
252 comments = ChangesetComment.query().all()
253 comments = ChangesetComment.query().all()
253 assert len(comments) == 1
254 assert len(comments) == 1
254 comment_id = comments[0].comment_id
255 comment_id = comments[0].comment_id
255
256
256 self.app.post(
257 self.app.post(
257 route_path('repo_commit_comment_delete',
258 route_path('repo_commit_comment_delete',
258 repo_name=backend.repo_name,
259 repo_name=backend.repo_name,
259 commit_id=commit_id,
260 commit_id=commit_id,
260 comment_id=comment_id),
261 comment_id=comment_id),
261 params={'csrf_token': self.csrf_token})
262 params={'csrf_token': self.csrf_token})
262
263
263 comments = ChangesetComment.query().all()
264 comments = ChangesetComment.query().all()
264 assert len(comments) == 0
265 assert len(comments) == 0
265
266
266 response = self.app.get(
267 response = self.app.get(
267 route_path('repo_commit',
268 route_path('repo_commit',
268 repo_name=backend.repo_name, commit_id=commit_id))
269 repo_name=backend.repo_name, commit_id=commit_id))
269 assert_comment_links(response, 0, 0)
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 def test_delete_forbidden_for_immutable_comments(self, backend):
430 def test_delete_forbidden_for_immutable_comments(self, backend):
272 self.log_user()
431 self.log_user()
273 commit_id = backend.repo.get_commit('300').raw_id
432 commit_id = backend.repo.get_commit('300').raw_id
274 text = u'CommentOnCommit'
433 text = u'CommentOnCommit'
275
434
276 params = {'text': text, 'csrf_token': self.csrf_token}
435 params = {'text': text, 'csrf_token': self.csrf_token}
277 self.app.post(
436 self.app.post(
278 route_path(
437 route_path(
279 'repo_commit_comment_create',
438 'repo_commit_comment_create',
280 repo_name=backend.repo_name, commit_id=commit_id),
439 repo_name=backend.repo_name, commit_id=commit_id),
281 params=params)
440 params=params)
282
441
283 comments = ChangesetComment.query().all()
442 comments = ChangesetComment.query().all()
284 assert len(comments) == 1
443 assert len(comments) == 1
285 comment_id = comments[0].comment_id
444 comment_id = comments[0].comment_id
286
445
287 comment = ChangesetComment.get(comment_id)
446 comment = ChangesetComment.get(comment_id)
288 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
447 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
289 Session().add(comment)
448 Session().add(comment)
290 Session().commit()
449 Session().commit()
291
450
292 self.app.post(
451 self.app.post(
293 route_path('repo_commit_comment_delete',
452 route_path('repo_commit_comment_delete',
294 repo_name=backend.repo_name,
453 repo_name=backend.repo_name,
295 commit_id=commit_id,
454 commit_id=commit_id,
296 comment_id=comment_id),
455 comment_id=comment_id),
297 params={'csrf_token': self.csrf_token},
456 params={'csrf_token': self.csrf_token},
298 status=403)
457 status=403)
299
458
300 @pytest.mark.parametrize('renderer, text_input, output', [
459 @pytest.mark.parametrize('renderer, text_input, output', [
301 ('rst', 'plain text', '<p>plain text</p>'),
460 ('rst', 'plain text', '<p>plain text</p>'),
302 ('rst', 'header\n======', '<h1 class="title">header</h1>'),
461 ('rst', 'header\n======', '<h1 class="title">header</h1>'),
303 ('rst', '*italics*', '<em>italics</em>'),
462 ('rst', '*italics*', '<em>italics</em>'),
304 ('rst', '**bold**', '<strong>bold</strong>'),
463 ('rst', '**bold**', '<strong>bold</strong>'),
305 ('markdown', 'plain text', '<p>plain text</p>'),
464 ('markdown', 'plain text', '<p>plain text</p>'),
306 ('markdown', '# header', '<h1>header</h1>'),
465 ('markdown', '# header', '<h1>header</h1>'),
307 ('markdown', '*italics*', '<em>italics</em>'),
466 ('markdown', '*italics*', '<em>italics</em>'),
308 ('markdown', '**bold**', '<strong>bold</strong>'),
467 ('markdown', '**bold**', '<strong>bold</strong>'),
309 ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain',
468 ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain',
310 'md-header', 'md-italics', 'md-bold', ])
469 'md-header', 'md-italics', 'md-bold', ])
311 def test_preview(self, renderer, text_input, output, backend, xhr_header):
470 def test_preview(self, renderer, text_input, output, backend, xhr_header):
312 self.log_user()
471 self.log_user()
313 params = {
472 params = {
314 'renderer': renderer,
473 'renderer': renderer,
315 'text': text_input,
474 'text': text_input,
316 'csrf_token': self.csrf_token
475 'csrf_token': self.csrf_token
317 }
476 }
318 commit_id = '0' * 16 # fake this for tests
477 commit_id = '0' * 16 # fake this for tests
319 response = self.app.post(
478 response = self.app.post(
320 route_path('repo_commit_comment_preview',
479 route_path('repo_commit_comment_preview',
321 repo_name=backend.repo_name, commit_id=commit_id,),
480 repo_name=backend.repo_name, commit_id=commit_id,),
322 params=params,
481 params=params,
323 extra_environ=xhr_header)
482 extra_environ=xhr_header)
324
483
325 response.mustcontain(output)
484 response.mustcontain(output)
326
485
327
486
328 def assert_comment_links(response, comments, inline_comments):
487 def assert_comment_links(response, comments, inline_comments):
329 if comments == 1:
488 if comments == 1:
330 comments_text = "%d General" % comments
489 comments_text = "%d General" % comments
331 else:
490 else:
332 comments_text = "%d General" % comments
491 comments_text = "%d General" % comments
333
492
334 if inline_comments == 1:
493 if inline_comments == 1:
335 inline_comments_text = "%d Inline" % inline_comments
494 inline_comments_text = "%d Inline" % inline_comments
336 else:
495 else:
337 inline_comments_text = "%d Inline" % inline_comments
496 inline_comments_text = "%d Inline" % inline_comments
338
497
339 if comments:
498 if comments:
340 response.mustcontain('<a href="#comments">%s</a>,' % comments_text)
499 response.mustcontain('<a href="#comments">%s</a>,' % comments_text)
341 else:
500 else:
342 response.mustcontain(comments_text)
501 response.mustcontain(comments_text)
343
502
344 if inline_comments:
503 if inline_comments:
345 response.mustcontain(
504 response.mustcontain(
346 'id="inline-comments-counter">%s' % inline_comments_text)
505 'id="inline-comments-counter">%s' % inline_comments_text)
347 else:
506 else:
348 response.mustcontain(inline_comments_text)
507 response.mustcontain(inline_comments_text)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
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