Show More
@@ -1,312 +1,321 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.compare |
|
15 | kallithea.controllers.compare | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | compare controller for pylons showing differences between two |
|
18 | compare controller for pylons showing differences between two | |
19 | repos, branches, bookmarks or tips |
|
19 | repos, branches, bookmarks or tips | |
20 |
|
20 | |||
21 | This file was forked by the Kallithea project in July 2014. |
|
21 | This file was forked by the Kallithea project in July 2014. | |
22 | Original author and date, and relevant copyright and licensing information is below: |
|
22 | Original author and date, and relevant copyright and licensing information is below: | |
23 | :created_on: May 6, 2012 |
|
23 | :created_on: May 6, 2012 | |
24 | :author: marcink |
|
24 | :author: marcink | |
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
26 | :license: GPLv3, see LICENSE.md for more details. |
|
26 | :license: GPLv3, see LICENSE.md for more details. | |
27 | """ |
|
27 | """ | |
28 |
|
28 | |||
29 |
|
29 | |||
30 | import logging |
|
30 | import logging | |
31 | import traceback |
|
31 | import traceback | |
32 | import re |
|
32 | import re | |
33 |
|
33 | |||
34 | from webob.exc import HTTPNotFound, HTTPBadRequest |
|
34 | from webob.exc import HTTPNotFound, HTTPBadRequest | |
35 | from pylons import request, response, session, tmpl_context as c, url |
|
35 | from pylons import request, response, session, tmpl_context as c, url | |
36 | from pylons.controllers.util import abort, redirect |
|
36 | from pylons.controllers.util import abort, redirect | |
37 | from pylons.i18n.translation import _ |
|
37 | from pylons.i18n.translation import _ | |
38 |
|
38 | |||
39 | from kallithea.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError |
|
39 | from kallithea.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError | |
40 | from kallithea.lib.vcs.utils import safe_str |
|
40 | from kallithea.lib.vcs.utils import safe_str | |
41 | from kallithea.lib.vcs.utils.hgcompat import unionrepo |
|
41 | from kallithea.lib.vcs.utils.hgcompat import unionrepo | |
42 | from kallithea.lib import helpers as h |
|
42 | from kallithea.lib import helpers as h | |
43 | from kallithea.lib.base import BaseRepoController, render |
|
43 | from kallithea.lib.base import BaseRepoController, render | |
44 | from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
44 | from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator | |
45 | from kallithea.lib import diffs |
|
45 | from kallithea.lib import diffs | |
46 | from kallithea.model.db import Repository |
|
46 | from kallithea.model.db import Repository | |
47 | from kallithea.lib.diffs import LimitedDiffContainer |
|
47 | from kallithea.lib.diffs import LimitedDiffContainer | |
48 |
|
48 | from kallithea.controllers.changeset import anchor_url, _ignorews_url,\ | ||
|
49 | _context_url, get_line_ctx, get_ignore_ws | |||
49 |
|
50 | |||
50 | log = logging.getLogger(__name__) |
|
51 | log = logging.getLogger(__name__) | |
51 |
|
52 | |||
52 |
|
53 | |||
53 | class CompareController(BaseRepoController): |
|
54 | class CompareController(BaseRepoController): | |
54 |
|
55 | |||
55 | def __before__(self): |
|
56 | def __before__(self): | |
56 | super(CompareController, self).__before__() |
|
57 | super(CompareController, self).__before__() | |
57 |
|
58 | |||
58 | def __get_rev_or_redirect(self, ref, repo, redirect_after=True, |
|
59 | def __get_rev_or_redirect(self, ref, repo, redirect_after=True, | |
59 | partial=False): |
|
60 | partial=False): | |
60 | """ |
|
61 | """ | |
61 | Safe way to get changeset if error occur it redirects to changeset with |
|
62 | Safe way to get changeset if error occur it redirects to changeset with | |
62 | proper message. If partial is set then don't do redirect raise Exception |
|
63 | proper message. If partial is set then don't do redirect raise Exception | |
63 | instead |
|
64 | instead | |
64 |
|
65 | |||
65 | :param ref: |
|
66 | :param ref: | |
66 | :param repo: |
|
67 | :param repo: | |
67 | :param redirect_after: |
|
68 | :param redirect_after: | |
68 | :param partial: |
|
69 | :param partial: | |
69 | """ |
|
70 | """ | |
70 | rev = ref[1] # default and used for git |
|
71 | rev = ref[1] # default and used for git | |
71 | if repo.scm_instance.alias == 'hg': |
|
72 | if repo.scm_instance.alias == 'hg': | |
72 | # lookup up the exact node id |
|
73 | # lookup up the exact node id | |
73 | _revset_predicates = { |
|
74 | _revset_predicates = { | |
74 | 'branch': 'branch', |
|
75 | 'branch': 'branch', | |
75 | 'book': 'bookmark', |
|
76 | 'book': 'bookmark', | |
76 | 'tag': 'tag', |
|
77 | 'tag': 'tag', | |
77 | 'rev': 'id', |
|
78 | 'rev': 'id', | |
78 | } |
|
79 | } | |
79 | rev_spec = "max(%s(%%s))" % _revset_predicates[ref[0]] |
|
80 | rev_spec = "max(%s(%%s))" % _revset_predicates[ref[0]] | |
80 | revs = repo.scm_instance._repo.revs(rev_spec, safe_str(ref[1])) |
|
81 | revs = repo.scm_instance._repo.revs(rev_spec, safe_str(ref[1])) | |
81 | if revs: |
|
82 | if revs: | |
82 | rev = revs[-1] |
|
83 | rev = revs[-1] | |
83 | # else: TODO: just report 'not found' |
|
84 | # else: TODO: just report 'not found' | |
84 |
|
85 | |||
85 | try: |
|
86 | try: | |
86 | return repo.scm_instance.get_changeset(rev).raw_id |
|
87 | return repo.scm_instance.get_changeset(rev).raw_id | |
87 | except EmptyRepositoryError, e: |
|
88 | except EmptyRepositoryError, e: | |
88 | if not redirect_after: |
|
89 | if not redirect_after: | |
89 | return None |
|
90 | return None | |
90 | h.flash(h.literal(_('There are no changesets yet')), |
|
91 | h.flash(h.literal(_('There are no changesets yet')), | |
91 | category='warning') |
|
92 | category='warning') | |
92 | redirect(url('summary_home', repo_name=repo.repo_name)) |
|
93 | redirect(url('summary_home', repo_name=repo.repo_name)) | |
93 |
|
94 | |||
94 | except RepositoryError, e: |
|
95 | except RepositoryError, e: | |
95 | log.error(traceback.format_exc()) |
|
96 | log.error(traceback.format_exc()) | |
96 | h.flash(safe_str(e), category='warning') |
|
97 | h.flash(safe_str(e), category='warning') | |
97 | if not partial: |
|
98 | if not partial: | |
98 | redirect(h.url('summary_home', repo_name=repo.repo_name)) |
|
99 | redirect(h.url('summary_home', repo_name=repo.repo_name)) | |
99 | raise HTTPBadRequest() |
|
100 | raise HTTPBadRequest() | |
100 |
|
101 | |||
101 | def _get_changesets(self, alias, org_repo, org_rev, other_repo, other_rev): |
|
102 | def _get_changesets(self, alias, org_repo, org_rev, other_repo, other_rev): | |
102 | """ |
|
103 | """ | |
103 | Returns lists of changesets that can be merged from org_repo@org_rev |
|
104 | Returns lists of changesets that can be merged from org_repo@org_rev | |
104 | to other_repo@other_rev |
|
105 | to other_repo@other_rev | |
105 | ... and the other way |
|
106 | ... and the other way | |
106 | ... and the ancestor that would be used for merge |
|
107 | ... and the ancestor that would be used for merge | |
107 |
|
108 | |||
108 | :param org_repo: repo object, that is most likely the orginal repo we forked from |
|
109 | :param org_repo: repo object, that is most likely the orginal repo we forked from | |
109 | :param org_rev: the revision we want our compare to be made |
|
110 | :param org_rev: the revision we want our compare to be made | |
110 | :param other_repo: repo object, mostl likely the fork of org_repo. It hass |
|
111 | :param other_repo: repo object, mostl likely the fork of org_repo. It hass | |
111 | all changesets that we need to obtain |
|
112 | all changesets that we need to obtain | |
112 | :param other_rev: revision we want out compare to be made on other_repo |
|
113 | :param other_rev: revision we want out compare to be made on other_repo | |
113 | """ |
|
114 | """ | |
114 | ancestor = None |
|
115 | ancestor = None | |
115 | if org_rev == other_rev: |
|
116 | if org_rev == other_rev: | |
116 | org_changesets = [] |
|
117 | org_changesets = [] | |
117 | other_changesets = [] |
|
118 | other_changesets = [] | |
118 | ancestor = org_rev |
|
119 | ancestor = org_rev | |
119 |
|
120 | |||
120 | elif alias == 'hg': |
|
121 | elif alias == 'hg': | |
121 | #case two independent repos |
|
122 | #case two independent repos | |
122 | if org_repo != other_repo: |
|
123 | if org_repo != other_repo: | |
123 | hgrepo = unionrepo.unionrepository(other_repo.baseui, |
|
124 | hgrepo = unionrepo.unionrepository(other_repo.baseui, | |
124 | other_repo.path, |
|
125 | other_repo.path, | |
125 | org_repo.path) |
|
126 | org_repo.path) | |
126 | # all ancestors of other_rev will be in other_repo and |
|
127 | # all ancestors of other_rev will be in other_repo and | |
127 | # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot |
|
128 | # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot | |
128 |
|
129 | |||
129 | #no remote compare do it on the same repository |
|
130 | #no remote compare do it on the same repository | |
130 | else: |
|
131 | else: | |
131 | hgrepo = other_repo._repo |
|
132 | hgrepo = other_repo._repo | |
132 |
|
133 | |||
133 | ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev) |
|
134 | ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev) | |
134 | if ancestors: |
|
135 | if ancestors: | |
135 | # pick arbitrary ancestor - but there is usually only one |
|
136 | # pick arbitrary ancestor - but there is usually only one | |
136 | ancestor = hgrepo[ancestors[0]].hex() |
|
137 | ancestor = hgrepo[ancestors[0]].hex() | |
137 |
|
138 | |||
138 | other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)", |
|
139 | other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)", | |
139 | other_rev, org_rev, org_rev) |
|
140 | other_rev, org_rev, org_rev) | |
140 | other_changesets = [other_repo.get_changeset(rev) for rev in other_revs] |
|
141 | other_changesets = [other_repo.get_changeset(rev) for rev in other_revs] | |
141 | org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)", |
|
142 | org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)", | |
142 | org_rev, other_rev, other_rev) |
|
143 | org_rev, other_rev, other_rev) | |
143 |
|
144 | |||
144 | org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs] |
|
145 | org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs] | |
145 |
|
146 | |||
146 | elif alias == 'git': |
|
147 | elif alias == 'git': | |
147 | if org_repo != other_repo: |
|
148 | if org_repo != other_repo: | |
148 | from dulwich.repo import Repo |
|
149 | from dulwich.repo import Repo | |
149 | from dulwich.client import SubprocessGitClient |
|
150 | from dulwich.client import SubprocessGitClient | |
150 |
|
151 | |||
151 | gitrepo = Repo(org_repo.path) |
|
152 | gitrepo = Repo(org_repo.path) | |
152 | SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo) |
|
153 | SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo) | |
153 |
|
154 | |||
154 | gitrepo_remote = Repo(other_repo.path) |
|
155 | gitrepo_remote = Repo(other_repo.path) | |
155 | SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote) |
|
156 | SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote) | |
156 |
|
157 | |||
157 | revs = [] |
|
158 | revs = [] | |
158 | for x in gitrepo_remote.get_walker(include=[other_rev], |
|
159 | for x in gitrepo_remote.get_walker(include=[other_rev], | |
159 | exclude=[org_rev]): |
|
160 | exclude=[org_rev]): | |
160 | revs.append(x.commit.id) |
|
161 | revs.append(x.commit.id) | |
161 |
|
162 | |||
162 | other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)] |
|
163 | other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)] | |
163 | if other_changesets: |
|
164 | if other_changesets: | |
164 | ancestor = other_changesets[0].parents[0].raw_id |
|
165 | ancestor = other_changesets[0].parents[0].raw_id | |
165 | else: |
|
166 | else: | |
166 | # no changesets from other repo, ancestor is the other_rev |
|
167 | # no changesets from other repo, ancestor is the other_rev | |
167 | ancestor = other_rev |
|
168 | ancestor = other_rev | |
168 |
|
169 | |||
169 | else: |
|
170 | else: | |
170 | so, se = org_repo.run_git_command( |
|
171 | so, se = org_repo.run_git_command( | |
171 | 'log --reverse --pretty="format: %%H" -s %s..%s' |
|
172 | 'log --reverse --pretty="format: %%H" -s %s..%s' | |
172 | % (org_rev, other_rev) |
|
173 | % (org_rev, other_rev) | |
173 | ) |
|
174 | ) | |
174 | other_changesets = [org_repo.get_changeset(cs) |
|
175 | other_changesets = [org_repo.get_changeset(cs) | |
175 | for cs in re.findall(r'[0-9a-fA-F]{40}', so)] |
|
176 | for cs in re.findall(r'[0-9a-fA-F]{40}', so)] | |
176 | org_changesets = [] |
|
177 | org_changesets = [] | |
177 |
|
178 | |||
178 | else: |
|
179 | else: | |
179 | raise Exception('Bad alias only git and hg is allowed') |
|
180 | raise Exception('Bad alias only git and hg is allowed') | |
180 |
|
181 | |||
181 | return other_changesets, org_changesets, ancestor |
|
182 | return other_changesets, org_changesets, ancestor | |
182 |
|
183 | |||
183 | @LoginRequired() |
|
184 | @LoginRequired() | |
184 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
185 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
185 | 'repository.admin') |
|
186 | 'repository.admin') | |
186 | def index(self, repo_name): |
|
187 | def index(self, repo_name): | |
187 | c.compare_home = True |
|
188 | c.compare_home = True | |
188 | org_repo = c.db_repo.repo_name |
|
189 | org_repo = c.db_repo.repo_name | |
189 | other_repo = request.GET.get('other_repo', org_repo) |
|
190 | other_repo = request.GET.get('other_repo', org_repo) | |
190 | c.org_repo = Repository.get_by_repo_name(org_repo) |
|
191 | c.org_repo = Repository.get_by_repo_name(org_repo) | |
191 | c.other_repo = Repository.get_by_repo_name(other_repo) |
|
192 | c.other_repo = Repository.get_by_repo_name(other_repo) | |
192 | c.org_ref_name = c.other_ref_name = _('Select changeset') |
|
193 | c.org_ref_name = c.other_ref_name = _('Select changeset') | |
193 | return render('compare/compare_diff.html') |
|
194 | return render('compare/compare_diff.html') | |
194 |
|
195 | |||
195 | @LoginRequired() |
|
196 | @LoginRequired() | |
196 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
197 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
197 | 'repository.admin') |
|
198 | 'repository.admin') | |
198 | def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name): |
|
199 | def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name): | |
199 | # org_ref will be evaluated in org_repo |
|
200 | # org_ref will be evaluated in org_repo | |
200 | org_repo = c.db_repo.repo_name |
|
201 | org_repo = c.db_repo.repo_name | |
201 | org_ref = (org_ref_type, org_ref_name) |
|
202 | org_ref = (org_ref_type, org_ref_name) | |
202 | # other_ref will be evaluated in other_repo |
|
203 | # other_ref will be evaluated in other_repo | |
203 | other_ref = (other_ref_type, other_ref_name) |
|
204 | other_ref = (other_ref_type, other_ref_name) | |
204 | other_repo = request.GET.get('other_repo', org_repo) |
|
205 | other_repo = request.GET.get('other_repo', org_repo) | |
205 | # If merge is True: |
|
206 | # If merge is True: | |
206 | # Show what org would get if merged with other: |
|
207 | # Show what org would get if merged with other: | |
207 | # List changesets that are ancestors of other but not of org. |
|
208 | # List changesets that are ancestors of other but not of org. | |
208 | # New changesets in org is thus ignored. |
|
209 | # New changesets in org is thus ignored. | |
209 | # Diff will be from common ancestor, and merges of org to other will thus be ignored. |
|
210 | # Diff will be from common ancestor, and merges of org to other will thus be ignored. | |
210 | # If merge is False: |
|
211 | # If merge is False: | |
211 | # Make a raw diff from org to other, no matter if related or not. |
|
212 | # Make a raw diff from org to other, no matter if related or not. | |
212 | # Changesets in one and not in the other will be ignored |
|
213 | # Changesets in one and not in the other will be ignored | |
213 | merge = bool(request.GET.get('merge')) |
|
214 | merge = bool(request.GET.get('merge')) | |
214 | # fulldiff disables cut_off_limit |
|
215 | # fulldiff disables cut_off_limit | |
215 | c.fulldiff = request.GET.get('fulldiff') |
|
216 | c.fulldiff = request.GET.get('fulldiff') | |
216 | # partial uses compare_cs.html template directly |
|
217 | # partial uses compare_cs.html template directly | |
217 | partial = request.environ.get('HTTP_X_PARTIAL_XHR') |
|
218 | partial = request.environ.get('HTTP_X_PARTIAL_XHR') | |
218 | # as_form puts hidden input field with changeset revisions |
|
219 | # as_form puts hidden input field with changeset revisions | |
219 | c.as_form = partial and request.GET.get('as_form') |
|
220 | c.as_form = partial and request.GET.get('as_form') | |
220 | # swap url for compare_diff page - never partial and never as_form |
|
221 | # swap url for compare_diff page - never partial and never as_form | |
221 | c.swap_url = h.url('compare_url', |
|
222 | c.swap_url = h.url('compare_url', | |
222 | repo_name=other_repo, |
|
223 | repo_name=other_repo, | |
223 | org_ref_type=other_ref_type, org_ref_name=other_ref_name, |
|
224 | org_ref_type=other_ref_type, org_ref_name=other_ref_name, | |
224 | other_repo=org_repo, |
|
225 | other_repo=org_repo, | |
225 | other_ref_type=org_ref_type, other_ref_name=org_ref_name, |
|
226 | other_ref_type=org_ref_type, other_ref_name=org_ref_name, | |
226 | merge=merge or '') |
|
227 | merge=merge or '') | |
227 |
|
228 | |||
|
229 | # set callbacks for generating markup for icons | |||
|
230 | c.ignorews_url = _ignorews_url | |||
|
231 | c.context_url = _context_url | |||
|
232 | ignore_whitespace = request.GET.get('ignorews') == '1' | |||
|
233 | line_context = request.GET.get('context', 3) | |||
|
234 | ||||
228 | org_repo = Repository.get_by_repo_name(org_repo) |
|
235 | org_repo = Repository.get_by_repo_name(org_repo) | |
229 | other_repo = Repository.get_by_repo_name(other_repo) |
|
236 | other_repo = Repository.get_by_repo_name(other_repo) | |
230 |
|
237 | |||
231 | if org_repo is None: |
|
238 | if org_repo is None: | |
232 | msg = 'Could not find org repo %s' % org_repo |
|
239 | msg = 'Could not find org repo %s' % org_repo | |
233 | log.error(msg) |
|
240 | log.error(msg) | |
234 | h.flash(msg, category='error') |
|
241 | h.flash(msg, category='error') | |
235 | return redirect(url('compare_home', repo_name=c.repo_name)) |
|
242 | return redirect(url('compare_home', repo_name=c.repo_name)) | |
236 |
|
243 | |||
237 | if other_repo is None: |
|
244 | if other_repo is None: | |
238 | msg = 'Could not find other repo %s' % other_repo |
|
245 | msg = 'Could not find other repo %s' % other_repo | |
239 | log.error(msg) |
|
246 | log.error(msg) | |
240 | h.flash(msg, category='error') |
|
247 | h.flash(msg, category='error') | |
241 | return redirect(url('compare_home', repo_name=c.repo_name)) |
|
248 | return redirect(url('compare_home', repo_name=c.repo_name)) | |
242 |
|
249 | |||
243 | if org_repo.scm_instance.alias != other_repo.scm_instance.alias: |
|
250 | if org_repo.scm_instance.alias != other_repo.scm_instance.alias: | |
244 | msg = 'compare of two different kind of remote repos not available' |
|
251 | msg = 'compare of two different kind of remote repos not available' | |
245 | log.error(msg) |
|
252 | log.error(msg) | |
246 | h.flash(msg, category='error') |
|
253 | h.flash(msg, category='error') | |
247 | return redirect(url('compare_home', repo_name=c.repo_name)) |
|
254 | return redirect(url('compare_home', repo_name=c.repo_name)) | |
248 |
|
255 | |||
249 | c.org_rev = self.__get_rev_or_redirect(ref=org_ref, repo=org_repo, partial=partial) |
|
256 | c.org_rev = self.__get_rev_or_redirect(ref=org_ref, repo=org_repo, partial=partial) | |
250 | c.other_rev = self.__get_rev_or_redirect(ref=other_ref, repo=other_repo, partial=partial) |
|
257 | c.other_rev = self.__get_rev_or_redirect(ref=other_ref, repo=other_repo, partial=partial) | |
251 |
|
258 | |||
252 | c.compare_home = False |
|
259 | c.compare_home = False | |
253 | c.org_repo = org_repo |
|
260 | c.org_repo = org_repo | |
254 | c.other_repo = other_repo |
|
261 | c.other_repo = other_repo | |
255 | c.org_ref_name = org_ref_name |
|
262 | c.org_ref_name = org_ref_name | |
256 | c.other_ref_name = other_ref_name |
|
263 | c.other_ref_name = other_ref_name | |
257 | c.org_ref_type = org_ref_type |
|
264 | c.org_ref_type = org_ref_type | |
258 | c.other_ref_type = other_ref_type |
|
265 | c.other_ref_type = other_ref_type | |
259 |
|
266 | |||
260 | c.cs_ranges, c.cs_ranges_org, c.ancestor = self._get_changesets( |
|
267 | c.cs_ranges, c.cs_ranges_org, c.ancestor = self._get_changesets( | |
261 | org_repo.scm_instance.alias, org_repo.scm_instance, c.org_rev, |
|
268 | org_repo.scm_instance.alias, org_repo.scm_instance, c.org_rev, | |
262 | other_repo.scm_instance, c.other_rev) |
|
269 | other_repo.scm_instance, c.other_rev) | |
263 | c.statuses = c.db_repo.statuses( |
|
270 | c.statuses = c.db_repo.statuses( | |
264 | [x.raw_id for x in c.cs_ranges]) |
|
271 | [x.raw_id for x in c.cs_ranges]) | |
265 |
|
272 | |||
266 | if partial: |
|
273 | if partial: | |
267 | return render('compare/compare_cs.html') |
|
274 | return render('compare/compare_cs.html') | |
268 | if merge and c.ancestor: |
|
275 | if merge and c.ancestor: | |
269 | # case we want a simple diff without incoming changesets, |
|
276 | # case we want a simple diff without incoming changesets, | |
270 | # previewing what will be merged. |
|
277 | # previewing what will be merged. | |
271 | # Make the diff on the other repo (which is known to have other_rev) |
|
278 | # Make the diff on the other repo (which is known to have other_rev) | |
272 | log.debug('Using ancestor %s as rev1 instead of %s' |
|
279 | log.debug('Using ancestor %s as rev1 instead of %s' | |
273 | % (c.ancestor, c.org_rev)) |
|
280 | % (c.ancestor, c.org_rev)) | |
274 | rev1 = c.ancestor |
|
281 | rev1 = c.ancestor | |
275 | org_repo = other_repo |
|
282 | org_repo = other_repo | |
276 | else: # comparing tips, not necessarily linearly related |
|
283 | else: # comparing tips, not necessarily linearly related | |
277 | if merge: |
|
284 | if merge: | |
278 | log.error('Unable to find ancestor revision') |
|
285 | log.error('Unable to find ancestor revision') | |
279 | if org_repo != other_repo: |
|
286 | if org_repo != other_repo: | |
280 | log.error('cannot compare across repos %s and %s', org_repo, other_repo) |
|
287 | log.error('cannot compare across repos %s and %s', org_repo, other_repo) | |
281 | raise HTTPNotFound |
|
288 | raise HTTPNotFound | |
282 | rev1 = c.org_rev |
|
289 | rev1 = c.org_rev | |
283 |
|
290 | |||
284 | diff_limit = self.cut_off_limit if not c.fulldiff else None |
|
291 | diff_limit = self.cut_off_limit if not c.fulldiff else None | |
285 |
|
292 | |||
286 | log.debug('running diff between %s and %s in %s' |
|
293 | log.debug('running diff between %s and %s in %s' | |
287 | % (rev1, c.other_rev, org_repo.scm_instance.path)) |
|
294 | % (rev1, c.other_rev, org_repo.scm_instance.path)) | |
288 |
txtdiff = org_repo.scm_instance.get_diff(rev1=rev1, rev2=c.other_rev |
|
295 | txtdiff = org_repo.scm_instance.get_diff(rev1=rev1, rev2=c.other_rev, | |
|
296 | ignore_whitespace=ignore_whitespace, | |||
|
297 | context=line_context) | |||
289 |
|
298 | |||
290 | diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', |
|
299 | diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', | |
291 | diff_limit=diff_limit) |
|
300 | diff_limit=diff_limit) | |
292 | _parsed = diff_processor.prepare() |
|
301 | _parsed = diff_processor.prepare() | |
293 |
|
302 | |||
294 | c.limited_diff = False |
|
303 | c.limited_diff = False | |
295 | if isinstance(_parsed, LimitedDiffContainer): |
|
304 | if isinstance(_parsed, LimitedDiffContainer): | |
296 | c.limited_diff = True |
|
305 | c.limited_diff = True | |
297 |
|
306 | |||
298 | c.files = [] |
|
307 | c.files = [] | |
299 | c.changes = {} |
|
308 | c.changes = {} | |
300 | c.lines_added = 0 |
|
309 | c.lines_added = 0 | |
301 | c.lines_deleted = 0 |
|
310 | c.lines_deleted = 0 | |
302 | for f in _parsed: |
|
311 | for f in _parsed: | |
303 | st = f['stats'] |
|
312 | st = f['stats'] | |
304 | if not st['binary']: |
|
313 | if not st['binary']: | |
305 | c.lines_added += st['added'] |
|
314 | c.lines_added += st['added'] | |
306 | c.lines_deleted += st['deleted'] |
|
315 | c.lines_deleted += st['deleted'] | |
307 | fid = h.FID('', f['filename']) |
|
316 | fid = h.FID('', f['filename']) | |
308 | c.files.append([fid, f['operation'], f['filename'], f['stats']]) |
|
317 | c.files.append([fid, f['operation'], f['filename'], f['stats']]) | |
309 | htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f]) |
|
318 | htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f]) | |
310 | c.changes[fid] = [f['operation'], f['filename'], htmldiff] |
|
319 | c.changes[fid] = [f['operation'], f['filename'], htmldiff] | |
311 |
|
320 | |||
312 | return render('compare/compare_diff.html') |
|
321 | return render('compare/compare_diff.html') |
@@ -1,581 +1,589 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.pullrequests |
|
15 | kallithea.controllers.pullrequests | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | pull requests controller for Kallithea for initializing pull requests |
|
18 | pull requests controller for Kallithea for initializing pull requests | |
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: May 7, 2012 |
|
22 | :created_on: May 7, 2012 | |
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 logging |
|
28 | import logging | |
29 | import traceback |
|
29 | import traceback | |
30 | import formencode |
|
30 | import formencode | |
31 |
|
31 | |||
32 | from webob.exc import HTTPNotFound, HTTPForbidden |
|
32 | from webob.exc import HTTPNotFound, HTTPForbidden | |
33 | from collections import defaultdict |
|
33 | from collections import defaultdict | |
34 | from itertools import groupby |
|
34 | from itertools import groupby | |
35 |
|
35 | |||
36 | from pylons import request, tmpl_context as c, url |
|
36 | from pylons import request, tmpl_context as c, url | |
37 | from pylons.controllers.util import redirect |
|
37 | from pylons.controllers.util import redirect | |
38 | from pylons.i18n.translation import _ |
|
38 | from pylons.i18n.translation import _ | |
39 |
|
39 | |||
40 | from kallithea.lib.compat import json |
|
40 | from kallithea.lib.compat import json | |
41 | from kallithea.lib.base import BaseRepoController, render |
|
41 | from kallithea.lib.base import BaseRepoController, render | |
42 | from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\ |
|
42 | from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\ | |
43 | NotAnonymous |
|
43 | NotAnonymous | |
44 | from kallithea.lib.helpers import Page |
|
44 | from kallithea.lib.helpers import Page | |
45 | from kallithea.lib import helpers as h |
|
45 | from kallithea.lib import helpers as h | |
46 | from kallithea.lib import diffs |
|
46 | from kallithea.lib import diffs | |
47 | from kallithea.lib.utils import action_logger, jsonify |
|
47 | from kallithea.lib.utils import action_logger, jsonify | |
48 | from kallithea.lib.vcs.utils import safe_str |
|
48 | from kallithea.lib.vcs.utils import safe_str | |
49 | from kallithea.lib.vcs.exceptions import EmptyRepositoryError |
|
49 | from kallithea.lib.vcs.exceptions import EmptyRepositoryError | |
50 | from kallithea.lib.diffs import LimitedDiffContainer |
|
50 | from kallithea.lib.diffs import LimitedDiffContainer | |
51 | from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment,\ |
|
51 | from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment,\ | |
52 | PullRequestReviewers |
|
52 | PullRequestReviewers | |
53 | from kallithea.model.pull_request import PullRequestModel |
|
53 | from kallithea.model.pull_request import PullRequestModel | |
54 | from kallithea.model.meta import Session |
|
54 | from kallithea.model.meta import Session | |
55 | from kallithea.model.repo import RepoModel |
|
55 | from kallithea.model.repo import RepoModel | |
56 | from kallithea.model.comment import ChangesetCommentsModel |
|
56 | from kallithea.model.comment import ChangesetCommentsModel | |
57 | from kallithea.model.changeset_status import ChangesetStatusModel |
|
57 | from kallithea.model.changeset_status import ChangesetStatusModel | |
58 | from kallithea.model.forms import PullRequestForm |
|
58 | from kallithea.model.forms import PullRequestForm | |
59 | from kallithea.lib.utils2 import safe_int |
|
59 | from kallithea.lib.utils2 import safe_int | |
|
60 | from kallithea.controllers.changeset import anchor_url, _ignorews_url,\ | |||
|
61 | _context_url, get_line_ctx, get_ignore_ws | |||
60 |
|
62 | |||
61 | log = logging.getLogger(__name__) |
|
63 | log = logging.getLogger(__name__) | |
62 |
|
64 | |||
63 |
|
65 | |||
64 | class PullrequestsController(BaseRepoController): |
|
66 | class PullrequestsController(BaseRepoController): | |
65 |
|
67 | |||
66 | def __before__(self): |
|
68 | def __before__(self): | |
67 | super(PullrequestsController, self).__before__() |
|
69 | super(PullrequestsController, self).__before__() | |
68 | repo_model = RepoModel() |
|
70 | repo_model = RepoModel() | |
69 | c.users_array = repo_model.get_users_js() |
|
71 | c.users_array = repo_model.get_users_js() | |
70 | c.user_groups_array = repo_model.get_user_groups_js() |
|
72 | c.user_groups_array = repo_model.get_user_groups_js() | |
71 |
|
73 | |||
72 | def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None): |
|
74 | def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None): | |
73 | """return a structure with repo's interesting changesets, suitable for |
|
75 | """return a structure with repo's interesting changesets, suitable for | |
74 | the selectors in pullrequest.html |
|
76 | the selectors in pullrequest.html | |
75 |
|
77 | |||
76 | rev: a revision that must be in the list somehow and selected by default |
|
78 | rev: a revision that must be in the list somehow and selected by default | |
77 | branch: a branch that must be in the list and selected by default - even if closed |
|
79 | branch: a branch that must be in the list and selected by default - even if closed | |
78 | branch_rev: a revision of which peers should be preferred and available.""" |
|
80 | branch_rev: a revision of which peers should be preferred and available.""" | |
79 | # list named branches that has been merged to this named branch - it should probably merge back |
|
81 | # list named branches that has been merged to this named branch - it should probably merge back | |
80 | peers = [] |
|
82 | peers = [] | |
81 |
|
83 | |||
82 | if rev: |
|
84 | if rev: | |
83 | rev = safe_str(rev) |
|
85 | rev = safe_str(rev) | |
84 |
|
86 | |||
85 | if branch: |
|
87 | if branch: | |
86 | branch = safe_str(branch) |
|
88 | branch = safe_str(branch) | |
87 |
|
89 | |||
88 | if branch_rev: |
|
90 | if branch_rev: | |
89 | branch_rev = safe_str(branch_rev) |
|
91 | branch_rev = safe_str(branch_rev) | |
90 | # not restricting to merge() would also get branch point and be better |
|
92 | # not restricting to merge() would also get branch point and be better | |
91 | # (especially because it would get the branch point) ... but is currently too expensive |
|
93 | # (especially because it would get the branch point) ... but is currently too expensive | |
92 | otherbranches = {} |
|
94 | otherbranches = {} | |
93 | for i in repo._repo.revs( |
|
95 | for i in repo._repo.revs( | |
94 | "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))", |
|
96 | "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))", | |
95 | branch_rev, branch_rev): |
|
97 | branch_rev, branch_rev): | |
96 | cs = repo.get_changeset(i) |
|
98 | cs = repo.get_changeset(i) | |
97 | otherbranches[cs.branch] = cs.raw_id |
|
99 | otherbranches[cs.branch] = cs.raw_id | |
98 | for abranch, node in otherbranches.iteritems(): |
|
100 | for abranch, node in otherbranches.iteritems(): | |
99 | selected = 'branch:%s:%s' % (abranch, node) |
|
101 | selected = 'branch:%s:%s' % (abranch, node) | |
100 | peers.append((selected, abranch)) |
|
102 | peers.append((selected, abranch)) | |
101 |
|
103 | |||
102 | selected = None |
|
104 | selected = None | |
103 |
|
105 | |||
104 | branches = [] |
|
106 | branches = [] | |
105 | for abranch, branchrev in repo.branches.iteritems(): |
|
107 | for abranch, branchrev in repo.branches.iteritems(): | |
106 | n = 'branch:%s:%s' % (abranch, branchrev) |
|
108 | n = 'branch:%s:%s' % (abranch, branchrev) | |
107 | branches.append((n, abranch)) |
|
109 | branches.append((n, abranch)) | |
108 | if rev == branchrev: |
|
110 | if rev == branchrev: | |
109 | selected = n |
|
111 | selected = n | |
110 | if branch == abranch: |
|
112 | if branch == abranch: | |
111 | selected = n |
|
113 | selected = n | |
112 | branch = None |
|
114 | branch = None | |
113 |
|
115 | |||
114 | if branch: # branch not in list - it is probably closed |
|
116 | if branch: # branch not in list - it is probably closed | |
115 | revs = repo._repo.revs('max(branch(%s))', branch) |
|
117 | revs = repo._repo.revs('max(branch(%s))', branch) | |
116 | if revs: |
|
118 | if revs: | |
117 | cs = repo.get_changeset(revs[0]) |
|
119 | cs = repo.get_changeset(revs[0]) | |
118 | selected = 'branch:%s:%s' % (branch, cs.raw_id) |
|
120 | selected = 'branch:%s:%s' % (branch, cs.raw_id) | |
119 | branches.append((selected, branch)) |
|
121 | branches.append((selected, branch)) | |
120 |
|
122 | |||
121 | bookmarks = [] |
|
123 | bookmarks = [] | |
122 | for bookmark, bookmarkrev in repo.bookmarks.iteritems(): |
|
124 | for bookmark, bookmarkrev in repo.bookmarks.iteritems(): | |
123 | n = 'book:%s:%s' % (bookmark, bookmarkrev) |
|
125 | n = 'book:%s:%s' % (bookmark, bookmarkrev) | |
124 | bookmarks.append((n, bookmark)) |
|
126 | bookmarks.append((n, bookmark)) | |
125 | if rev == bookmarkrev: |
|
127 | if rev == bookmarkrev: | |
126 | selected = n |
|
128 | selected = n | |
127 |
|
129 | |||
128 | tags = [] |
|
130 | tags = [] | |
129 | for tag, tagrev in repo.tags.iteritems(): |
|
131 | for tag, tagrev in repo.tags.iteritems(): | |
130 | n = 'tag:%s:%s' % (tag, tagrev) |
|
132 | n = 'tag:%s:%s' % (tag, tagrev) | |
131 | tags.append((n, tag)) |
|
133 | tags.append((n, tag)) | |
132 | if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better |
|
134 | if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better | |
133 | selected = n |
|
135 | selected = n | |
134 |
|
136 | |||
135 | # prio 1: rev was selected as existing entry above |
|
137 | # prio 1: rev was selected as existing entry above | |
136 |
|
138 | |||
137 | # prio 2: create special entry for rev; rev _must_ be used |
|
139 | # prio 2: create special entry for rev; rev _must_ be used | |
138 | specials = [] |
|
140 | specials = [] | |
139 | if rev and selected is None: |
|
141 | if rev and selected is None: | |
140 | selected = 'rev:%s:%s' % (rev, rev) |
|
142 | selected = 'rev:%s:%s' % (rev, rev) | |
141 | specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))] |
|
143 | specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))] | |
142 |
|
144 | |||
143 | # prio 3: most recent peer branch |
|
145 | # prio 3: most recent peer branch | |
144 | if peers and not selected: |
|
146 | if peers and not selected: | |
145 | selected = peers[0][0][0] |
|
147 | selected = peers[0][0][0] | |
146 |
|
148 | |||
147 | # prio 4: tip revision |
|
149 | # prio 4: tip revision | |
148 | if not selected: |
|
150 | if not selected: | |
149 | if h.is_hg(repo): |
|
151 | if h.is_hg(repo): | |
150 | if 'tip' in repo.tags: |
|
152 | if 'tip' in repo.tags: | |
151 | selected = 'tag:tip:%s' % repo.tags['tip'] |
|
153 | selected = 'tag:tip:%s' % repo.tags['tip'] | |
152 | else: |
|
154 | else: | |
153 | selected = 'tag:null:0' |
|
155 | selected = 'tag:null:0' | |
154 | tags.append((selected, 'null')) |
|
156 | tags.append((selected, 'null')) | |
155 | else: |
|
157 | else: | |
156 | if 'master' in repo.branches: |
|
158 | if 'master' in repo.branches: | |
157 | selected = 'branch:master:%s' % repo.branches['master'] |
|
159 | selected = 'branch:master:%s' % repo.branches['master'] | |
158 | else: |
|
160 | else: | |
159 | k, v = repo.branches.items()[0] |
|
161 | k, v = repo.branches.items()[0] | |
160 | selected = 'branch:%s:%s' % (k, v) |
|
162 | selected = 'branch:%s:%s' % (k, v) | |
161 |
|
163 | |||
162 | groups = [(specials, _("Special")), |
|
164 | groups = [(specials, _("Special")), | |
163 | (peers, _("Peer branches")), |
|
165 | (peers, _("Peer branches")), | |
164 | (bookmarks, _("Bookmarks")), |
|
166 | (bookmarks, _("Bookmarks")), | |
165 | (branches, _("Branches")), |
|
167 | (branches, _("Branches")), | |
166 | (tags, _("Tags")), |
|
168 | (tags, _("Tags")), | |
167 | ] |
|
169 | ] | |
168 | return [g for g in groups if g[0]], selected |
|
170 | return [g for g in groups if g[0]], selected | |
169 |
|
171 | |||
170 | def _get_is_allowed_change_status(self, pull_request): |
|
172 | def _get_is_allowed_change_status(self, pull_request): | |
171 | owner = self.authuser.user_id == pull_request.user_id |
|
173 | owner = self.authuser.user_id == pull_request.user_id | |
172 | reviewer = self.authuser.user_id in [x.user_id for x in |
|
174 | reviewer = self.authuser.user_id in [x.user_id for x in | |
173 | pull_request.reviewers] |
|
175 | pull_request.reviewers] | |
174 | return self.authuser.admin or owner or reviewer |
|
176 | return self.authuser.admin or owner or reviewer | |
175 |
|
177 | |||
176 | def _load_compare_data(self, pull_request, enable_comments=True): |
|
178 | def _load_compare_data(self, pull_request, enable_comments=True): | |
177 | """ |
|
179 | """ | |
178 | Load context data needed for generating compare diff |
|
180 | Load context data needed for generating compare diff | |
179 |
|
181 | |||
180 | :param pull_request: |
|
182 | :param pull_request: | |
181 | """ |
|
183 | """ | |
182 | c.org_repo = pull_request.org_repo |
|
184 | c.org_repo = pull_request.org_repo | |
183 | (c.org_ref_type, |
|
185 | (c.org_ref_type, | |
184 | c.org_ref_name, |
|
186 | c.org_ref_name, | |
185 | c.org_rev) = pull_request.org_ref.split(':') |
|
187 | c.org_rev) = pull_request.org_ref.split(':') | |
186 |
|
188 | |||
187 | c.other_repo = c.org_repo |
|
189 | c.other_repo = c.org_repo | |
188 | (c.other_ref_type, |
|
190 | (c.other_ref_type, | |
189 | c.other_ref_name, |
|
191 | c.other_ref_name, | |
190 | c.other_rev) = pull_request.other_ref.split(':') |
|
192 | c.other_rev) = pull_request.other_ref.split(':') | |
191 |
|
193 | |||
192 | c.cs_ranges = [c.org_repo.get_changeset(x) for x in pull_request.revisions] |
|
194 | c.cs_ranges = [c.org_repo.get_changeset(x) for x in pull_request.revisions] | |
193 | c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ... |
|
195 | c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ... | |
194 |
|
196 | |||
195 | c.statuses = c.org_repo.statuses([x.raw_id for x in c.cs_ranges]) |
|
197 | c.statuses = c.org_repo.statuses([x.raw_id for x in c.cs_ranges]) | |
196 |
|
198 | |||
|
199 | ignore_whitespace = request.GET.get('ignorews') == '1' | |||
|
200 | line_context = request.GET.get('context', 3) | |||
|
201 | c.ignorews_url = _ignorews_url | |||
|
202 | c.context_url = _context_url | |||
197 | c.fulldiff = request.GET.get('fulldiff') |
|
203 | c.fulldiff = request.GET.get('fulldiff') | |
198 | diff_limit = self.cut_off_limit if not c.fulldiff else None |
|
204 | diff_limit = self.cut_off_limit if not c.fulldiff else None | |
199 |
|
205 | |||
200 | # we swap org/other ref since we run a simple diff on one repo |
|
206 | # we swap org/other ref since we run a simple diff on one repo | |
201 | log.debug('running diff between %s and %s in %s' |
|
207 | log.debug('running diff between %s and %s in %s' | |
202 | % (c.other_rev, c.org_rev, c.org_repo.scm_instance.path)) |
|
208 | % (c.other_rev, c.org_rev, c.org_repo.scm_instance.path)) | |
203 |
txtdiff = c.org_repo.scm_instance.get_diff(rev1=safe_str(c.other_rev), rev2=safe_str(c.org_rev) |
|
209 | txtdiff = c.org_repo.scm_instance.get_diff(rev1=safe_str(c.other_rev), rev2=safe_str(c.org_rev), | |
|
210 | ignore_whitespace=ignore_whitespace, | |||
|
211 | context=line_context) | |||
204 |
|
212 | |||
205 | diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', |
|
213 | diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', | |
206 | diff_limit=diff_limit) |
|
214 | diff_limit=diff_limit) | |
207 | _parsed = diff_processor.prepare() |
|
215 | _parsed = diff_processor.prepare() | |
208 |
|
216 | |||
209 | c.limited_diff = False |
|
217 | c.limited_diff = False | |
210 | if isinstance(_parsed, LimitedDiffContainer): |
|
218 | if isinstance(_parsed, LimitedDiffContainer): | |
211 | c.limited_diff = True |
|
219 | c.limited_diff = True | |
212 |
|
220 | |||
213 | c.files = [] |
|
221 | c.files = [] | |
214 | c.changes = {} |
|
222 | c.changes = {} | |
215 | c.lines_added = 0 |
|
223 | c.lines_added = 0 | |
216 | c.lines_deleted = 0 |
|
224 | c.lines_deleted = 0 | |
217 |
|
225 | |||
218 | for f in _parsed: |
|
226 | for f in _parsed: | |
219 | st = f['stats'] |
|
227 | st = f['stats'] | |
220 | c.lines_added += st['added'] |
|
228 | c.lines_added += st['added'] | |
221 | c.lines_deleted += st['deleted'] |
|
229 | c.lines_deleted += st['deleted'] | |
222 | fid = h.FID('', f['filename']) |
|
230 | fid = h.FID('', f['filename']) | |
223 | c.files.append([fid, f['operation'], f['filename'], f['stats']]) |
|
231 | c.files.append([fid, f['operation'], f['filename'], f['stats']]) | |
224 | htmldiff = diff_processor.as_html(enable_comments=enable_comments, |
|
232 | htmldiff = diff_processor.as_html(enable_comments=enable_comments, | |
225 | parsed_lines=[f]) |
|
233 | parsed_lines=[f]) | |
226 | c.changes[fid] = [f['operation'], f['filename'], htmldiff] |
|
234 | c.changes[fid] = [f['operation'], f['filename'], htmldiff] | |
227 |
|
235 | |||
228 | @LoginRequired() |
|
236 | @LoginRequired() | |
229 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
237 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
230 | 'repository.admin') |
|
238 | 'repository.admin') | |
231 | def show_all(self, repo_name): |
|
239 | def show_all(self, repo_name): | |
232 | c.from_ = request.GET.get('from_') or '' |
|
240 | c.from_ = request.GET.get('from_') or '' | |
233 | c.closed = request.GET.get('closed') or '' |
|
241 | c.closed = request.GET.get('closed') or '' | |
234 | c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed) |
|
242 | c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed) | |
235 | c.repo_name = repo_name |
|
243 | c.repo_name = repo_name | |
236 | p = safe_int(request.GET.get('page', 1), 1) |
|
244 | p = safe_int(request.GET.get('page', 1), 1) | |
237 |
|
245 | |||
238 | c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10) |
|
246 | c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10) | |
239 |
|
247 | |||
240 | c.pullrequest_data = render('/pullrequests/pullrequest_data.html') |
|
248 | c.pullrequest_data = render('/pullrequests/pullrequest_data.html') | |
241 |
|
249 | |||
242 | if request.environ.get('HTTP_X_PARTIAL_XHR'): |
|
250 | if request.environ.get('HTTP_X_PARTIAL_XHR'): | |
243 | return c.pullrequest_data |
|
251 | return c.pullrequest_data | |
244 |
|
252 | |||
245 | return render('/pullrequests/pullrequest_show_all.html') |
|
253 | return render('/pullrequests/pullrequest_show_all.html') | |
246 |
|
254 | |||
247 | @LoginRequired() |
|
255 | @LoginRequired() | |
248 | def show_my(self): # my_account_my_pullrequests |
|
256 | def show_my(self): # my_account_my_pullrequests | |
249 | c.show_closed = request.GET.get('pr_show_closed') |
|
257 | c.show_closed = request.GET.get('pr_show_closed') | |
250 | return render('/pullrequests/pullrequest_show_my.html') |
|
258 | return render('/pullrequests/pullrequest_show_my.html') | |
251 |
|
259 | |||
252 | @NotAnonymous() |
|
260 | @NotAnonymous() | |
253 | def show_my_data(self): |
|
261 | def show_my_data(self): | |
254 | c.show_closed = request.GET.get('pr_show_closed') |
|
262 | c.show_closed = request.GET.get('pr_show_closed') | |
255 |
|
263 | |||
256 | def _filter(pr): |
|
264 | def _filter(pr): | |
257 | s = sorted(pr, key=lambda o: o.created_on, reverse=True) |
|
265 | s = sorted(pr, key=lambda o: o.created_on, reverse=True) | |
258 | if not c.show_closed: |
|
266 | if not c.show_closed: | |
259 | s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s) |
|
267 | s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s) | |
260 | return s |
|
268 | return s | |
261 |
|
269 | |||
262 | c.my_pull_requests = _filter(PullRequest.query()\ |
|
270 | c.my_pull_requests = _filter(PullRequest.query()\ | |
263 | .filter(PullRequest.user_id == |
|
271 | .filter(PullRequest.user_id == | |
264 | self.authuser.user_id)\ |
|
272 | self.authuser.user_id)\ | |
265 | .all()) |
|
273 | .all()) | |
266 |
|
274 | |||
267 | c.participate_in_pull_requests = _filter(PullRequest.query()\ |
|
275 | c.participate_in_pull_requests = _filter(PullRequest.query()\ | |
268 | .join(PullRequestReviewers)\ |
|
276 | .join(PullRequestReviewers)\ | |
269 | .filter(PullRequestReviewers.user_id == |
|
277 | .filter(PullRequestReviewers.user_id == | |
270 | self.authuser.user_id)\ |
|
278 | self.authuser.user_id)\ | |
271 | ) |
|
279 | ) | |
272 |
|
280 | |||
273 | return render('/pullrequests/pullrequest_show_my_data.html') |
|
281 | return render('/pullrequests/pullrequest_show_my_data.html') | |
274 |
|
282 | |||
275 | @LoginRequired() |
|
283 | @LoginRequired() | |
276 | @NotAnonymous() |
|
284 | @NotAnonymous() | |
277 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
285 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
278 | 'repository.admin') |
|
286 | 'repository.admin') | |
279 | def index(self): |
|
287 | def index(self): | |
280 | org_repo = c.db_repo |
|
288 | org_repo = c.db_repo | |
281 |
|
289 | |||
282 | try: |
|
290 | try: | |
283 | org_repo.scm_instance.get_changeset() |
|
291 | org_repo.scm_instance.get_changeset() | |
284 | except EmptyRepositoryError, e: |
|
292 | except EmptyRepositoryError, e: | |
285 | h.flash(h.literal(_('There are no changesets yet')), |
|
293 | h.flash(h.literal(_('There are no changesets yet')), | |
286 | category='warning') |
|
294 | category='warning') | |
287 | redirect(url('summary_home', repo_name=org_repo.repo_name)) |
|
295 | redirect(url('summary_home', repo_name=org_repo.repo_name)) | |
288 |
|
296 | |||
289 | org_rev = request.GET.get('rev_end') |
|
297 | org_rev = request.GET.get('rev_end') | |
290 | # rev_start is not directly useful - its parent could however be used |
|
298 | # rev_start is not directly useful - its parent could however be used | |
291 | # as default for other and thus give a simple compare view |
|
299 | # as default for other and thus give a simple compare view | |
292 | #other_rev = request.POST.get('rev_start') |
|
300 | #other_rev = request.POST.get('rev_start') | |
293 | branch = request.GET.get('branch') |
|
301 | branch = request.GET.get('branch') | |
294 |
|
302 | |||
295 | c.org_repos = [] |
|
303 | c.org_repos = [] | |
296 | c.org_repos.append((org_repo.repo_name, org_repo.repo_name)) |
|
304 | c.org_repos.append((org_repo.repo_name, org_repo.repo_name)) | |
297 | c.default_org_repo = org_repo.repo_name |
|
305 | c.default_org_repo = org_repo.repo_name | |
298 | c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch) |
|
306 | c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch) | |
299 |
|
307 | |||
300 | c.other_repos = [] |
|
308 | c.other_repos = [] | |
301 | other_repos_info = {} |
|
309 | other_repos_info = {} | |
302 |
|
310 | |||
303 | def add_other_repo(repo, branch_rev=None): |
|
311 | def add_other_repo(repo, branch_rev=None): | |
304 | if repo.repo_name in other_repos_info: # shouldn't happen |
|
312 | if repo.repo_name in other_repos_info: # shouldn't happen | |
305 | return |
|
313 | return | |
306 | c.other_repos.append((repo.repo_name, repo.repo_name)) |
|
314 | c.other_repos.append((repo.repo_name, repo.repo_name)) | |
307 | other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev) |
|
315 | other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev) | |
308 | other_repos_info[repo.repo_name] = { |
|
316 | other_repos_info[repo.repo_name] = { | |
309 | 'user': dict(user_id=repo.user.user_id, |
|
317 | 'user': dict(user_id=repo.user.user_id, | |
310 | username=repo.user.username, |
|
318 | username=repo.user.username, | |
311 | firstname=repo.user.firstname, |
|
319 | firstname=repo.user.firstname, | |
312 | lastname=repo.user.lastname, |
|
320 | lastname=repo.user.lastname, | |
313 | gravatar_link=h.gravatar_url(repo.user.email, 14)), |
|
321 | gravatar_link=h.gravatar_url(repo.user.email, 14)), | |
314 | 'description': repo.description.split('\n', 1)[0], |
|
322 | 'description': repo.description.split('\n', 1)[0], | |
315 | 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs') |
|
323 | 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs') | |
316 | } |
|
324 | } | |
317 |
|
325 | |||
318 | # add org repo to other so we can open pull request against peer branches on itself |
|
326 | # add org repo to other so we can open pull request against peer branches on itself | |
319 | add_other_repo(org_repo, branch_rev=org_rev) |
|
327 | add_other_repo(org_repo, branch_rev=org_rev) | |
320 | c.default_other_repo = org_repo.repo_name |
|
328 | c.default_other_repo = org_repo.repo_name | |
321 |
|
329 | |||
322 | # gather forks and add to this list ... even though it is rare to |
|
330 | # gather forks and add to this list ... even though it is rare to | |
323 | # request forks to pull from their parent |
|
331 | # request forks to pull from their parent | |
324 | for fork in org_repo.forks: |
|
332 | for fork in org_repo.forks: | |
325 | add_other_repo(fork) |
|
333 | add_other_repo(fork) | |
326 |
|
334 | |||
327 | # add parents of this fork also, but only if it's not empty |
|
335 | # add parents of this fork also, but only if it's not empty | |
328 | if org_repo.parent and org_repo.parent.scm_instance.revisions: |
|
336 | if org_repo.parent and org_repo.parent.scm_instance.revisions: | |
329 | add_other_repo(org_repo.parent) |
|
337 | add_other_repo(org_repo.parent) | |
330 | c.default_other_repo = org_repo.parent.repo_name |
|
338 | c.default_other_repo = org_repo.parent.repo_name | |
331 |
|
339 | |||
332 | c.default_other_repo_info = other_repos_info[c.default_other_repo] |
|
340 | c.default_other_repo_info = other_repos_info[c.default_other_repo] | |
333 | c.other_repos_info = json.dumps(other_repos_info) |
|
341 | c.other_repos_info = json.dumps(other_repos_info) | |
334 |
|
342 | |||
335 | return render('/pullrequests/pullrequest.html') |
|
343 | return render('/pullrequests/pullrequest.html') | |
336 |
|
344 | |||
337 | @LoginRequired() |
|
345 | @LoginRequired() | |
338 | @NotAnonymous() |
|
346 | @NotAnonymous() | |
339 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
347 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
340 | 'repository.admin') |
|
348 | 'repository.admin') | |
341 | def create(self, repo_name): |
|
349 | def create(self, repo_name): | |
342 | repo = RepoModel()._get_repo(repo_name) |
|
350 | repo = RepoModel()._get_repo(repo_name) | |
343 | try: |
|
351 | try: | |
344 | _form = PullRequestForm(repo.repo_id)().to_python(request.POST) |
|
352 | _form = PullRequestForm(repo.repo_id)().to_python(request.POST) | |
345 | except formencode.Invalid, errors: |
|
353 | except formencode.Invalid, errors: | |
346 | log.error(traceback.format_exc()) |
|
354 | log.error(traceback.format_exc()) | |
347 | if errors.error_dict.get('revisions'): |
|
355 | if errors.error_dict.get('revisions'): | |
348 | msg = 'Revisions: %s' % errors.error_dict['revisions'] |
|
356 | msg = 'Revisions: %s' % errors.error_dict['revisions'] | |
349 | elif errors.error_dict.get('pullrequest_title'): |
|
357 | elif errors.error_dict.get('pullrequest_title'): | |
350 | msg = _('Pull request requires a title with min. 3 chars') |
|
358 | msg = _('Pull request requires a title with min. 3 chars') | |
351 | else: |
|
359 | else: | |
352 | msg = _('Error creating pull request: %s') % errors.msg |
|
360 | msg = _('Error creating pull request: %s') % errors.msg | |
353 |
|
361 | |||
354 | h.flash(msg, 'error') |
|
362 | h.flash(msg, 'error') | |
355 | return redirect(url('pullrequest_home', repo_name=repo_name)) ## would rather just go back to form ... |
|
363 | return redirect(url('pullrequest_home', repo_name=repo_name)) ## would rather just go back to form ... | |
356 |
|
364 | |||
357 | org_repo = _form['org_repo'] |
|
365 | org_repo = _form['org_repo'] | |
358 | org_ref = _form['org_ref'] # will end with merge_rev but have symbolic name |
|
366 | org_ref = _form['org_ref'] # will end with merge_rev but have symbolic name | |
359 | other_repo = _form['other_repo'] |
|
367 | other_repo = _form['other_repo'] | |
360 | other_ref = 'rev:ancestor:%s' % _form['ancestor_rev'] # could be calculated from other_ref ... |
|
368 | other_ref = 'rev:ancestor:%s' % _form['ancestor_rev'] # could be calculated from other_ref ... | |
361 | revisions = [x for x in reversed(_form['revisions'])] |
|
369 | revisions = [x for x in reversed(_form['revisions'])] | |
362 | reviewers = _form['review_members'] |
|
370 | reviewers = _form['review_members'] | |
363 |
|
371 | |||
364 | title = _form['pullrequest_title'] |
|
372 | title = _form['pullrequest_title'] | |
365 | if not title: |
|
373 | if not title: | |
366 | title = '%s#%s to %s' % (org_repo, org_ref.split(':', 2)[1], other_repo) |
|
374 | title = '%s#%s to %s' % (org_repo, org_ref.split(':', 2)[1], other_repo) | |
367 | description = _form['pullrequest_desc'] |
|
375 | description = _form['pullrequest_desc'] | |
368 | try: |
|
376 | try: | |
369 | pull_request = PullRequestModel().create( |
|
377 | pull_request = PullRequestModel().create( | |
370 | self.authuser.user_id, org_repo, org_ref, other_repo, |
|
378 | self.authuser.user_id, org_repo, org_ref, other_repo, | |
371 | other_ref, revisions, reviewers, title, description |
|
379 | other_ref, revisions, reviewers, title, description | |
372 | ) |
|
380 | ) | |
373 | Session().commit() |
|
381 | Session().commit() | |
374 | h.flash(_('Successfully opened new pull request'), |
|
382 | h.flash(_('Successfully opened new pull request'), | |
375 | category='success') |
|
383 | category='success') | |
376 | except Exception: |
|
384 | except Exception: | |
377 | h.flash(_('Error occurred while creating pull request'), |
|
385 | h.flash(_('Error occurred while creating pull request'), | |
378 | category='error') |
|
386 | category='error') | |
379 | log.error(traceback.format_exc()) |
|
387 | log.error(traceback.format_exc()) | |
380 | return redirect(url('pullrequest_home', repo_name=repo_name)) |
|
388 | return redirect(url('pullrequest_home', repo_name=repo_name)) | |
381 |
|
389 | |||
382 | return redirect(url('pullrequest_show', repo_name=other_repo, |
|
390 | return redirect(url('pullrequest_show', repo_name=other_repo, | |
383 | pull_request_id=pull_request.pull_request_id)) |
|
391 | pull_request_id=pull_request.pull_request_id)) | |
384 |
|
392 | |||
385 | @LoginRequired() |
|
393 | @LoginRequired() | |
386 | @NotAnonymous() |
|
394 | @NotAnonymous() | |
387 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
395 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
388 | 'repository.admin') |
|
396 | 'repository.admin') | |
389 | @jsonify |
|
397 | @jsonify | |
390 | def update(self, repo_name, pull_request_id): |
|
398 | def update(self, repo_name, pull_request_id): | |
391 | pull_request = PullRequest.get_or_404(pull_request_id) |
|
399 | pull_request = PullRequest.get_or_404(pull_request_id) | |
392 | if pull_request.is_closed(): |
|
400 | if pull_request.is_closed(): | |
393 | raise HTTPForbidden() |
|
401 | raise HTTPForbidden() | |
394 | #only owner or admin can update it |
|
402 | #only owner or admin can update it | |
395 | owner = pull_request.author.user_id == c.authuser.user_id |
|
403 | owner = pull_request.author.user_id == c.authuser.user_id | |
396 | repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) |
|
404 | repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) | |
397 | if h.HasPermissionAny('hg.admin') or repo_admin or owner: |
|
405 | if h.HasPermissionAny('hg.admin') or repo_admin or owner: | |
398 | reviewers_ids = map(int, filter(lambda v: v not in [None, ''], |
|
406 | reviewers_ids = map(int, filter(lambda v: v not in [None, ''], | |
399 | request.POST.get('reviewers_ids', '').split(','))) |
|
407 | request.POST.get('reviewers_ids', '').split(','))) | |
400 |
|
408 | |||
401 | PullRequestModel().update_reviewers(pull_request_id, reviewers_ids) |
|
409 | PullRequestModel().update_reviewers(pull_request_id, reviewers_ids) | |
402 | Session().commit() |
|
410 | Session().commit() | |
403 | return True |
|
411 | return True | |
404 | raise HTTPForbidden() |
|
412 | raise HTTPForbidden() | |
405 |
|
413 | |||
406 | @LoginRequired() |
|
414 | @LoginRequired() | |
407 | @NotAnonymous() |
|
415 | @NotAnonymous() | |
408 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
416 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
409 | 'repository.admin') |
|
417 | 'repository.admin') | |
410 | @jsonify |
|
418 | @jsonify | |
411 | def delete(self, repo_name, pull_request_id): |
|
419 | def delete(self, repo_name, pull_request_id): | |
412 | pull_request = PullRequest.get_or_404(pull_request_id) |
|
420 | pull_request = PullRequest.get_or_404(pull_request_id) | |
413 | #only owner can delete it ! |
|
421 | #only owner can delete it ! | |
414 | if pull_request.author.user_id == c.authuser.user_id: |
|
422 | if pull_request.author.user_id == c.authuser.user_id: | |
415 | PullRequestModel().delete(pull_request) |
|
423 | PullRequestModel().delete(pull_request) | |
416 | Session().commit() |
|
424 | Session().commit() | |
417 | h.flash(_('Successfully deleted pull request'), |
|
425 | h.flash(_('Successfully deleted pull request'), | |
418 | category='success') |
|
426 | category='success') | |
419 | return redirect(url('my_account_pullrequests')) |
|
427 | return redirect(url('my_account_pullrequests')) | |
420 | raise HTTPForbidden() |
|
428 | raise HTTPForbidden() | |
421 |
|
429 | |||
422 | @LoginRequired() |
|
430 | @LoginRequired() | |
423 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
431 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
424 | 'repository.admin') |
|
432 | 'repository.admin') | |
425 | def show(self, repo_name, pull_request_id): |
|
433 | def show(self, repo_name, pull_request_id): | |
426 | repo_model = RepoModel() |
|
434 | repo_model = RepoModel() | |
427 | c.users_array = repo_model.get_users_js() |
|
435 | c.users_array = repo_model.get_users_js() | |
428 | c.user_groups_array = repo_model.get_user_groups_js() |
|
436 | c.user_groups_array = repo_model.get_user_groups_js() | |
429 | c.pull_request = PullRequest.get_or_404(pull_request_id) |
|
437 | c.pull_request = PullRequest.get_or_404(pull_request_id) | |
430 | c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request) |
|
438 | c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request) | |
431 | cc_model = ChangesetCommentsModel() |
|
439 | cc_model = ChangesetCommentsModel() | |
432 | cs_model = ChangesetStatusModel() |
|
440 | cs_model = ChangesetStatusModel() | |
433 | _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo, |
|
441 | _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo, | |
434 | pull_request=c.pull_request, |
|
442 | pull_request=c.pull_request, | |
435 | with_revisions=True) |
|
443 | with_revisions=True) | |
436 |
|
444 | |||
437 | cs_statuses = defaultdict(list) |
|
445 | cs_statuses = defaultdict(list) | |
438 | for st in _cs_statuses: |
|
446 | for st in _cs_statuses: | |
439 | cs_statuses[st.author.username] += [st] |
|
447 | cs_statuses[st.author.username] += [st] | |
440 |
|
448 | |||
441 | c.pull_request_reviewers = [] |
|
449 | c.pull_request_reviewers = [] | |
442 | c.pull_request_pending_reviewers = [] |
|
450 | c.pull_request_pending_reviewers = [] | |
443 | for o in c.pull_request.reviewers: |
|
451 | for o in c.pull_request.reviewers: | |
444 | st = cs_statuses.get(o.user.username, None) |
|
452 | st = cs_statuses.get(o.user.username, None) | |
445 | if st: |
|
453 | if st: | |
446 | sorter = lambda k: k.version |
|
454 | sorter = lambda k: k.version | |
447 | st = [(x, list(y)[0]) |
|
455 | st = [(x, list(y)[0]) | |
448 | for x, y in (groupby(sorted(st, key=sorter), sorter))] |
|
456 | for x, y in (groupby(sorted(st, key=sorter), sorter))] | |
449 | else: |
|
457 | else: | |
450 | c.pull_request_pending_reviewers.append(o.user) |
|
458 | c.pull_request_pending_reviewers.append(o.user) | |
451 | c.pull_request_reviewers.append([o.user, st]) |
|
459 | c.pull_request_reviewers.append([o.user, st]) | |
452 |
|
460 | |||
453 | # pull_requests repo_name we opened it against |
|
461 | # pull_requests repo_name we opened it against | |
454 | # ie. other_repo must match |
|
462 | # ie. other_repo must match | |
455 | if repo_name != c.pull_request.other_repo.repo_name: |
|
463 | if repo_name != c.pull_request.other_repo.repo_name: | |
456 | raise HTTPNotFound |
|
464 | raise HTTPNotFound | |
457 |
|
465 | |||
458 | # load compare data into template context |
|
466 | # load compare data into template context | |
459 | enable_comments = not c.pull_request.is_closed() |
|
467 | enable_comments = not c.pull_request.is_closed() | |
460 | self._load_compare_data(c.pull_request, enable_comments=enable_comments) |
|
468 | self._load_compare_data(c.pull_request, enable_comments=enable_comments) | |
461 |
|
469 | |||
462 | # inline comments |
|
470 | # inline comments | |
463 | c.inline_cnt = 0 |
|
471 | c.inline_cnt = 0 | |
464 | c.inline_comments = cc_model.get_inline_comments( |
|
472 | c.inline_comments = cc_model.get_inline_comments( | |
465 | c.db_repo.repo_id, |
|
473 | c.db_repo.repo_id, | |
466 | pull_request=pull_request_id) |
|
474 | pull_request=pull_request_id) | |
467 | # count inline comments |
|
475 | # count inline comments | |
468 | for __, lines in c.inline_comments: |
|
476 | for __, lines in c.inline_comments: | |
469 | for comments in lines.values(): |
|
477 | for comments in lines.values(): | |
470 | c.inline_cnt += len(comments) |
|
478 | c.inline_cnt += len(comments) | |
471 | # comments |
|
479 | # comments | |
472 | c.comments = cc_model.get_comments(c.db_repo.repo_id, |
|
480 | c.comments = cc_model.get_comments(c.db_repo.repo_id, | |
473 | pull_request=pull_request_id) |
|
481 | pull_request=pull_request_id) | |
474 |
|
482 | |||
475 | # (badly named) pull-request status calculation based on reviewer votes |
|
483 | # (badly named) pull-request status calculation based on reviewer votes | |
476 | c.current_changeset_status = cs_model.calculate_status( |
|
484 | c.current_changeset_status = cs_model.calculate_status( | |
477 | c.pull_request_reviewers, |
|
485 | c.pull_request_reviewers, | |
478 | ) |
|
486 | ) | |
479 | c.changeset_statuses = ChangesetStatus.STATUSES |
|
487 | c.changeset_statuses = ChangesetStatus.STATUSES | |
480 |
|
488 | |||
481 | c.as_form = False |
|
489 | c.as_form = False | |
482 | c.ancestor = None # there is one - but right here we don't know which |
|
490 | c.ancestor = None # there is one - but right here we don't know which | |
483 | return render('/pullrequests/pullrequest_show.html') |
|
491 | return render('/pullrequests/pullrequest_show.html') | |
484 |
|
492 | |||
485 | @LoginRequired() |
|
493 | @LoginRequired() | |
486 | @NotAnonymous() |
|
494 | @NotAnonymous() | |
487 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
495 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
488 | 'repository.admin') |
|
496 | 'repository.admin') | |
489 | @jsonify |
|
497 | @jsonify | |
490 | def comment(self, repo_name, pull_request_id): |
|
498 | def comment(self, repo_name, pull_request_id): | |
491 | pull_request = PullRequest.get_or_404(pull_request_id) |
|
499 | pull_request = PullRequest.get_or_404(pull_request_id) | |
492 | if pull_request.is_closed(): |
|
500 | if pull_request.is_closed(): | |
493 | raise HTTPForbidden() |
|
501 | raise HTTPForbidden() | |
494 |
|
502 | |||
495 | status = request.POST.get('changeset_status') |
|
503 | status = request.POST.get('changeset_status') | |
496 | change_status = request.POST.get('change_changeset_status') |
|
504 | change_status = request.POST.get('change_changeset_status') | |
497 | text = request.POST.get('text') |
|
505 | text = request.POST.get('text') | |
498 | close_pr = request.POST.get('save_close') |
|
506 | close_pr = request.POST.get('save_close') | |
499 |
|
507 | |||
500 | allowed_to_change_status = self._get_is_allowed_change_status(pull_request) |
|
508 | allowed_to_change_status = self._get_is_allowed_change_status(pull_request) | |
501 | if status and change_status and allowed_to_change_status: |
|
509 | if status and change_status and allowed_to_change_status: | |
502 | _def = (_('Status change -> %s') |
|
510 | _def = (_('Status change -> %s') | |
503 | % ChangesetStatus.get_status_lbl(status)) |
|
511 | % ChangesetStatus.get_status_lbl(status)) | |
504 | if close_pr: |
|
512 | if close_pr: | |
505 | _def = _('Closing with') + ' ' + _def |
|
513 | _def = _('Closing with') + ' ' + _def | |
506 | text = text or _def |
|
514 | text = text or _def | |
507 | comm = ChangesetCommentsModel().create( |
|
515 | comm = ChangesetCommentsModel().create( | |
508 | text=text, |
|
516 | text=text, | |
509 | repo=c.db_repo.repo_id, |
|
517 | repo=c.db_repo.repo_id, | |
510 | user=c.authuser.user_id, |
|
518 | user=c.authuser.user_id, | |
511 | pull_request=pull_request_id, |
|
519 | pull_request=pull_request_id, | |
512 | f_path=request.POST.get('f_path'), |
|
520 | f_path=request.POST.get('f_path'), | |
513 | line_no=request.POST.get('line'), |
|
521 | line_no=request.POST.get('line'), | |
514 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
522 | status_change=(ChangesetStatus.get_status_lbl(status) | |
515 | if status and change_status |
|
523 | if status and change_status | |
516 | and allowed_to_change_status else None), |
|
524 | and allowed_to_change_status else None), | |
517 | closing_pr=close_pr |
|
525 | closing_pr=close_pr | |
518 | ) |
|
526 | ) | |
519 |
|
527 | |||
520 | action_logger(self.authuser, |
|
528 | action_logger(self.authuser, | |
521 | 'user_commented_pull_request:%s' % pull_request_id, |
|
529 | 'user_commented_pull_request:%s' % pull_request_id, | |
522 | c.db_repo, self.ip_addr, self.sa) |
|
530 | c.db_repo, self.ip_addr, self.sa) | |
523 |
|
531 | |||
524 | if allowed_to_change_status: |
|
532 | if allowed_to_change_status: | |
525 | # get status if set ! |
|
533 | # get status if set ! | |
526 | if status and change_status: |
|
534 | if status and change_status: | |
527 | ChangesetStatusModel().set_status( |
|
535 | ChangesetStatusModel().set_status( | |
528 | c.db_repo.repo_id, |
|
536 | c.db_repo.repo_id, | |
529 | status, |
|
537 | status, | |
530 | c.authuser.user_id, |
|
538 | c.authuser.user_id, | |
531 | comm, |
|
539 | comm, | |
532 | pull_request=pull_request_id |
|
540 | pull_request=pull_request_id | |
533 | ) |
|
541 | ) | |
534 |
|
542 | |||
535 | if close_pr: |
|
543 | if close_pr: | |
536 | if status in ['rejected', 'approved']: |
|
544 | if status in ['rejected', 'approved']: | |
537 | PullRequestModel().close_pull_request(pull_request_id) |
|
545 | PullRequestModel().close_pull_request(pull_request_id) | |
538 | action_logger(self.authuser, |
|
546 | action_logger(self.authuser, | |
539 | 'user_closed_pull_request:%s' % pull_request_id, |
|
547 | 'user_closed_pull_request:%s' % pull_request_id, | |
540 | c.db_repo, self.ip_addr, self.sa) |
|
548 | c.db_repo, self.ip_addr, self.sa) | |
541 | else: |
|
549 | else: | |
542 | h.flash(_('Closing pull request on other statuses than ' |
|
550 | h.flash(_('Closing pull request on other statuses than ' | |
543 | 'rejected or approved forbidden'), |
|
551 | 'rejected or approved forbidden'), | |
544 | category='warning') |
|
552 | category='warning') | |
545 |
|
553 | |||
546 | Session().commit() |
|
554 | Session().commit() | |
547 |
|
555 | |||
548 | if not request.environ.get('HTTP_X_PARTIAL_XHR'): |
|
556 | if not request.environ.get('HTTP_X_PARTIAL_XHR'): | |
549 | return redirect(h.url('pullrequest_show', repo_name=repo_name, |
|
557 | return redirect(h.url('pullrequest_show', repo_name=repo_name, | |
550 | pull_request_id=pull_request_id)) |
|
558 | pull_request_id=pull_request_id)) | |
551 |
|
559 | |||
552 | data = { |
|
560 | data = { | |
553 | 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), |
|
561 | 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), | |
554 | } |
|
562 | } | |
555 | if comm: |
|
563 | if comm: | |
556 | c.co = comm |
|
564 | c.co = comm | |
557 | data.update(comm.get_dict()) |
|
565 | data.update(comm.get_dict()) | |
558 | data.update({'rendered_text': |
|
566 | data.update({'rendered_text': | |
559 | render('changeset/changeset_comment_block.html')}) |
|
567 | render('changeset/changeset_comment_block.html')}) | |
560 |
|
568 | |||
561 | return data |
|
569 | return data | |
562 |
|
570 | |||
563 | @LoginRequired() |
|
571 | @LoginRequired() | |
564 | @NotAnonymous() |
|
572 | @NotAnonymous() | |
565 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
573 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
566 | 'repository.admin') |
|
574 | 'repository.admin') | |
567 | @jsonify |
|
575 | @jsonify | |
568 | def delete_comment(self, repo_name, comment_id): |
|
576 | def delete_comment(self, repo_name, comment_id): | |
569 | co = ChangesetComment.get(comment_id) |
|
577 | co = ChangesetComment.get(comment_id) | |
570 | if co.pull_request.is_closed(): |
|
578 | if co.pull_request.is_closed(): | |
571 | #don't allow deleting comments on closed pull request |
|
579 | #don't allow deleting comments on closed pull request | |
572 | raise HTTPForbidden() |
|
580 | raise HTTPForbidden() | |
573 |
|
581 | |||
574 | owner = co.author.user_id == c.authuser.user_id |
|
582 | owner = co.author.user_id == c.authuser.user_id | |
575 | repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) |
|
583 | repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) | |
576 | if h.HasPermissionAny('hg.admin') or repo_admin or owner: |
|
584 | if h.HasPermissionAny('hg.admin') or repo_admin or owner: | |
577 | ChangesetCommentsModel().delete(comment=co) |
|
585 | ChangesetCommentsModel().delete(comment=co) | |
578 | Session().commit() |
|
586 | Session().commit() | |
579 | return True |
|
587 | return True | |
580 | else: |
|
588 | else: | |
581 | raise HTTPForbidden() |
|
589 | raise HTTPForbidden() |
@@ -1,92 +1,94 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 | ##usage: |
|
2 | ##usage: | |
3 | ## <%namespace name="diff_block" file="/changeset/diff_block.html"/> |
|
3 | ## <%namespace name="diff_block" file="/changeset/diff_block.html"/> | |
4 | ## ${diff_block.diff_block(change)} |
|
4 | ## ${diff_block.diff_block(change)} | |
5 | ## |
|
5 | ## | |
6 | <%def name="diff_block(change)"> |
|
6 | <%def name="diff_block(change)"> | |
7 | <div class="diff-collapse"> |
|
7 | <div class="diff-collapse"> | |
8 | <span target="${'diff-container-%s' % (id(change))}" class="diff-collapse-button">↑ ${_('Collapse diff')} ↑</span> |
|
8 | <span target="${'diff-container-%s' % (id(change))}" class="diff-collapse-button">↑ ${_('Collapse diff')} ↑</span> | |
9 | </div> |
|
9 | </div> | |
10 | <div class="diff-container" id="${'diff-container-%s' % (id(change))}"> |
|
10 | <div class="diff-container" id="${'diff-container-%s' % (id(change))}"> | |
11 | %for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems(): |
|
11 | %for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems(): | |
12 | <div id="${FID}_target" style="clear:both;margin-top:25px"></div> |
|
12 | <div id="${FID}_target" style="clear:both;margin-top:25px"></div> | |
13 | <div id="${FID}" class="diffblock margined comm"> |
|
13 | <div id="${FID}" class="diffblock margined comm"> | |
14 | <div class="code-header"> |
|
14 | <div class="code-header"> | |
15 | <div class="changeset_header"> |
|
15 | <div class="changeset_header"> | |
16 | <div class="changeset_file"> |
|
16 | <div class="changeset_file"> | |
17 | ${h.link_to_if(change!='D',h.safe_unicode(path),h.url('files_home',repo_name=c.repo_name, |
|
17 | ${h.link_to_if(change!='D',h.safe_unicode(path),h.url('files_home',repo_name=c.repo_name, | |
18 | revision=cs2,f_path=h.safe_unicode(path)))} |
|
18 | revision=cs2,f_path=h.safe_unicode(path)))} | |
19 | </div> |
|
19 | </div> | |
20 | <div class="diff-actions"> |
|
20 | <div class="diff-actions"> | |
21 | <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}"> |
|
21 | <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}"> | |
22 | <img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/> |
|
22 | <img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/> | |
23 | </a> |
|
23 | </a> | |
24 | <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}"> |
|
24 | <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}"> | |
25 | <img class="icon" src="${h.url('/images/icons/application_double.png')}"/> |
|
25 | <img class="icon" src="${h.url('/images/icons/application_double.png')}"/> | |
26 | </a> |
|
26 | </a> | |
27 | <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> |
|
27 | <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> | |
28 | <img class="icon" src="${h.url('/images/icons/page_white.png')}"/> |
|
28 | <img class="icon" src="${h.url('/images/icons/page_white.png')}"/> | |
29 | </a> |
|
29 | </a> | |
30 | <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> |
|
30 | <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> | |
31 | <img class="icon" src="${h.url('/images/icons/page_save.png')}"/> |
|
31 | <img class="icon" src="${h.url('/images/icons/page_save.png')}"/> | |
32 | </a> |
|
32 | </a> | |
33 | ${c.ignorews_url(request.GET, h.FID(cs2,path))} |
|
33 | ${c.ignorews_url(request.GET, h.FID(cs2,path))} | |
34 | ${c.context_url(request.GET, h.FID(cs2,path))} |
|
34 | ${c.context_url(request.GET, h.FID(cs2,path))} | |
35 | </div> |
|
35 | </div> | |
36 | <span style="float:right;margin-top:-3px"> |
|
36 | <span style="float:right;margin-top:-3px"> | |
37 | <label> |
|
37 | <label> | |
38 | ${_('Show inline comments')} |
|
38 | ${_('Show inline comments')} | |
39 | ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(cs2,path))} |
|
39 | ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(cs2,path))} | |
40 | </label> |
|
40 | </label> | |
41 | </span> |
|
41 | </span> | |
42 | </div> |
|
42 | </div> | |
43 | </div> |
|
43 | </div> | |
44 | <div class="code-body"> |
|
44 | <div class="code-body"> | |
45 | <div class="full_f_path" path="${h.safe_unicode(path)}"></div> |
|
45 | <div class="full_f_path" path="${h.safe_unicode(path)}"></div> | |
46 | ${diff|n} |
|
46 | ${diff|n} | |
47 | </div> |
|
47 | </div> | |
48 | </div> |
|
48 | </div> | |
49 | %endfor |
|
49 | %endfor | |
50 | </div> |
|
50 | </div> | |
51 | </%def> |
|
51 | </%def> | |
52 |
|
52 | |||
53 | <%def name="diff_block_simple(change)"> |
|
53 | <%def name="diff_block_simple(change)"> | |
54 |
|
54 | |||
55 | %for op,filenode_path,diff in change: |
|
55 | %for op,filenode_path,diff in change: | |
56 | <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div> |
|
56 | <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div> | |
57 | <div id="${h.FID('',filenode_path)}" class="diffblock margined comm"> |
|
57 | <div id="${h.FID('',filenode_path)}" class="diffblock margined comm"> | |
58 | <div class="code-header"> |
|
58 | <div class="code-header"> | |
59 | <div class="changeset_header"> |
|
59 | <div class="changeset_header"> | |
60 | <div class="changeset_file"> |
|
60 | <div class="changeset_file"> | |
61 | ${h.safe_unicode(filenode_path)} | |
|
61 | ${h.safe_unicode(filenode_path)} | | |
62 | ## TODO: link to ancestor and head of other instead of exactly other |
|
62 | ## TODO: link to ancestor and head of other instead of exactly other | |
63 | %if op == 'A': |
|
63 | %if op == 'A': | |
64 | ${_('Added')} |
|
64 | ${_('Added')} | |
65 | <a class="spantag" href="${h.url('files_home', repo_name=c.org_repo.repo_name, f_path=filenode_path, revision=c.org_rev)}">${h.short_id(c.org_ref_name) if c.org_ref_type=='rev' else c.org_ref_name}</a> |
|
65 | <a class="spantag" href="${h.url('files_home', repo_name=c.org_repo.repo_name, f_path=filenode_path, revision=c.org_rev)}">${h.short_id(c.org_ref_name) if c.org_ref_type=='rev' else c.org_ref_name}</a> | |
66 | %elif op == 'M': |
|
66 | %elif op == 'M': | |
67 | <a class="spantag" href="${h.url('files_home', repo_name=c.org_repo.repo_name, f_path=filenode_path, revision=c.org_rev)}">${h.short_id(c.org_ref_name) if c.org_ref_type=='rev' else c.org_ref_name}</a> |
|
67 | <a class="spantag" href="${h.url('files_home', repo_name=c.org_repo.repo_name, f_path=filenode_path, revision=c.org_rev)}">${h.short_id(c.org_ref_name) if c.org_ref_type=='rev' else c.org_ref_name}</a> | |
68 | <i class="icon-arrow-right"></i> |
|
68 | <i class="icon-arrow-right"></i> | |
69 | <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.other_rev)}">${h.short_id(c.other_ref_name) if c.other_ref_type=='rev' else c.other_ref_name}</a> |
|
69 | <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.other_rev)}">${h.short_id(c.other_ref_name) if c.other_ref_type=='rev' else c.other_ref_name}</a> | |
70 | %elif op == 'D': |
|
70 | %elif op == 'D': | |
71 | ${_('Deleted')} |
|
71 | ${_('Deleted')} | |
72 | <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.other_rev)}">${h.short_id(c.other_ref_name) if c.other_ref_type=='rev' else c.other_ref_name}</a> |
|
72 | <a class="spantag" href="${h.url('files_home', repo_name=c.other_repo.repo_name, f_path=filenode_path, revision=c.other_rev)}">${h.short_id(c.other_ref_name) if c.other_ref_type=='rev' else c.other_ref_name}</a> | |
73 | %else: |
|
73 | %else: | |
74 | ${op}??? |
|
74 | ${op}??? | |
75 | %endif |
|
75 | %endif | |
76 | </div> |
|
76 | </div> | |
77 |
|
|
77 | <div class="diff-actions"> | |
78 | %if c.other_repo.repo_name == c.repo_name: |
|
78 | %if c.other_repo.repo_name == c.repo_name: | |
79 | <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode_path),diff2=c.other_rev,diff1=c.org_rev,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}"> |
|
79 | <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode_path),diff2=c.other_rev,diff1=c.org_rev,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}"> | |
80 | <img class="icon" src="${h.url('/images/icons/application_double.png')}"/> |
|
80 | <img class="icon" src="${h.url('/images/icons/application_double.png')}"/> | |
81 | </a> |
|
81 | </a> | |
82 | %endif |
|
82 | %endif | |
|
83 | ${c.ignorews_url(request.GET)} | |||
|
84 | ${c.context_url(request.GET)} | |||
83 |
|
|
85 | </div> | |
84 | </div> |
|
86 | </div> | |
85 | </div> |
|
87 | </div> | |
86 | <div class="code-body"> |
|
88 | <div class="code-body"> | |
87 | <div class="full_f_path" path="${h.safe_unicode(filenode_path)}"></div> |
|
89 | <div class="full_f_path" path="${h.safe_unicode(filenode_path)}"></div> | |
88 | ${diff|n} |
|
90 | ${diff|n} | |
89 | </div> |
|
91 | </div> | |
90 | </div> |
|
92 | </div> | |
91 | %endfor |
|
93 | %endfor | |
92 | </%def> |
|
94 | </%def> |
@@ -1,205 +1,208 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 | <%inherit file="/base/base.html"/> |
|
2 | <%inherit file="/base/base.html"/> | |
3 |
|
3 | |||
4 | <%def name="title()"> |
|
4 | <%def name="title()"> | |
5 | %if c.compare_home: |
|
5 | %if c.compare_home: | |
6 | ${_('%s Compare') % c.repo_name} |
|
6 | ${_('%s Compare') % c.repo_name} | |
7 | %else: |
|
7 | %else: | |
8 | ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.org_repo.repo_name, c.org_ref_name)} > ${'%s@%s' % (c.other_repo.repo_name, c.other_ref_name)} |
|
8 | ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.org_repo.repo_name, c.org_ref_name)} > ${'%s@%s' % (c.other_repo.repo_name, c.other_ref_name)} | |
9 | %endif |
|
9 | %endif | |
10 | %if c.site_name: |
|
10 | %if c.site_name: | |
11 | · ${c.site_name} |
|
11 | · ${c.site_name} | |
12 | %endif |
|
12 | %endif | |
13 | </%def> |
|
13 | </%def> | |
14 |
|
14 | |||
15 | <%def name="breadcrumbs_links()"> |
|
15 | <%def name="breadcrumbs_links()"> | |
16 | ${_('Compare revisions')} |
|
16 | ${_('Compare revisions')} | |
17 | </%def> |
|
17 | </%def> | |
18 |
|
18 | |||
19 | <%def name="page_nav()"> |
|
19 | <%def name="page_nav()"> | |
20 | ${self.menu('repositories')} |
|
20 | ${self.menu('repositories')} | |
21 | </%def> |
|
21 | </%def> | |
22 |
|
22 | |||
23 | <%def name="main()"> |
|
23 | <%def name="main()"> | |
24 | ${self.repo_context_bar('changelog')} |
|
24 | ${self.repo_context_bar('changelog')} | |
25 | <div class="box"> |
|
25 | <div class="box"> | |
26 | <!-- box / title --> |
|
26 | <!-- box / title --> | |
27 | <div class="title"> |
|
27 | <div class="title"> | |
28 | ${self.breadcrumbs()} |
|
28 | ${self.breadcrumbs()} | |
29 | </div> |
|
29 | </div> | |
30 | <div class="table"> |
|
30 | <div class="table"> | |
31 | <div id="body" class="diffblock"> |
|
31 | <div id="body" class="diffblock"> | |
32 | <div class="code-header"> |
|
32 | <div class="code-header"> | |
33 | <div> |
|
33 | <div> | |
34 | ${h.hidden('compare_org')} <i class="icon-ellipsis-horizontal" style="color: #999; vertical-align: -12px; padding: 0px 0px 0px 2px"></i> ${h.hidden('compare_other')} |
|
34 | ${h.hidden('compare_org')} <i class="icon-ellipsis-horizontal" style="color: #999; vertical-align: -12px; padding: 0px 0px 0px 2px"></i> ${h.hidden('compare_other')} | |
35 | %if not c.compare_home: |
|
35 | %if not c.compare_home: | |
36 | <a class="btn btn-small" href="${c.swap_url}"><i class="icon-refresh"></i> ${_('Swap')}</a> |
|
36 | <a class="btn btn-small" href="${c.swap_url}"><i class="icon-refresh"></i> ${_('Swap')}</a> | |
37 | %endif |
|
37 | %endif | |
38 | <div id="compare_revs" class="btn btn-small"><i class="icon-loop"></i> ${_('Compare Revisions')}</div> |
|
38 | <div id="compare_revs" class="btn btn-small"><i class="icon-loop"></i> ${_('Compare Revisions')}</div> | |
39 | </div> |
|
39 | </div> | |
40 | </div> |
|
40 | </div> | |
41 | </div> |
|
41 | </div> | |
42 |
|
42 | |||
43 | %if c.compare_home: |
|
43 | %if c.compare_home: | |
44 | <div id="changeset_compare_view_content"> |
|
44 | <div id="changeset_compare_view_content"> | |
45 | <div style="color:#999;font-size: 18px">${_('Compare revisions, branches, bookmarks or tags.')}</div> |
|
45 | <div style="color:#999;font-size: 18px">${_('Compare revisions, branches, bookmarks or tags.')}</div> | |
46 | </div> |
|
46 | </div> | |
47 | %else: |
|
47 | %else: | |
48 | <div id="changeset_compare_view_content"> |
|
48 | <div id="changeset_compare_view_content"> | |
49 | ##CS |
|
49 | ##CS | |
50 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div> |
|
50 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div> | |
51 | <%include file="compare_cs.html" /> |
|
51 | <%include file="compare_cs.html" /> | |
52 |
|
52 | |||
53 | ## FILES |
|
53 | ## FILES | |
54 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px"> |
|
54 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px"> | |
55 |
|
55 | |||
56 | % if c.limited_diff: |
|
56 | % if c.limited_diff: | |
57 | ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)} |
|
57 | ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)} | |
58 | % else: |
|
58 | % else: | |
59 | ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}: |
|
59 | ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}: | |
60 | %endif |
|
60 | %endif | |
61 |
|
61 | |||
|
62 | ${c.ignorews_url(request.GET)} | |||
|
63 | ${c.context_url(request.GET)} | |||
|
64 | ||||
62 | </div> |
|
65 | </div> | |
63 | <div class="cs_files"> |
|
66 | <div class="cs_files"> | |
64 | %if not c.files: |
|
67 | %if not c.files: | |
65 | <span class="empty_data">${_('No files')}</span> |
|
68 | <span class="empty_data">${_('No files')}</span> | |
66 | %endif |
|
69 | %endif | |
67 | %for fid, change, f, stat in c.files: |
|
70 | %for fid, change, f, stat in c.files: | |
68 | <div class="cs_${change}"> |
|
71 | <div class="cs_${change}"> | |
69 | <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid, **request.GET.mixed()))}</div> |
|
72 | <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid, **request.GET.mixed()))}</div> | |
70 | <div class="changes">${h.fancy_file_stats(stat)}</div> |
|
73 | <div class="changes">${h.fancy_file_stats(stat)}</div> | |
71 | </div> |
|
74 | </div> | |
72 | %endfor |
|
75 | %endfor | |
73 | </div> |
|
76 | </div> | |
74 | % if c.limited_diff: |
|
77 | % if c.limited_diff: | |
75 | <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h5> |
|
78 | <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h5> | |
76 | % endif |
|
79 | % endif | |
77 | </div> |
|
80 | </div> | |
78 |
|
81 | |||
79 | ## diff block |
|
82 | ## diff block | |
80 | <%namespace name="diff_block" file="/changeset/diff_block.html"/> |
|
83 | <%namespace name="diff_block" file="/changeset/diff_block.html"/> | |
81 | %for fid, change, f, stat in c.files: |
|
84 | %for fid, change, f, stat in c.files: | |
82 | ${diff_block.diff_block_simple([c.changes[fid]])} |
|
85 | ${diff_block.diff_block_simple([c.changes[fid]])} | |
83 | %endfor |
|
86 | %endfor | |
84 | % if c.limited_diff: |
|
87 | % if c.limited_diff: | |
85 | <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h4> |
|
88 | <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h4> | |
86 | % endif |
|
89 | % endif | |
87 | %endif |
|
90 | %endif | |
88 | </div> |
|
91 | </div> | |
89 |
|
92 | |||
90 | </div> |
|
93 | </div> | |
91 | <script type="text/javascript"> |
|
94 | <script type="text/javascript"> | |
92 |
|
95 | |||
93 | $(document).ready(function(){ |
|
96 | $(document).ready(function(){ | |
94 | var cache = {} |
|
97 | var cache = {} | |
95 | $("#compare_org").select2({ |
|
98 | $("#compare_org").select2({ | |
96 | placeholder: "${'%s@%s' % (c.org_repo.repo_name, c.org_ref_name)}", |
|
99 | placeholder: "${'%s@%s' % (c.org_repo.repo_name, c.org_ref_name)}", | |
97 | formatSelection: function(obj){ |
|
100 | formatSelection: function(obj){ | |
98 | return '{0}@{1}'.format("${c.org_repo.repo_name}", obj.text) |
|
101 | return '{0}@{1}'.format("${c.org_repo.repo_name}", obj.text) | |
99 | }, |
|
102 | }, | |
100 | dropdownAutoWidth: true, |
|
103 | dropdownAutoWidth: true, | |
101 | query: function(query){ |
|
104 | query: function(query){ | |
102 | var key = 'cache'; |
|
105 | var key = 'cache'; | |
103 | var cached = cache[key] ; |
|
106 | var cached = cache[key] ; | |
104 | if(cached) { |
|
107 | if(cached) { | |
105 | var data = {results: []}; |
|
108 | var data = {results: []}; | |
106 | //filter results |
|
109 | //filter results | |
107 | $.each(cached.results, function(){ |
|
110 | $.each(cached.results, function(){ | |
108 | var section = this.text; |
|
111 | var section = this.text; | |
109 | var children = []; |
|
112 | var children = []; | |
110 | $.each(this.children, function(){ |
|
113 | $.each(this.children, function(){ | |
111 | if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){ |
|
114 | if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){ | |
112 | children.push({'id': this.id, 'text': this.text}) |
|
115 | children.push({'id': this.id, 'text': this.text}) | |
113 | } |
|
116 | } | |
114 | }) |
|
117 | }) | |
115 | data.results.push({'text': section, 'children': children}) |
|
118 | data.results.push({'text': section, 'children': children}) | |
116 | }); |
|
119 | }); | |
117 | //push the typed in changeset |
|
120 | //push the typed in changeset | |
118 | data.results.push({'text':_TM['Specify changeset'], |
|
121 | data.results.push({'text':_TM['Specify changeset'], | |
119 | 'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]}) |
|
122 | 'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]}) | |
120 | query.callback(data); |
|
123 | query.callback(data); | |
121 | }else{ |
|
124 | }else{ | |
122 | $.ajax({ |
|
125 | $.ajax({ | |
123 | url: pyroutes.url('repo_refs_data', {'repo_name': '${c.org_repo.repo_name}'}), |
|
126 | url: pyroutes.url('repo_refs_data', {'repo_name': '${c.org_repo.repo_name}'}), | |
124 | data: {}, |
|
127 | data: {}, | |
125 | dataType: 'json', |
|
128 | dataType: 'json', | |
126 | type: 'GET', |
|
129 | type: 'GET', | |
127 | success: function(data) { |
|
130 | success: function(data) { | |
128 | cache[key] = data; |
|
131 | cache[key] = data; | |
129 | query.callback(data); |
|
132 | query.callback(data); | |
130 | } |
|
133 | } | |
131 | }) |
|
134 | }) | |
132 | } |
|
135 | } | |
133 | }, |
|
136 | }, | |
134 | }); |
|
137 | }); | |
135 | $("#compare_other").select2({ |
|
138 | $("#compare_other").select2({ | |
136 | placeholder: "${'%s@%s' % (c.other_repo.repo_name, c.other_ref_name)}", |
|
139 | placeholder: "${'%s@%s' % (c.other_repo.repo_name, c.other_ref_name)}", | |
137 | dropdownAutoWidth: true, |
|
140 | dropdownAutoWidth: true, | |
138 | formatSelection: function(obj){ |
|
141 | formatSelection: function(obj){ | |
139 | return '{0}@{1}'.format("${c.other_repo.repo_name}", obj.text) |
|
142 | return '{0}@{1}'.format("${c.other_repo.repo_name}", obj.text) | |
140 | }, |
|
143 | }, | |
141 | query: function(query){ |
|
144 | query: function(query){ | |
142 | var key = 'cache2'; |
|
145 | var key = 'cache2'; | |
143 | var cached = cache[key] ; |
|
146 | var cached = cache[key] ; | |
144 | if(cached) { |
|
147 | if(cached) { | |
145 | var data = {results: []}; |
|
148 | var data = {results: []}; | |
146 | //filter results |
|
149 | //filter results | |
147 | $.each(cached.results, function(){ |
|
150 | $.each(cached.results, function(){ | |
148 | var section = this.text; |
|
151 | var section = this.text; | |
149 | var children = []; |
|
152 | var children = []; | |
150 | $.each(this.children, function(){ |
|
153 | $.each(this.children, function(){ | |
151 | if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){ |
|
154 | if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){ | |
152 | children.push({'id': this.id, 'text': this.text}) |
|
155 | children.push({'id': this.id, 'text': this.text}) | |
153 | } |
|
156 | } | |
154 | }) |
|
157 | }) | |
155 | data.results.push({'text': section, 'children': children}) |
|
158 | data.results.push({'text': section, 'children': children}) | |
156 | }); |
|
159 | }); | |
157 | //push the typed in changeset |
|
160 | //push the typed in changeset | |
158 | data.results.push({'text':_TM['Specify changeset'], |
|
161 | data.results.push({'text':_TM['Specify changeset'], | |
159 | 'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]}) |
|
162 | 'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]}) | |
160 | query.callback(data); |
|
163 | query.callback(data); | |
161 | }else{ |
|
164 | }else{ | |
162 | $.ajax({ |
|
165 | $.ajax({ | |
163 | url: pyroutes.url('repo_refs_data', {'repo_name': '${c.other_repo.repo_name}'}), |
|
166 | url: pyroutes.url('repo_refs_data', {'repo_name': '${c.other_repo.repo_name}'}), | |
164 | data: {}, |
|
167 | data: {}, | |
165 | dataType: 'json', |
|
168 | dataType: 'json', | |
166 | type: 'GET', |
|
169 | type: 'GET', | |
167 | success: function(data) { |
|
170 | success: function(data) { | |
168 | cache[key] = data; |
|
171 | cache[key] = data; | |
169 | query.callback({results: data.results}); |
|
172 | query.callback({results: data.results}); | |
170 | } |
|
173 | } | |
171 | }) |
|
174 | }) | |
172 | } |
|
175 | } | |
173 | }, |
|
176 | }, | |
174 | }); |
|
177 | }); | |
175 |
|
178 | |||
176 | var values_changed = function() { |
|
179 | var values_changed = function() { | |
177 | var values = $('#compare_org').select2('data') && $('#compare_other').select2('data'); |
|
180 | var values = $('#compare_org').select2('data') && $('#compare_other').select2('data'); | |
178 | if (values) { |
|
181 | if (values) { | |
179 | $('#compare_revs').removeClass("disabled"); |
|
182 | $('#compare_revs').removeClass("disabled"); | |
180 | // TODO: the swap button ... if any |
|
183 | // TODO: the swap button ... if any | |
181 | } else { |
|
184 | } else { | |
182 | $('#compare_revs').addClass("disabled"); |
|
185 | $('#compare_revs').addClass("disabled"); | |
183 | // TODO: the swap button ... if any |
|
186 | // TODO: the swap button ... if any | |
184 | } |
|
187 | } | |
185 | } |
|
188 | } | |
186 | values_changed(); |
|
189 | values_changed(); | |
187 | $('#compare_org').change(values_changed); |
|
190 | $('#compare_org').change(values_changed); | |
188 | $('#compare_other').change(values_changed); |
|
191 | $('#compare_other').change(values_changed); | |
189 | $('#compare_revs').on('click', function(e){ |
|
192 | $('#compare_revs').on('click', function(e){ | |
190 | var org = $('#compare_org').select2('data'); |
|
193 | var org = $('#compare_org').select2('data'); | |
191 | var other = $('#compare_other').select2('data'); |
|
194 | var other = $('#compare_other').select2('data'); | |
192 | if (!org || !other) { |
|
195 | if (!org || !other) { | |
193 | return; |
|
196 | return; | |
194 | } |
|
197 | } | |
195 |
|
198 | |||
196 | var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='__other_ref_type__',org_ref_name='__org__',other_ref_type='__org_ref_type__',other_ref_name='__other__', other_repo=c.other_repo.repo_name)}"; |
|
199 | var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='__other_ref_type__',org_ref_name='__org__',other_ref_type='__org_ref_type__',other_ref_name='__other__', other_repo=c.other_repo.repo_name)}"; | |
197 | var u = compare_url.replace('__other_ref_type__',org.type) |
|
200 | var u = compare_url.replace('__other_ref_type__',org.type) | |
198 | .replace('__org__',org.text) |
|
201 | .replace('__org__',org.text) | |
199 | .replace('__org_ref_type__',other.type) |
|
202 | .replace('__org_ref_type__',other.type) | |
200 | .replace('__other__',other.text); |
|
203 | .replace('__other__',other.text); | |
201 | window.location = u; |
|
204 | window.location = u; | |
202 | }); |
|
205 | }); | |
203 | }); |
|
206 | }); | |
204 | </script> |
|
207 | </script> | |
205 | </%def> |
|
208 | </%def> |
@@ -1,269 +1,271 b'' | |||||
1 | <%inherit file="/base/base.html"/> |
|
1 | <%inherit file="/base/base.html"/> | |
2 |
|
2 | |||
3 | <%def name="title()"> |
|
3 | <%def name="title()"> | |
4 | ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)} |
|
4 | ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)} | |
5 | %if c.site_name: |
|
5 | %if c.site_name: | |
6 | · ${c.site_name} |
|
6 | · ${c.site_name} | |
7 | %endif |
|
7 | %endif | |
8 | </%def> |
|
8 | </%def> | |
9 |
|
9 | |||
10 | <%def name="breadcrumbs_links()"> |
|
10 | <%def name="breadcrumbs_links()"> | |
11 | ${_('Pull request #%s') % c.pull_request.pull_request_id} |
|
11 | ${_('Pull request #%s') % c.pull_request.pull_request_id} | |
12 | </%def> |
|
12 | </%def> | |
13 |
|
13 | |||
14 | <%def name="page_nav()"> |
|
14 | <%def name="page_nav()"> | |
15 | ${self.menu('repositories')} |
|
15 | ${self.menu('repositories')} | |
16 | </%def> |
|
16 | </%def> | |
17 |
|
17 | |||
18 | <%def name="main()"> |
|
18 | <%def name="main()"> | |
19 | ${self.repo_context_bar('showpullrequest')} |
|
19 | ${self.repo_context_bar('showpullrequest')} | |
20 | <div class="box"> |
|
20 | <div class="box"> | |
21 | <!-- box / title --> |
|
21 | <!-- box / title --> | |
22 | <div class="title"> |
|
22 | <div class="title"> | |
23 | ${self.breadcrumbs()} |
|
23 | ${self.breadcrumbs()} | |
24 | </div> |
|
24 | </div> | |
25 |
|
25 | |||
26 | <div class="form pr-box" style="float: left"> |
|
26 | <div class="form pr-box" style="float: left"> | |
27 | <div class="pr-details-title ${'closed' if c.pull_request.is_closed() else ''}"> |
|
27 | <div class="pr-details-title ${'closed' if c.pull_request.is_closed() else ''}"> | |
28 | ${_('Title')}: ${c.pull_request.title} |
|
28 | ${_('Title')}: ${c.pull_request.title} | |
29 | %if c.pull_request.is_closed(): |
|
29 | %if c.pull_request.is_closed(): | |
30 | (${_('Closed')}) |
|
30 | (${_('Closed')}) | |
31 | %endif |
|
31 | %endif | |
32 | </div> |
|
32 | </div> | |
33 | <div id="summary" class="fields"> |
|
33 | <div id="summary" class="fields"> | |
34 | <div class="field"> |
|
34 | <div class="field"> | |
35 | <div class="label-summary"> |
|
35 | <div class="label-summary"> | |
36 | <label>${_('Review status')}:</label> |
|
36 | <label>${_('Review status')}:</label> | |
37 | </div> |
|
37 | </div> | |
38 | <div class="input"> |
|
38 | <div class="input"> | |
39 | <div class="changeset-status-container" style="float:none;clear:both"> |
|
39 | <div class="changeset-status-container" style="float:none;clear:both"> | |
40 | %if c.current_changeset_status: |
|
40 | %if c.current_changeset_status: | |
41 | <div class="changeset-status-ico" style="padding:0px 4px 0px 0px"> |
|
41 | <div class="changeset-status-ico" style="padding:0px 4px 0px 0px"> | |
42 | <img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" title="${_('Pull request status calculated from votes')}"/></div> |
|
42 | <img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" title="${_('Pull request status calculated from votes')}"/></div> | |
43 | <div class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}"> |
|
43 | <div class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}"> | |
44 | %if c.pull_request.is_closed(): |
|
44 | %if c.pull_request.is_closed(): | |
45 | ${_('Closed')}, |
|
45 | ${_('Closed')}, | |
46 | %endif |
|
46 | %endif | |
47 | ${h.changeset_status_lbl(c.current_changeset_status)} |
|
47 | ${h.changeset_status_lbl(c.current_changeset_status)} | |
48 | </div> |
|
48 | </div> | |
49 |
|
49 | |||
50 | %endif |
|
50 | %endif | |
51 | </div> |
|
51 | </div> | |
52 | </div> |
|
52 | </div> | |
53 | </div> |
|
53 | </div> | |
54 | <div class="field"> |
|
54 | <div class="field"> | |
55 | <div class="label-summary"> |
|
55 | <div class="label-summary"> | |
56 | <label>${_('Still not reviewed by')}:</label> |
|
56 | <label>${_('Still not reviewed by')}:</label> | |
57 | </div> |
|
57 | </div> | |
58 | <div class="input"> |
|
58 | <div class="input"> | |
59 | % if len(c.pull_request_pending_reviewers) > 0: |
|
59 | % if len(c.pull_request_pending_reviewers) > 0: | |
60 | <div class="tooltip" title="${h.tooltip(', '.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div> |
|
60 | <div class="tooltip" title="${h.tooltip(', '.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div> | |
61 | %else: |
|
61 | %else: | |
62 | <div>${_('Pull request was reviewed by all reviewers')}</div> |
|
62 | <div>${_('Pull request was reviewed by all reviewers')}</div> | |
63 | %endif |
|
63 | %endif | |
64 | </div> |
|
64 | </div> | |
65 | </div> |
|
65 | </div> | |
66 | <div class="field"> |
|
66 | <div class="field"> | |
67 | <div class="label-summary"> |
|
67 | <div class="label-summary"> | |
68 | <label>${_('Origin repository')}:</label> |
|
68 | <label>${_('Origin repository')}:</label> | |
69 | </div> |
|
69 | </div> | |
70 | <div class="input"> |
|
70 | <div class="input"> | |
71 | <div> |
|
71 | <div> | |
72 | <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span> |
|
72 | <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span> | |
73 |
|
73 | |||
74 | ## branch link is only valid if it is a branch |
|
74 | ## branch link is only valid if it is a branch | |
75 | <span class="spantag"><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name, anchor=c.org_ref_name)}">${c.org_ref_type}: ${c.org_ref_name}</a></span> |
|
75 | <span class="spantag"><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name, anchor=c.org_ref_name)}">${c.org_ref_type}: ${c.org_ref_name}</a></span> | |
76 | </div> |
|
76 | </div> | |
77 | </div> |
|
77 | </div> | |
78 | </div> |
|
78 | </div> | |
79 | <div class="field"> |
|
79 | <div class="field"> | |
80 | <div class="label-summary"> |
|
80 | <div class="label-summary"> | |
81 | <label>${_('Pull changes')}:</label> |
|
81 | <label>${_('Pull changes')}:</label> | |
82 | </div> |
|
82 | </div> | |
83 | <div class="input"> |
|
83 | <div class="input"> | |
84 | <div> |
|
84 | <div> | |
85 | ## TODO: use cs_ranges[-1] or org_ref_parts[1] in both cases? |
|
85 | ## TODO: use cs_ranges[-1] or org_ref_parts[1] in both cases? | |
86 | %if h.is_hg(c.pull_request.org_repo): |
|
86 | %if h.is_hg(c.pull_request.org_repo): | |
87 | <span style="font-family: monospace">hg pull ${c.pull_request.org_repo.clone_url()} -r ${h.short_id(c.cs_ranges[-1].raw_id)}</span> |
|
87 | <span style="font-family: monospace">hg pull ${c.pull_request.org_repo.clone_url()} -r ${h.short_id(c.cs_ranges[-1].raw_id)}</span> | |
88 | %elif h.is_git(c.pull_request.org_repo): |
|
88 | %elif h.is_git(c.pull_request.org_repo): | |
89 | <span style="font-family: monospace">git pull ${c.pull_request.org_repo.clone_url()} ${c.pull_request.org_ref_parts[1]}</span> |
|
89 | <span style="font-family: monospace">git pull ${c.pull_request.org_repo.clone_url()} ${c.pull_request.org_ref_parts[1]}</span> | |
90 | %endif |
|
90 | %endif | |
91 | </div> |
|
91 | </div> | |
92 | </div> |
|
92 | </div> | |
93 | </div> |
|
93 | </div> | |
94 | <div class="field"> |
|
94 | <div class="field"> | |
95 | <div class="label-summary"> |
|
95 | <div class="label-summary"> | |
96 | <label>${_('Description')}:</label> |
|
96 | <label>${_('Description')}:</label> | |
97 | </div> |
|
97 | </div> | |
98 | <div class="input"> |
|
98 | <div class="input"> | |
99 | <div style="white-space:pre-wrap">${h.urlify_commit(c.pull_request.description, c.repo_name)}</div> |
|
99 | <div style="white-space:pre-wrap">${h.urlify_commit(c.pull_request.description, c.repo_name)}</div> | |
100 | </div> |
|
100 | </div> | |
101 | </div> |
|
101 | </div> | |
102 | <div class="field"> |
|
102 | <div class="field"> | |
103 | <div class="label-summary"> |
|
103 | <div class="label-summary"> | |
104 | <label>${_('Created on')}:</label> |
|
104 | <label>${_('Created on')}:</label> | |
105 | </div> |
|
105 | </div> | |
106 | <div class="input"> |
|
106 | <div class="input"> | |
107 | <div>${h.fmt_date(c.pull_request.created_on)}</div> |
|
107 | <div>${h.fmt_date(c.pull_request.created_on)}</div> | |
108 | </div> |
|
108 | </div> | |
109 | </div> |
|
109 | </div> | |
110 | </div> |
|
110 | </div> | |
111 | </div> |
|
111 | </div> | |
112 | ## REVIEWERS |
|
112 | ## REVIEWERS | |
113 | <div style="float:left; border-left:1px dashed #eee"> |
|
113 | <div style="float:left; border-left:1px dashed #eee"> | |
114 | <div class="pr-details-title">${_('Pull request reviewers')}</div> |
|
114 | <div class="pr-details-title">${_('Pull request reviewers')}</div> | |
115 | <div id="reviewers" style="padding:0px 0px 5px 10px"> |
|
115 | <div id="reviewers" style="padding:0px 0px 5px 10px"> | |
116 | ## members goes here ! |
|
116 | ## members goes here ! | |
117 | <div> |
|
117 | <div> | |
118 | <ul id="review_members" class="group_members"> |
|
118 | <ul id="review_members" class="group_members"> | |
119 | %for member,status in c.pull_request_reviewers: |
|
119 | %for member,status in c.pull_request_reviewers: | |
120 | <li id="reviewer_${member.user_id}"> |
|
120 | <li id="reviewer_${member.user_id}"> | |
121 | <div class="reviewers_member"> |
|
121 | <div class="reviewers_member"> | |
122 | <div class="reviewer_status tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}"> |
|
122 | <div class="reviewer_status tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}"> | |
123 | <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/> |
|
123 | <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/> | |
124 | </div> |
|
124 | </div> | |
125 | <div class="reviewer_gravatar gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div> |
|
125 | <div class="reviewer_gravatar gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div> | |
126 | <div style="float:left;">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div> |
|
126 | <div style="float:left;">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div> | |
127 | <input type="hidden" value="${member.user_id}" name="review_members" /> |
|
127 | <input type="hidden" value="${member.user_id}" name="review_members" /> | |
128 | %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.user_id == c.authuser.user_id): |
|
128 | %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.user_id == c.authuser.user_id): | |
129 | <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id})" title="${_('Remove reviewer')}"> |
|
129 | <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id})" title="${_('Remove reviewer')}"> | |
130 | <i class="icon-remove-sign" style="color: #FF4444;"></i> |
|
130 | <i class="icon-remove-sign" style="color: #FF4444;"></i> | |
131 | </div> |
|
131 | </div> | |
132 | %endif |
|
132 | %endif | |
133 | </div> |
|
133 | </div> | |
134 | </li> |
|
134 | </li> | |
135 | %endfor |
|
135 | %endfor | |
136 | </ul> |
|
136 | </ul> | |
137 | </div> |
|
137 | </div> | |
138 | %if not c.pull_request.is_closed(): |
|
138 | %if not c.pull_request.is_closed(): | |
139 | <div class='ac'> |
|
139 | <div class='ac'> | |
140 | %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.author.user_id == c.authuser.user_id: |
|
140 | %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.author.user_id == c.authuser.user_id: | |
141 | <div class="reviewer_ac"> |
|
141 | <div class="reviewer_ac"> | |
142 | ${h.text('user', class_='yui-ac-input')} |
|
142 | ${h.text('user', class_='yui-ac-input')} | |
143 | <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span> |
|
143 | <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span> | |
144 | <div id="reviewers_container"></div> |
|
144 | <div id="reviewers_container"></div> | |
145 | </div> |
|
145 | </div> | |
146 | <div style="padding:0px 10px"> |
|
146 | <div style="padding:0px 10px"> | |
147 | <span id="update_pull_request" class="btn btn-mini">${_('Save Changes')}</span> |
|
147 | <span id="update_pull_request" class="btn btn-mini">${_('Save Changes')}</span> | |
148 | </div> |
|
148 | </div> | |
149 | %endif |
|
149 | %endif | |
150 | </div> |
|
150 | </div> | |
151 | %endif |
|
151 | %endif | |
152 | </div> |
|
152 | </div> | |
153 | </div> |
|
153 | </div> | |
154 |
|
154 | |||
155 | <div style="overflow: auto; clear: both"> |
|
155 | <div style="overflow: auto; clear: both"> | |
156 | ##DIFF |
|
156 | ##DIFF | |
157 | <div class="table" style="float:left;clear:none"> |
|
157 | <div class="table" style="float:left;clear:none"> | |
158 |
<div |
|
158 | <div class="diffblock"> | |
159 |
<div style=" |
|
159 | <div style="padding:5px"> | |
|
160 | ${_('Compare view')} | |||
|
161 | </div> | |||
160 | </div> |
|
162 | </div> | |
161 | <div id="changeset_compare_view_content"> |
|
163 | <div id="changeset_compare_view_content"> | |
162 | ##CS |
|
164 | ##CS | |
163 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div> |
|
165 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div> | |
164 | <%include file="/compare/compare_cs.html" /> |
|
166 | <%include file="/compare/compare_cs.html" /> | |
165 |
|
167 | |||
166 | ## FILES |
|
168 | ## FILES | |
167 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px"> |
|
169 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px"> | |
168 |
|
170 | |||
169 | % if c.limited_diff: |
|
171 | % if c.limited_diff: | |
170 | ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)} |
|
172 | ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)} | |
171 | % else: |
|
173 | % else: | |
172 | ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}: |
|
174 | ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}: | |
173 | %endif |
|
175 | %endif | |
174 |
|
176 | |||
175 | </div> |
|
177 | </div> | |
176 | <div class="cs_files"> |
|
178 | <div class="cs_files"> | |
177 | %if not c.files: |
|
179 | %if not c.files: | |
178 | <span class="empty_data">${_('No files')}</span> |
|
180 | <span class="empty_data">${_('No files')}</span> | |
179 | %endif |
|
181 | %endif | |
180 | %for fid, change, f, stat in c.files: |
|
182 | %for fid, change, f, stat in c.files: | |
181 | <div class="cs_${change}"> |
|
183 | <div class="cs_${change}"> | |
182 | <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div> |
|
184 | <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div> | |
183 | <div class="changes">${h.fancy_file_stats(stat)}</div> |
|
185 | <div class="changes">${h.fancy_file_stats(stat)}</div> | |
184 | </div> |
|
186 | </div> | |
185 | %endfor |
|
187 | %endfor | |
186 | </div> |
|
188 | </div> | |
187 | % if c.limited_diff: |
|
189 | % if c.limited_diff: | |
188 | <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h5> |
|
190 | <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h5> | |
189 | % endif |
|
191 | % endif | |
190 | </div> |
|
192 | </div> | |
191 | </div> |
|
193 | </div> | |
192 | </div> |
|
194 | </div> | |
193 | <script> |
|
195 | <script> | |
194 | var _USERS_AC_DATA = ${c.users_array|n}; |
|
196 | var _USERS_AC_DATA = ${c.users_array|n}; | |
195 | var _GROUPS_AC_DATA = ${c.user_groups_array|n}; |
|
197 | var _GROUPS_AC_DATA = ${c.user_groups_array|n}; | |
196 | // TODO: switch this to pyroutes |
|
198 | // TODO: switch this to pyroutes | |
197 | AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"; |
|
199 | AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"; | |
198 | AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"; |
|
200 | AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"; | |
199 |
|
201 | |||
200 | pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']); |
|
202 | pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']); | |
201 | pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']); |
|
203 | pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']); | |
202 | pyroutes.register('pullrequest_update', "${url('pullrequest_update',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']); |
|
204 | pyroutes.register('pullrequest_update', "${url('pullrequest_update',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']); | |
203 |
|
205 | |||
204 | </script> |
|
206 | </script> | |
205 |
|
207 | |||
206 | ## diff block |
|
208 | ## diff block | |
207 | <%namespace name="diff_block" file="/changeset/diff_block.html"/> |
|
209 | <%namespace name="diff_block" file="/changeset/diff_block.html"/> | |
208 | %for fid, change, f, stat in c.files: |
|
210 | %for fid, change, f, stat in c.files: | |
209 | ${diff_block.diff_block_simple([c.changes[fid]])} |
|
211 | ${diff_block.diff_block_simple([c.changes[fid]])} | |
210 | %endfor |
|
212 | %endfor | |
211 | % if c.limited_diff: |
|
213 | % if c.limited_diff: | |
212 | <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h4> |
|
214 | <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h4> | |
213 | % endif |
|
215 | % endif | |
214 |
|
216 | |||
215 |
|
217 | |||
216 | ## template for inline comment form |
|
218 | ## template for inline comment form | |
217 | <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> |
|
219 | <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> | |
218 | ${comment.comment_inline_form()} |
|
220 | ${comment.comment_inline_form()} | |
219 |
|
221 | |||
220 | ## render comments and inlines |
|
222 | ## render comments and inlines | |
221 | ${comment.generate_comments(include_pr=True)} |
|
223 | ${comment.generate_comments(include_pr=True)} | |
222 |
|
224 | |||
223 | % if not c.pull_request.is_closed(): |
|
225 | % if not c.pull_request.is_closed(): | |
224 | ## main comment form and it status |
|
226 | ## main comment form and it status | |
225 | ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, |
|
227 | ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, | |
226 | pull_request_id=c.pull_request.pull_request_id), |
|
228 | pull_request_id=c.pull_request.pull_request_id), | |
227 | c.current_changeset_status, |
|
229 | c.current_changeset_status, | |
228 | is_pr=True, change_status=c.allowed_to_change_status)} |
|
230 | is_pr=True, change_status=c.allowed_to_change_status)} | |
229 | %endif |
|
231 | %endif | |
230 |
|
232 | |||
231 | <script type="text/javascript"> |
|
233 | <script type="text/javascript"> | |
232 | YUE.onDOMReady(function(){ |
|
234 | YUE.onDOMReady(function(){ | |
233 | PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA); |
|
235 | PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA); | |
234 |
|
236 | |||
235 | YUE.on(YUQ('.show-inline-comments'),'change',function(e){ |
|
237 | YUE.on(YUQ('.show-inline-comments'),'change',function(e){ | |
236 | var show = 'none'; |
|
238 | var show = 'none'; | |
237 | var target = e.currentTarget; |
|
239 | var target = e.currentTarget; | |
238 | if(target.checked){ |
|
240 | if(target.checked){ | |
239 | var show = '' |
|
241 | var show = '' | |
240 | } |
|
242 | } | |
241 | var boxid = YUD.getAttribute(target,'id_for'); |
|
243 | var boxid = YUD.getAttribute(target,'id_for'); | |
242 | var comments = YUQ('#{0} .inline-comments'.format(boxid)); |
|
244 | var comments = YUQ('#{0} .inline-comments'.format(boxid)); | |
243 | for(c in comments){ |
|
245 | for(c in comments){ | |
244 | YUD.setStyle(comments[c],'display',show); |
|
246 | YUD.setStyle(comments[c],'display',show); | |
245 | } |
|
247 | } | |
246 | var btns = YUQ('#{0} .inline-comments-button'.format(boxid)); |
|
248 | var btns = YUQ('#{0} .inline-comments-button'.format(boxid)); | |
247 | for(c in btns){ |
|
249 | for(c in btns){ | |
248 | YUD.setStyle(btns[c],'display',show); |
|
250 | YUD.setStyle(btns[c],'display',show); | |
249 | } |
|
251 | } | |
250 | }) |
|
252 | }) | |
251 |
|
253 | |||
252 | YUE.on(YUQ('.line'),'click',function(e){ |
|
254 | YUE.on(YUQ('.line'),'click',function(e){ | |
253 | var tr = e.currentTarget; |
|
255 | var tr = e.currentTarget; | |
254 | injectInlineForm(tr); |
|
256 | injectInlineForm(tr); | |
255 | }); |
|
257 | }); | |
256 |
|
258 | |||
257 | // inject comments into they proper positions |
|
259 | // inject comments into they proper positions | |
258 | var file_comments = YUQ('.inline-comment-placeholder'); |
|
260 | var file_comments = YUQ('.inline-comment-placeholder'); | |
259 | renderInlineComments(file_comments); |
|
261 | renderInlineComments(file_comments); | |
260 |
|
262 | |||
261 | YUE.on(YUD.get('update_pull_request'),'click',function(e){ |
|
263 | YUE.on(YUD.get('update_pull_request'),'click',function(e){ | |
262 | updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}"); |
|
264 | updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}"); | |
263 | }) |
|
265 | }) | |
264 | }) |
|
266 | }) | |
265 | </script> |
|
267 | </script> | |
266 |
|
268 | |||
267 | </div> |
|
269 | </div> | |
268 |
|
270 | |||
269 | </%def> |
|
271 | </%def> |
General Comments 0
You need to be logged in to leave comments.
Login now