Show More
@@ -1,490 +1,490 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 | kallithea.controllers.changeset |
|
15 | kallithea.controllers.changeset | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | changeset controller showing changes between revisions |
|
18 | changeset controller showing changes between revisions | |
19 |
|
19 | |||
20 | This file was forked by the Kallithea project in July 2014. |
|
20 | This file was forked by the Kallithea project in July 2014. | |
21 | Original author and date, and relevant copyright and licensing information is below: |
|
21 | Original author and date, and relevant copyright and licensing information is below: | |
22 | :created_on: Apr 25, 2010 |
|
22 | :created_on: Apr 25, 2010 | |
23 | :author: marcink |
|
23 | :author: marcink | |
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
25 | :license: GPLv3, see LICENSE.md for more details. |
|
25 | :license: GPLv3, see LICENSE.md for more details. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 | import binascii |
|
28 | import binascii | |
29 | import logging |
|
29 | import logging | |
30 | import traceback |
|
30 | import traceback | |
31 | from collections import OrderedDict, defaultdict |
|
31 | from collections import OrderedDict, defaultdict | |
32 |
|
32 | |||
33 | from tg import request, response |
|
33 | from tg import request, response | |
34 | from tg import tmpl_context as c |
|
34 | from tg import tmpl_context as c | |
35 | from tg.i18n import ugettext as _ |
|
35 | from tg.i18n import ugettext as _ | |
36 |
from webob.exc import HTTPBadRequest, HTTPForbidden, |
|
36 | from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPNotFound | |
37 |
|
37 | |||
38 | import kallithea.lib.helpers as h |
|
38 | import kallithea.lib.helpers as h | |
39 | from kallithea.lib import diffs |
|
39 | from kallithea.lib import diffs | |
40 | from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired |
|
40 | from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired | |
41 | from kallithea.lib.base import BaseRepoController, jsonify, render |
|
41 | from kallithea.lib.base import BaseRepoController, jsonify, render | |
42 | from kallithea.lib.graphmod import graph_data |
|
42 | from kallithea.lib.graphmod import graph_data | |
43 | from kallithea.lib.utils import action_logger |
|
43 | from kallithea.lib.utils import action_logger | |
44 | from kallithea.lib.utils2 import ascii_str, safe_str |
|
44 | from kallithea.lib.utils2 import ascii_str, safe_str | |
45 | from kallithea.lib.vcs.backends.base import EmptyChangeset |
|
45 | from kallithea.lib.vcs.backends.base import EmptyChangeset | |
46 | from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError |
|
46 | from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError | |
47 | from kallithea.model.changeset_status import ChangesetStatusModel |
|
47 | from kallithea.model.changeset_status import ChangesetStatusModel | |
48 | from kallithea.model.comment import ChangesetCommentsModel |
|
48 | from kallithea.model.comment import ChangesetCommentsModel | |
49 | from kallithea.model.db import ChangesetComment, ChangesetStatus |
|
49 | from kallithea.model.db import ChangesetComment, ChangesetStatus | |
50 | from kallithea.model.meta import Session |
|
50 | from kallithea.model.meta import Session | |
51 | from kallithea.model.pull_request import PullRequestModel |
|
51 | from kallithea.model.pull_request import PullRequestModel | |
52 |
|
52 | |||
53 |
|
53 | |||
54 | log = logging.getLogger(__name__) |
|
54 | log = logging.getLogger(__name__) | |
55 |
|
55 | |||
56 |
|
56 | |||
57 | def _update_with_GET(params, GET): |
|
57 | def _update_with_GET(params, GET): | |
58 | for k in ['diff1', 'diff2', 'diff']: |
|
58 | for k in ['diff1', 'diff2', 'diff']: | |
59 | params[k] += GET.getall(k) |
|
59 | params[k] += GET.getall(k) | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | def anchor_url(revision, path, GET): |
|
62 | def anchor_url(revision, path, GET): | |
63 | fid = h.FID(revision, path) |
|
63 | fid = h.FID(revision, path) | |
64 | return h.url.current(anchor=fid, **dict(GET)) |
|
64 | return h.url.current(anchor=fid, **dict(GET)) | |
65 |
|
65 | |||
66 |
|
66 | |||
67 | def get_ignore_ws(fid, GET): |
|
67 | def get_ignore_ws(fid, GET): | |
68 | ig_ws_global = GET.get('ignorews') |
|
68 | ig_ws_global = GET.get('ignorews') | |
69 | ig_ws = [k for k in GET.getall(fid) if k.startswith('WS')] |
|
69 | ig_ws = [k for k in GET.getall(fid) if k.startswith('WS')] | |
70 | if ig_ws: |
|
70 | if ig_ws: | |
71 | try: |
|
71 | try: | |
72 | return int(ig_ws[0].split(':')[-1]) |
|
72 | return int(ig_ws[0].split(':')[-1]) | |
73 | except ValueError: |
|
73 | except ValueError: | |
74 | raise HTTPBadRequest() |
|
74 | raise HTTPBadRequest() | |
75 | return ig_ws_global |
|
75 | return ig_ws_global | |
76 |
|
76 | |||
77 |
|
77 | |||
78 | def _ignorews_url(GET, fileid=None): |
|
78 | def _ignorews_url(GET, fileid=None): | |
79 | fileid = str(fileid) if fileid else None |
|
79 | fileid = str(fileid) if fileid else None | |
80 | params = defaultdict(list) |
|
80 | params = defaultdict(list) | |
81 | _update_with_GET(params, GET) |
|
81 | _update_with_GET(params, GET) | |
82 | lbl = _('Show whitespace') |
|
82 | lbl = _('Show whitespace') | |
83 | ig_ws = get_ignore_ws(fileid, GET) |
|
83 | ig_ws = get_ignore_ws(fileid, GET) | |
84 | ln_ctx = get_line_ctx(fileid, GET) |
|
84 | ln_ctx = get_line_ctx(fileid, GET) | |
85 | # global option |
|
85 | # global option | |
86 | if fileid is None: |
|
86 | if fileid is None: | |
87 | if ig_ws is None: |
|
87 | if ig_ws is None: | |
88 | params['ignorews'] += [1] |
|
88 | params['ignorews'] += [1] | |
89 | lbl = _('Ignore whitespace') |
|
89 | lbl = _('Ignore whitespace') | |
90 | ctx_key = 'context' |
|
90 | ctx_key = 'context' | |
91 | ctx_val = ln_ctx |
|
91 | ctx_val = ln_ctx | |
92 | # per file options |
|
92 | # per file options | |
93 | else: |
|
93 | else: | |
94 | if ig_ws is None: |
|
94 | if ig_ws is None: | |
95 | params[fileid] += ['WS:1'] |
|
95 | params[fileid] += ['WS:1'] | |
96 | lbl = _('Ignore whitespace') |
|
96 | lbl = _('Ignore whitespace') | |
97 |
|
97 | |||
98 | ctx_key = fileid |
|
98 | ctx_key = fileid | |
99 | ctx_val = 'C:%s' % ln_ctx |
|
99 | ctx_val = 'C:%s' % ln_ctx | |
100 | # if we have passed in ln_ctx pass it along to our params |
|
100 | # if we have passed in ln_ctx pass it along to our params | |
101 | if ln_ctx: |
|
101 | if ln_ctx: | |
102 | params[ctx_key] += [ctx_val] |
|
102 | params[ctx_key] += [ctx_val] | |
103 |
|
103 | |||
104 | params['anchor'] = fileid |
|
104 | params['anchor'] = fileid | |
105 | icon = h.literal('<i class="icon-strike"></i>') |
|
105 | icon = h.literal('<i class="icon-strike"></i>') | |
106 | return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'}) |
|
106 | return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'}) | |
107 |
|
107 | |||
108 |
|
108 | |||
109 | def get_line_ctx(fid, GET): |
|
109 | def get_line_ctx(fid, GET): | |
110 | ln_ctx_global = GET.get('context') |
|
110 | ln_ctx_global = GET.get('context') | |
111 | if fid: |
|
111 | if fid: | |
112 | ln_ctx = [k for k in GET.getall(fid) if k.startswith('C')] |
|
112 | ln_ctx = [k for k in GET.getall(fid) if k.startswith('C')] | |
113 | else: |
|
113 | else: | |
114 | _ln_ctx = [k for k in GET if k.startswith('C')] |
|
114 | _ln_ctx = [k for k in GET if k.startswith('C')] | |
115 | ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global |
|
115 | ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global | |
116 | if ln_ctx: |
|
116 | if ln_ctx: | |
117 | ln_ctx = [ln_ctx] |
|
117 | ln_ctx = [ln_ctx] | |
118 |
|
118 | |||
119 | if ln_ctx: |
|
119 | if ln_ctx: | |
120 | retval = ln_ctx[0].split(':')[-1] |
|
120 | retval = ln_ctx[0].split(':')[-1] | |
121 | else: |
|
121 | else: | |
122 | retval = ln_ctx_global |
|
122 | retval = ln_ctx_global | |
123 |
|
123 | |||
124 | try: |
|
124 | try: | |
125 | return int(retval) |
|
125 | return int(retval) | |
126 | except Exception: |
|
126 | except Exception: | |
127 | return 3 |
|
127 | return 3 | |
128 |
|
128 | |||
129 |
|
129 | |||
130 | def _context_url(GET, fileid=None): |
|
130 | def _context_url(GET, fileid=None): | |
131 | """ |
|
131 | """ | |
132 | Generates url for context lines |
|
132 | Generates url for context lines | |
133 |
|
133 | |||
134 | :param fileid: |
|
134 | :param fileid: | |
135 | """ |
|
135 | """ | |
136 |
|
136 | |||
137 | fileid = str(fileid) if fileid else None |
|
137 | fileid = str(fileid) if fileid else None | |
138 | ig_ws = get_ignore_ws(fileid, GET) |
|
138 | ig_ws = get_ignore_ws(fileid, GET) | |
139 | ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2 |
|
139 | ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2 | |
140 |
|
140 | |||
141 | params = defaultdict(list) |
|
141 | params = defaultdict(list) | |
142 | _update_with_GET(params, GET) |
|
142 | _update_with_GET(params, GET) | |
143 |
|
143 | |||
144 | # global option |
|
144 | # global option | |
145 | if fileid is None: |
|
145 | if fileid is None: | |
146 | if ln_ctx > 0: |
|
146 | if ln_ctx > 0: | |
147 | params['context'] += [ln_ctx] |
|
147 | params['context'] += [ln_ctx] | |
148 |
|
148 | |||
149 | if ig_ws: |
|
149 | if ig_ws: | |
150 | ig_ws_key = 'ignorews' |
|
150 | ig_ws_key = 'ignorews' | |
151 | ig_ws_val = 1 |
|
151 | ig_ws_val = 1 | |
152 |
|
152 | |||
153 | # per file option |
|
153 | # per file option | |
154 | else: |
|
154 | else: | |
155 | params[fileid] += ['C:%s' % ln_ctx] |
|
155 | params[fileid] += ['C:%s' % ln_ctx] | |
156 | ig_ws_key = fileid |
|
156 | ig_ws_key = fileid | |
157 | ig_ws_val = 'WS:%s' % 1 |
|
157 | ig_ws_val = 'WS:%s' % 1 | |
158 |
|
158 | |||
159 | if ig_ws: |
|
159 | if ig_ws: | |
160 | params[ig_ws_key] += [ig_ws_val] |
|
160 | params[ig_ws_key] += [ig_ws_val] | |
161 |
|
161 | |||
162 | lbl = _('Increase diff context to %(num)s lines') % {'num': ln_ctx} |
|
162 | lbl = _('Increase diff context to %(num)s lines') % {'num': ln_ctx} | |
163 |
|
163 | |||
164 | params['anchor'] = fileid |
|
164 | params['anchor'] = fileid | |
165 | icon = h.literal('<i class="icon-sort"></i>') |
|
165 | icon = h.literal('<i class="icon-sort"></i>') | |
166 | return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'}) |
|
166 | return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'}) | |
167 |
|
167 | |||
168 |
|
168 | |||
169 | def create_cs_pr_comment(repo_name, revision=None, pull_request=None, allowed_to_change_status=True): |
|
169 | def create_cs_pr_comment(repo_name, revision=None, pull_request=None, allowed_to_change_status=True): | |
170 | """ |
|
170 | """ | |
171 | Add a comment to the specified changeset or pull request, using POST values |
|
171 | Add a comment to the specified changeset or pull request, using POST values | |
172 | from the request. |
|
172 | from the request. | |
173 |
|
173 | |||
174 | Comments can be inline (when a file path and line number is specified in |
|
174 | Comments can be inline (when a file path and line number is specified in | |
175 | POST) or general comments. |
|
175 | POST) or general comments. | |
176 | A comment can be accompanied by a review status change (accepted, rejected, |
|
176 | A comment can be accompanied by a review status change (accepted, rejected, | |
177 | etc.). Pull requests can be closed or deleted. |
|
177 | etc.). Pull requests can be closed or deleted. | |
178 |
|
178 | |||
179 | Parameter 'allowed_to_change_status' is used for both status changes and |
|
179 | Parameter 'allowed_to_change_status' is used for both status changes and | |
180 | closing of pull requests. For deleting of pull requests, more specific |
|
180 | closing of pull requests. For deleting of pull requests, more specific | |
181 | checks are done. |
|
181 | checks are done. | |
182 | """ |
|
182 | """ | |
183 |
|
183 | |||
184 | assert request.environ.get('HTTP_X_PARTIAL_XHR') |
|
184 | assert request.environ.get('HTTP_X_PARTIAL_XHR') | |
185 | if pull_request: |
|
185 | if pull_request: | |
186 | pull_request_id = pull_request.pull_request_id |
|
186 | pull_request_id = pull_request.pull_request_id | |
187 | else: |
|
187 | else: | |
188 | pull_request_id = None |
|
188 | pull_request_id = None | |
189 |
|
189 | |||
190 | status = request.POST.get('changeset_status') |
|
190 | status = request.POST.get('changeset_status') | |
191 | close_pr = request.POST.get('save_close') |
|
191 | close_pr = request.POST.get('save_close') | |
192 | delete = request.POST.get('save_delete') |
|
192 | delete = request.POST.get('save_delete') | |
193 | f_path = request.POST.get('f_path') |
|
193 | f_path = request.POST.get('f_path') | |
194 | line_no = request.POST.get('line') |
|
194 | line_no = request.POST.get('line') | |
195 |
|
195 | |||
196 | if (status or close_pr or delete) and (f_path or line_no): |
|
196 | if (status or close_pr or delete) and (f_path or line_no): | |
197 | # status votes and closing is only possible in general comments |
|
197 | # status votes and closing is only possible in general comments | |
198 | raise HTTPBadRequest() |
|
198 | raise HTTPBadRequest() | |
199 |
|
199 | |||
200 | if not allowed_to_change_status: |
|
200 | if not allowed_to_change_status: | |
201 | if status or close_pr: |
|
201 | if status or close_pr: | |
202 | h.flash(_('No permission to change status'), 'error') |
|
202 | h.flash(_('No permission to change status'), 'error') | |
203 | raise HTTPForbidden() |
|
203 | raise HTTPForbidden() | |
204 |
|
204 | |||
205 | if pull_request and delete == "delete": |
|
205 | if pull_request and delete == "delete": | |
206 | if (pull_request.owner_id == request.authuser.user_id or |
|
206 | if (pull_request.owner_id == request.authuser.user_id or | |
207 | h.HasPermissionAny('hg.admin')() or |
|
207 | h.HasPermissionAny('hg.admin')() or | |
208 | h.HasRepoPermissionLevel('admin')(pull_request.org_repo.repo_name) or |
|
208 | h.HasRepoPermissionLevel('admin')(pull_request.org_repo.repo_name) or | |
209 | h.HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name) |
|
209 | h.HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name) | |
210 | ) and not pull_request.is_closed(): |
|
210 | ) and not pull_request.is_closed(): | |
211 | PullRequestModel().delete(pull_request) |
|
211 | PullRequestModel().delete(pull_request) | |
212 | Session().commit() |
|
212 | Session().commit() | |
213 | h.flash(_('Successfully deleted pull request %s') % pull_request_id, |
|
213 | h.flash(_('Successfully deleted pull request %s') % pull_request_id, | |
214 | category='success') |
|
214 | category='success') | |
215 | return { |
|
215 | return { | |
216 | 'location': h.url('my_pullrequests'), # or repo pr list? |
|
216 | 'location': h.url('my_pullrequests'), # or repo pr list? | |
217 | } |
|
217 | } | |
218 | raise HTTPForbidden() |
|
218 | raise HTTPForbidden() | |
219 |
|
219 | |||
220 | text = request.POST.get('text', '').strip() |
|
220 | text = request.POST.get('text', '').strip() | |
221 |
|
221 | |||
222 | comment = ChangesetCommentsModel().create( |
|
222 | comment = ChangesetCommentsModel().create( | |
223 | text=text, |
|
223 | text=text, | |
224 | repo=c.db_repo.repo_id, |
|
224 | repo=c.db_repo.repo_id, | |
225 | author=request.authuser.user_id, |
|
225 | author=request.authuser.user_id, | |
226 | revision=revision, |
|
226 | revision=revision, | |
227 | pull_request=pull_request_id, |
|
227 | pull_request=pull_request_id, | |
228 | f_path=f_path or None, |
|
228 | f_path=f_path or None, | |
229 | line_no=line_no or None, |
|
229 | line_no=line_no or None, | |
230 | status_change=ChangesetStatus.get_status_lbl(status) if status else None, |
|
230 | status_change=ChangesetStatus.get_status_lbl(status) if status else None, | |
231 | closing_pr=close_pr, |
|
231 | closing_pr=close_pr, | |
232 | ) |
|
232 | ) | |
233 |
|
233 | |||
234 | if status: |
|
234 | if status: | |
235 | ChangesetStatusModel().set_status( |
|
235 | ChangesetStatusModel().set_status( | |
236 | c.db_repo.repo_id, |
|
236 | c.db_repo.repo_id, | |
237 | status, |
|
237 | status, | |
238 | request.authuser.user_id, |
|
238 | request.authuser.user_id, | |
239 | comment, |
|
239 | comment, | |
240 | revision=revision, |
|
240 | revision=revision, | |
241 | pull_request=pull_request_id, |
|
241 | pull_request=pull_request_id, | |
242 | ) |
|
242 | ) | |
243 |
|
243 | |||
244 | if pull_request: |
|
244 | if pull_request: | |
245 | action = 'user_commented_pull_request:%s' % pull_request_id |
|
245 | action = 'user_commented_pull_request:%s' % pull_request_id | |
246 | else: |
|
246 | else: | |
247 | action = 'user_commented_revision:%s' % revision |
|
247 | action = 'user_commented_revision:%s' % revision | |
248 | action_logger(request.authuser, action, c.db_repo, request.ip_addr) |
|
248 | action_logger(request.authuser, action, c.db_repo, request.ip_addr) | |
249 |
|
249 | |||
250 | if pull_request and close_pr: |
|
250 | if pull_request and close_pr: | |
251 | PullRequestModel().close_pull_request(pull_request_id) |
|
251 | PullRequestModel().close_pull_request(pull_request_id) | |
252 | action_logger(request.authuser, |
|
252 | action_logger(request.authuser, | |
253 | 'user_closed_pull_request:%s' % pull_request_id, |
|
253 | 'user_closed_pull_request:%s' % pull_request_id, | |
254 | c.db_repo, request.ip_addr) |
|
254 | c.db_repo, request.ip_addr) | |
255 |
|
255 | |||
256 | Session().commit() |
|
256 | Session().commit() | |
257 |
|
257 | |||
258 | data = { |
|
258 | data = { | |
259 | 'target_id': h.safeid(request.POST.get('f_path')), |
|
259 | 'target_id': h.safeid(request.POST.get('f_path')), | |
260 | } |
|
260 | } | |
261 | if comment is not None: |
|
261 | if comment is not None: | |
262 | c.comment = comment |
|
262 | c.comment = comment | |
263 | data.update(comment.get_dict()) |
|
263 | data.update(comment.get_dict()) | |
264 | data.update({'rendered_text': |
|
264 | data.update({'rendered_text': | |
265 | render('changeset/changeset_comment_block.html')}) |
|
265 | render('changeset/changeset_comment_block.html')}) | |
266 |
|
266 | |||
267 | return data |
|
267 | return data | |
268 |
|
268 | |||
269 | def delete_cs_pr_comment(repo_name, comment_id): |
|
269 | def delete_cs_pr_comment(repo_name, comment_id): | |
270 | """Delete a comment from a changeset or pull request""" |
|
270 | """Delete a comment from a changeset or pull request""" | |
271 | co = ChangesetComment.get_or_404(comment_id) |
|
271 | co = ChangesetComment.get_or_404(comment_id) | |
272 | if co.repo.repo_name != repo_name: |
|
272 | if co.repo.repo_name != repo_name: | |
273 | raise HTTPNotFound() |
|
273 | raise HTTPNotFound() | |
274 | if co.pull_request and co.pull_request.is_closed(): |
|
274 | if co.pull_request and co.pull_request.is_closed(): | |
275 | # don't allow deleting comments on closed pull request |
|
275 | # don't allow deleting comments on closed pull request | |
276 | raise HTTPForbidden() |
|
276 | raise HTTPForbidden() | |
277 |
|
277 | |||
278 | owner = co.author_id == request.authuser.user_id |
|
278 | owner = co.author_id == request.authuser.user_id | |
279 | repo_admin = h.HasRepoPermissionLevel('admin')(repo_name) |
|
279 | repo_admin = h.HasRepoPermissionLevel('admin')(repo_name) | |
280 | if h.HasPermissionAny('hg.admin')() or repo_admin or owner: |
|
280 | if h.HasPermissionAny('hg.admin')() or repo_admin or owner: | |
281 | ChangesetCommentsModel().delete(comment=co) |
|
281 | ChangesetCommentsModel().delete(comment=co) | |
282 | Session().commit() |
|
282 | Session().commit() | |
283 | return True |
|
283 | return True | |
284 | else: |
|
284 | else: | |
285 | raise HTTPForbidden() |
|
285 | raise HTTPForbidden() | |
286 |
|
286 | |||
287 | class ChangesetController(BaseRepoController): |
|
287 | class ChangesetController(BaseRepoController): | |
288 |
|
288 | |||
289 | def _before(self, *args, **kwargs): |
|
289 | def _before(self, *args, **kwargs): | |
290 | super(ChangesetController, self)._before(*args, **kwargs) |
|
290 | super(ChangesetController, self)._before(*args, **kwargs) | |
291 | c.affected_files_cut_off = 60 |
|
291 | c.affected_files_cut_off = 60 | |
292 |
|
292 | |||
293 | def _index(self, revision, method): |
|
293 | def _index(self, revision, method): | |
294 | c.pull_request = None |
|
294 | c.pull_request = None | |
295 | c.anchor_url = anchor_url |
|
295 | c.anchor_url = anchor_url | |
296 | c.ignorews_url = _ignorews_url |
|
296 | c.ignorews_url = _ignorews_url | |
297 | c.context_url = _context_url |
|
297 | c.context_url = _context_url | |
298 | c.fulldiff = request.GET.get('fulldiff') # for reporting number of changed files |
|
298 | c.fulldiff = request.GET.get('fulldiff') # for reporting number of changed files | |
299 | # get ranges of revisions if preset |
|
299 | # get ranges of revisions if preset | |
300 | rev_range = revision.split('...')[:2] |
|
300 | rev_range = revision.split('...')[:2] | |
301 | enable_comments = True |
|
301 | enable_comments = True | |
302 | c.cs_repo = c.db_repo |
|
302 | c.cs_repo = c.db_repo | |
303 | try: |
|
303 | try: | |
304 | if len(rev_range) == 2: |
|
304 | if len(rev_range) == 2: | |
305 | enable_comments = False |
|
305 | enable_comments = False | |
306 | rev_start = rev_range[0] |
|
306 | rev_start = rev_range[0] | |
307 | rev_end = rev_range[1] |
|
307 | rev_end = rev_range[1] | |
308 | rev_ranges = c.db_repo_scm_instance.get_changesets(start=rev_start, |
|
308 | rev_ranges = c.db_repo_scm_instance.get_changesets(start=rev_start, | |
309 | end=rev_end) |
|
309 | end=rev_end) | |
310 | else: |
|
310 | else: | |
311 | rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)] |
|
311 | rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)] | |
312 |
|
312 | |||
313 | c.cs_ranges = list(rev_ranges) |
|
313 | c.cs_ranges = list(rev_ranges) | |
314 | if not c.cs_ranges: |
|
314 | if not c.cs_ranges: | |
315 | raise RepositoryError('Changeset range returned empty result') |
|
315 | raise RepositoryError('Changeset range returned empty result') | |
316 |
|
316 | |||
317 | except (ChangesetDoesNotExistError, EmptyRepositoryError): |
|
317 | except (ChangesetDoesNotExistError, EmptyRepositoryError): | |
318 | log.debug(traceback.format_exc()) |
|
318 | log.debug(traceback.format_exc()) | |
319 | msg = _('Such revision does not exist for this repository') |
|
319 | msg = _('Such revision does not exist for this repository') | |
320 | h.flash(msg, category='error') |
|
320 | h.flash(msg, category='error') | |
321 | raise HTTPNotFound() |
|
321 | raise HTTPNotFound() | |
322 |
|
322 | |||
323 | c.changes = OrderedDict() |
|
323 | c.changes = OrderedDict() | |
324 |
|
324 | |||
325 | c.lines_added = 0 # count of lines added |
|
325 | c.lines_added = 0 # count of lines added | |
326 | c.lines_deleted = 0 # count of lines removes |
|
326 | c.lines_deleted = 0 # count of lines removes | |
327 |
|
327 | |||
328 | c.changeset_statuses = ChangesetStatus.STATUSES |
|
328 | c.changeset_statuses = ChangesetStatus.STATUSES | |
329 | comments = dict() |
|
329 | comments = dict() | |
330 | c.statuses = [] |
|
330 | c.statuses = [] | |
331 | c.inline_comments = [] |
|
331 | c.inline_comments = [] | |
332 | c.inline_cnt = 0 |
|
332 | c.inline_cnt = 0 | |
333 |
|
333 | |||
334 | # Iterate over ranges (default changeset view is always one changeset) |
|
334 | # Iterate over ranges (default changeset view is always one changeset) | |
335 | for changeset in c.cs_ranges: |
|
335 | for changeset in c.cs_ranges: | |
336 | if method == 'show': |
|
336 | if method == 'show': | |
337 | c.statuses.extend([ChangesetStatusModel().get_status( |
|
337 | c.statuses.extend([ChangesetStatusModel().get_status( | |
338 | c.db_repo.repo_id, changeset.raw_id)]) |
|
338 | c.db_repo.repo_id, changeset.raw_id)]) | |
339 |
|
339 | |||
340 | # Changeset comments |
|
340 | # Changeset comments | |
341 | comments.update((com.comment_id, com) |
|
341 | comments.update((com.comment_id, com) | |
342 | for com in ChangesetCommentsModel() |
|
342 | for com in ChangesetCommentsModel() | |
343 | .get_comments(c.db_repo.repo_id, |
|
343 | .get_comments(c.db_repo.repo_id, | |
344 | revision=changeset.raw_id)) |
|
344 | revision=changeset.raw_id)) | |
345 |
|
345 | |||
346 | # Status change comments - mostly from pull requests |
|
346 | # Status change comments - mostly from pull requests | |
347 | comments.update((st.comment_id, st.comment) |
|
347 | comments.update((st.comment_id, st.comment) | |
348 | for st in ChangesetStatusModel() |
|
348 | for st in ChangesetStatusModel() | |
349 | .get_statuses(c.db_repo.repo_id, |
|
349 | .get_statuses(c.db_repo.repo_id, | |
350 | changeset.raw_id, with_revisions=True) |
|
350 | changeset.raw_id, with_revisions=True) | |
351 | if st.comment_id is not None) |
|
351 | if st.comment_id is not None) | |
352 |
|
352 | |||
353 | inlines = ChangesetCommentsModel() \ |
|
353 | inlines = ChangesetCommentsModel() \ | |
354 | .get_inline_comments(c.db_repo.repo_id, |
|
354 | .get_inline_comments(c.db_repo.repo_id, | |
355 | revision=changeset.raw_id) |
|
355 | revision=changeset.raw_id) | |
356 | c.inline_comments.extend(inlines) |
|
356 | c.inline_comments.extend(inlines) | |
357 |
|
357 | |||
358 | cs2 = changeset.raw_id |
|
358 | cs2 = changeset.raw_id | |
359 | cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset().raw_id |
|
359 | cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset().raw_id | |
360 | context_lcl = get_line_ctx('', request.GET) |
|
360 | context_lcl = get_line_ctx('', request.GET) | |
361 | ign_whitespace_lcl = get_ignore_ws('', request.GET) |
|
361 | ign_whitespace_lcl = get_ignore_ws('', request.GET) | |
362 |
|
362 | |||
363 | raw_diff = diffs.get_diff(c.db_repo_scm_instance, cs1, cs2, |
|
363 | raw_diff = diffs.get_diff(c.db_repo_scm_instance, cs1, cs2, | |
364 | ignore_whitespace=ign_whitespace_lcl, context=context_lcl) |
|
364 | ignore_whitespace=ign_whitespace_lcl, context=context_lcl) | |
365 | diff_limit = None if c.fulldiff else self.cut_off_limit |
|
365 | diff_limit = None if c.fulldiff else self.cut_off_limit | |
366 | file_diff_data = [] |
|
366 | file_diff_data = [] | |
367 | if method == 'show': |
|
367 | if method == 'show': | |
368 | diff_processor = diffs.DiffProcessor(raw_diff, |
|
368 | diff_processor = diffs.DiffProcessor(raw_diff, | |
369 | vcs=c.db_repo_scm_instance.alias, |
|
369 | vcs=c.db_repo_scm_instance.alias, | |
370 | diff_limit=diff_limit) |
|
370 | diff_limit=diff_limit) | |
371 | c.limited_diff = diff_processor.limited_diff |
|
371 | c.limited_diff = diff_processor.limited_diff | |
372 | for f in diff_processor.parsed: |
|
372 | for f in diff_processor.parsed: | |
373 | st = f['stats'] |
|
373 | st = f['stats'] | |
374 | c.lines_added += st['added'] |
|
374 | c.lines_added += st['added'] | |
375 | c.lines_deleted += st['deleted'] |
|
375 | c.lines_deleted += st['deleted'] | |
376 | filename = f['filename'] |
|
376 | filename = f['filename'] | |
377 | fid = h.FID(changeset.raw_id, filename) |
|
377 | fid = h.FID(changeset.raw_id, filename) | |
378 | url_fid = h.FID('', filename) |
|
378 | url_fid = h.FID('', filename) | |
379 | html_diff = diffs.as_html(enable_comments=enable_comments, parsed_lines=[f]) |
|
379 | html_diff = diffs.as_html(enable_comments=enable_comments, parsed_lines=[f]) | |
380 | file_diff_data.append((fid, url_fid, f['operation'], f['old_filename'], filename, html_diff, st)) |
|
380 | file_diff_data.append((fid, url_fid, f['operation'], f['old_filename'], filename, html_diff, st)) | |
381 | else: |
|
381 | else: | |
382 | # downloads/raw we only need RAW diff nothing else |
|
382 | # downloads/raw we only need RAW diff nothing else | |
383 | file_diff_data.append(('', None, None, None, raw_diff, None)) |
|
383 | file_diff_data.append(('', None, None, None, raw_diff, None)) | |
384 | c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data) |
|
384 | c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data) | |
385 |
|
385 | |||
386 | # sort comments in creation order |
|
386 | # sort comments in creation order | |
387 | c.comments = [com for com_id, com in sorted(comments.items())] |
|
387 | c.comments = [com for com_id, com in sorted(comments.items())] | |
388 |
|
388 | |||
389 | # count inline comments |
|
389 | # count inline comments | |
390 | for __, lines in c.inline_comments: |
|
390 | for __, lines in c.inline_comments: | |
391 | for comments in lines.values(): |
|
391 | for comments in lines.values(): | |
392 | c.inline_cnt += len(comments) |
|
392 | c.inline_cnt += len(comments) | |
393 |
|
393 | |||
394 | if len(c.cs_ranges) == 1: |
|
394 | if len(c.cs_ranges) == 1: | |
395 | c.changeset = c.cs_ranges[0] |
|
395 | c.changeset = c.cs_ranges[0] | |
396 | c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id |
|
396 | c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id | |
397 | for x in c.changeset.parents]) |
|
397 | for x in c.changeset.parents]) | |
398 | c.changeset_graft_source_hash = ascii_str(c.changeset.extra.get(b'source', b'')) |
|
398 | c.changeset_graft_source_hash = ascii_str(c.changeset.extra.get(b'source', b'')) | |
399 | c.changeset_transplant_source_hash = ascii_str(binascii.hexlify(c.changeset.extra.get(b'transplant_source', b''))) |
|
399 | c.changeset_transplant_source_hash = ascii_str(binascii.hexlify(c.changeset.extra.get(b'transplant_source', b''))) | |
400 | if method == 'download': |
|
400 | if method == 'download': | |
401 | response.content_type = 'text/plain' |
|
401 | response.content_type = 'text/plain' | |
402 | response.content_disposition = 'attachment; filename=%s.diff' \ |
|
402 | response.content_disposition = 'attachment; filename=%s.diff' \ | |
403 | % revision[:12] |
|
403 | % revision[:12] | |
404 | return raw_diff |
|
404 | return raw_diff | |
405 | elif method == 'patch': |
|
405 | elif method == 'patch': | |
406 | response.content_type = 'text/plain' |
|
406 | response.content_type = 'text/plain' | |
407 | c.diff = safe_str(raw_diff) |
|
407 | c.diff = safe_str(raw_diff) | |
408 | return render('changeset/patch_changeset.html') |
|
408 | return render('changeset/patch_changeset.html') | |
409 | elif method == 'raw': |
|
409 | elif method == 'raw': | |
410 | response.content_type = 'text/plain' |
|
410 | response.content_type = 'text/plain' | |
411 | return raw_diff |
|
411 | return raw_diff | |
412 | elif method == 'show': |
|
412 | elif method == 'show': | |
413 | if len(c.cs_ranges) == 1: |
|
413 | if len(c.cs_ranges) == 1: | |
414 | return render('changeset/changeset.html') |
|
414 | return render('changeset/changeset.html') | |
415 | else: |
|
415 | else: | |
416 | c.cs_ranges_org = None |
|
416 | c.cs_ranges_org = None | |
417 | c.cs_comments = {} |
|
417 | c.cs_comments = {} | |
418 | revs = [ctx.revision for ctx in reversed(c.cs_ranges)] |
|
418 | revs = [ctx.revision for ctx in reversed(c.cs_ranges)] | |
419 | c.jsdata = graph_data(c.db_repo_scm_instance, revs) |
|
419 | c.jsdata = graph_data(c.db_repo_scm_instance, revs) | |
420 | return render('changeset/changeset_range.html') |
|
420 | return render('changeset/changeset_range.html') | |
421 |
|
421 | |||
422 | @LoginRequired(allow_default_user=True) |
|
422 | @LoginRequired(allow_default_user=True) | |
423 | @HasRepoPermissionLevelDecorator('read') |
|
423 | @HasRepoPermissionLevelDecorator('read') | |
424 | def index(self, revision, method='show'): |
|
424 | def index(self, revision, method='show'): | |
425 | return self._index(revision, method=method) |
|
425 | return self._index(revision, method=method) | |
426 |
|
426 | |||
427 | @LoginRequired(allow_default_user=True) |
|
427 | @LoginRequired(allow_default_user=True) | |
428 | @HasRepoPermissionLevelDecorator('read') |
|
428 | @HasRepoPermissionLevelDecorator('read') | |
429 | def changeset_raw(self, revision): |
|
429 | def changeset_raw(self, revision): | |
430 | return self._index(revision, method='raw') |
|
430 | return self._index(revision, method='raw') | |
431 |
|
431 | |||
432 | @LoginRequired(allow_default_user=True) |
|
432 | @LoginRequired(allow_default_user=True) | |
433 | @HasRepoPermissionLevelDecorator('read') |
|
433 | @HasRepoPermissionLevelDecorator('read') | |
434 | def changeset_patch(self, revision): |
|
434 | def changeset_patch(self, revision): | |
435 | return self._index(revision, method='patch') |
|
435 | return self._index(revision, method='patch') | |
436 |
|
436 | |||
437 | @LoginRequired(allow_default_user=True) |
|
437 | @LoginRequired(allow_default_user=True) | |
438 | @HasRepoPermissionLevelDecorator('read') |
|
438 | @HasRepoPermissionLevelDecorator('read') | |
439 | def changeset_download(self, revision): |
|
439 | def changeset_download(self, revision): | |
440 | return self._index(revision, method='download') |
|
440 | return self._index(revision, method='download') | |
441 |
|
441 | |||
442 | @LoginRequired() |
|
442 | @LoginRequired() | |
443 | @HasRepoPermissionLevelDecorator('read') |
|
443 | @HasRepoPermissionLevelDecorator('read') | |
444 | @jsonify |
|
444 | @jsonify | |
445 | def comment(self, repo_name, revision): |
|
445 | def comment(self, repo_name, revision): | |
446 | return create_cs_pr_comment(repo_name, revision=revision) |
|
446 | return create_cs_pr_comment(repo_name, revision=revision) | |
447 |
|
447 | |||
448 | @LoginRequired() |
|
448 | @LoginRequired() | |
449 | @HasRepoPermissionLevelDecorator('read') |
|
449 | @HasRepoPermissionLevelDecorator('read') | |
450 | @jsonify |
|
450 | @jsonify | |
451 | def delete_comment(self, repo_name, comment_id): |
|
451 | def delete_comment(self, repo_name, comment_id): | |
452 | return delete_cs_pr_comment(repo_name, comment_id) |
|
452 | return delete_cs_pr_comment(repo_name, comment_id) | |
453 |
|
453 | |||
454 | @LoginRequired(allow_default_user=True) |
|
454 | @LoginRequired(allow_default_user=True) | |
455 | @HasRepoPermissionLevelDecorator('read') |
|
455 | @HasRepoPermissionLevelDecorator('read') | |
456 | @jsonify |
|
456 | @jsonify | |
457 | def changeset_info(self, repo_name, revision): |
|
457 | def changeset_info(self, repo_name, revision): | |
458 | if request.is_xhr: |
|
458 | if request.is_xhr: | |
459 | try: |
|
459 | try: | |
460 | return c.db_repo_scm_instance.get_changeset(revision) |
|
460 | return c.db_repo_scm_instance.get_changeset(revision) | |
461 | except ChangesetDoesNotExistError as e: |
|
461 | except ChangesetDoesNotExistError as e: | |
462 | return EmptyChangeset(message=str(e)) |
|
462 | return EmptyChangeset(message=str(e)) | |
463 | else: |
|
463 | else: | |
464 | raise HTTPBadRequest() |
|
464 | raise HTTPBadRequest() | |
465 |
|
465 | |||
466 | @LoginRequired(allow_default_user=True) |
|
466 | @LoginRequired(allow_default_user=True) | |
467 | @HasRepoPermissionLevelDecorator('read') |
|
467 | @HasRepoPermissionLevelDecorator('read') | |
468 | @jsonify |
|
468 | @jsonify | |
469 | def changeset_children(self, repo_name, revision): |
|
469 | def changeset_children(self, repo_name, revision): | |
470 | if request.is_xhr: |
|
470 | if request.is_xhr: | |
471 | changeset = c.db_repo_scm_instance.get_changeset(revision) |
|
471 | changeset = c.db_repo_scm_instance.get_changeset(revision) | |
472 | result = {"results": []} |
|
472 | result = {"results": []} | |
473 | if changeset.children: |
|
473 | if changeset.children: | |
474 | result = {"results": changeset.children} |
|
474 | result = {"results": changeset.children} | |
475 | return result |
|
475 | return result | |
476 | else: |
|
476 | else: | |
477 | raise HTTPBadRequest() |
|
477 | raise HTTPBadRequest() | |
478 |
|
478 | |||
479 | @LoginRequired(allow_default_user=True) |
|
479 | @LoginRequired(allow_default_user=True) | |
480 | @HasRepoPermissionLevelDecorator('read') |
|
480 | @HasRepoPermissionLevelDecorator('read') | |
481 | @jsonify |
|
481 | @jsonify | |
482 | def changeset_parents(self, repo_name, revision): |
|
482 | def changeset_parents(self, repo_name, revision): | |
483 | if request.is_xhr: |
|
483 | if request.is_xhr: | |
484 | changeset = c.db_repo_scm_instance.get_changeset(revision) |
|
484 | changeset = c.db_repo_scm_instance.get_changeset(revision) | |
485 | result = {"results": []} |
|
485 | result = {"results": []} | |
486 | if changeset.parents: |
|
486 | if changeset.parents: | |
487 | result = {"results": changeset.parents} |
|
487 | result = {"results": changeset.parents} | |
488 | return result |
|
488 | return result | |
489 | else: |
|
489 | else: | |
490 | raise HTTPBadRequest() |
|
490 | raise HTTPBadRequest() |
@@ -1,21 +1,15 b'' | |||||
1 | from kallithea.lib.vcs.exceptions import VCSError |
|
|||
2 |
|
||||
3 |
|
||||
4 |
|
|
1 | def import_class(class_path): | |
5 | """ |
|
2 | """ | |
6 | Returns class from the given path. |
|
3 | Returns class from the given path. | |
7 |
|
4 | |||
8 | For example, in order to get class located at |
|
5 | For example, in order to get class located at | |
9 | ``vcs.backends.hg.MercurialRepository``: |
|
6 | ``vcs.backends.hg.MercurialRepository``: | |
10 |
|
7 | |||
11 | try: |
|
|||
12 |
|
|
8 | hgrepo = import_class('vcs.backends.hg.MercurialRepository') | |
13 | except VCSError: |
|
|||
14 | # handle error |
|
|||
15 | """ |
|
9 | """ | |
16 | splitted = class_path.split('.') |
|
10 | splitted = class_path.split('.') | |
17 | mod_path = '.'.join(splitted[:-1]) |
|
11 | mod_path = '.'.join(splitted[:-1]) | |
18 | class_name = splitted[-1] |
|
12 | class_name = splitted[-1] | |
19 | class_mod = __import__(mod_path, {}, {}, [class_name]) |
|
13 | class_mod = __import__(mod_path, {}, {}, [class_name]) | |
20 | cls = getattr(class_mod, class_name) |
|
14 | cls = getattr(class_mod, class_name) | |
21 | return cls |
|
15 | return cls |
@@ -1,162 +1,162 b'' | |||||
1 | #!/usr/bin/env python3 |
|
1 | #!/usr/bin/env python3 | |
2 | # -*- coding: utf-8 -*- |
|
2 | # -*- coding: utf-8 -*- | |
3 | import os |
|
3 | import os | |
4 | import platform |
|
4 | import platform | |
5 | import sys |
|
5 | import sys | |
6 |
|
6 | |||
7 | import setuptools |
|
7 | import setuptools | |
8 | # monkey patch setuptools to use distutils owner/group functionality |
|
8 | # monkey patch setuptools to use distutils owner/group functionality | |
9 | from setuptools.command import sdist |
|
9 | from setuptools.command import sdist | |
10 |
|
10 | |||
11 |
|
11 | |||
12 | if sys.version_info < (3, 6): |
|
12 | if sys.version_info < (3, 6): | |
13 | raise Exception('Kallithea requires Python 3.6 or later') |
|
13 | raise Exception('Kallithea requires Python 3.6 or later') | |
14 |
|
14 | |||
15 |
|
15 | |||
16 | here = os.path.abspath(os.path.dirname(__file__)) |
|
16 | here = os.path.abspath(os.path.dirname(__file__)) | |
17 |
|
17 | |||
18 |
|
18 | |||
19 | def _get_meta_var(name, data, callback_handler=None): |
|
19 | def _get_meta_var(name, data, callback_handler=None): | |
20 | import re |
|
20 | import re | |
21 | matches = re.compile(r'(?:%s)\s*=\s*(.*)' % name).search(data) |
|
21 | matches = re.compile(r'(?:%s)\s*=\s*(.*)' % name).search(data) | |
22 | if matches: |
|
22 | if matches: | |
23 | if not callable(callback_handler): |
|
23 | if not callable(callback_handler): | |
24 | callback_handler = lambda v: v |
|
24 | callback_handler = lambda v: v | |
25 |
|
25 | |||
26 | return callback_handler(eval(matches.groups()[0])) |
|
26 | return callback_handler(eval(matches.groups()[0])) | |
27 |
|
27 | |||
28 | _meta = open(os.path.join(here, 'kallithea', '__init__.py'), 'r') |
|
28 | _meta = open(os.path.join(here, 'kallithea', '__init__.py'), 'r') | |
29 | _metadata = _meta.read() |
|
29 | _metadata = _meta.read() | |
30 | _meta.close() |
|
30 | _meta.close() | |
31 |
|
31 | |||
32 | callback = lambda V: ('.'.join(map(str, V[:3])) + '.'.join(V[3:])) |
|
32 | callback = lambda V: ('.'.join(map(str, V[:3])) + '.'.join(V[3:])) | |
33 | __version__ = _get_meta_var('VERSION', _metadata, callback) |
|
33 | __version__ = _get_meta_var('VERSION', _metadata, callback) | |
34 | __license__ = _get_meta_var('__license__', _metadata) |
|
34 | __license__ = _get_meta_var('__license__', _metadata) | |
35 | __author__ = _get_meta_var('__author__', _metadata) |
|
35 | __author__ = _get_meta_var('__author__', _metadata) | |
36 | __url__ = _get_meta_var('__url__', _metadata) |
|
36 | __url__ = _get_meta_var('__url__', _metadata) | |
37 | # defines current platform |
|
37 | # defines current platform | |
38 | __platform__ = platform.system() |
|
38 | __platform__ = platform.system() | |
39 |
|
39 | |||
40 | is_windows = __platform__ in ['Windows'] |
|
40 | is_windows = __platform__ in ['Windows'] | |
41 |
|
41 | |||
42 | requirements = [ |
|
42 | requirements = [ | |
43 | "alembic >= 1.0.10, < 1.5", |
|
43 | "alembic >= 1.0.10, < 1.5", | |
44 | "gearbox >= 0.1.0, < 1", |
|
44 | "gearbox >= 0.1.0, < 1", | |
45 | "waitress >= 0.8.8, < 1.5", |
|
45 | "waitress >= 0.8.8, < 1.5", | |
46 | "WebOb >= 1.8, < 1.9", |
|
46 | "WebOb >= 1.8, < 1.9", | |
47 | "backlash >= 0.1.2, < 1", |
|
47 | "backlash >= 0.1.2, < 1", | |
48 | "TurboGears2 >= 2.4, < 2.5", |
|
48 | "TurboGears2 >= 2.4, < 2.5", | |
49 | "tgext.routes >= 0.2.0, < 1", |
|
49 | "tgext.routes >= 0.2.0, < 1", | |
50 | "Beaker >= 1.10.1, < 2", |
|
50 | "Beaker >= 1.10.1, < 2", | |
51 | "WebHelpers2 >= 2.0, < 2.1", |
|
51 | "WebHelpers2 >= 2.0, < 2.1", | |
52 | "FormEncode >= 1.3.1, < 1.4", |
|
52 | "FormEncode >= 1.3.1, < 1.4", | |
53 | "SQLAlchemy >= 1.2.9, < 1.4", |
|
53 | "SQLAlchemy >= 1.2.9, < 1.4", | |
54 | "Mako >= 0.9.1, < 1.2", |
|
54 | "Mako >= 0.9.1, < 1.2", | |
55 | "Pygments >= 2.2.0, < 2.6", |
|
55 | "Pygments >= 2.2.0, < 2.6", | |
56 | "Whoosh >= 2.7.1, < 2.8", |
|
56 | "Whoosh >= 2.7.1, < 2.8", | |
57 | "celery >= 3.1, < 4.0", # TODO: celery 4 doesn't work |
|
57 | "celery >= 3.1, < 4.0", # TODO: celery 4 doesn't work | |
58 | "Babel >= 1.3, < 2.9", |
|
58 | "Babel >= 1.3, < 2.9", | |
59 | "python-dateutil >= 2.1.0, < 2.9", |
|
59 | "python-dateutil >= 2.1.0, < 2.9", | |
60 | "Markdown >= 2.2.1, < 3.2", |
|
60 | "Markdown >= 2.2.1, < 3.2", | |
61 | "docutils >= 0.11, < 0.17", |
|
61 | "docutils >= 0.11, < 0.17", | |
62 | "URLObject >= 2.3.4, < 2.5", |
|
62 | "URLObject >= 2.3.4, < 2.5", | |
63 | "Routes >= 2.0, < 2.5", |
|
63 | "Routes >= 2.0, < 2.5", | |
64 | "dulwich >= 0.19.0, < 0.20", |
|
64 | "dulwich >= 0.19.0, < 0.20", | |
65 | "mercurial >= 5.2, < 5.4", |
|
65 | "mercurial >= 5.2, < 5.4", | |
66 | "decorator >= 4.2.1, < 4.5", |
|
66 | "decorator >= 4.2.1, < 4.5", | |
67 | "Paste >= 2.0.3, < 3.4", |
|
67 | "Paste >= 2.0.3, < 3.4", | |
68 | "bleach >= 3.0, < 3.2", |
|
68 | "bleach >= 3.0, < 3.2", | |
69 | "Click >= 7.0, < 8", |
|
69 | "Click >= 7.0, < 8", | |
70 | "ipaddr >= 2.2.0, < 2.3", |
|
70 | "ipaddr >= 2.2.0, < 2.3", | |
71 | "paginate >= 0.5, < 0.6", |
|
71 | "paginate >= 0.5, < 0.6", | |
72 | "paginate_sqlalchemy >= 0.3.0, < 0.4", |
|
72 | "paginate_sqlalchemy >= 0.3.0, < 0.4", | |
73 | ] |
|
73 | ] | |
74 |
|
74 | |||
75 | if not is_windows: |
|
75 | if not is_windows: | |
76 | requirements.append("bcrypt >= 3.1.0, < 3.2") |
|
76 | requirements.append("bcrypt >= 3.1.0, < 3.2") | |
77 |
|
77 | |||
78 | dependency_links = [ |
|
78 | dependency_links = [ | |
79 | ] |
|
79 | ] | |
80 |
|
80 | |||
81 | classifiers = [ |
|
81 | classifiers = [ | |
82 | 'Development Status :: 4 - Beta', |
|
82 | 'Development Status :: 4 - Beta', | |
83 | 'Environment :: Web Environment', |
|
83 | 'Environment :: Web Environment', | |
84 | 'Framework :: Pylons', |
|
84 | 'Framework :: Pylons', | |
85 | 'Intended Audience :: Developers', |
|
85 | 'Intended Audience :: Developers', | |
86 | 'License :: OSI Approved :: GNU General Public License (GPL)', |
|
86 | 'License :: OSI Approved :: GNU General Public License (GPL)', | |
87 | 'Operating System :: OS Independent', |
|
87 | 'Operating System :: OS Independent', | |
88 | 'Programming Language :: Python :: 3.6', |
|
88 | 'Programming Language :: Python :: 3.6', | |
89 | 'Programming Language :: Python :: 3.7', |
|
89 | 'Programming Language :: Python :: 3.7', | |
90 | 'Programming Language :: Python :: 3.8', |
|
90 | 'Programming Language :: Python :: 3.8', | |
91 | 'Topic :: Software Development :: Version Control', |
|
91 | 'Topic :: Software Development :: Version Control', | |
92 | ] |
|
92 | ] | |
93 |
|
93 | |||
94 |
|
94 | |||
95 | # additional files from project that goes somewhere in the filesystem |
|
95 | # additional files from project that goes somewhere in the filesystem | |
96 | # relative to sys.prefix |
|
96 | # relative to sys.prefix | |
97 | data_files = [] |
|
97 | data_files = [] | |
98 |
|
98 | |||
99 | description = ('Kallithea is a fast and powerful management tool ' |
|
99 | description = ('Kallithea is a fast and powerful management tool ' | |
100 | 'for Mercurial and Git with a built in push/pull server, ' |
|
100 | 'for Mercurial and Git with a built in push/pull server, ' | |
101 | 'full text search and code-review.') |
|
101 | 'full text search and code-review.') | |
102 |
|
102 | |||
103 | keywords = ' '.join([ |
|
103 | keywords = ' '.join([ | |
104 | 'kallithea', 'mercurial', 'git', 'code review', |
|
104 | 'kallithea', 'mercurial', 'git', 'code review', | |
105 | 'repo groups', 'ldap', 'repository management', 'hgweb replacement', |
|
105 | 'repo groups', 'ldap', 'repository management', 'hgweb replacement', | |
106 | 'hgwebdir', 'gitweb replacement', 'serving hgweb', |
|
106 | 'hgwebdir', 'gitweb replacement', 'serving hgweb', | |
107 | ]) |
|
107 | ]) | |
108 |
|
108 | |||
109 | # long description |
|
109 | # long description | |
110 | README_FILE = 'README.rst' |
|
110 | README_FILE = 'README.rst' | |
111 | try: |
|
111 | try: | |
112 | long_description = open(README_FILE).read() |
|
112 | long_description = open(README_FILE).read() | |
113 | except IOError as err: |
|
113 | except IOError as err: | |
114 | sys.stderr.write( |
|
114 | sys.stderr.write( | |
115 | "[WARNING] Cannot find file specified as long_description (%s)\n" |
|
115 | "[WARNING] Cannot find file specified as long_description (%s): %s\n" | |
116 | % README_FILE |
|
116 | % (README_FILE, err) | |
117 | ) |
|
117 | ) | |
118 | long_description = description |
|
118 | long_description = description | |
119 |
|
119 | |||
120 |
|
120 | |||
121 | sdist_org = sdist.sdist |
|
121 | sdist_org = sdist.sdist | |
122 | class sdist_new(sdist_org): |
|
122 | class sdist_new(sdist_org): | |
123 | def initialize_options(self): |
|
123 | def initialize_options(self): | |
124 | sdist_org.initialize_options(self) |
|
124 | sdist_org.initialize_options(self) | |
125 | self.owner = self.group = 'root' |
|
125 | self.owner = self.group = 'root' | |
126 | sdist.sdist = sdist_new |
|
126 | sdist.sdist = sdist_new | |
127 |
|
127 | |||
128 | packages = setuptools.find_packages(exclude=['ez_setup']) |
|
128 | packages = setuptools.find_packages(exclude=['ez_setup']) | |
129 |
|
129 | |||
130 | setuptools.setup( |
|
130 | setuptools.setup( | |
131 | name='Kallithea', |
|
131 | name='Kallithea', | |
132 | version=__version__, |
|
132 | version=__version__, | |
133 | description=description, |
|
133 | description=description, | |
134 | long_description=long_description, |
|
134 | long_description=long_description, | |
135 | keywords=keywords, |
|
135 | keywords=keywords, | |
136 | license=__license__, |
|
136 | license=__license__, | |
137 | author=__author__, |
|
137 | author=__author__, | |
138 | author_email='kallithea@sfconservancy.org', |
|
138 | author_email='kallithea@sfconservancy.org', | |
139 | dependency_links=dependency_links, |
|
139 | dependency_links=dependency_links, | |
140 | url=__url__, |
|
140 | url=__url__, | |
141 | install_requires=requirements, |
|
141 | install_requires=requirements, | |
142 | classifiers=classifiers, |
|
142 | classifiers=classifiers, | |
143 | data_files=data_files, |
|
143 | data_files=data_files, | |
144 | packages=packages, |
|
144 | packages=packages, | |
145 | include_package_data=True, |
|
145 | include_package_data=True, | |
146 | message_extractors={'kallithea': [ |
|
146 | message_extractors={'kallithea': [ | |
147 | ('**.py', 'python', None), |
|
147 | ('**.py', 'python', None), | |
148 | ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}), |
|
148 | ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}), | |
149 | ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}), |
|
149 | ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}), | |
150 | ('public/**', 'ignore', None)]}, |
|
150 | ('public/**', 'ignore', None)]}, | |
151 | zip_safe=False, |
|
151 | zip_safe=False, | |
152 | entry_points=""" |
|
152 | entry_points=""" | |
153 | [console_scripts] |
|
153 | [console_scripts] | |
154 | kallithea-api = kallithea.bin.kallithea_api:main |
|
154 | kallithea-api = kallithea.bin.kallithea_api:main | |
155 | kallithea-gist = kallithea.bin.kallithea_gist:main |
|
155 | kallithea-gist = kallithea.bin.kallithea_gist:main | |
156 | kallithea-config = kallithea.bin.kallithea_config:main |
|
156 | kallithea-config = kallithea.bin.kallithea_config:main | |
157 | kallithea-cli = kallithea.bin.kallithea_cli:cli |
|
157 | kallithea-cli = kallithea.bin.kallithea_cli:cli | |
158 |
|
158 | |||
159 | [paste.app_factory] |
|
159 | [paste.app_factory] | |
160 | main = kallithea.config.middleware:make_app |
|
160 | main = kallithea.config.middleware:make_app | |
161 | """, |
|
161 | """, | |
162 | ) |
|
162 | ) |
General Comments 0
You need to be logged in to leave comments.
Login now