##// END OF EJS Templates
controllers: consistently use c.cs_comments and cs_statuses...
Mads Kiilerich -
r6766:f0ec7be7 default
parent child Browse files
Show More
@@ -1,194 +1,194 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.changelog
15 kallithea.controllers.changelog
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 changelog controller for Kallithea
18 changelog controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 21, 2010
22 :created_on: Apr 21, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from tg import request, session, tmpl_context as c
31 from tg import request, session, tmpl_context as c
32 from tg.i18n import ugettext as _
32 from tg.i18n import ugettext as _
33 from webob.exc import HTTPFound, HTTPNotFound, HTTPBadRequest
33 from webob.exc import HTTPFound, HTTPNotFound, HTTPBadRequest
34
34
35 import kallithea.lib.helpers as h
35 import kallithea.lib.helpers as h
36 from kallithea.config.routing import url
36 from kallithea.config.routing import url
37 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
37 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
38 from kallithea.lib.base import BaseRepoController, render
38 from kallithea.lib.base import BaseRepoController, render
39 from kallithea.lib.graphmod import graph_data
39 from kallithea.lib.graphmod import graph_data
40 from kallithea.lib.page import RepoPage
40 from kallithea.lib.page import RepoPage
41 from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
41 from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
42 ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
42 ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
43 from kallithea.lib.utils2 import safe_int, safe_str
43 from kallithea.lib.utils2 import safe_int, safe_str
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def _load_changelog_summary():
49 def _load_changelog_summary():
50 # also used from summary ...
50 # also used from summary ...
51 p = safe_int(request.GET.get('page'), 1)
51 p = safe_int(request.GET.get('page'), 1)
52 size = safe_int(request.GET.get('size'), 10)
52 size = safe_int(request.GET.get('size'), 10)
53
53
54 def url_generator(**kw):
54 def url_generator(**kw):
55 return url('changelog_summary_home',
55 return url('changelog_summary_home',
56 repo_name=c.db_repo.repo_name, size=size, **kw)
56 repo_name=c.db_repo.repo_name, size=size, **kw)
57
57
58 collection = c.db_repo_scm_instance
58 collection = c.db_repo_scm_instance
59
59
60 c.repo_changesets = RepoPage(collection, page=p,
60 c.repo_changesets = RepoPage(collection, page=p,
61 items_per_page=size,
61 items_per_page=size,
62 url=url_generator)
62 url=url_generator)
63 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
63 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
64 c.comments = c.db_repo.get_comments(page_revisions)
64 c.cs_comments = c.db_repo.get_comments(page_revisions)
65 c.statuses = c.db_repo.statuses(page_revisions)
65 c.cs_statuses = c.db_repo.statuses(page_revisions)
66
66
67
67
68 class ChangelogController(BaseRepoController):
68 class ChangelogController(BaseRepoController):
69
69
70 def _before(self, *args, **kwargs):
70 def _before(self, *args, **kwargs):
71 super(ChangelogController, self)._before(*args, **kwargs)
71 super(ChangelogController, self)._before(*args, **kwargs)
72 c.affected_files_cut_off = 60
72 c.affected_files_cut_off = 60
73
73
74 @staticmethod
74 @staticmethod
75 def __get_cs(rev, repo):
75 def __get_cs(rev, repo):
76 """
76 """
77 Safe way to get changeset. If error occur fail with error message.
77 Safe way to get changeset. If error occur fail with error message.
78
78
79 :param rev: revision to fetch
79 :param rev: revision to fetch
80 :param repo: repo instance
80 :param repo: repo instance
81 """
81 """
82
82
83 try:
83 try:
84 return c.db_repo_scm_instance.get_changeset(rev)
84 return c.db_repo_scm_instance.get_changeset(rev)
85 except EmptyRepositoryError as e:
85 except EmptyRepositoryError as e:
86 h.flash(h.literal(_('There are no changesets yet')),
86 h.flash(h.literal(_('There are no changesets yet')),
87 category='error')
87 category='error')
88 except RepositoryError as e:
88 except RepositoryError as e:
89 log.error(traceback.format_exc())
89 log.error(traceback.format_exc())
90 h.flash(safe_str(e), category='error')
90 h.flash(safe_str(e), category='error')
91 raise HTTPBadRequest()
91 raise HTTPBadRequest()
92
92
93 @LoginRequired()
93 @LoginRequired()
94 @HasRepoPermissionLevelDecorator('read')
94 @HasRepoPermissionLevelDecorator('read')
95 def index(self, repo_name, revision=None, f_path=None):
95 def index(self, repo_name, revision=None, f_path=None):
96 # Fix URL after page size form submission via GET
96 # Fix URL after page size form submission via GET
97 # TODO: Somehow just don't send this extra junk in the GET URL
97 # TODO: Somehow just don't send this extra junk in the GET URL
98 if request.GET.get('set'):
98 if request.GET.get('set'):
99 request.GET.pop('set', None)
99 request.GET.pop('set', None)
100 if revision is None:
100 if revision is None:
101 raise HTTPFound(location=url('changelog_home', repo_name=repo_name, **request.GET))
101 raise HTTPFound(location=url('changelog_home', repo_name=repo_name, **request.GET))
102 raise HTTPFound(location=url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET))
102 raise HTTPFound(location=url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET))
103
103
104 limit = 2000
104 limit = 2000
105 default = 100
105 default = 100
106 if request.GET.get('size'):
106 if request.GET.get('size'):
107 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
107 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
108 session['changelog_size'] = c.size
108 session['changelog_size'] = c.size
109 session.save()
109 session.save()
110 else:
110 else:
111 c.size = int(session.get('changelog_size', default))
111 c.size = int(session.get('changelog_size', default))
112 # min size must be 1
112 # min size must be 1
113 c.size = max(c.size, 1)
113 c.size = max(c.size, 1)
114 p = safe_int(request.GET.get('page'), 1)
114 p = safe_int(request.GET.get('page'), 1)
115 branch_name = request.GET.get('branch', None)
115 branch_name = request.GET.get('branch', None)
116 if (branch_name and
116 if (branch_name and
117 branch_name not in c.db_repo_scm_instance.branches and
117 branch_name not in c.db_repo_scm_instance.branches and
118 branch_name not in c.db_repo_scm_instance.closed_branches and
118 branch_name not in c.db_repo_scm_instance.closed_branches and
119 not revision):
119 not revision):
120 raise HTTPFound(location=url('changelog_file_home', repo_name=c.repo_name,
120 raise HTTPFound(location=url('changelog_file_home', repo_name=c.repo_name,
121 revision=branch_name, f_path=f_path or ''))
121 revision=branch_name, f_path=f_path or ''))
122
122
123 if revision == 'tip':
123 if revision == 'tip':
124 revision = None
124 revision = None
125
125
126 c.changelog_for_path = f_path
126 c.changelog_for_path = f_path
127 try:
127 try:
128
128
129 if f_path:
129 if f_path:
130 log.debug('generating changelog for path %s', f_path)
130 log.debug('generating changelog for path %s', f_path)
131 # get the history for the file !
131 # get the history for the file !
132 tip_cs = c.db_repo_scm_instance.get_changeset()
132 tip_cs = c.db_repo_scm_instance.get_changeset()
133 try:
133 try:
134 collection = tip_cs.get_file_history(f_path)
134 collection = tip_cs.get_file_history(f_path)
135 except (NodeDoesNotExistError, ChangesetError):
135 except (NodeDoesNotExistError, ChangesetError):
136 #this node is not present at tip !
136 #this node is not present at tip !
137 try:
137 try:
138 cs = self.__get_cs(revision, repo_name)
138 cs = self.__get_cs(revision, repo_name)
139 collection = cs.get_file_history(f_path)
139 collection = cs.get_file_history(f_path)
140 except RepositoryError as e:
140 except RepositoryError as e:
141 h.flash(safe_str(e), category='warning')
141 h.flash(safe_str(e), category='warning')
142 raise HTTPFound(location=h.url('changelog_home', repo_name=repo_name))
142 raise HTTPFound(location=h.url('changelog_home', repo_name=repo_name))
143 collection = list(reversed(collection))
143 collection = list(reversed(collection))
144 else:
144 else:
145 collection = c.db_repo_scm_instance.get_changesets(start=0, end=revision,
145 collection = c.db_repo_scm_instance.get_changesets(start=0, end=revision,
146 branch_name=branch_name)
146 branch_name=branch_name)
147 c.total_cs = len(collection)
147 c.total_cs = len(collection)
148
148
149 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
149 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
150 items_per_page=c.size, branch=branch_name,)
150 items_per_page=c.size, branch=branch_name,)
151
151
152 page_revisions = [x.raw_id for x in c.pagination]
152 page_revisions = [x.raw_id for x in c.pagination]
153 c.comments = c.db_repo.get_comments(page_revisions)
153 c.cs_comments = c.db_repo.get_comments(page_revisions)
154 c.statuses = c.db_repo.statuses(page_revisions)
154 c.cs_statuses = c.db_repo.statuses(page_revisions)
155 except EmptyRepositoryError as e:
155 except EmptyRepositoryError as e:
156 h.flash(safe_str(e), category='warning')
156 h.flash(safe_str(e), category='warning')
157 raise HTTPFound(location=url('summary_home', repo_name=c.repo_name))
157 raise HTTPFound(location=url('summary_home', repo_name=c.repo_name))
158 except (RepositoryError, ChangesetDoesNotExistError, Exception) as e:
158 except (RepositoryError, ChangesetDoesNotExistError, Exception) as e:
159 log.error(traceback.format_exc())
159 log.error(traceback.format_exc())
160 h.flash(safe_str(e), category='error')
160 h.flash(safe_str(e), category='error')
161 raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name))
161 raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name))
162
162
163 c.branch_name = branch_name
163 c.branch_name = branch_name
164 c.branch_filters = [('', _('None'))] + \
164 c.branch_filters = [('', _('None'))] + \
165 [(k, k) for k in c.db_repo_scm_instance.branches.keys()]
165 [(k, k) for k in c.db_repo_scm_instance.branches.keys()]
166 if c.db_repo_scm_instance.closed_branches:
166 if c.db_repo_scm_instance.closed_branches:
167 prefix = _('(closed)') + ' '
167 prefix = _('(closed)') + ' '
168 c.branch_filters += [('-', '-')] + \
168 c.branch_filters += [('-', '-')] + \
169 [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches.keys()]
169 [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches.keys()]
170 revs = []
170 revs = []
171 if not f_path:
171 if not f_path:
172 revs = [x.revision for x in c.pagination]
172 revs = [x.revision for x in c.pagination]
173 c.jsdata = graph_data(c.db_repo_scm_instance, revs)
173 c.jsdata = graph_data(c.db_repo_scm_instance, revs)
174
174
175 c.revision = revision # requested revision ref
175 c.revision = revision # requested revision ref
176 c.first_revision = c.pagination[0] # pagination is never empty here!
176 c.first_revision = c.pagination[0] # pagination is never empty here!
177 return render('changelog/changelog.html')
177 return render('changelog/changelog.html')
178
178
179 @LoginRequired()
179 @LoginRequired()
180 @HasRepoPermissionLevelDecorator('read')
180 @HasRepoPermissionLevelDecorator('read')
181 def changelog_details(self, cs):
181 def changelog_details(self, cs):
182 if request.environ.get('HTTP_X_PARTIAL_XHR'):
182 if request.environ.get('HTTP_X_PARTIAL_XHR'):
183 c.cs = c.db_repo_scm_instance.get_changeset(cs)
183 c.cs = c.db_repo_scm_instance.get_changeset(cs)
184 return render('changelog/changelog_details.html')
184 return render('changelog/changelog_details.html')
185 raise HTTPNotFound()
185 raise HTTPNotFound()
186
186
187 @LoginRequired()
187 @LoginRequired()
188 @HasRepoPermissionLevelDecorator('read')
188 @HasRepoPermissionLevelDecorator('read')
189 def changelog_summary(self, repo_name):
189 def changelog_summary(self, repo_name):
190 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 if request.environ.get('HTTP_X_PARTIAL_XHR'):
191 _load_changelog_summary()
191 _load_changelog_summary()
192
192
193 return render('changelog/changelog_summary_data.html')
193 return render('changelog/changelog_summary_data.html')
194 raise HTTPNotFound()
194 raise HTTPNotFound()
@@ -1,295 +1,295 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 showing differences between two
18 compare controller 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 re
31 import re
32
32
33 from tg import request, tmpl_context as c
33 from tg import request, tmpl_context as c
34 from tg.i18n import ugettext as _
34 from tg.i18n import ugettext as _
35 from webob.exc import HTTPFound, HTTPBadRequest, HTTPNotFound
35 from webob.exc import HTTPFound, HTTPBadRequest, HTTPNotFound
36
36
37 from kallithea.config.routing import url
37 from kallithea.config.routing import url
38 from kallithea.lib.utils2 import safe_str, safe_int
38 from kallithea.lib.utils2 import safe_str, safe_int
39 from kallithea.lib.vcs.utils.hgcompat import unionrepo
39 from kallithea.lib.vcs.utils.hgcompat import unionrepo
40 from kallithea.lib import helpers as h
40 from kallithea.lib import helpers as h
41 from kallithea.lib.base import BaseRepoController, render
41 from kallithea.lib.base import BaseRepoController, render
42 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
42 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
43 from kallithea.lib import diffs
43 from kallithea.lib import diffs
44 from kallithea.model.db import Repository
44 from kallithea.model.db import Repository
45 from kallithea.lib.diffs import LimitedDiffContainer
45 from kallithea.lib.diffs import LimitedDiffContainer
46 from kallithea.controllers.changeset import _ignorews_url, _context_url
46 from kallithea.controllers.changeset import _ignorews_url, _context_url
47 from kallithea.lib.graphmod import graph_data
47 from kallithea.lib.graphmod import graph_data
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class CompareController(BaseRepoController):
52 class CompareController(BaseRepoController):
53
53
54 def _before(self, *args, **kwargs):
54 def _before(self, *args, **kwargs):
55 super(CompareController, self)._before(*args, **kwargs)
55 super(CompareController, self)._before(*args, **kwargs)
56
56
57 # The base repository has already been retrieved.
57 # The base repository has already been retrieved.
58 c.a_repo = c.db_repo
58 c.a_repo = c.db_repo
59
59
60 # Retrieve the "changeset" repository (default: same as base).
60 # Retrieve the "changeset" repository (default: same as base).
61 other_repo = request.GET.get('other_repo', None)
61 other_repo = request.GET.get('other_repo', None)
62 if other_repo is None:
62 if other_repo is None:
63 c.cs_repo = c.a_repo
63 c.cs_repo = c.a_repo
64 else:
64 else:
65 c.cs_repo = Repository.get_by_repo_name(other_repo)
65 c.cs_repo = Repository.get_by_repo_name(other_repo)
66 if c.cs_repo is None:
66 if c.cs_repo is None:
67 msg = _('Could not find other repository %s') % other_repo
67 msg = _('Could not find other repository %s') % other_repo
68 h.flash(msg, category='error')
68 h.flash(msg, category='error')
69 raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
69 raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
70
70
71 # Verify that it's even possible to compare these two repositories.
71 # Verify that it's even possible to compare these two repositories.
72 if c.a_repo.scm_instance.alias != c.cs_repo.scm_instance.alias:
72 if c.a_repo.scm_instance.alias != c.cs_repo.scm_instance.alias:
73 msg = _('Cannot compare repositories of different types')
73 msg = _('Cannot compare repositories of different types')
74 h.flash(msg, category='error')
74 h.flash(msg, category='error')
75 raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
75 raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
76
76
77 @staticmethod
77 @staticmethod
78 def _get_changesets(alias, org_repo, org_rev, other_repo, other_rev):
78 def _get_changesets(alias, org_repo, org_rev, other_repo, other_rev):
79 """
79 """
80 Returns lists of changesets that can be merged from org_repo@org_rev
80 Returns lists of changesets that can be merged from org_repo@org_rev
81 to other_repo@other_rev
81 to other_repo@other_rev
82 ... and the other way
82 ... and the other way
83 ... and the ancestors that would be used for merge
83 ... and the ancestors that would be used for merge
84
84
85 :param org_repo: repo object, that is most likely the original repo we forked from
85 :param org_repo: repo object, that is most likely the original repo we forked from
86 :param org_rev: the revision we want our compare to be made
86 :param org_rev: the revision we want our compare to be made
87 :param other_repo: repo object, most likely the fork of org_repo. It has
87 :param other_repo: repo object, most likely the fork of org_repo. It has
88 all changesets that we need to obtain
88 all changesets that we need to obtain
89 :param other_rev: revision we want out compare to be made on other_repo
89 :param other_rev: revision we want out compare to be made on other_repo
90 """
90 """
91 ancestors = None
91 ancestors = None
92 if org_rev == other_rev:
92 if org_rev == other_rev:
93 org_changesets = []
93 org_changesets = []
94 other_changesets = []
94 other_changesets = []
95
95
96 elif alias == 'hg':
96 elif alias == 'hg':
97 #case two independent repos
97 #case two independent repos
98 if org_repo != other_repo:
98 if org_repo != other_repo:
99 hgrepo = unionrepo.unionrepository(other_repo.baseui,
99 hgrepo = unionrepo.unionrepository(other_repo.baseui,
100 other_repo.path,
100 other_repo.path,
101 org_repo.path)
101 org_repo.path)
102 # all ancestors of other_rev will be in other_repo and
102 # all ancestors of other_rev will be in other_repo and
103 # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot
103 # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot
104
104
105 #no remote compare do it on the same repository
105 #no remote compare do it on the same repository
106 else:
106 else:
107 hgrepo = other_repo._repo
107 hgrepo = other_repo._repo
108
108
109 ancestors = [hgrepo[ancestor].hex() for ancestor in
109 ancestors = [hgrepo[ancestor].hex() for ancestor in
110 hgrepo.revs("id(%s) & ::id(%s)", other_rev, org_rev)]
110 hgrepo.revs("id(%s) & ::id(%s)", other_rev, org_rev)]
111 if ancestors:
111 if ancestors:
112 log.debug("shortcut found: %s is already an ancestor of %s", other_rev, org_rev)
112 log.debug("shortcut found: %s is already an ancestor of %s", other_rev, org_rev)
113 else:
113 else:
114 log.debug("no shortcut found: %s is not an ancestor of %s", other_rev, org_rev)
114 log.debug("no shortcut found: %s is not an ancestor of %s", other_rev, org_rev)
115 ancestors = [hgrepo[ancestor].hex() for ancestor in
115 ancestors = [hgrepo[ancestor].hex() for ancestor in
116 hgrepo.revs("heads(::id(%s) & ::id(%s))", org_rev, other_rev)] # FIXME: expensive!
116 hgrepo.revs("heads(::id(%s) & ::id(%s))", org_rev, other_rev)] # FIXME: expensive!
117
117
118 other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
118 other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
119 other_rev, org_rev, org_rev)
119 other_rev, org_rev, org_rev)
120 other_changesets = [other_repo.get_changeset(rev) for rev in other_revs]
120 other_changesets = [other_repo.get_changeset(rev) for rev in other_revs]
121 org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
121 org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
122 org_rev, other_rev, other_rev)
122 org_rev, other_rev, other_rev)
123 org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs]
123 org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs]
124
124
125 elif alias == 'git':
125 elif alias == 'git':
126 if org_repo != other_repo:
126 if org_repo != other_repo:
127 from dulwich.repo import Repo
127 from dulwich.repo import Repo
128 from dulwich.client import SubprocessGitClient
128 from dulwich.client import SubprocessGitClient
129
129
130 gitrepo = Repo(org_repo.path)
130 gitrepo = Repo(org_repo.path)
131 SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo)
131 SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo)
132
132
133 gitrepo_remote = Repo(other_repo.path)
133 gitrepo_remote = Repo(other_repo.path)
134 SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote)
134 SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote)
135
135
136 revs = [
136 revs = [
137 x.commit.id
137 x.commit.id
138 for x in gitrepo_remote.get_walker(include=[other_rev],
138 for x in gitrepo_remote.get_walker(include=[other_rev],
139 exclude=[org_rev])
139 exclude=[org_rev])
140 ]
140 ]
141 other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
141 other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
142 if other_changesets:
142 if other_changesets:
143 ancestors = [other_changesets[0].parents[0].raw_id]
143 ancestors = [other_changesets[0].parents[0].raw_id]
144 else:
144 else:
145 # no changesets from other repo, ancestor is the other_rev
145 # no changesets from other repo, ancestor is the other_rev
146 ancestors = [other_rev]
146 ancestors = [other_rev]
147
147
148 gitrepo.close()
148 gitrepo.close()
149 gitrepo_remote.close()
149 gitrepo_remote.close()
150
150
151 else:
151 else:
152 so, se = org_repo.run_git_command(
152 so, se = org_repo.run_git_command(
153 ['log', '--reverse', '--pretty=format:%H',
153 ['log', '--reverse', '--pretty=format:%H',
154 '-s', '%s..%s' % (org_rev, other_rev)]
154 '-s', '%s..%s' % (org_rev, other_rev)]
155 )
155 )
156 other_changesets = [org_repo.get_changeset(cs)
156 other_changesets = [org_repo.get_changeset(cs)
157 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
157 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
158 so, se = org_repo.run_git_command(
158 so, se = org_repo.run_git_command(
159 ['merge-base', org_rev, other_rev]
159 ['merge-base', org_rev, other_rev]
160 )
160 )
161 ancestors = [re.findall(r'[0-9a-fA-F]{40}', so)[0]]
161 ancestors = [re.findall(r'[0-9a-fA-F]{40}', so)[0]]
162 org_changesets = []
162 org_changesets = []
163
163
164 else:
164 else:
165 raise Exception('Bad alias only git and hg is allowed')
165 raise Exception('Bad alias only git and hg is allowed')
166
166
167 return other_changesets, org_changesets, ancestors
167 return other_changesets, org_changesets, ancestors
168
168
169 @LoginRequired()
169 @LoginRequired()
170 @HasRepoPermissionLevelDecorator('read')
170 @HasRepoPermissionLevelDecorator('read')
171 def index(self, repo_name):
171 def index(self, repo_name):
172 c.compare_home = True
172 c.compare_home = True
173 c.a_ref_name = c.cs_ref_name = None
173 c.a_ref_name = c.cs_ref_name = None
174 return render('compare/compare_diff.html')
174 return render('compare/compare_diff.html')
175
175
176 @LoginRequired()
176 @LoginRequired()
177 @HasRepoPermissionLevelDecorator('read')
177 @HasRepoPermissionLevelDecorator('read')
178 def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name):
178 def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name):
179 org_ref_name = org_ref_name.strip()
179 org_ref_name = org_ref_name.strip()
180 other_ref_name = other_ref_name.strip()
180 other_ref_name = other_ref_name.strip()
181
181
182 # If merge is True:
182 # If merge is True:
183 # Show what org would get if merged with other:
183 # Show what org would get if merged with other:
184 # List changesets that are ancestors of other but not of org.
184 # List changesets that are ancestors of other but not of org.
185 # New changesets in org is thus ignored.
185 # New changesets in org is thus ignored.
186 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
186 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
187 # If merge is False:
187 # If merge is False:
188 # Make a raw diff from org to other, no matter if related or not.
188 # Make a raw diff from org to other, no matter if related or not.
189 # Changesets in one and not in the other will be ignored
189 # Changesets in one and not in the other will be ignored
190 merge = bool(request.GET.get('merge'))
190 merge = bool(request.GET.get('merge'))
191 # fulldiff disables cut_off_limit
191 # fulldiff disables cut_off_limit
192 c.fulldiff = request.GET.get('fulldiff')
192 c.fulldiff = request.GET.get('fulldiff')
193 # partial uses compare_cs.html template directly
193 # partial uses compare_cs.html template directly
194 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
194 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
195 # is_ajax_preview puts hidden input field with changeset revisions
195 # is_ajax_preview puts hidden input field with changeset revisions
196 c.is_ajax_preview = partial and request.GET.get('is_ajax_preview')
196 c.is_ajax_preview = partial and request.GET.get('is_ajax_preview')
197 # swap url for compare_diff page - never partial and never is_ajax_preview
197 # swap url for compare_diff page - never partial and never is_ajax_preview
198 c.swap_url = h.url('compare_url',
198 c.swap_url = h.url('compare_url',
199 repo_name=c.cs_repo.repo_name,
199 repo_name=c.cs_repo.repo_name,
200 org_ref_type=other_ref_type, org_ref_name=other_ref_name,
200 org_ref_type=other_ref_type, org_ref_name=other_ref_name,
201 other_repo=c.a_repo.repo_name,
201 other_repo=c.a_repo.repo_name,
202 other_ref_type=org_ref_type, other_ref_name=org_ref_name,
202 other_ref_type=org_ref_type, other_ref_name=org_ref_name,
203 merge=merge or '')
203 merge=merge or '')
204
204
205 # set callbacks for generating markup for icons
205 # set callbacks for generating markup for icons
206 c.ignorews_url = _ignorews_url
206 c.ignorews_url = _ignorews_url
207 c.context_url = _context_url
207 c.context_url = _context_url
208 ignore_whitespace = request.GET.get('ignorews') == '1'
208 ignore_whitespace = request.GET.get('ignorews') == '1'
209 line_context = safe_int(request.GET.get('context'), 3)
209 line_context = safe_int(request.GET.get('context'), 3)
210
210
211 c.a_rev = self._get_ref_rev(c.a_repo, org_ref_type, org_ref_name,
211 c.a_rev = self._get_ref_rev(c.a_repo, org_ref_type, org_ref_name,
212 returnempty=True)
212 returnempty=True)
213 c.cs_rev = self._get_ref_rev(c.cs_repo, other_ref_type, other_ref_name)
213 c.cs_rev = self._get_ref_rev(c.cs_repo, other_ref_type, other_ref_name)
214
214
215 c.compare_home = False
215 c.compare_home = False
216 c.a_ref_name = org_ref_name
216 c.a_ref_name = org_ref_name
217 c.a_ref_type = org_ref_type
217 c.a_ref_type = org_ref_type
218 c.cs_ref_name = other_ref_name
218 c.cs_ref_name = other_ref_name
219 c.cs_ref_type = other_ref_type
219 c.cs_ref_type = other_ref_type
220
220
221 c.cs_ranges, c.cs_ranges_org, c.ancestors = self._get_changesets(
221 c.cs_ranges, c.cs_ranges_org, c.ancestors = self._get_changesets(
222 c.a_repo.scm_instance.alias, c.a_repo.scm_instance, c.a_rev,
222 c.a_repo.scm_instance.alias, c.a_repo.scm_instance, c.a_rev,
223 c.cs_repo.scm_instance, c.cs_rev)
223 c.cs_repo.scm_instance, c.cs_rev)
224 raw_ids = [x.raw_id for x in c.cs_ranges]
224 raw_ids = [x.raw_id for x in c.cs_ranges]
225 c.cs_comments = c.cs_repo.get_comments(raw_ids)
225 c.cs_comments = c.cs_repo.get_comments(raw_ids)
226 c.statuses = c.cs_repo.statuses(raw_ids)
226 c.cs_statuses = c.cs_repo.statuses(raw_ids)
227
227
228 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
228 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
229 c.jsdata = graph_data(c.cs_repo.scm_instance, revs)
229 c.jsdata = graph_data(c.cs_repo.scm_instance, revs)
230
230
231 if partial:
231 if partial:
232 return render('compare/compare_cs.html')
232 return render('compare/compare_cs.html')
233
233
234 org_repo = c.a_repo
234 org_repo = c.a_repo
235 other_repo = c.cs_repo
235 other_repo = c.cs_repo
236
236
237 if merge:
237 if merge:
238 rev1 = msg = None
238 rev1 = msg = None
239 if not c.cs_ranges:
239 if not c.cs_ranges:
240 msg = _('Cannot show empty diff')
240 msg = _('Cannot show empty diff')
241 elif not c.ancestors:
241 elif not c.ancestors:
242 msg = _('No ancestor found for merge diff')
242 msg = _('No ancestor found for merge diff')
243 elif len(c.ancestors) == 1:
243 elif len(c.ancestors) == 1:
244 rev1 = c.ancestors[0]
244 rev1 = c.ancestors[0]
245 else:
245 else:
246 msg = _('Multiple merge ancestors found for merge compare')
246 msg = _('Multiple merge ancestors found for merge compare')
247 if rev1 is None:
247 if rev1 is None:
248 h.flash(msg, category='error')
248 h.flash(msg, category='error')
249 log.error(msg)
249 log.error(msg)
250 raise HTTPNotFound
250 raise HTTPNotFound
251
251
252 # case we want a simple diff without incoming changesets,
252 # case we want a simple diff without incoming changesets,
253 # previewing what will be merged.
253 # previewing what will be merged.
254 # Make the diff on the other repo (which is known to have other_rev)
254 # Make the diff on the other repo (which is known to have other_rev)
255 log.debug('Using ancestor %s as rev1 instead of %s',
255 log.debug('Using ancestor %s as rev1 instead of %s',
256 rev1, c.a_rev)
256 rev1, c.a_rev)
257 org_repo = other_repo
257 org_repo = other_repo
258 else: # comparing tips, not necessarily linearly related
258 else: # comparing tips, not necessarily linearly related
259 if org_repo != other_repo:
259 if org_repo != other_repo:
260 # TODO: we could do this by using hg unionrepo
260 # TODO: we could do this by using hg unionrepo
261 log.error('cannot compare across repos %s and %s', org_repo, other_repo)
261 log.error('cannot compare across repos %s and %s', org_repo, other_repo)
262 h.flash(_('Cannot compare repositories without using common ancestor'), category='error')
262 h.flash(_('Cannot compare repositories without using common ancestor'), category='error')
263 raise HTTPBadRequest
263 raise HTTPBadRequest
264 rev1 = c.a_rev
264 rev1 = c.a_rev
265
265
266 diff_limit = self.cut_off_limit if not c.fulldiff else None
266 diff_limit = self.cut_off_limit if not c.fulldiff else None
267
267
268 log.debug('running diff between %s and %s in %s',
268 log.debug('running diff between %s and %s in %s',
269 rev1, c.cs_rev, org_repo.scm_instance.path)
269 rev1, c.cs_rev, org_repo.scm_instance.path)
270 txtdiff = org_repo.scm_instance.get_diff(rev1=rev1, rev2=c.cs_rev,
270 txtdiff = org_repo.scm_instance.get_diff(rev1=rev1, rev2=c.cs_rev,
271 ignore_whitespace=ignore_whitespace,
271 ignore_whitespace=ignore_whitespace,
272 context=line_context)
272 context=line_context)
273
273
274 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
274 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
275 diff_limit=diff_limit)
275 diff_limit=diff_limit)
276 _parsed = diff_processor.prepare()
276 _parsed = diff_processor.prepare()
277
277
278 c.limited_diff = False
278 c.limited_diff = False
279 if isinstance(_parsed, LimitedDiffContainer):
279 if isinstance(_parsed, LimitedDiffContainer):
280 c.limited_diff = True
280 c.limited_diff = True
281
281
282 c.file_diff_data = []
282 c.file_diff_data = []
283 c.lines_added = 0
283 c.lines_added = 0
284 c.lines_deleted = 0
284 c.lines_deleted = 0
285 for f in _parsed:
285 for f in _parsed:
286 st = f['stats']
286 st = f['stats']
287 c.lines_added += st['added']
287 c.lines_added += st['added']
288 c.lines_deleted += st['deleted']
288 c.lines_deleted += st['deleted']
289 filename = f['filename']
289 filename = f['filename']
290 fid = h.FID('', filename)
290 fid = h.FID('', filename)
291 diff = diff_processor.as_html(enable_comments=False,
291 diff = diff_processor.as_html(enable_comments=False,
292 parsed_lines=[f])
292 parsed_lines=[f])
293 c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st))
293 c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st))
294
294
295 return render('compare/compare_diff.html')
295 return render('compare/compare_diff.html')
@@ -1,747 +1,746 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 tg import request, tmpl_context as c
32 from tg import request, tmpl_context as c
33 from tg.i18n import ugettext as _
33 from tg.i18n import ugettext as _
34 from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest
34 from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest
35
35
36 from kallithea.config.routing import url
36 from kallithea.config.routing import url
37 from kallithea.lib import helpers as h
37 from kallithea.lib import helpers as h
38 from kallithea.lib import diffs
38 from kallithea.lib import diffs
39 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
39 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
40 NotAnonymous
40 NotAnonymous
41 from kallithea.lib.base import BaseRepoController, render, jsonify
41 from kallithea.lib.base import BaseRepoController, render, jsonify
42 from kallithea.lib.diffs import LimitedDiffContainer
42 from kallithea.lib.diffs import LimitedDiffContainer
43 from kallithea.lib.page import Page
43 from kallithea.lib.page import Page
44 from kallithea.lib.utils import action_logger
44 from kallithea.lib.utils import action_logger
45 from kallithea.lib.vcs.exceptions import EmptyRepositoryError, ChangesetDoesNotExistError
45 from kallithea.lib.vcs.exceptions import EmptyRepositoryError, ChangesetDoesNotExistError
46 from kallithea.lib.vcs.utils import safe_str
46 from kallithea.lib.vcs.utils import safe_str
47 from kallithea.lib.vcs.utils.hgcompat import unionrepo
47 from kallithea.lib.vcs.utils.hgcompat import unionrepo
48 from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment, \
48 from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment, \
49 PullRequestReviewer, Repository, User
49 PullRequestReviewer, Repository, User
50 from kallithea.model.pull_request import CreatePullRequestAction, CreatePullRequestIterationAction, PullRequestModel
50 from kallithea.model.pull_request import CreatePullRequestAction, CreatePullRequestIterationAction, PullRequestModel
51 from kallithea.model.meta import Session
51 from kallithea.model.meta import Session
52 from kallithea.model.repo import RepoModel
52 from kallithea.model.repo import RepoModel
53 from kallithea.model.comment import ChangesetCommentsModel
53 from kallithea.model.comment import ChangesetCommentsModel
54 from kallithea.model.changeset_status import ChangesetStatusModel
54 from kallithea.model.changeset_status import ChangesetStatusModel
55 from kallithea.model.forms import PullRequestForm, PullRequestPostForm
55 from kallithea.model.forms import PullRequestForm, PullRequestPostForm
56 from kallithea.lib.utils2 import safe_int
56 from kallithea.lib.utils2 import safe_int
57 from kallithea.controllers.changeset import _ignorews_url, _context_url, \
57 from kallithea.controllers.changeset import _ignorews_url, _context_url, \
58 create_comment
58 create_comment
59 from kallithea.controllers.compare import CompareController
59 from kallithea.controllers.compare import CompareController
60 from kallithea.lib.graphmod import graph_data
60 from kallithea.lib.graphmod import graph_data
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 def _get_reviewer(user_id):
65 def _get_reviewer(user_id):
66 """Look up user by ID and validate it as a potential reviewer."""
66 """Look up user by ID and validate it as a potential reviewer."""
67 try:
67 try:
68 user = User.get(int(user_id))
68 user = User.get(int(user_id))
69 except ValueError:
69 except ValueError:
70 user = None
70 user = None
71
71
72 if user is None or user.is_default_user:
72 if user is None or user.is_default_user:
73 h.flash(_('Invalid reviewer "%s" specified') % user_id, category='error')
73 h.flash(_('Invalid reviewer "%s" specified') % user_id, category='error')
74 raise HTTPBadRequest()
74 raise HTTPBadRequest()
75
75
76 return user
76 return user
77
77
78
78
79 class PullrequestsController(BaseRepoController):
79 class PullrequestsController(BaseRepoController):
80
80
81 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
81 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
82 """return a structure with repo's interesting changesets, suitable for
82 """return a structure with repo's interesting changesets, suitable for
83 the selectors in pullrequest.html
83 the selectors in pullrequest.html
84
84
85 rev: a revision that must be in the list somehow and selected by default
85 rev: a revision that must be in the list somehow and selected by default
86 branch: a branch that must be in the list and selected by default - even if closed
86 branch: a branch that must be in the list and selected by default - even if closed
87 branch_rev: a revision of which peers should be preferred and available."""
87 branch_rev: a revision of which peers should be preferred and available."""
88 # list named branches that has been merged to this named branch - it should probably merge back
88 # list named branches that has been merged to this named branch - it should probably merge back
89 peers = []
89 peers = []
90
90
91 if rev:
91 if rev:
92 rev = safe_str(rev)
92 rev = safe_str(rev)
93
93
94 if branch:
94 if branch:
95 branch = safe_str(branch)
95 branch = safe_str(branch)
96
96
97 if branch_rev:
97 if branch_rev:
98 branch_rev = safe_str(branch_rev)
98 branch_rev = safe_str(branch_rev)
99 # a revset not restricting to merge() would be better
99 # a revset not restricting to merge() would be better
100 # (especially because it would get the branch point)
100 # (especially because it would get the branch point)
101 # ... but is currently too expensive
101 # ... but is currently too expensive
102 # including branches of children could be nice too
102 # including branches of children could be nice too
103 peerbranches = set()
103 peerbranches = set()
104 for i in repo._repo.revs(
104 for i in repo._repo.revs(
105 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
105 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
106 branch_rev, branch_rev):
106 branch_rev, branch_rev):
107 abranch = repo.get_changeset(i).branch
107 abranch = repo.get_changeset(i).branch
108 if abranch not in peerbranches:
108 if abranch not in peerbranches:
109 n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
109 n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
110 peers.append((n, abranch))
110 peers.append((n, abranch))
111 peerbranches.add(abranch)
111 peerbranches.add(abranch)
112
112
113 selected = None
113 selected = None
114 tiprev = repo.tags.get('tip')
114 tiprev = repo.tags.get('tip')
115 tipbranch = None
115 tipbranch = None
116
116
117 branches = []
117 branches = []
118 for abranch, branchrev in repo.branches.iteritems():
118 for abranch, branchrev in repo.branches.iteritems():
119 n = 'branch:%s:%s' % (abranch, branchrev)
119 n = 'branch:%s:%s' % (abranch, branchrev)
120 desc = abranch
120 desc = abranch
121 if branchrev == tiprev:
121 if branchrev == tiprev:
122 tipbranch = abranch
122 tipbranch = abranch
123 desc = '%s (current tip)' % desc
123 desc = '%s (current tip)' % desc
124 branches.append((n, desc))
124 branches.append((n, desc))
125 if rev == branchrev:
125 if rev == branchrev:
126 selected = n
126 selected = n
127 if branch == abranch:
127 if branch == abranch:
128 if not rev:
128 if not rev:
129 selected = n
129 selected = n
130 branch = None
130 branch = None
131 if branch: # branch not in list - it is probably closed
131 if branch: # branch not in list - it is probably closed
132 branchrev = repo.closed_branches.get(branch)
132 branchrev = repo.closed_branches.get(branch)
133 if branchrev:
133 if branchrev:
134 n = 'branch:%s:%s' % (branch, branchrev)
134 n = 'branch:%s:%s' % (branch, branchrev)
135 branches.append((n, _('%s (closed)') % branch))
135 branches.append((n, _('%s (closed)') % branch))
136 selected = n
136 selected = n
137 branch = None
137 branch = None
138 if branch:
138 if branch:
139 log.debug('branch %r not found in %s', branch, repo)
139 log.debug('branch %r not found in %s', branch, repo)
140
140
141 bookmarks = []
141 bookmarks = []
142 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
142 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
143 n = 'book:%s:%s' % (bookmark, bookmarkrev)
143 n = 'book:%s:%s' % (bookmark, bookmarkrev)
144 bookmarks.append((n, bookmark))
144 bookmarks.append((n, bookmark))
145 if rev == bookmarkrev:
145 if rev == bookmarkrev:
146 selected = n
146 selected = n
147
147
148 tags = []
148 tags = []
149 for tag, tagrev in repo.tags.iteritems():
149 for tag, tagrev in repo.tags.iteritems():
150 if tag == 'tip':
150 if tag == 'tip':
151 continue
151 continue
152 n = 'tag:%s:%s' % (tag, tagrev)
152 n = 'tag:%s:%s' % (tag, tagrev)
153 tags.append((n, tag))
153 tags.append((n, tag))
154 # note: even if rev == tagrev, don't select the static tag - it must be chosen explicitly
154 # note: even if rev == tagrev, don't select the static tag - it must be chosen explicitly
155
155
156 # prio 1: rev was selected as existing entry above
156 # prio 1: rev was selected as existing entry above
157
157
158 # prio 2: create special entry for rev; rev _must_ be used
158 # prio 2: create special entry for rev; rev _must_ be used
159 specials = []
159 specials = []
160 if rev and selected is None:
160 if rev and selected is None:
161 selected = 'rev:%s:%s' % (rev, rev)
161 selected = 'rev:%s:%s' % (rev, rev)
162 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
162 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
163
163
164 # prio 3: most recent peer branch
164 # prio 3: most recent peer branch
165 if peers and not selected:
165 if peers and not selected:
166 selected = peers[0][0]
166 selected = peers[0][0]
167
167
168 # prio 4: tip revision
168 # prio 4: tip revision
169 if not selected:
169 if not selected:
170 if h.is_hg(repo):
170 if h.is_hg(repo):
171 if tipbranch:
171 if tipbranch:
172 selected = 'branch:%s:%s' % (tipbranch, tiprev)
172 selected = 'branch:%s:%s' % (tipbranch, tiprev)
173 else:
173 else:
174 selected = 'tag:null:' + repo.EMPTY_CHANGESET
174 selected = 'tag:null:' + repo.EMPTY_CHANGESET
175 tags.append((selected, 'null'))
175 tags.append((selected, 'null'))
176 else:
176 else:
177 if 'master' in repo.branches:
177 if 'master' in repo.branches:
178 selected = 'branch:master:%s' % repo.branches['master']
178 selected = 'branch:master:%s' % repo.branches['master']
179 else:
179 else:
180 k, v = repo.branches.items()[0]
180 k, v = repo.branches.items()[0]
181 selected = 'branch:%s:%s' % (k, v)
181 selected = 'branch:%s:%s' % (k, v)
182
182
183 groups = [(specials, _("Special")),
183 groups = [(specials, _("Special")),
184 (peers, _("Peer branches")),
184 (peers, _("Peer branches")),
185 (bookmarks, _("Bookmarks")),
185 (bookmarks, _("Bookmarks")),
186 (branches, _("Branches")),
186 (branches, _("Branches")),
187 (tags, _("Tags")),
187 (tags, _("Tags")),
188 ]
188 ]
189 return [g for g in groups if g[0]], selected
189 return [g for g in groups if g[0]], selected
190
190
191 def _get_is_allowed_change_status(self, pull_request):
191 def _get_is_allowed_change_status(self, pull_request):
192 if pull_request.is_closed():
192 if pull_request.is_closed():
193 return False
193 return False
194
194
195 owner = request.authuser.user_id == pull_request.owner_id
195 owner = request.authuser.user_id == pull_request.owner_id
196 reviewer = PullRequestReviewer.query() \
196 reviewer = PullRequestReviewer.query() \
197 .filter(PullRequestReviewer.pull_request == pull_request) \
197 .filter(PullRequestReviewer.pull_request == pull_request) \
198 .filter(PullRequestReviewer.user_id == request.authuser.user_id) \
198 .filter(PullRequestReviewer.user_id == request.authuser.user_id) \
199 .count() != 0
199 .count() != 0
200
200
201 return request.authuser.admin or owner or reviewer
201 return request.authuser.admin or owner or reviewer
202
202
203 @LoginRequired()
203 @LoginRequired()
204 @HasRepoPermissionLevelDecorator('read')
204 @HasRepoPermissionLevelDecorator('read')
205 def show_all(self, repo_name):
205 def show_all(self, repo_name):
206 c.from_ = request.GET.get('from_') or ''
206 c.from_ = request.GET.get('from_') or ''
207 c.closed = request.GET.get('closed') or ''
207 c.closed = request.GET.get('closed') or ''
208 p = safe_int(request.GET.get('page'), 1)
208 p = safe_int(request.GET.get('page'), 1)
209
209
210 q = PullRequest.query(include_closed=c.closed, sorted=True)
210 q = PullRequest.query(include_closed=c.closed, sorted=True)
211 if c.from_:
211 if c.from_:
212 q = q.filter_by(org_repo=c.db_repo)
212 q = q.filter_by(org_repo=c.db_repo)
213 else:
213 else:
214 q = q.filter_by(other_repo=c.db_repo)
214 q = q.filter_by(other_repo=c.db_repo)
215 c.pull_requests = q.all()
215 c.pull_requests = q.all()
216
216
217 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100)
217 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100)
218
218
219 return render('/pullrequests/pullrequest_show_all.html')
219 return render('/pullrequests/pullrequest_show_all.html')
220
220
221 @LoginRequired()
221 @LoginRequired()
222 @NotAnonymous()
222 @NotAnonymous()
223 def show_my(self):
223 def show_my(self):
224 c.closed = request.GET.get('closed') or ''
224 c.closed = request.GET.get('closed') or ''
225
225
226 c.my_pull_requests = PullRequest.query(
226 c.my_pull_requests = PullRequest.query(
227 include_closed=c.closed,
227 include_closed=c.closed,
228 sorted=True,
228 sorted=True,
229 ).filter_by(owner_id=request.authuser.user_id).all()
229 ).filter_by(owner_id=request.authuser.user_id).all()
230
230
231 c.participate_in_pull_requests = []
231 c.participate_in_pull_requests = []
232 c.participate_in_pull_requests_todo = []
232 c.participate_in_pull_requests_todo = []
233 done_status = set([ChangesetStatus.STATUS_APPROVED, ChangesetStatus.STATUS_REJECTED])
233 done_status = set([ChangesetStatus.STATUS_APPROVED, ChangesetStatus.STATUS_REJECTED])
234 for pr in PullRequest.query(
234 for pr in PullRequest.query(
235 include_closed=c.closed,
235 include_closed=c.closed,
236 reviewer_id=request.authuser.user_id,
236 reviewer_id=request.authuser.user_id,
237 sorted=True,
237 sorted=True,
238 ):
238 ):
239 status = pr.user_review_status(request.authuser.user_id) # very inefficient!!!
239 status = pr.user_review_status(request.authuser.user_id) # very inefficient!!!
240 if status in done_status:
240 if status in done_status:
241 c.participate_in_pull_requests.append(pr)
241 c.participate_in_pull_requests.append(pr)
242 else:
242 else:
243 c.participate_in_pull_requests_todo.append(pr)
243 c.participate_in_pull_requests_todo.append(pr)
244
244
245 return render('/pullrequests/pullrequest_show_my.html')
245 return render('/pullrequests/pullrequest_show_my.html')
246
246
247 @LoginRequired()
247 @LoginRequired()
248 @NotAnonymous()
248 @NotAnonymous()
249 @HasRepoPermissionLevelDecorator('read')
249 @HasRepoPermissionLevelDecorator('read')
250 def index(self):
250 def index(self):
251 org_repo = c.db_repo
251 org_repo = c.db_repo
252 org_scm_instance = org_repo.scm_instance
252 org_scm_instance = org_repo.scm_instance
253 try:
253 try:
254 org_scm_instance.get_changeset()
254 org_scm_instance.get_changeset()
255 except EmptyRepositoryError as e:
255 except EmptyRepositoryError as e:
256 h.flash(h.literal(_('There are no changesets yet')),
256 h.flash(h.literal(_('There are no changesets yet')),
257 category='warning')
257 category='warning')
258 raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name))
258 raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name))
259
259
260 org_rev = request.GET.get('rev_end')
260 org_rev = request.GET.get('rev_end')
261 # rev_start is not directly useful - its parent could however be used
261 # rev_start is not directly useful - its parent could however be used
262 # as default for other and thus give a simple compare view
262 # as default for other and thus give a simple compare view
263 rev_start = request.GET.get('rev_start')
263 rev_start = request.GET.get('rev_start')
264 other_rev = None
264 other_rev = None
265 if rev_start:
265 if rev_start:
266 starters = org_repo.get_changeset(rev_start).parents
266 starters = org_repo.get_changeset(rev_start).parents
267 if starters:
267 if starters:
268 other_rev = starters[0].raw_id
268 other_rev = starters[0].raw_id
269 else:
269 else:
270 other_rev = org_repo.scm_instance.EMPTY_CHANGESET
270 other_rev = org_repo.scm_instance.EMPTY_CHANGESET
271 branch = request.GET.get('branch')
271 branch = request.GET.get('branch')
272
272
273 c.cs_repos = [(org_repo.repo_name, org_repo.repo_name)]
273 c.cs_repos = [(org_repo.repo_name, org_repo.repo_name)]
274 c.default_cs_repo = org_repo.repo_name
274 c.default_cs_repo = org_repo.repo_name
275 c.cs_refs, c.default_cs_ref = self._get_repo_refs(org_scm_instance, rev=org_rev, branch=branch)
275 c.cs_refs, c.default_cs_ref = self._get_repo_refs(org_scm_instance, rev=org_rev, branch=branch)
276
276
277 default_cs_ref_type, default_cs_branch, default_cs_rev = c.default_cs_ref.split(':')
277 default_cs_ref_type, default_cs_branch, default_cs_rev = c.default_cs_ref.split(':')
278 if default_cs_ref_type != 'branch':
278 if default_cs_ref_type != 'branch':
279 default_cs_branch = org_repo.get_changeset(default_cs_rev).branch
279 default_cs_branch = org_repo.get_changeset(default_cs_rev).branch
280
280
281 # add org repo to other so we can open pull request against peer branches on itself
281 # add org repo to other so we can open pull request against peer branches on itself
282 c.a_repos = [(org_repo.repo_name, '%s (self)' % org_repo.repo_name)]
282 c.a_repos = [(org_repo.repo_name, '%s (self)' % org_repo.repo_name)]
283
283
284 if org_repo.parent:
284 if org_repo.parent:
285 # add parent of this fork also and select it.
285 # add parent of this fork also and select it.
286 # use the same branch on destination as on source, if available.
286 # use the same branch on destination as on source, if available.
287 c.a_repos.append((org_repo.parent.repo_name, '%s (parent)' % org_repo.parent.repo_name))
287 c.a_repos.append((org_repo.parent.repo_name, '%s (parent)' % org_repo.parent.repo_name))
288 c.a_repo = org_repo.parent
288 c.a_repo = org_repo.parent
289 c.a_refs, c.default_a_ref = self._get_repo_refs(
289 c.a_refs, c.default_a_ref = self._get_repo_refs(
290 org_repo.parent.scm_instance, branch=default_cs_branch, rev=other_rev)
290 org_repo.parent.scm_instance, branch=default_cs_branch, rev=other_rev)
291
291
292 else:
292 else:
293 c.a_repo = org_repo
293 c.a_repo = org_repo
294 c.a_refs, c.default_a_ref = self._get_repo_refs(org_scm_instance, rev=other_rev)
294 c.a_refs, c.default_a_ref = self._get_repo_refs(org_scm_instance, rev=other_rev)
295
295
296 # gather forks and add to this list ... even though it is rare to
296 # gather forks and add to this list ... even though it is rare to
297 # request forks to pull from their parent
297 # request forks to pull from their parent
298 for fork in org_repo.forks:
298 for fork in org_repo.forks:
299 c.a_repos.append((fork.repo_name, fork.repo_name))
299 c.a_repos.append((fork.repo_name, fork.repo_name))
300
300
301 return render('/pullrequests/pullrequest.html')
301 return render('/pullrequests/pullrequest.html')
302
302
303 @LoginRequired()
303 @LoginRequired()
304 @NotAnonymous()
304 @NotAnonymous()
305 @HasRepoPermissionLevelDecorator('read')
305 @HasRepoPermissionLevelDecorator('read')
306 @jsonify
306 @jsonify
307 def repo_info(self, repo_name):
307 def repo_info(self, repo_name):
308 repo = c.db_repo
308 repo = c.db_repo
309 refs, selected_ref = self._get_repo_refs(repo.scm_instance)
309 refs, selected_ref = self._get_repo_refs(repo.scm_instance)
310 return {
310 return {
311 'description': repo.description.split('\n', 1)[0],
311 'description': repo.description.split('\n', 1)[0],
312 'selected_ref': selected_ref,
312 'selected_ref': selected_ref,
313 'refs': refs,
313 'refs': refs,
314 }
314 }
315
315
316 @LoginRequired()
316 @LoginRequired()
317 @NotAnonymous()
317 @NotAnonymous()
318 @HasRepoPermissionLevelDecorator('read')
318 @HasRepoPermissionLevelDecorator('read')
319 def create(self, repo_name):
319 def create(self, repo_name):
320 repo = c.db_repo
320 repo = c.db_repo
321 try:
321 try:
322 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
322 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
323 except formencode.Invalid as errors:
323 except formencode.Invalid as errors:
324 log.error(traceback.format_exc())
324 log.error(traceback.format_exc())
325 log.error(str(errors))
325 log.error(str(errors))
326 msg = _('Error creating pull request: %s') % errors.msg
326 msg = _('Error creating pull request: %s') % errors.msg
327 h.flash(msg, 'error')
327 h.flash(msg, 'error')
328 raise HTTPBadRequest
328 raise HTTPBadRequest
329
329
330 # heads up: org and other might seem backward here ...
330 # heads up: org and other might seem backward here ...
331 org_ref = _form['org_ref'] # will have merge_rev as rev but symbolic name
331 org_ref = _form['org_ref'] # will have merge_rev as rev but symbolic name
332 org_repo = Repository.guess_instance(_form['org_repo'])
332 org_repo = Repository.guess_instance(_form['org_repo'])
333
333
334 other_ref = _form['other_ref'] # will have symbolic name and head revision
334 other_ref = _form['other_ref'] # will have symbolic name and head revision
335 other_repo = Repository.guess_instance(_form['other_repo'])
335 other_repo = Repository.guess_instance(_form['other_repo'])
336
336
337 reviewers = []
337 reviewers = []
338
338
339 title = _form['pullrequest_title']
339 title = _form['pullrequest_title']
340 description = _form['pullrequest_desc'].strip()
340 description = _form['pullrequest_desc'].strip()
341 owner = User.get(request.authuser.user_id)
341 owner = User.get(request.authuser.user_id)
342
342
343 try:
343 try:
344 cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
344 cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
345 except CreatePullRequestAction.ValidationError as e:
345 except CreatePullRequestAction.ValidationError as e:
346 h.flash(str(e), category='error', logf=log.error)
346 h.flash(str(e), category='error', logf=log.error)
347 raise HTTPNotFound
347 raise HTTPNotFound
348
348
349 try:
349 try:
350 pull_request = cmd.execute()
350 pull_request = cmd.execute()
351 Session().commit()
351 Session().commit()
352 except Exception:
352 except Exception:
353 h.flash(_('Error occurred while creating pull request'),
353 h.flash(_('Error occurred while creating pull request'),
354 category='error')
354 category='error')
355 log.error(traceback.format_exc())
355 log.error(traceback.format_exc())
356 raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name))
356 raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name))
357
357
358 h.flash(_('Successfully opened new pull request'),
358 h.flash(_('Successfully opened new pull request'),
359 category='success')
359 category='success')
360 raise HTTPFound(location=pull_request.url())
360 raise HTTPFound(location=pull_request.url())
361
361
362 def create_new_iteration(self, old_pull_request, new_rev, title, description, reviewers):
362 def create_new_iteration(self, old_pull_request, new_rev, title, description, reviewers):
363 owner = User.get(request.authuser.user_id)
363 owner = User.get(request.authuser.user_id)
364 new_org_rev = self._get_ref_rev(old_pull_request.org_repo, 'rev', new_rev)
364 new_org_rev = self._get_ref_rev(old_pull_request.org_repo, 'rev', new_rev)
365 new_other_rev = self._get_ref_rev(old_pull_request.other_repo, old_pull_request.other_ref_parts[0], old_pull_request.other_ref_parts[1])
365 new_other_rev = self._get_ref_rev(old_pull_request.other_repo, old_pull_request.other_ref_parts[0], old_pull_request.other_ref_parts[1])
366 try:
366 try:
367 cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
367 cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
368 except CreatePullRequestAction.ValidationError as e:
368 except CreatePullRequestAction.ValidationError as e:
369 h.flash(str(e), category='error', logf=log.error)
369 h.flash(str(e), category='error', logf=log.error)
370 raise HTTPNotFound
370 raise HTTPNotFound
371
371
372 try:
372 try:
373 pull_request = cmd.execute()
373 pull_request = cmd.execute()
374 Session().commit()
374 Session().commit()
375 except Exception:
375 except Exception:
376 h.flash(_('Error occurred while creating pull request'),
376 h.flash(_('Error occurred while creating pull request'),
377 category='error')
377 category='error')
378 log.error(traceback.format_exc())
378 log.error(traceback.format_exc())
379 raise HTTPFound(location=old_pull_request.url())
379 raise HTTPFound(location=old_pull_request.url())
380
380
381 h.flash(_('New pull request iteration created'),
381 h.flash(_('New pull request iteration created'),
382 category='success')
382 category='success')
383 raise HTTPFound(location=pull_request.url())
383 raise HTTPFound(location=pull_request.url())
384
384
385 # pullrequest_post for PR editing
385 # pullrequest_post for PR editing
386 @LoginRequired()
386 @LoginRequired()
387 @NotAnonymous()
387 @NotAnonymous()
388 @HasRepoPermissionLevelDecorator('read')
388 @HasRepoPermissionLevelDecorator('read')
389 def post(self, repo_name, pull_request_id):
389 def post(self, repo_name, pull_request_id):
390 pull_request = PullRequest.get_or_404(pull_request_id)
390 pull_request = PullRequest.get_or_404(pull_request_id)
391 if pull_request.is_closed():
391 if pull_request.is_closed():
392 raise HTTPForbidden()
392 raise HTTPForbidden()
393 assert pull_request.other_repo.repo_name == repo_name
393 assert pull_request.other_repo.repo_name == repo_name
394 #only owner or admin can update it
394 #only owner or admin can update it
395 owner = pull_request.owner_id == request.authuser.user_id
395 owner = pull_request.owner_id == request.authuser.user_id
396 repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
396 repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
397 if not (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
397 if not (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
398 raise HTTPForbidden()
398 raise HTTPForbidden()
399
399
400 _form = PullRequestPostForm()().to_python(request.POST)
400 _form = PullRequestPostForm()().to_python(request.POST)
401
401
402 cur_reviewers = set(pull_request.get_reviewer_users())
402 cur_reviewers = set(pull_request.get_reviewer_users())
403 new_reviewers = set(_get_reviewer(s) for s in _form['review_members'])
403 new_reviewers = set(_get_reviewer(s) for s in _form['review_members'])
404 old_reviewers = set(_get_reviewer(s) for s in _form['org_review_members'])
404 old_reviewers = set(_get_reviewer(s) for s in _form['org_review_members'])
405
405
406 other_added = cur_reviewers - old_reviewers
406 other_added = cur_reviewers - old_reviewers
407 other_removed = old_reviewers - cur_reviewers
407 other_removed = old_reviewers - cur_reviewers
408
408
409 if other_added:
409 if other_added:
410 h.flash(_('Meanwhile, the following reviewers have been added: %s') %
410 h.flash(_('Meanwhile, the following reviewers have been added: %s') %
411 (', '.join(u.username for u in other_added)),
411 (', '.join(u.username for u in other_added)),
412 category='warning')
412 category='warning')
413 if other_removed:
413 if other_removed:
414 h.flash(_('Meanwhile, the following reviewers have been removed: %s') %
414 h.flash(_('Meanwhile, the following reviewers have been removed: %s') %
415 (', '.join(u.username for u in other_removed)),
415 (', '.join(u.username for u in other_removed)),
416 category='warning')
416 category='warning')
417
417
418 if _form['updaterev']:
418 if _form['updaterev']:
419 return self.create_new_iteration(pull_request,
419 return self.create_new_iteration(pull_request,
420 _form['updaterev'],
420 _form['updaterev'],
421 _form['pullrequest_title'],
421 _form['pullrequest_title'],
422 _form['pullrequest_desc'],
422 _form['pullrequest_desc'],
423 new_reviewers)
423 new_reviewers)
424
424
425 added_reviewers = new_reviewers - old_reviewers - cur_reviewers
425 added_reviewers = new_reviewers - old_reviewers - cur_reviewers
426 removed_reviewers = (old_reviewers - new_reviewers) & cur_reviewers
426 removed_reviewers = (old_reviewers - new_reviewers) & cur_reviewers
427
427
428 old_description = pull_request.description
428 old_description = pull_request.description
429 pull_request.title = _form['pullrequest_title']
429 pull_request.title = _form['pullrequest_title']
430 pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
430 pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
431 pull_request.owner = User.get_by_username(_form['owner'])
431 pull_request.owner = User.get_by_username(_form['owner'])
432 user = User.get(request.authuser.user_id)
432 user = User.get(request.authuser.user_id)
433
433
434 PullRequestModel().mention_from_description(user, pull_request, old_description)
434 PullRequestModel().mention_from_description(user, pull_request, old_description)
435 PullRequestModel().add_reviewers(user, pull_request, added_reviewers)
435 PullRequestModel().add_reviewers(user, pull_request, added_reviewers)
436 PullRequestModel().remove_reviewers(user, pull_request, removed_reviewers)
436 PullRequestModel().remove_reviewers(user, pull_request, removed_reviewers)
437
437
438 Session().commit()
438 Session().commit()
439 h.flash(_('Pull request updated'), category='success')
439 h.flash(_('Pull request updated'), category='success')
440
440
441 raise HTTPFound(location=pull_request.url())
441 raise HTTPFound(location=pull_request.url())
442
442
443 @LoginRequired()
443 @LoginRequired()
444 @NotAnonymous()
444 @NotAnonymous()
445 @HasRepoPermissionLevelDecorator('read')
445 @HasRepoPermissionLevelDecorator('read')
446 @jsonify
446 @jsonify
447 def delete(self, repo_name, pull_request_id):
447 def delete(self, repo_name, pull_request_id):
448 pull_request = PullRequest.get_or_404(pull_request_id)
448 pull_request = PullRequest.get_or_404(pull_request_id)
449 #only owner can delete it !
449 #only owner can delete it !
450 if pull_request.owner_id == request.authuser.user_id:
450 if pull_request.owner_id == request.authuser.user_id:
451 PullRequestModel().delete(pull_request)
451 PullRequestModel().delete(pull_request)
452 Session().commit()
452 Session().commit()
453 h.flash(_('Successfully deleted pull request'),
453 h.flash(_('Successfully deleted pull request'),
454 category='success')
454 category='success')
455 raise HTTPFound(location=url('my_pullrequests'))
455 raise HTTPFound(location=url('my_pullrequests'))
456 raise HTTPForbidden()
456 raise HTTPForbidden()
457
457
458 @LoginRequired()
458 @LoginRequired()
459 @HasRepoPermissionLevelDecorator('read')
459 @HasRepoPermissionLevelDecorator('read')
460 def show(self, repo_name, pull_request_id, extra=None):
460 def show(self, repo_name, pull_request_id, extra=None):
461 repo_model = RepoModel()
461 repo_model = RepoModel()
462 c.users_array = repo_model.get_users_js()
462 c.users_array = repo_model.get_users_js()
463 c.user_groups_array = repo_model.get_user_groups_js()
463 c.user_groups_array = repo_model.get_user_groups_js()
464 c.pull_request = PullRequest.get_or_404(pull_request_id)
464 c.pull_request = PullRequest.get_or_404(pull_request_id)
465 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
465 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
466 cc_model = ChangesetCommentsModel()
466 cc_model = ChangesetCommentsModel()
467 cs_model = ChangesetStatusModel()
467 cs_model = ChangesetStatusModel()
468
468
469 # pull_requests repo_name we opened it against
469 # pull_requests repo_name we opened it against
470 # ie. other_repo must match
470 # ie. other_repo must match
471 if repo_name != c.pull_request.other_repo.repo_name:
471 if repo_name != c.pull_request.other_repo.repo_name:
472 raise HTTPNotFound
472 raise HTTPNotFound
473
473
474 # load compare data into template context
474 # load compare data into template context
475 c.cs_repo = c.pull_request.org_repo
475 c.cs_repo = c.pull_request.org_repo
476 (c.cs_ref_type,
476 (c.cs_ref_type,
477 c.cs_ref_name,
477 c.cs_ref_name,
478 c.cs_rev) = c.pull_request.org_ref.split(':')
478 c.cs_rev) = c.pull_request.org_ref.split(':')
479
479
480 c.a_repo = c.pull_request.other_repo
480 c.a_repo = c.pull_request.other_repo
481 (c.a_ref_type,
481 (c.a_ref_type,
482 c.a_ref_name,
482 c.a_ref_name,
483 c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor
483 c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor
484
484
485 org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
485 org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
486 try:
486 try:
487 c.cs_ranges = [org_scm_instance.get_changeset(x)
487 c.cs_ranges = [org_scm_instance.get_changeset(x)
488 for x in c.pull_request.revisions]
488 for x in c.pull_request.revisions]
489 except ChangesetDoesNotExistError:
489 except ChangesetDoesNotExistError:
490 c.cs_ranges = []
490 c.cs_ranges = []
491 h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
491 h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
492 'error')
492 'error')
493 c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
493 c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
494 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
494 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
495 c.jsdata = graph_data(org_scm_instance, revs)
495 c.jsdata = graph_data(org_scm_instance, revs)
496
496
497 c.is_range = False
497 c.is_range = False
498 try:
498 try:
499 if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
499 if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
500 cs_a = org_scm_instance.get_changeset(c.a_rev)
500 cs_a = org_scm_instance.get_changeset(c.a_rev)
501 root_parents = c.cs_ranges[0].parents
501 root_parents = c.cs_ranges[0].parents
502 c.is_range = cs_a in root_parents
502 c.is_range = cs_a in root_parents
503 #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
503 #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
504 except ChangesetDoesNotExistError: # probably because c.a_rev not found
504 except ChangesetDoesNotExistError: # probably because c.a_rev not found
505 pass
505 pass
506 except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing
506 except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing
507 pass
507 pass
508
508
509 avail_revs = set()
509 avail_revs = set()
510 avail_show = []
510 avail_show = []
511 c.cs_branch_name = c.cs_ref_name
511 c.cs_branch_name = c.cs_ref_name
512 c.a_branch_name = None
512 c.a_branch_name = None
513 other_scm_instance = c.a_repo.scm_instance
513 other_scm_instance = c.a_repo.scm_instance
514 c.update_msg = ""
514 c.update_msg = ""
515 c.update_msg_other = ""
515 c.update_msg_other = ""
516 try:
516 try:
517 if not c.cs_ranges:
517 if not c.cs_ranges:
518 c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev
518 c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev
519 elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
519 elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
520 if c.cs_ref_type != 'branch':
520 if c.cs_ref_type != 'branch':
521 c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
521 c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
522 c.a_branch_name = c.a_ref_name
522 c.a_branch_name = c.a_ref_name
523 if c.a_ref_type != 'branch':
523 if c.a_ref_type != 'branch':
524 try:
524 try:
525 c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
525 c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
526 except EmptyRepositoryError:
526 except EmptyRepositoryError:
527 c.a_branch_name = 'null' # not a branch name ... but close enough
527 c.a_branch_name = 'null' # not a branch name ... but close enough
528 # candidates: descendants of old head that are on the right branch
528 # candidates: descendants of old head that are on the right branch
529 # and not are the old head itself ...
529 # and not are the old head itself ...
530 # and nothing at all if old head is a descendant of target ref name
530 # and nothing at all if old head is a descendant of target ref name
531 if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
531 if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
532 c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
532 c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
533 elif c.pull_request.is_closed():
533 elif c.pull_request.is_closed():
534 c.update_msg = _('This pull request has been closed and can not be updated.')
534 c.update_msg = _('This pull request has been closed and can not be updated.')
535 else: # look for descendants of PR head on source branch in org repo
535 else: # look for descendants of PR head on source branch in org repo
536 avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
536 avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
537 revs[0], c.cs_branch_name)
537 revs[0], c.cs_branch_name)
538 if len(avail_revs) > 1: # more than just revs[0]
538 if len(avail_revs) > 1: # more than just revs[0]
539 # also show changesets that not are descendants but would be merged in
539 # also show changesets that not are descendants but would be merged in
540 targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
540 targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
541 if org_scm_instance.path != other_scm_instance.path:
541 if org_scm_instance.path != other_scm_instance.path:
542 # Note: org_scm_instance.path must come first so all
542 # Note: org_scm_instance.path must come first so all
543 # valid revision numbers are 100% org_scm compatible
543 # valid revision numbers are 100% org_scm compatible
544 # - both for avail_revs and for revset results
544 # - both for avail_revs and for revset results
545 hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
545 hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
546 org_scm_instance.path,
546 org_scm_instance.path,
547 other_scm_instance.path)
547 other_scm_instance.path)
548 else:
548 else:
549 hgrepo = org_scm_instance._repo
549 hgrepo = org_scm_instance._repo
550 show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
550 show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
551 avail_revs, revs[0], targethead))
551 avail_revs, revs[0], targethead))
552 c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name
552 c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name
553 else:
553 else:
554 show = set()
554 show = set()
555 avail_revs = set() # drop revs[0]
555 avail_revs = set() # drop revs[0]
556 c.update_msg = _('No additional changesets found for iterating on this pull request.')
556 c.update_msg = _('No additional changesets found for iterating on this pull request.')
557
557
558 # TODO: handle branch heads that not are tip-most
558 # TODO: handle branch heads that not are tip-most
559 brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
559 brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
560 if brevs:
560 if brevs:
561 # also show changesets that are on branch but neither ancestors nor descendants
561 # also show changesets that are on branch but neither ancestors nor descendants
562 show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
562 show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
563 show.add(revs[0]) # make sure graph shows this so we can see how they relate
563 show.add(revs[0]) # make sure graph shows this so we can see how they relate
564 c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
564 c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
565 h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))
565 h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))
566
566
567 avail_show = sorted(show, reverse=True)
567 avail_show = sorted(show, reverse=True)
568
568
569 elif org_scm_instance.alias == 'git':
569 elif org_scm_instance.alias == 'git':
570 c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not
570 c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not
571 c.update_msg = _("Git pull requests don't support iterating yet.")
571 c.update_msg = _("Git pull requests don't support iterating yet.")
572 except ChangesetDoesNotExistError:
572 except ChangesetDoesNotExistError:
573 c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev
573 c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev
574
574
575 c.avail_revs = avail_revs
575 c.avail_revs = avail_revs
576 c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
576 c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
577 c.avail_jsdata = graph_data(org_scm_instance, avail_show)
577 c.avail_jsdata = graph_data(org_scm_instance, avail_show)
578
578
579 raw_ids = [x.raw_id for x in c.cs_ranges]
579 raw_ids = [x.raw_id for x in c.cs_ranges]
580 c.cs_comments = c.cs_repo.get_comments(raw_ids)
580 c.cs_comments = c.cs_repo.get_comments(raw_ids)
581 c.statuses = c.cs_repo.statuses(raw_ids)
581 c.cs_statuses = c.cs_repo.statuses(raw_ids)
582
582
583 ignore_whitespace = request.GET.get('ignorews') == '1'
583 ignore_whitespace = request.GET.get('ignorews') == '1'
584 line_context = safe_int(request.GET.get('context'), 3)
584 line_context = safe_int(request.GET.get('context'), 3)
585 c.ignorews_url = _ignorews_url
585 c.ignorews_url = _ignorews_url
586 c.context_url = _context_url
586 c.context_url = _context_url
587 c.fulldiff = request.GET.get('fulldiff')
587 c.fulldiff = request.GET.get('fulldiff')
588 diff_limit = self.cut_off_limit if not c.fulldiff else None
588 diff_limit = self.cut_off_limit if not c.fulldiff else None
589
589
590 # we swap org/other ref since we run a simple diff on one repo
590 # we swap org/other ref since we run a simple diff on one repo
591 log.debug('running diff between %s and %s in %s',
591 log.debug('running diff between %s and %s in %s',
592 c.a_rev, c.cs_rev, org_scm_instance.path)
592 c.a_rev, c.cs_rev, org_scm_instance.path)
593 try:
593 try:
594 txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
594 txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
595 ignore_whitespace=ignore_whitespace,
595 ignore_whitespace=ignore_whitespace,
596 context=line_context)
596 context=line_context)
597 except ChangesetDoesNotExistError:
597 except ChangesetDoesNotExistError:
598 txtdiff = _("The diff can't be shown - the PR revisions could not be found.")
598 txtdiff = _("The diff can't be shown - the PR revisions could not be found.")
599 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
599 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
600 diff_limit=diff_limit)
600 diff_limit=diff_limit)
601 _parsed = diff_processor.prepare()
601 _parsed = diff_processor.prepare()
602
602
603 c.limited_diff = False
603 c.limited_diff = False
604 if isinstance(_parsed, LimitedDiffContainer):
604 if isinstance(_parsed, LimitedDiffContainer):
605 c.limited_diff = True
605 c.limited_diff = True
606
606
607 c.file_diff_data = []
607 c.file_diff_data = []
608 c.lines_added = 0
608 c.lines_added = 0
609 c.lines_deleted = 0
609 c.lines_deleted = 0
610
610
611 for f in _parsed:
611 for f in _parsed:
612 st = f['stats']
612 st = f['stats']
613 c.lines_added += st['added']
613 c.lines_added += st['added']
614 c.lines_deleted += st['deleted']
614 c.lines_deleted += st['deleted']
615 filename = f['filename']
615 filename = f['filename']
616 fid = h.FID('', filename)
616 fid = h.FID('', filename)
617 diff = diff_processor.as_html(enable_comments=True,
617 diff = diff_processor.as_html(enable_comments=True,
618 parsed_lines=[f])
618 parsed_lines=[f])
619 c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st))
619 c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st))
620
620
621 # inline comments
621 # inline comments
622 c.inline_cnt = 0
622 c.inline_cnt = 0
623 c.inline_comments = cc_model.get_inline_comments(
623 c.inline_comments = cc_model.get_inline_comments(
624 c.db_repo.repo_id,
624 c.db_repo.repo_id,
625 pull_request=pull_request_id)
625 pull_request=pull_request_id)
626 # count inline comments
626 # count inline comments
627 for __, lines in c.inline_comments:
627 for __, lines in c.inline_comments:
628 for comments in lines.values():
628 for comments in lines.values():
629 c.inline_cnt += len(comments)
629 c.inline_cnt += len(comments)
630 # comments
630 # comments
631 c.comments = cc_model.get_comments(c.db_repo.repo_id,
631 c.comments = cc_model.get_comments(c.db_repo.repo_id, pull_request=pull_request_id)
632 pull_request=pull_request_id)
633
632
634 # (badly named) pull-request status calculation based on reviewer votes
633 # (badly named) pull-request status calculation based on reviewer votes
635 (c.pull_request_reviewers,
634 (c.pull_request_reviewers,
636 c.pull_request_pending_reviewers,
635 c.pull_request_pending_reviewers,
637 c.current_voting_result,
636 c.current_voting_result,
638 ) = cs_model.calculate_pull_request_result(c.pull_request)
637 ) = cs_model.calculate_pull_request_result(c.pull_request)
639 c.changeset_statuses = ChangesetStatus.STATUSES
638 c.changeset_statuses = ChangesetStatus.STATUSES
640
639
641 c.is_ajax_preview = False
640 c.is_ajax_preview = False
642 c.ancestors = None # [c.a_rev] ... but that is shown in an other way
641 c.ancestors = None # [c.a_rev] ... but that is shown in an other way
643 return render('/pullrequests/pullrequest_show.html')
642 return render('/pullrequests/pullrequest_show.html')
644
643
645 @LoginRequired()
644 @LoginRequired()
646 @NotAnonymous()
645 @NotAnonymous()
647 @HasRepoPermissionLevelDecorator('read')
646 @HasRepoPermissionLevelDecorator('read')
648 @jsonify
647 @jsonify
649 def comment(self, repo_name, pull_request_id):
648 def comment(self, repo_name, pull_request_id):
650 pull_request = PullRequest.get_or_404(pull_request_id)
649 pull_request = PullRequest.get_or_404(pull_request_id)
651
650
652 status = request.POST.get('changeset_status')
651 status = request.POST.get('changeset_status')
653 close_pr = request.POST.get('save_close')
652 close_pr = request.POST.get('save_close')
654 delete = request.POST.get('save_delete')
653 delete = request.POST.get('save_delete')
655 f_path = request.POST.get('f_path')
654 f_path = request.POST.get('f_path')
656 line_no = request.POST.get('line')
655 line_no = request.POST.get('line')
657
656
658 if (status or close_pr or delete) and (f_path or line_no):
657 if (status or close_pr or delete) and (f_path or line_no):
659 # status votes and closing is only possible in general comments
658 # status votes and closing is only possible in general comments
660 raise HTTPBadRequest()
659 raise HTTPBadRequest()
661
660
662 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
661 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
663 if not allowed_to_change_status:
662 if not allowed_to_change_status:
664 if status or close_pr:
663 if status or close_pr:
665 h.flash(_('No permission to change pull request status'), 'error')
664 h.flash(_('No permission to change pull request status'), 'error')
666 raise HTTPForbidden()
665 raise HTTPForbidden()
667
666
668 if delete == "delete":
667 if delete == "delete":
669 if (pull_request.owner_id == request.authuser.user_id or
668 if (pull_request.owner_id == request.authuser.user_id or
670 h.HasPermissionAny('hg.admin')() or
669 h.HasPermissionAny('hg.admin')() or
671 h.HasRepoPermissionLevel('admin')(pull_request.org_repo.repo_name) or
670 h.HasRepoPermissionLevel('admin')(pull_request.org_repo.repo_name) or
672 h.HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name)
671 h.HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name)
673 ) and not pull_request.is_closed():
672 ) and not pull_request.is_closed():
674 PullRequestModel().delete(pull_request)
673 PullRequestModel().delete(pull_request)
675 Session().commit()
674 Session().commit()
676 h.flash(_('Successfully deleted pull request %s') % pull_request_id,
675 h.flash(_('Successfully deleted pull request %s') % pull_request_id,
677 category='success')
676 category='success')
678 return {
677 return {
679 'location': url('my_pullrequests'), # or repo pr list?
678 'location': url('my_pullrequests'), # or repo pr list?
680 }
679 }
681 raise HTTPFound(location=url('my_pullrequests')) # or repo pr list?
680 raise HTTPFound(location=url('my_pullrequests')) # or repo pr list?
682 raise HTTPForbidden()
681 raise HTTPForbidden()
683
682
684 text = request.POST.get('text', '').strip()
683 text = request.POST.get('text', '').strip()
685
684
686 comment = create_comment(
685 comment = create_comment(
687 text,
686 text,
688 status,
687 status,
689 pull_request_id=pull_request_id,
688 pull_request_id=pull_request_id,
690 f_path=f_path,
689 f_path=f_path,
691 line_no=line_no,
690 line_no=line_no,
692 closing_pr=close_pr,
691 closing_pr=close_pr,
693 )
692 )
694
693
695 action_logger(request.authuser,
694 action_logger(request.authuser,
696 'user_commented_pull_request:%s' % pull_request_id,
695 'user_commented_pull_request:%s' % pull_request_id,
697 c.db_repo, request.ip_addr)
696 c.db_repo, request.ip_addr)
698
697
699 if status:
698 if status:
700 ChangesetStatusModel().set_status(
699 ChangesetStatusModel().set_status(
701 c.db_repo.repo_id,
700 c.db_repo.repo_id,
702 status,
701 status,
703 request.authuser.user_id,
702 request.authuser.user_id,
704 comment,
703 comment,
705 pull_request=pull_request_id
704 pull_request=pull_request_id
706 )
705 )
707
706
708 if close_pr:
707 if close_pr:
709 PullRequestModel().close_pull_request(pull_request_id)
708 PullRequestModel().close_pull_request(pull_request_id)
710 action_logger(request.authuser,
709 action_logger(request.authuser,
711 'user_closed_pull_request:%s' % pull_request_id,
710 'user_closed_pull_request:%s' % pull_request_id,
712 c.db_repo, request.ip_addr)
711 c.db_repo, request.ip_addr)
713
712
714 Session().commit()
713 Session().commit()
715
714
716 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
715 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
717 raise HTTPFound(location=pull_request.url())
716 raise HTTPFound(location=pull_request.url())
718
717
719 data = {
718 data = {
720 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
719 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
721 }
720 }
722 if comment is not None:
721 if comment is not None:
723 c.comment = comment
722 c.comment = comment
724 data.update(comment.get_dict())
723 data.update(comment.get_dict())
725 data.update({'rendered_text':
724 data.update({'rendered_text':
726 render('changeset/changeset_comment_block.html')})
725 render('changeset/changeset_comment_block.html')})
727
726
728 return data
727 return data
729
728
730 @LoginRequired()
729 @LoginRequired()
731 @NotAnonymous()
730 @NotAnonymous()
732 @HasRepoPermissionLevelDecorator('read')
731 @HasRepoPermissionLevelDecorator('read')
733 @jsonify
732 @jsonify
734 def delete_comment(self, repo_name, comment_id):
733 def delete_comment(self, repo_name, comment_id):
735 co = ChangesetComment.get(comment_id)
734 co = ChangesetComment.get(comment_id)
736 if co.pull_request.is_closed():
735 if co.pull_request.is_closed():
737 #don't allow deleting comments on closed pull request
736 #don't allow deleting comments on closed pull request
738 raise HTTPForbidden()
737 raise HTTPForbidden()
739
738
740 owner = co.author_id == request.authuser.user_id
739 owner = co.author_id == request.authuser.user_id
741 repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
740 repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
742 if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
741 if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
743 ChangesetCommentsModel().delete(comment=co)
742 ChangesetCommentsModel().delete(comment=co)
744 Session().commit()
743 Session().commit()
745 return True
744 return True
746 else:
745 else:
747 raise HTTPForbidden()
746 raise HTTPForbidden()
@@ -1,335 +1,335 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%block name="title">
5 <%block name="title">
6 ${_('%s Changelog') % c.repo_name}
6 ${_('%s Changelog') % c.repo_name}
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path}
8 /${c.changelog_for_path}
9 %endif
9 %endif
10 </%block>
10 </%block>
11
11
12 <%def name="breadcrumbs_links()">
12 <%def name="breadcrumbs_links()">
13 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
13 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
14 ${_('Changelog')}
14 ${_('Changelog')}
15 %if c.changelog_for_path:
15 %if c.changelog_for_path:
16 - /${c.changelog_for_path}
16 - /${c.changelog_for_path}
17 %endif
17 %endif
18 %if c.revision:
18 %if c.revision:
19 @ ${h.short_id(c.first_revision.raw_id)}
19 @ ${h.short_id(c.first_revision.raw_id)}
20 %endif
20 %endif
21 - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
21 - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
22 </%def>
22 </%def>
23
23
24 <%block name="header_menu">
24 <%block name="header_menu">
25 ${self.menu('repositories')}
25 ${self.menu('repositories')}
26 </%block>
26 </%block>
27
27
28 <%def name="main()">
28 <%def name="main()">
29 ${self.repo_context_bar('changelog', c.first_revision.raw_id if c.first_revision else None)}
29 ${self.repo_context_bar('changelog', c.first_revision.raw_id if c.first_revision else None)}
30 <div class="panel panel-primary">
30 <div class="panel panel-primary">
31 <div class="panel-heading clearfix">
31 <div class="panel-heading clearfix">
32 ${self.breadcrumbs()}
32 ${self.breadcrumbs()}
33 </div>
33 </div>
34 <div class="panel-body changelog-panel">
34 <div class="panel-body changelog-panel">
35 %if c.pagination:
35 %if c.pagination:
36 <div class="changelog-heading clearfix" style="${'display:none' if c.changelog_for_path else ''}">
36 <div class="changelog-heading clearfix" style="${'display:none' if c.changelog_for_path else ''}">
37 <div class="pull-left">
37 <div class="pull-left">
38 ${h.form(h.url.current(),method='get',class_="form-inline")}
38 ${h.form(h.url.current(),method='get',class_="form-inline")}
39 ${h.submit('set',_('Show'),class_="btn btn-default btn-sm")}
39 ${h.submit('set',_('Show'),class_="btn btn-default btn-sm")}
40 ${h.text('size',size=3,value=c.size,class_='form-control')}
40 ${h.text('size',size=3,value=c.size,class_='form-control')}
41 ${_('revisions')}
41 ${_('revisions')}
42 %if c.branch_name:
42 %if c.branch_name:
43 ${h.hidden('branch', c.branch_name)}
43 ${h.hidden('branch', c.branch_name)}
44 %endif
44 %endif
45 <a href="#" class="btn btn-default btn-sm" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
45 <a href="#" class="btn btn-default btn-sm" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
46 ${h.end_form()}
46 ${h.end_form()}
47 </div>
47 </div>
48 <div class="pull-right">
48 <div class="pull-right">
49 <a href="#" class="btn btn-default btn-sm" id="rev_range_container" style="display:none"></a>
49 <a href="#" class="btn btn-default btn-sm" id="rev_range_container" style="display:none"></a>
50 %if c.revision:
50 %if c.revision:
51 <a class="btn btn-default btn-sm" href="${h.url('changelog_home', repo_name=c.repo_name)}">
51 <a class="btn btn-default btn-sm" href="${h.url('changelog_home', repo_name=c.repo_name)}">
52 ${_('Go to tip of repository')}
52 ${_('Go to tip of repository')}
53 </a>
53 </a>
54 %endif
54 %endif
55 %if c.db_repo.fork:
55 %if c.db_repo.fork:
56 <a id="compare_fork"
56 <a id="compare_fork"
57 title="${_('Compare fork with %s' % c.db_repo.fork.repo_name)}"
57 title="${_('Compare fork with %s' % c.db_repo.fork.repo_name)}"
58 href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1],other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}"
58 href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1],other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}"
59 class="btn btn-default btn-sm"><i class="icon-git-compare"></i> ${_('Compare fork with parent repository (%s)' % c.db_repo.fork.repo_name)}</a>
59 class="btn btn-default btn-sm"><i class="icon-git-compare"></i> ${_('Compare fork with parent repository (%s)' % c.db_repo.fork.repo_name)}</a>
60 %endif
60 %endif
61 ## text and href of open_new_pr is controlled from javascript
61 ## text and href of open_new_pr is controlled from javascript
62 <a id="open_new_pr" class="btn btn-default btn-sm"></a>
62 <a id="open_new_pr" class="btn btn-default btn-sm"></a>
63 ${_("Branch filter:")} ${h.select('branch_filter',c.branch_name,c.branch_filters)}
63 ${_("Branch filter:")} ${h.select('branch_filter',c.branch_name,c.branch_filters)}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <div id="graph_nodes">
67 <div id="graph_nodes">
68 <canvas id="graph_canvas" style="width:0"></canvas>
68 <canvas id="graph_canvas" style="width:0"></canvas>
69 </div>
69 </div>
70 <div id="graph_content" style="${'margin: 0px' if c.changelog_for_path else ''}">
70 <div id="graph_content" style="${'margin: 0px' if c.changelog_for_path else ''}">
71
71
72 <table class="table" id="changesets">
72 <table class="table" id="changesets">
73 <tbody>
73 <tbody>
74 %for cnt,cs in enumerate(c.pagination):
74 %for cnt,cs in enumerate(c.pagination):
75 <tr id="chg_${cnt+1}" class="${'mergerow' if len(cs.parents) > 1 else ''}">
75 <tr id="chg_${cnt+1}" class="${'mergerow' if len(cs.parents) > 1 else ''}">
76 <td class="checkbox-column">
76 <td class="checkbox-column">
77 %if c.changelog_for_path:
77 %if c.changelog_for_path:
78 ${h.checkbox(cs.raw_id,class_="changeset_range", disabled="disabled")}
78 ${h.checkbox(cs.raw_id,class_="changeset_range", disabled="disabled")}
79 %else:
79 %else:
80 ${h.checkbox(cs.raw_id,class_="changeset_range")}
80 ${h.checkbox(cs.raw_id,class_="changeset_range")}
81 %endif
81 %endif
82 </td>
82 </td>
83 <td class="status">
83 <td class="status">
84 %if c.statuses.get(cs.raw_id):
84 %if c.cs_statuses.get(cs.raw_id):
85 %if c.statuses.get(cs.raw_id)[2]:
85 %if c.cs_statuses.get(cs.raw_id)[2]:
86 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s\nClick to open associated pull request %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[5].username, c.statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
86 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s\nClick to open associated pull request %s') % (c.cs_statuses.get(cs.raw_id)[1], c.cs_statuses.get(cs.raw_id)[5].username, c.cs_statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.cs_statuses.get(cs.raw_id)[3],pull_request_id=c.cs_statuses.get(cs.raw_id)[2])}">
87 <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
87 <i class="icon-circle changeset-status-${c.cs_statuses.get(cs.raw_id)[0]}"></i>
88 </a>
88 </a>
89 %else:
89 %else:
90 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[5].username)}"
90 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s') % (c.cs_statuses.get(cs.raw_id)[1], c.cs_statuses.get(cs.raw_id)[5].username)}"
91 href="${c.comments[cs.raw_id][0].url()}">
91 href="${c.cs_comments[cs.raw_id][0].url()}">
92 <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
92 <i class="icon-circle changeset-status-${c.cs_statuses.get(cs.raw_id)[0]}"></i>
93 </a>
93 </a>
94 %endif
94 %endif
95 %endif
95 %endif
96 </td>
96 </td>
97 <td class="author" data-toggle="tooltip" title="${cs.author}">
97 <td class="author" data-toggle="tooltip" title="${cs.author}">
98 ${h.gravatar(h.email_or_none(cs.author), size=16)}
98 ${h.gravatar(h.email_or_none(cs.author), size=16)}
99 <span class="user">${h.person(cs.author)}</span>
99 <span class="user">${h.person(cs.author)}</span>
100 </td>
100 </td>
101 <td class="hash">
101 <td class="hash">
102 ${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id), class_='changeset_hash')}
102 ${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id), class_='changeset_hash')}
103 </td>
103 </td>
104 <td class="date">
104 <td class="date">
105 <div class="date" data-toggle="tooltip" title="${h.fmt_date(cs.date)}">${h.age(cs.date,True)}</div>
105 <div class="date" data-toggle="tooltip" title="${h.fmt_date(cs.date)}">${h.age(cs.date,True)}</div>
106 </td>
106 </td>
107 <% message_lines = cs.message.splitlines() %>
107 <% message_lines = cs.message.splitlines() %>
108 %if len(message_lines) > 1:
108 %if len(message_lines) > 1:
109 <td class="expand_commit" title="${_('Expand commit message')}">
109 <td class="expand_commit" title="${_('Expand commit message')}">
110 <i class="icon-align-left"></i>
110 <i class="icon-align-left"></i>
111 </td>
111 </td>
112 %else:
112 %else:
113 <td></td>
113 <td></td>
114 %endif
114 %endif
115 <td class="mid">
115 <td class="mid">
116 <div class="log-container">
116 <div class="log-container">
117 <div class="message">
117 <div class="message">
118 <div class="message-firstline">${h.urlify_text(message_lines[0], c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
118 <div class="message-firstline">${h.urlify_text(message_lines[0], c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
119 %if len(message_lines) > 1:
119 %if len(message_lines) > 1:
120 <div class="message-full hidden">${h.urlify_text(cs.message, c.repo_name)}</div>
120 <div class="message-full hidden">${h.urlify_text(cs.message, c.repo_name)}</div>
121 %endif
121 %endif
122 </div>
122 </div>
123 <div class="extra-container">
123 <div class="extra-container">
124 %if c.comments.get(cs.raw_id):
124 %if c.cs_comments.get(cs.raw_id):
125 <a class="comments-container comments-cnt" href="${c.comments[cs.raw_id][0].url()}" data-toggle="tooltip" title="${_('%s comments') % len(c.comments[cs.raw_id])}">
125 <a class="comments-container comments-cnt" href="${c.cs_comments[cs.raw_id][0].url()}" data-toggle="tooltip" title="${_('%s comments') % len(c.cs_comments[cs.raw_id])}">
126 ${len(c.comments[cs.raw_id])}
126 ${len(c.cs_comments[cs.raw_id])}
127 <i class="icon-comment-discussion"></i>
127 <i class="icon-comment-discussion"></i>
128 </a>
128 </a>
129 %endif
129 %endif
130 %if cs.bumped:
130 %if cs.bumped:
131 <span class="bumpedtag" title="Bumped">
131 <span class="bumpedtag" title="Bumped">
132 Bumped
132 Bumped
133 </span>
133 </span>
134 %endif
134 %endif
135 %if cs.divergent:
135 %if cs.divergent:
136 <span class="divergenttag" title="Divergent">
136 <span class="divergenttag" title="Divergent">
137 Divergent
137 Divergent
138 </span>
138 </span>
139 %endif
139 %endif
140 %if cs.extinct:
140 %if cs.extinct:
141 <span class="extincttag" title="Extinct">
141 <span class="extincttag" title="Extinct">
142 Extinct
142 Extinct
143 </span>
143 </span>
144 %endif
144 %endif
145 %if cs.unstable:
145 %if cs.unstable:
146 <span class="unstabletag" title="Unstable">
146 <span class="unstabletag" title="Unstable">
147 Unstable
147 Unstable
148 </span>
148 </span>
149 %endif
149 %endif
150 %if cs.phase:
150 %if cs.phase:
151 <span class="phasetag" title="Phase">
151 <span class="phasetag" title="Phase">
152 ${cs.phase}
152 ${cs.phase}
153 </span>
153 </span>
154 %endif
154 %endif
155 %if h.is_hg(c.db_repo_scm_instance):
155 %if h.is_hg(c.db_repo_scm_instance):
156 %for book in cs.bookmarks:
156 %for book in cs.bookmarks:
157 <span class="booktag" title="${_('Bookmark %s') % book}">
157 <span class="booktag" title="${_('Bookmark %s') % book}">
158 ${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
158 ${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
159 </span>
159 </span>
160 %endfor
160 %endfor
161 %endif
161 %endif
162 %for tag in cs.tags:
162 %for tag in cs.tags:
163 <span class="tagtag" title="${_('Tag %s') % tag}">
163 <span class="tagtag" title="${_('Tag %s') % tag}">
164 ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
164 ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
165 </span>
165 </span>
166 %endfor
166 %endfor
167 %if (not c.branch_name) and cs.branch:
167 %if (not c.branch_name) and cs.branch:
168 <span class="branchtag" title="${_('Branch %s' % cs.branch)}">
168 <span class="branchtag" title="${_('Branch %s' % cs.branch)}">
169 ${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
169 ${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
170 </span>
170 </span>
171 %endif
171 %endif
172 </div>
172 </div>
173 </div>
173 </div>
174 </td>
174 </td>
175 </tr>
175 </tr>
176 %endfor
176 %endfor
177 </tbody>
177 </tbody>
178 </table>
178 </table>
179
179
180 <input type="checkbox" id="singlerange" style="display:none"/>
180 <input type="checkbox" id="singlerange" style="display:none"/>
181
181
182 </div>
182 </div>
183
183
184 ${c.pagination.pager()}
184 ${c.pagination.pager()}
185
185
186 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
186 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
187 <script type="text/javascript">
187 <script type="text/javascript">
188 var jsdata = ${h.js(c.jsdata)};
188 var jsdata = ${h.js(c.jsdata)};
189 var graph = new BranchRenderer('graph_canvas', 'graph_content', 'chg_');
189 var graph = new BranchRenderer('graph_canvas', 'graph_content', 'chg_');
190
190
191 $(document).ready(function(){
191 $(document).ready(function(){
192 var $checkboxes = $('.changeset_range');
192 var $checkboxes = $('.changeset_range');
193
193
194 pyroutes.register('changeset_home', ${h.js(h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s'))}, ['repo_name', 'revision']);
194 pyroutes.register('changeset_home', ${h.js(h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s'))}, ['repo_name', 'revision']);
195
195
196 var checkbox_checker = function(e) {
196 var checkbox_checker = function(e) {
197 var $checked_checkboxes = $checkboxes.filter(':checked');
197 var $checked_checkboxes = $checkboxes.filter(':checked');
198 var $singlerange = $('#singlerange');
198 var $singlerange = $('#singlerange');
199
199
200 $('#rev_range_container').hide();
200 $('#rev_range_container').hide();
201 $checkboxes.show();
201 $checkboxes.show();
202 $singlerange.show();
202 $singlerange.show();
203
203
204 if ($checked_checkboxes.length > 0) {
204 if ($checked_checkboxes.length > 0) {
205 $checked_checkboxes.first().parent('td').append($singlerange);
205 $checked_checkboxes.first().parent('td').append($singlerange);
206 var singlerange = $singlerange.prop('checked');
206 var singlerange = $singlerange.prop('checked');
207 var rev_end = $checked_checkboxes.first().prop('name');
207 var rev_end = $checked_checkboxes.first().prop('name');
208 if ($checked_checkboxes.length > 1 || singlerange) {
208 if ($checked_checkboxes.length > 1 || singlerange) {
209 var rev_start = $checked_checkboxes.last().prop('name');
209 var rev_start = $checked_checkboxes.last().prop('name');
210 $('#rev_range_container').prop('href',
210 $('#rev_range_container').prop('href',
211 pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},
211 pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},
212 'revision': rev_start + '...' + rev_end}));
212 'revision': rev_start + '...' + rev_end}));
213 $('#rev_range_container').html(
213 $('#rev_range_container').html(
214 _TM['Show Selected Changesets {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
214 _TM['Show Selected Changesets {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
215 $('#rev_range_container').show();
215 $('#rev_range_container').show();
216 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
216 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
217 {'repo_name': ${h.js(c.repo_name)},
217 {'repo_name': ${h.js(c.repo_name)},
218 'rev_start': rev_start,
218 'rev_start': rev_start,
219 'rev_end': rev_end}));
219 'rev_end': rev_end}));
220 $('#open_new_pr').html(_TM['Open New Pull Request for {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
220 $('#open_new_pr').html(_TM['Open New Pull Request for {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
221 } else {
221 } else {
222 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
222 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
223 {'repo_name': ${h.js(c.repo_name)},
223 {'repo_name': ${h.js(c.repo_name)},
224 'rev_end': rev_end}));
224 'rev_end': rev_end}));
225 $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(rev_end.substr(0, 12)));
225 $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(rev_end.substr(0, 12)));
226 }
226 }
227 $('#rev_range_clear').show();
227 $('#rev_range_clear').show();
228 $('#compare_fork').hide();
228 $('#compare_fork').hide();
229
229
230 var disabled = true;
230 var disabled = true;
231 $checkboxes.each(function(){
231 $checkboxes.each(function(){
232 var $this = $(this);
232 var $this = $(this);
233 if (disabled) {
233 if (disabled) {
234 if ($this.prop('checked')) {
234 if ($this.prop('checked')) {
235 $this.closest('tr').removeClass('out-of-range');
235 $this.closest('tr').removeClass('out-of-range');
236 disabled = singlerange;
236 disabled = singlerange;
237 } else {
237 } else {
238 $this.closest('tr').addClass('out-of-range');
238 $this.closest('tr').addClass('out-of-range');
239 }
239 }
240 } else {
240 } else {
241 $this.closest('tr').removeClass('out-of-range');
241 $this.closest('tr').removeClass('out-of-range');
242 disabled = $this.prop('checked');
242 disabled = $this.prop('checked');
243 }
243 }
244 });
244 });
245
245
246 if ($checked_checkboxes.length + (singlerange ? 1 : 0) >= 2) {
246 if ($checked_checkboxes.length + (singlerange ? 1 : 0) >= 2) {
247 $checkboxes.hide();
247 $checkboxes.hide();
248 $checked_checkboxes.show();
248 $checked_checkboxes.show();
249 if (!singlerange)
249 if (!singlerange)
250 $singlerange.hide();
250 $singlerange.hide();
251 }
251 }
252 } else {
252 } else {
253 $('#singlerange').hide().prop('checked', false);
253 $('#singlerange').hide().prop('checked', false);
254 $('#rev_range_clear').hide();
254 $('#rev_range_clear').hide();
255 %if c.revision:
255 %if c.revision:
256 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
256 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
257 {'repo_name': ${h.js(c.repo_name)},
257 {'repo_name': ${h.js(c.repo_name)},
258 'rev_end':${h.js(c.first_revision.raw_id)}}));
258 'rev_end':${h.js(c.first_revision.raw_id)}}));
259 $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.revision)}));
259 $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.revision)}));
260 %else:
260 %else:
261 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
261 $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
262 {'repo_name': ${h.js(c.repo_name)},
262 {'repo_name': ${h.js(c.repo_name)},
263 'branch':${h.js(c.first_revision.branch)}}));
263 'branch':${h.js(c.first_revision.branch)}}));
264 $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.first_revision.branch)}));
264 $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.first_revision.branch)}));
265 %endif
265 %endif
266 $('#compare_fork').show();
266 $('#compare_fork').show();
267 $checkboxes.closest('tr').removeClass('out-of-range');
267 $checkboxes.closest('tr').removeClass('out-of-range');
268 }
268 }
269 };
269 };
270 checkbox_checker();
270 checkbox_checker();
271 $checkboxes.click(function() {
271 $checkboxes.click(function() {
272 checkbox_checker();
272 checkbox_checker();
273 graph.render(jsdata);
273 graph.render(jsdata);
274 });
274 });
275 $('#singlerange').click(checkbox_checker);
275 $('#singlerange').click(checkbox_checker);
276
276
277 $('#rev_range_clear').click(function(e){
277 $('#rev_range_clear').click(function(e){
278 $checkboxes.prop('checked', false);
278 $checkboxes.prop('checked', false);
279 checkbox_checker();
279 checkbox_checker();
280 graph.render(jsdata);
280 graph.render(jsdata);
281 });
281 });
282
282
283 var $msgs = $('.message');
283 var $msgs = $('.message');
284 // get first element height
284 // get first element height
285 var el = $('#graph_content tr')[0];
285 var el = $('#graph_content tr')[0];
286 var row_h = el.clientHeight;
286 var row_h = el.clientHeight;
287 $msgs.each(function() {
287 $msgs.each(function() {
288 var m = this;
288 var m = this;
289
289
290 var h = m.clientHeight;
290 var h = m.clientHeight;
291 if(h > row_h){
291 if(h > row_h){
292 var offset = row_h - (h+12);
292 var offset = row_h - (h+12);
293 $(m.nextElementSibling).css('display', 'block');
293 $(m.nextElementSibling).css('display', 'block');
294 $(m.nextElementSibling).css('margin-top', offset+'px');
294 $(m.nextElementSibling).css('margin-top', offset+'px');
295 }
295 }
296 });
296 });
297
297
298 $('.expand_commit').on('click',function(e){
298 $('.expand_commit').on('click',function(e){
299 $(this).next('.mid').find('.message > div').toggleClass('hidden');
299 $(this).next('.mid').find('.message > div').toggleClass('hidden');
300
300
301 //redraw the graph, r and jsdata are bound outside function
301 //redraw the graph, r and jsdata are bound outside function
302 graph.render(jsdata);
302 graph.render(jsdata);
303 });
303 });
304
304
305 // change branch filter
305 // change branch filter
306 $("#branch_filter").select2({
306 $("#branch_filter").select2({
307 dropdownAutoWidth: true,
307 dropdownAutoWidth: true,
308 maxResults: 50,
308 maxResults: 50,
309 sortResults: branchSort
309 sortResults: branchSort
310 });
310 });
311
311
312 $("#branch_filter").change(function(e){
312 $("#branch_filter").change(function(e){
313 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
313 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
314 if(selected_branch != ''){
314 if(selected_branch != ''){
315 window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)},
315 window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)},
316 'branch': selected_branch});
316 'branch': selected_branch});
317 }else{
317 }else{
318 window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)}});
318 window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)}});
319 }
319 }
320 $("#changelog").hide();
320 $("#changelog").hide();
321 });
321 });
322
322
323 graph.render(jsdata);
323 graph.render(jsdata);
324 });
324 });
325
325
326 $(window).resize(function(){
326 $(window).resize(function(){
327 graph.render(jsdata);
327 graph.render(jsdata);
328 });
328 });
329 </script>
329 </script>
330 %else:
330 %else:
331 ${_('There are no changes yet')}
331 ${_('There are no changes yet')}
332 %endif
332 %endif
333 </div>
333 </div>
334 </div>
334 </div>
335 </%def>
335 </%def>
@@ -1,105 +1,105 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 %if c.repo_changesets:
2 %if c.repo_changesets:
3 <table class="table">
3 <table class="table">
4 <tr>
4 <tr>
5 <th class="left"></th>
5 <th class="left"></th>
6 <th class="left"></th>
6 <th class="left"></th>
7 <th class="left">${_('Revision')}</th>
7 <th class="left">${_('Revision')}</th>
8 <th class="left">${_('Commit Message')}</th>
8 <th class="left">${_('Commit Message')}</th>
9 <th class="left">${_('Age')}</th>
9 <th class="left">${_('Age')}</th>
10 <th class="left">${_('Author')}</th>
10 <th class="left">${_('Author')}</th>
11 <th class="left">${_('Refs')}</th>
11 <th class="left">${_('Refs')}</th>
12 </tr>
12 </tr>
13 %for cnt,cs in enumerate(c.repo_changesets):
13 %for cnt,cs in enumerate(c.repo_changesets):
14 <tr class="parity${cnt%2} ${'mergerow' if len(cs.parents) > 1 else ''}">
14 <tr class="parity${cnt%2} ${'mergerow' if len(cs.parents) > 1 else ''}">
15 <td class="compact">
15 <td class="compact">
16 %if c.statuses.get(cs.raw_id):
16 %if c.cs_statuses.get(cs.raw_id):
17 %if c.statuses.get(cs.raw_id)[2]:
17 %if c.cs_statuses.get(cs.raw_id)[2]:
18 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s\nClick to open associated pull request %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[5].username, c.statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
18 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s\nClick to open associated pull request %s') % (c.cs_statuses.get(cs.raw_id)[1], c.cs_statuses.get(cs.raw_id)[5].username, c.cs_statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.cs_statuses.get(cs.raw_id)[3],pull_request_id=c.cs_statuses.get(cs.raw_id)[2])}">
19 <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
19 <i class="icon-circle changeset-status-${c.cs_statuses.get(cs.raw_id)[0]}"></i>
20 </a>
20 </a>
21 %else:
21 %else:
22 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[5].username)}"
22 <a data-toggle="tooltip" title="${_('Changeset status: %s by %s') % (c.cs_statuses.get(cs.raw_id)[1], c.cs_statuses.get(cs.raw_id)[5].username)}"
23 href="${c.comments[cs.raw_id][0].url()}">
23 href="${c.cs_comments[cs.raw_id][0].url()}">
24 <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
24 <i class="icon-circle changeset-status-${c.cs_statuses.get(cs.raw_id)[0]}"></i>
25 </a>
25 </a>
26 %endif
26 %endif
27 %endif
27 %endif
28 </td>
28 </td>
29 <td class="compact">
29 <td class="compact">
30 %if c.comments.get(cs.raw_id,[]):
30 %if c.cs_comments.get(cs.raw_id,[]):
31 <div class="comments-container">
31 <div class="comments-container">
32 <div title="${('comments')}">
32 <div title="${('comments')}">
33 <a href="${c.comments[cs.raw_id][0].url()}">
33 <a href="${c.cs_comments[cs.raw_id][0].url()}">
34 <i class="icon-comment"></i>${len(c.comments[cs.raw_id])}
34 <i class="icon-comment"></i>${len(c.cs_comments[cs.raw_id])}
35 </a>
35 </a>
36 </div>
36 </div>
37 </div>
37 </div>
38 %endif
38 %endif
39 </td>
39 </td>
40 <td>
40 <td>
41 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}" class="revision-link">${h.show_id(cs)}</a>
41 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}" class="revision-link">${h.show_id(cs)}</a>
42 </td>
42 </td>
43 <td>
43 <td>
44 ${h.urlify_text(h.chop_at(cs.message,'\n'),c.repo_name, h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
44 ${h.urlify_text(h.chop_at(cs.message,'\n'),c.repo_name, h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
45 </td>
45 </td>
46 <td><span data-toggle="tooltip" title="${h.fmt_date(cs.date)}">
46 <td><span data-toggle="tooltip" title="${h.fmt_date(cs.date)}">
47 ${h.age(cs.date)}</span>
47 ${h.age(cs.date)}</span>
48 </td>
48 </td>
49 <td title="${cs.author}">${h.person(cs.author)}</td>
49 <td title="${cs.author}">${h.person(cs.author)}</td>
50 <td>
50 <td>
51 %if h.is_hg(c.db_repo_scm_instance):
51 %if h.is_hg(c.db_repo_scm_instance):
52 %for book in cs.bookmarks:
52 %for book in cs.bookmarks:
53 <span class="booktag" title="${_('Bookmark %s') % book}">
53 <span class="booktag" title="${_('Bookmark %s') % book}">
54 ${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
54 ${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
55 </span>
55 </span>
56 %endfor
56 %endfor
57 %endif
57 %endif
58 %for tag in cs.tags:
58 %for tag in cs.tags:
59 <span class="tagtag" title="${_('Tag %s') % tag}">
59 <span class="tagtag" title="${_('Tag %s') % tag}">
60 ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
60 ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
61 </span>
61 </span>
62 %endfor
62 %endfor
63 %if cs.branch:
63 %if cs.branch:
64 <span class="branchtag" title="${_('Branch %s' % cs.branch)}">
64 <span class="branchtag" title="${_('Branch %s' % cs.branch)}">
65 ${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
65 ${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
66 </span>
66 </span>
67 %endif
67 %endif
68 </td>
68 </td>
69 </tr>
69 </tr>
70 %endfor
70 %endfor
71
71
72 </table>
72 </table>
73
73
74 ${c.repo_changesets.pager()}
74 ${c.repo_changesets.pager()}
75
75
76 %else:
76 %else:
77
77
78 %if h.HasRepoPermissionLevel('write')(c.repo_name):
78 %if h.HasRepoPermissionLevel('write')(c.repo_name):
79 <h4>${_('Add or upload files directly via Kallithea')}</h4>
79 <h4>${_('Add or upload files directly via Kallithea')}</h4>
80 <div>
80 <div>
81 <div id="add_node_id" class="add_node">
81 <div id="add_node_id" class="add_node">
82 <a class="btn btn-default btn-xs" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}">${_('Add New File')}</a>
82 <a class="btn btn-default btn-xs" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}">${_('Add New File')}</a>
83 </div>
83 </div>
84 </div>
84 </div>
85 %endif
85 %endif
86
86
87
87
88 <h4>${_('Push new repository')}</h4>
88 <h4>${_('Push new repository')}</h4>
89 <pre>
89 <pre>
90 ${c.db_repo_scm_instance.alias} clone ${c.clone_repo_url}
90 ${c.db_repo_scm_instance.alias} clone ${c.clone_repo_url}
91 ${c.db_repo_scm_instance.alias} add README # add first file
91 ${c.db_repo_scm_instance.alias} add README # add first file
92 ${c.db_repo_scm_instance.alias} commit -m "Initial" # commit with message
92 ${c.db_repo_scm_instance.alias} commit -m "Initial" # commit with message
93 ${c.db_repo_scm_instance.alias} push ${'origin master' if h.is_git(c.db_repo_scm_instance) else ''} # push changes back
93 ${c.db_repo_scm_instance.alias} push ${'origin master' if h.is_git(c.db_repo_scm_instance) else ''} # push changes back
94 </pre>
94 </pre>
95
95
96 <h4>${_('Existing repository?')}</h4>
96 <h4>${_('Existing repository?')}</h4>
97 <pre>
97 <pre>
98 %if h.is_git(c.db_repo_scm_instance):
98 %if h.is_git(c.db_repo_scm_instance):
99 git remote add origin ${c.clone_repo_url}
99 git remote add origin ${c.clone_repo_url}
100 git push -u origin master
100 git push -u origin master
101 %else:
101 %else:
102 hg push ${c.clone_repo_url}
102 hg push ${c.clone_repo_url}
103 %endif
103 %endif
104 </pre>
104 </pre>
105 %endif
105 %endif
@@ -1,143 +1,143 b''
1 ## Changesets table !
1 ## Changesets table !
2 <div>
2 <div>
3 %if not c.cs_ranges:
3 %if not c.cs_ranges:
4 <span class="empty_data">${_('No changesets')}</span>
4 <span class="empty_data">${_('No changesets')}</span>
5 %else:
5 %else:
6
6
7 %if c.ancestors:
7 %if c.ancestors:
8 <div class="ancestor">
8 <div class="ancestor">
9 %if len(c.ancestors) > 1:
9 %if len(c.ancestors) > 1:
10 <div class="text-danger">
10 <div class="text-danger">
11 ${_('Criss cross merge situation with multiple merge ancestors detected!')}
11 ${_('Criss cross merge situation with multiple merge ancestors detected!')}
12 </div>
12 </div>
13 <div>
13 <div>
14 ${_('Please merge the target branch to your branch before creating a pull request.')}
14 ${_('Please merge the target branch to your branch before creating a pull request.')}
15 </div>
15 </div>
16 %endif
16 %endif
17 <div>
17 <div>
18 ${_('Merge Ancestor')}:
18 ${_('Merge Ancestor')}:
19 %for ancestor in c.ancestors:
19 %for ancestor in c.ancestors:
20 ${h.link_to(h.short_id(ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=ancestor), class_="changeset_hash")}
20 ${h.link_to(h.short_id(ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=ancestor), class_="changeset_hash")}
21 %endfor
21 %endfor
22 </div>
22 </div>
23 </div>
23 </div>
24 %endif
24 %endif
25
25
26 <div id="graph_nodes">
26 <div id="graph_nodes">
27 <canvas id="graph_canvas"></canvas>
27 <canvas id="graph_canvas"></canvas>
28 </div>
28 </div>
29
29
30 <div id="graph_content_pr">
30 <div id="graph_content_pr">
31
31
32 <table class="table compare_view_commits">
32 <table class="table compare_view_commits">
33 %for cnt, cs in enumerate(reversed(c.cs_ranges)):
33 %for cnt, cs in enumerate(reversed(c.cs_ranges)):
34 <tr id="chg_${cnt+1}" class="${'mergerow' if len(cs.parents) > 1 else ''}">
34 <tr id="chg_${cnt+1}" class="${'mergerow' if len(cs.parents) > 1 else ''}">
35 <td>
35 <td>
36 %if cs.raw_id in c.statuses:
36 %if cs.raw_id in c.cs_statuses:
37 <i class="icon-circle changeset-status-${c.statuses[cs.raw_id][0]}" title="${_('Changeset status: %s') % c.statuses[cs.raw_id][1]}"></i>
37 <i class="icon-circle changeset-status-${c.cs_statuses[cs.raw_id][0]}" title="${_('Changeset status: %s') % c.cs_statuses[cs.raw_id][1]}"></i>
38 %endif
38 %endif
39 %if c.cs_comments.get(cs.raw_id):
39 %if c.cs_comments.get(cs.raw_id):
40 <div class="comments-container">
40 <div class="comments-container">
41 <div class="comments-cnt" title="${_('Changeset has comments')}">
41 <div class="comments-cnt" title="${_('Changeset has comments')}">
42 <a href="${c.cs_comments[cs.raw_id][0].url()}">
42 <a href="${c.cs_comments[cs.raw_id][0].url()}">
43 ${len(c.cs_comments[cs.raw_id])}
43 ${len(c.cs_comments[cs.raw_id])}
44 <i class="icon-comment"></i>
44 <i class="icon-comment"></i>
45 </a>
45 </a>
46 </div>
46 </div>
47 </div>
47 </div>
48 %endif
48 %endif
49 </td>
49 </td>
50 <td class="changeset-logical-index">
50 <td class="changeset-logical-index">
51 <%
51 <%
52 num_cs = len(c.cs_ranges)
52 num_cs = len(c.cs_ranges)
53 index = num_cs - cnt
53 index = num_cs - cnt
54 if index == 1:
54 if index == 1:
55 title = _('First (oldest) changeset in this list')
55 title = _('First (oldest) changeset in this list')
56 elif index == num_cs:
56 elif index == num_cs:
57 title = _('Last (most recent) changeset in this list')
57 title = _('Last (most recent) changeset in this list')
58 else:
58 else:
59 title = _('Position in this list of changesets')
59 title = _('Position in this list of changesets')
60 %>
60 %>
61 <span data-toggle="tooltip" title="${title}">
61 <span data-toggle="tooltip" title="${title}">
62 ${index}
62 ${index}
63 </span>
63 </span>
64 </td>
64 </td>
65 <td><span data-toggle="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
65 <td><span data-toggle="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
66 <td class="author">
66 <td class="author">
67 ${h.gravatar(h.email_or_none(cs.author), size=16)}
67 ${h.gravatar(h.email_or_none(cs.author), size=16)}
68 <span data-toggle="tooltip" title="${cs.author}" class="user">${h.person(cs.author)}</span>
68 <span data-toggle="tooltip" title="${cs.author}" class="user">${h.person(cs.author)}</span>
69 </td>
69 </td>
70 <td>${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id), class_='changeset_hash')}</td>
70 <td>${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id), class_='changeset_hash')}</td>
71 <td>
71 <td>
72 %if cs.branch:
72 %if cs.branch:
73 <span class="branchtag">${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.cs_repo.repo_name,branch=cs.branch))}</span>
73 <span class="branchtag">${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.cs_repo.repo_name,branch=cs.branch))}</span>
74 %endif
74 %endif
75 </td>
75 </td>
76 <td class="expand_commit" title="${_('Expand commit message')}">
76 <td class="expand_commit" title="${_('Expand commit message')}">
77 <i class="icon-align-left"></i>
77 <i class="icon-align-left"></i>
78 </td>
78 </td>
79 <td class="mid">
79 <td class="mid">
80 <div class="pull-right">
80 <div class="pull-right">
81 %for tag in cs.tags:
81 %for tag in cs.tags:
82 <span class="tagtag" title="${_('Tag %s') % tag}">
82 <span class="tagtag" title="${_('Tag %s') % tag}">
83 ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
83 ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
84 </span>
84 </span>
85 %endfor
85 %endfor
86 </div>
86 </div>
87 <div class="message">${h.urlify_text(cs.message, c.repo_name)}</div>
87 <div class="message">${h.urlify_text(cs.message, c.repo_name)}</div>
88 </td>
88 </td>
89 </tr>
89 </tr>
90 %endfor
90 %endfor
91 </table>
91 </table>
92
92
93 </div>
93 </div>
94
94
95 %if c.is_ajax_preview:
95 %if c.is_ajax_preview:
96 <h5>
96 <h5>
97 ## links should perhaps use ('rev', c.a_rev) instead ...
97 ## links should perhaps use ('rev', c.a_rev) instead ...
98 ${h.link_to(_('Show merge diff'),
98 ${h.link_to(_('Show merge diff'),
99 h.url('compare_url',
99 h.url('compare_url',
100 repo_name=c.a_repo.repo_name,
100 repo_name=c.a_repo.repo_name,
101 org_ref_type=c.a_ref_type, org_ref_name=c.a_ref_name,
101 org_ref_type=c.a_ref_type, org_ref_name=c.a_ref_name,
102 other_repo=c.cs_repo.repo_name,
102 other_repo=c.cs_repo.repo_name,
103 other_ref_type=c.cs_ref_type, other_ref_name=c.cs_ref_name,
103 other_ref_type=c.cs_ref_type, other_ref_name=c.cs_ref_name,
104 merge='1')
104 merge='1')
105 )}
105 )}
106 </h5>
106 </h5>
107 %endif
107 %endif
108 %if c.cs_ranges_org is not None:
108 %if c.cs_ranges_org is not None:
109 ## TODO: list actual changesets?
109 ## TODO: list actual changesets?
110 <div>
110 <div>
111 ${h.link_to_ref(c.cs_repo.repo_name, c.cs_ref_type, c.cs_ref_name, c.cs_rev)}
111 ${h.link_to_ref(c.cs_repo.repo_name, c.cs_ref_type, c.cs_ref_name, c.cs_rev)}
112 ${_('is')}
112 ${_('is')}
113 <a href="${c.swap_url}">${_('%s changesets') % (len(c.cs_ranges_org))}</a>
113 <a href="${c.swap_url}">${_('%s changesets') % (len(c.cs_ranges_org))}</a>
114 ${_('behind')}
114 ${_('behind')}
115 ${h.link_to_ref(c.a_repo.repo_name, c.a_ref_type, c.a_ref_name)}
115 ${h.link_to_ref(c.a_repo.repo_name, c.a_ref_type, c.a_ref_name)}
116 </div>
116 </div>
117 %endif
117 %endif
118 %endif
118 %endif
119 </div>
119 </div>
120
120
121 %if c.is_ajax_preview:
121 %if c.is_ajax_preview:
122 <div id="jsdata" style="display:none">${h.js(c.jsdata)}</div>
122 <div id="jsdata" style="display:none">${h.js(c.jsdata)}</div>
123 %else:
123 %else:
124 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
124 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
125 %endif
125 %endif
126
126
127 <script type="text/javascript">
127 <script type="text/javascript">
128 var jsdata = ${h.js(c.jsdata)};
128 var jsdata = ${h.js(c.jsdata)};
129 var graph = new BranchRenderer('graph_canvas', 'graph_content_pr', 'chg_');
129 var graph = new BranchRenderer('graph_canvas', 'graph_content_pr', 'chg_');
130
130
131 $(document).ready(function(){
131 $(document).ready(function(){
132 graph.render(jsdata);
132 graph.render(jsdata);
133
133
134 $('.expand_commit').click(function(e){
134 $('.expand_commit').click(function(e){
135 $(this).next('.mid').find('.message').toggleClass('expanded');
135 $(this).next('.mid').find('.message').toggleClass('expanded');
136 graph.render(jsdata);
136 graph.render(jsdata);
137 });
137 });
138 });
138 });
139 $(window).resize(function(){
139 $(window).resize(function(){
140 graph.render(jsdata);
140 graph.render(jsdata);
141 });
141 });
142
142
143 </script>
143 </script>
General Comments 0
You need to be logged in to leave comments. Login now