##// END OF EJS Templates
compare/pullrequest: introduce merge parameter...
Mads Kiilerich -
r3486:2053053e beta
parent child Browse files
Show More
@@ -1,177 +1,188 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.compare
3 rhodecode.controllers.compare
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 compare controller for pylons showing differences between two
6 compare controller for pylons showing differences between two
7 repos, branches, bookmarks or tips
7 repos, branches, bookmarks or tips
8
8
9 :created_on: May 6, 2012
9 :created_on: May 6, 2012
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from webob.exc import HTTPNotFound
29 from webob.exc import HTTPNotFound
30 from pylons import request, response, session, tmpl_context as c, url
30 from pylons import request, response, session, tmpl_context as c, url
31 from pylons.controllers.util import abort, redirect
31 from pylons.controllers.util import abort, redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib import diffs
38 from rhodecode.lib import diffs
39
39
40 from rhodecode.model.db import Repository
40 from rhodecode.model.db import Repository
41 from rhodecode.model.pull_request import PullRequestModel
41 from rhodecode.model.pull_request import PullRequestModel
42 from webob.exc import HTTPBadRequest
42 from webob.exc import HTTPBadRequest
43 from rhodecode.lib.diffs import LimitedDiffContainer
43 from rhodecode.lib.diffs import LimitedDiffContainer
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class CompareController(BaseRepoController):
49 class CompareController(BaseRepoController):
50
50
51 @LoginRequired()
51 @LoginRequired()
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 'repository.admin')
53 'repository.admin')
54 def __before__(self):
54 def __before__(self):
55 super(CompareController, self).__before__()
55 super(CompareController, self).__before__()
56
56
57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
58 partial=False):
58 partial=False):
59 """
59 """
60 Safe way to get changeset if error occur it redirects to changeset with
60 Safe way to get changeset if error occur it redirects to changeset with
61 proper message. If partial is set then don't do redirect raise Exception
61 proper message. If partial is set then don't do redirect raise Exception
62 instead
62 instead
63
63
64 :param rev: revision to fetch
64 :param rev: revision to fetch
65 :param repo: repo instance
65 :param repo: repo instance
66 """
66 """
67
67
68 try:
68 try:
69 type_, rev = rev
69 type_, rev = rev
70 return repo.scm_instance.get_changeset(rev)
70 return repo.scm_instance.get_changeset(rev)
71 except EmptyRepositoryError, e:
71 except EmptyRepositoryError, e:
72 if not redirect_after:
72 if not redirect_after:
73 return None
73 return None
74 h.flash(h.literal(_('There are no changesets yet')),
74 h.flash(h.literal(_('There are no changesets yet')),
75 category='warning')
75 category='warning')
76 redirect(url('summary_home', repo_name=repo.repo_name))
76 redirect(url('summary_home', repo_name=repo.repo_name))
77
77
78 except RepositoryError, e:
78 except RepositoryError, e:
79 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
80 h.flash(str(e), category='warning')
80 h.flash(str(e), category='warning')
81 if not partial:
81 if not partial:
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
83 raise HTTPBadRequest()
83 raise HTTPBadRequest()
84
84
85 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
85 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
86 # org_ref will be evaluated in org_repo
86 # org_ref will be evaluated in org_repo
87 org_repo = c.rhodecode_db_repo.repo_name
87 org_repo = c.rhodecode_db_repo.repo_name
88 org_ref = (org_ref_type, org_ref)
88 org_ref = (org_ref_type, org_ref)
89 # other_ref will be evaluated in other_repo
89 # other_ref will be evaluated in other_repo
90 other_ref = (other_ref_type, other_ref)
90 other_ref = (other_ref_type, other_ref)
91 other_repo = request.GET.get('other_repo', org_repo)
91 other_repo = request.GET.get('other_repo', org_repo)
92 # If merge is True:
93 # Show what org would get if merged with other:
94 # List changesets that are ancestors of other but not of org.
95 # New changesets in org is thus ignored.
96 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
97 # If merge is False:
98 # Make a raw diff from org to other, no matter if related or not.
99 # Changesets in one and not in the other will be ignored
100 merge = bool(request.GET.get('merge'))
92 # fulldiff disables cut_off_limit
101 # fulldiff disables cut_off_limit
93 c.fulldiff = request.GET.get('fulldiff')
102 c.fulldiff = request.GET.get('fulldiff')
94 # partial uses compare_cs.html template directly
103 # partial uses compare_cs.html template directly
95 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
104 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
96 # as_form puts hidden input field with changeset revisions
105 # as_form puts hidden input field with changeset revisions
97 c.as_form = partial and request.GET.get('as_form')
106 c.as_form = partial and request.GET.get('as_form')
98 # swap url for compare_diff page - never partial and never as_form
107 # swap url for compare_diff page - never partial and never as_form
99 c.swap_url = h.url('compare_url',
108 c.swap_url = h.url('compare_url',
100 repo_name=other_repo,
109 repo_name=other_repo,
101 org_ref_type=other_ref[0], org_ref=other_ref[1],
110 org_ref_type=other_ref[0], org_ref=other_ref[1],
102 other_repo=org_repo,
111 other_repo=org_repo,
103 other_ref_type=org_ref[0], other_ref=org_ref[1])
112 other_ref_type=org_ref[0], other_ref=org_ref[1],
113 merge=merge or '')
104
114
105 org_repo = Repository.get_by_repo_name(org_repo)
115 org_repo = Repository.get_by_repo_name(org_repo)
106 other_repo = Repository.get_by_repo_name(other_repo)
116 other_repo = Repository.get_by_repo_name(other_repo)
107
117
108 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
118 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
109 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
119 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
110
120
111 if org_repo is None:
121 if org_repo is None:
112 log.error('Could not find org repo %s' % org_repo)
122 log.error('Could not find org repo %s' % org_repo)
113 raise HTTPNotFound
123 raise HTTPNotFound
114 if other_repo is None:
124 if other_repo is None:
115 log.error('Could not find other repo %s' % other_repo)
125 log.error('Could not find other repo %s' % other_repo)
116 raise HTTPNotFound
126 raise HTTPNotFound
117
127
118 if org_repo != other_repo and h.is_git(org_repo):
128 if org_repo != other_repo and h.is_git(org_repo):
119 log.error('compare of two remote repos not available for GIT REPOS')
129 log.error('compare of two remote repos not available for GIT REPOS')
120 raise HTTPNotFound
130 raise HTTPNotFound
121
131
122 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
132 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
123 log.error('compare of two different kind of remote repos not available')
133 log.error('compare of two different kind of remote repos not available')
124 raise HTTPNotFound
134 raise HTTPNotFound
125
135
126 c.org_repo = org_repo
136 c.org_repo = org_repo
127 c.other_repo = other_repo
137 c.other_repo = other_repo
128 c.org_ref = org_ref[1]
138 c.org_ref = org_ref[1]
129 c.other_ref = other_ref[1]
139 c.other_ref = other_ref[1]
130 c.org_ref_type = org_ref[0]
140 c.org_ref_type = org_ref[0]
131 c.other_ref_type = other_ref[0]
141 c.other_ref_type = other_ref[0]
132
142
133 c.cs_ranges, ancestor = PullRequestModel().get_compare_data(
143 c.cs_ranges, c.ancestor = PullRequestModel().get_compare_data(
134 org_repo, org_ref, other_repo, other_ref)
144 org_repo, org_ref, other_repo, other_ref, merge)
135
145
136 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
146 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
137 c.cs_ranges])
147 c.cs_ranges])
138 if partial:
148 if partial:
149 assert c.ancestor
139 return render('compare/compare_cs.html')
150 return render('compare/compare_cs.html')
140
151
141 if ancestor and org_repo != other_repo:
152 if c.ancestor:
153 assert merge
142 # case we want a simple diff without incoming changesets,
154 # case we want a simple diff without incoming changesets,
143 # previewing what will be merged.
155 # previewing what will be merged.
144 # Make the diff on the forked repo, with
156 # Make the diff on the other repo (which is known to have other_ref)
145 # revision that is common ancestor
146 log.debug('Using ancestor %s as org_ref instead of %s'
157 log.debug('Using ancestor %s as org_ref instead of %s'
147 % (ancestor, org_ref))
158 % (c.ancestor, org_ref))
148 org_ref = ('rev', ancestor)
159 org_ref = ('rev', c.ancestor)
149 org_repo = other_repo
160 org_repo = other_repo
150
161
151 diff_limit = self.cut_off_limit if not c.fulldiff else None
162 diff_limit = self.cut_off_limit if not c.fulldiff else None
152
163
153 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref)
164 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref)
154
165
155 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
166 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
156 diff_limit=diff_limit)
167 diff_limit=diff_limit)
157 _parsed = diff_processor.prepare()
168 _parsed = diff_processor.prepare()
158
169
159 c.limited_diff = False
170 c.limited_diff = False
160 if isinstance(_parsed, LimitedDiffContainer):
171 if isinstance(_parsed, LimitedDiffContainer):
161 c.limited_diff = True
172 c.limited_diff = True
162
173
163 c.files = []
174 c.files = []
164 c.changes = {}
175 c.changes = {}
165 c.lines_added = 0
176 c.lines_added = 0
166 c.lines_deleted = 0
177 c.lines_deleted = 0
167 for f in _parsed:
178 for f in _parsed:
168 st = f['stats']
179 st = f['stats']
169 if st[0] != 'b':
180 if st[0] != 'b':
170 c.lines_added += st[0]
181 c.lines_added += st[0]
171 c.lines_deleted += st[1]
182 c.lines_deleted += st[1]
172 fid = h.FID('', f['filename'])
183 fid = h.FID('', f['filename'])
173 c.files.append([fid, f['operation'], f['filename'], f['stats']])
184 c.files.append([fid, f['operation'], f['filename'], f['stats']])
174 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
185 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
175 c.changes[fid] = [f['operation'], f['filename'], diff]
186 c.changes[fid] = [f['operation'], f['filename'], diff]
176
187
177 return render('compare/compare_diff.html')
188 return render('compare/compare_diff.html')
@@ -1,479 +1,476 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib import diffs
42 from rhodecode.lib import diffs
43 from rhodecode.lib.utils import action_logger, jsonify
43 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.diffs import LimitedDiffContainer
46 from rhodecode.lib.diffs import LimitedDiffContainer
47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
48 ChangesetComment
48 ChangesetComment
49 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.comment import ChangesetCommentsModel
52 from rhodecode.model.comment import ChangesetCommentsModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
54 from rhodecode.model.forms import PullRequestForm
54 from rhodecode.model.forms import PullRequestForm
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class PullrequestsController(BaseRepoController):
59 class PullrequestsController(BaseRepoController):
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
63 'repository.admin')
63 'repository.admin')
64 def __before__(self):
64 def __before__(self):
65 super(PullrequestsController, self).__before__()
65 super(PullrequestsController, self).__before__()
66 repo_model = RepoModel()
66 repo_model = RepoModel()
67 c.users_array = repo_model.get_users_js()
67 c.users_array = repo_model.get_users_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
69
69
70 def _get_repo_refs(self, repo, rev=None):
70 def _get_repo_refs(self, repo, rev=None):
71 """return a structure with repo's interesting changesets, suitable for
71 """return a structure with repo's interesting changesets, suitable for
72 the selectors in pullrequest.html"""
72 the selectors in pullrequest.html"""
73 branches = [('branch:%s:%s' % (k, v), k)
73 branches = [('branch:%s:%s' % (k, v), k)
74 for k, v in repo.branches.iteritems()]
74 for k, v in repo.branches.iteritems()]
75 bookmarks = [('book:%s:%s' % (k, v), k)
75 bookmarks = [('book:%s:%s' % (k, v), k)
76 for k, v in repo.bookmarks.iteritems()]
76 for k, v in repo.bookmarks.iteritems()]
77 tags = [('tag:%s:%s' % (k, v), k)
77 tags = [('tag:%s:%s' % (k, v), k)
78 for k, v in repo.tags.iteritems()
78 for k, v in repo.tags.iteritems()
79 if k != 'tip']
79 if k != 'tip']
80
80
81 tip = repo.tags['tip']
81 tip = repo.tags['tip']
82 colontip = ':' + tip
82 colontip = ':' + tip
83 tips = [x[1] for x in branches + bookmarks + tags
83 tips = [x[1] for x in branches + bookmarks + tags
84 if x[0].endswith(colontip)]
84 if x[0].endswith(colontip)]
85 selected = 'tag:tip:%s' % tip
85 selected = 'tag:tip:%s' % tip
86 special = [(selected, 'tip (%s)' % ', '.join(tips))]
86 special = [(selected, 'tip (%s)' % ', '.join(tips))]
87
87
88 if rev:
88 if rev:
89 selected = 'rev:%s:%s' % (rev, rev)
89 selected = 'rev:%s:%s' % (rev, rev)
90 special.append((selected, rev))
90 special.append((selected, rev))
91
91
92 return [(special, _("Special")),
92 return [(special, _("Special")),
93 (bookmarks, _("Bookmarks")),
93 (bookmarks, _("Bookmarks")),
94 (branches, _("Branches")),
94 (branches, _("Branches")),
95 (tags, _("Tags")),
95 (tags, _("Tags")),
96 ], selected
96 ], selected
97
97
98 def _get_is_allowed_change_status(self, pull_request):
98 def _get_is_allowed_change_status(self, pull_request):
99 owner = self.rhodecode_user.user_id == pull_request.user_id
99 owner = self.rhodecode_user.user_id == pull_request.user_id
100 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
100 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
101 pull_request.reviewers]
101 pull_request.reviewers]
102 return (self.rhodecode_user.admin or owner or reviewer)
102 return (self.rhodecode_user.admin or owner or reviewer)
103
103
104 def show_all(self, repo_name):
104 def show_all(self, repo_name):
105 c.pull_requests = PullRequestModel().get_all(repo_name)
105 c.pull_requests = PullRequestModel().get_all(repo_name)
106 c.repo_name = repo_name
106 c.repo_name = repo_name
107 return render('/pullrequests/pullrequest_show_all.html')
107 return render('/pullrequests/pullrequest_show_all.html')
108
108
109 @NotAnonymous()
109 @NotAnonymous()
110 def index(self):
110 def index(self):
111 org_repo = c.rhodecode_db_repo
111 org_repo = c.rhodecode_db_repo
112
112
113 if org_repo.scm_instance.alias != 'hg':
113 if org_repo.scm_instance.alias != 'hg':
114 log.error('Review not available for GIT REPOS')
114 log.error('Review not available for GIT REPOS')
115 raise HTTPNotFound
115 raise HTTPNotFound
116
116
117 try:
117 try:
118 org_repo.scm_instance.get_changeset()
118 org_repo.scm_instance.get_changeset()
119 except EmptyRepositoryError, e:
119 except EmptyRepositoryError, e:
120 h.flash(h.literal(_('There are no changesets yet')),
120 h.flash(h.literal(_('There are no changesets yet')),
121 category='warning')
121 category='warning')
122 redirect(url('summary_home', repo_name=org_repo.repo_name))
122 redirect(url('summary_home', repo_name=org_repo.repo_name))
123
123
124 org_rev = request.GET.get('rev_end')
124 org_rev = request.GET.get('rev_end')
125 # rev_start is not directly useful - its parent could however be used
125 # rev_start is not directly useful - its parent could however be used
126 # as default for other and thus give a simple compare view
126 # as default for other and thus give a simple compare view
127 #other_rev = request.POST.get('rev_start')
127 #other_rev = request.POST.get('rev_start')
128
128
129 other_repos_info = {}
129 other_repos_info = {}
130
130
131 c.org_repos = []
131 c.org_repos = []
132 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
132 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
133 c.default_org_repo = org_repo.repo_name
133 c.default_org_repo = org_repo.repo_name
134 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
134 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
135
135
136 c.other_repos = []
136 c.other_repos = []
137 # add org repo to other so we can open pull request against itself
137 # add org repo to other so we can open pull request against itself
138 c.other_repos.extend(c.org_repos)
138 c.other_repos.extend(c.org_repos)
139 c.default_other_repo = org_repo.repo_name
139 c.default_other_repo = org_repo.repo_name
140 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance)
140 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance)
141 usr_data = lambda usr: dict(user_id=usr.user_id,
141 usr_data = lambda usr: dict(user_id=usr.user_id,
142 username=usr.username,
142 username=usr.username,
143 firstname=usr.firstname,
143 firstname=usr.firstname,
144 lastname=usr.lastname,
144 lastname=usr.lastname,
145 gravatar_link=h.gravatar_url(usr.email, 14))
145 gravatar_link=h.gravatar_url(usr.email, 14))
146 other_repos_info[org_repo.repo_name] = {
146 other_repos_info[org_repo.repo_name] = {
147 'user': usr_data(org_repo.user),
147 'user': usr_data(org_repo.user),
148 'description': org_repo.description,
148 'description': org_repo.description,
149 'revs': h.select('other_ref', c.default_other_ref,
149 'revs': h.select('other_ref', c.default_other_ref,
150 c.default_other_refs, class_='refs')
150 c.default_other_refs, class_='refs')
151 }
151 }
152
152
153 # gather forks and add to this list ... even though it is rare to
153 # gather forks and add to this list ... even though it is rare to
154 # request forks to pull their parent
154 # request forks to pull their parent
155 for fork in org_repo.forks:
155 for fork in org_repo.forks:
156 c.other_repos.append((fork.repo_name, fork.repo_name))
156 c.other_repos.append((fork.repo_name, fork.repo_name))
157 refs, default_ref = self._get_repo_refs(fork.scm_instance)
157 refs, default_ref = self._get_repo_refs(fork.scm_instance)
158 other_repos_info[fork.repo_name] = {
158 other_repos_info[fork.repo_name] = {
159 'user': usr_data(fork.user),
159 'user': usr_data(fork.user),
160 'description': fork.description,
160 'description': fork.description,
161 'revs': h.select('other_ref', default_ref, refs, class_='refs')
161 'revs': h.select('other_ref', default_ref, refs, class_='refs')
162 }
162 }
163
163
164 # add parents of this fork also, but only if it's not empty
164 # add parents of this fork also, but only if it's not empty
165 if org_repo.parent and org_repo.parent.scm_instance.revisions:
165 if org_repo.parent and org_repo.parent.scm_instance.revisions:
166 c.default_other_repo = org_repo.parent.repo_name
166 c.default_other_repo = org_repo.parent.repo_name
167 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.parent.scm_instance)
167 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.parent.scm_instance)
168 c.other_repos.append((org_repo.parent.repo_name, org_repo.parent.repo_name))
168 c.other_repos.append((org_repo.parent.repo_name, org_repo.parent.repo_name))
169 other_repos_info[org_repo.parent.repo_name] = {
169 other_repos_info[org_repo.parent.repo_name] = {
170 'user': usr_data(org_repo.parent.user),
170 'user': usr_data(org_repo.parent.user),
171 'description': org_repo.parent.description,
171 'description': org_repo.parent.description,
172 'revs': h.select('other_ref', c.default_other_ref,
172 'revs': h.select('other_ref', c.default_other_ref,
173 c.default_other_refs, class_='refs')
173 c.default_other_refs, class_='refs')
174 }
174 }
175
175
176 c.other_repos_info = json.dumps(other_repos_info)
176 c.other_repos_info = json.dumps(other_repos_info)
177 # other repo owner
177 # other repo owner
178 c.review_members = []
178 c.review_members = []
179 return render('/pullrequests/pullrequest.html')
179 return render('/pullrequests/pullrequest.html')
180
180
181 @NotAnonymous()
181 @NotAnonymous()
182 def create(self, repo_name):
182 def create(self, repo_name):
183 repo = RepoModel()._get_repo(repo_name)
183 repo = RepoModel()._get_repo(repo_name)
184 try:
184 try:
185 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
185 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
186 except formencode.Invalid, errors:
186 except formencode.Invalid, errors:
187 log.error(traceback.format_exc())
187 log.error(traceback.format_exc())
188 if errors.error_dict.get('revisions'):
188 if errors.error_dict.get('revisions'):
189 msg = 'Revisions: %s' % errors.error_dict['revisions']
189 msg = 'Revisions: %s' % errors.error_dict['revisions']
190 elif errors.error_dict.get('pullrequest_title'):
190 elif errors.error_dict.get('pullrequest_title'):
191 msg = _('Pull request requires a title with min. 3 chars')
191 msg = _('Pull request requires a title with min. 3 chars')
192 else:
192 else:
193 msg = _('error during creation of pull request')
193 msg = _('error during creation of pull request')
194
194
195 h.flash(msg, 'error')
195 h.flash(msg, 'error')
196 return redirect(url('pullrequest_home', repo_name=repo_name))
196 return redirect(url('pullrequest_home', repo_name=repo_name))
197
197
198 org_repo = _form['org_repo']
198 org_repo = _form['org_repo']
199 org_ref = _form['org_ref']
199 org_ref = 'rev:merge:%s' % _form['merge_rev']
200 other_repo = _form['other_repo']
200 other_repo = _form['other_repo']
201 other_ref = _form['other_ref']
201 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
202 revisions = _form['revisions']
202 revisions = _form['revisions']
203 reviewers = _form['review_members']
203 reviewers = _form['review_members']
204
204
205 title = _form['pullrequest_title']
205 title = _form['pullrequest_title']
206 description = _form['pullrequest_desc']
206 description = _form['pullrequest_desc']
207
207
208 try:
208 try:
209 pull_request = PullRequestModel().create(
209 pull_request = PullRequestModel().create(
210 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
210 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
211 other_ref, revisions, reviewers, title, description
211 other_ref, revisions, reviewers, title, description
212 )
212 )
213 Session().commit()
213 Session().commit()
214 h.flash(_('Successfully opened new pull request'),
214 h.flash(_('Successfully opened new pull request'),
215 category='success')
215 category='success')
216 except Exception:
216 except Exception:
217 h.flash(_('Error occurred during sending pull request'),
217 h.flash(_('Error occurred during sending pull request'),
218 category='error')
218 category='error')
219 log.error(traceback.format_exc())
219 log.error(traceback.format_exc())
220 return redirect(url('pullrequest_home', repo_name=repo_name))
220 return redirect(url('pullrequest_home', repo_name=repo_name))
221
221
222 return redirect(url('pullrequest_show', repo_name=other_repo,
222 return redirect(url('pullrequest_show', repo_name=other_repo,
223 pull_request_id=pull_request.pull_request_id))
223 pull_request_id=pull_request.pull_request_id))
224
224
225 @NotAnonymous()
225 @NotAnonymous()
226 @jsonify
226 @jsonify
227 def update(self, repo_name, pull_request_id):
227 def update(self, repo_name, pull_request_id):
228 pull_request = PullRequest.get_or_404(pull_request_id)
228 pull_request = PullRequest.get_or_404(pull_request_id)
229 if pull_request.is_closed():
229 if pull_request.is_closed():
230 raise HTTPForbidden()
230 raise HTTPForbidden()
231 #only owner or admin can update it
231 #only owner or admin can update it
232 owner = pull_request.author.user_id == c.rhodecode_user.user_id
232 owner = pull_request.author.user_id == c.rhodecode_user.user_id
233 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
233 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
234 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
234 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
235 request.POST.get('reviewers_ids', '').split(',')))
235 request.POST.get('reviewers_ids', '').split(',')))
236
236
237 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
237 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
238 Session().commit()
238 Session().commit()
239 return True
239 return True
240 raise HTTPForbidden()
240 raise HTTPForbidden()
241
241
242 @NotAnonymous()
242 @NotAnonymous()
243 @jsonify
243 @jsonify
244 def delete(self, repo_name, pull_request_id):
244 def delete(self, repo_name, pull_request_id):
245 pull_request = PullRequest.get_or_404(pull_request_id)
245 pull_request = PullRequest.get_or_404(pull_request_id)
246 #only owner can delete it !
246 #only owner can delete it !
247 if pull_request.author.user_id == c.rhodecode_user.user_id:
247 if pull_request.author.user_id == c.rhodecode_user.user_id:
248 PullRequestModel().delete(pull_request)
248 PullRequestModel().delete(pull_request)
249 Session().commit()
249 Session().commit()
250 h.flash(_('Successfully deleted pull request'),
250 h.flash(_('Successfully deleted pull request'),
251 category='success')
251 category='success')
252 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
252 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
253 raise HTTPForbidden()
253 raise HTTPForbidden()
254
254
255 def _load_compare_data(self, pull_request, enable_comments=True):
255 def _load_compare_data(self, pull_request, enable_comments=True):
256 """
256 """
257 Load context data needed for generating compare diff
257 Load context data needed for generating compare diff
258
258
259 :param pull_request:
259 :param pull_request:
260 :type pull_request:
260 :type pull_request:
261 """
261 """
262 org_repo = pull_request.org_repo
262 org_repo = pull_request.org_repo
263 (org_ref_type,
263 (org_ref_type,
264 org_ref_name,
264 org_ref_name,
265 org_ref_rev) = pull_request.org_ref.split(':')
265 org_ref_rev) = pull_request.org_ref.split(':')
266
266
267 other_repo = org_repo
267 other_repo = org_repo
268 (other_ref_type,
268 (other_ref_type,
269 other_ref_name,
269 other_ref_name,
270 other_ref_rev) = pull_request.other_ref.split(':')
270 other_ref_rev) = pull_request.other_ref.split(':')
271
271
272 # despite opening revisions for bookmarks/branches/tags, we always
272 # despite opening revisions for bookmarks/branches/tags, we always
273 # convert this to rev to prevent changes after bookmark or branch change
273 # convert this to rev to prevent changes after bookmark or branch change
274 org_ref = ('rev', org_ref_rev)
274 org_ref = ('rev', org_ref_rev)
275 other_ref = ('rev', other_ref_rev)
275 other_ref = ('rev', other_ref_rev)
276
276
277 c.org_repo = org_repo
277 c.org_repo = org_repo
278 c.other_repo = other_repo
278 c.other_repo = other_repo
279
279
280 c.fulldiff = fulldiff = request.GET.get('fulldiff')
280 c.fulldiff = fulldiff = request.GET.get('fulldiff')
281
281
282 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
282 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
283
283
284 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
285 if c.cs_ranges[0].parents
286 else EmptyChangeset(), 'raw_id'))
287
288 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
284 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
289
285
290 c.org_ref = org_ref[1]
286 c.org_ref = org_ref[1]
291 c.org_ref_type = org_ref[0]
287 c.org_ref_type = org_ref[0]
292 c.other_ref = other_ref[1]
288 c.other_ref = other_ref[1]
293 c.other_ref_type = other_ref[0]
289 c.other_ref_type = other_ref[0]
294
290
295 diff_limit = self.cut_off_limit if not fulldiff else None
291 diff_limit = self.cut_off_limit if not fulldiff else None
296
292
297 #we swap org/other ref since we run a simple diff on one repo
293 #we swap org/other ref since we run a simple diff on one repo
298 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
294 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
299
295
300 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
296 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
301 diff_limit=diff_limit)
297 diff_limit=diff_limit)
302 _parsed = diff_processor.prepare()
298 _parsed = diff_processor.prepare()
303
299
304 c.limited_diff = False
300 c.limited_diff = False
305 if isinstance(_parsed, LimitedDiffContainer):
301 if isinstance(_parsed, LimitedDiffContainer):
306 c.limited_diff = True
302 c.limited_diff = True
307
303
308 c.files = []
304 c.files = []
309 c.changes = {}
305 c.changes = {}
310 c.lines_added = 0
306 c.lines_added = 0
311 c.lines_deleted = 0
307 c.lines_deleted = 0
312 for f in _parsed:
308 for f in _parsed:
313 st = f['stats']
309 st = f['stats']
314 if st[0] != 'b':
310 if st[0] != 'b':
315 c.lines_added += st[0]
311 c.lines_added += st[0]
316 c.lines_deleted += st[1]
312 c.lines_deleted += st[1]
317 fid = h.FID('', f['filename'])
313 fid = h.FID('', f['filename'])
318 c.files.append([fid, f['operation'], f['filename'], f['stats']])
314 c.files.append([fid, f['operation'], f['filename'], f['stats']])
319 diff = diff_processor.as_html(enable_comments=enable_comments,
315 diff = diff_processor.as_html(enable_comments=enable_comments,
320 parsed_lines=[f])
316 parsed_lines=[f])
321 c.changes[fid] = [f['operation'], f['filename'], diff]
317 c.changes[fid] = [f['operation'], f['filename'], diff]
322
318
323 def show(self, repo_name, pull_request_id):
319 def show(self, repo_name, pull_request_id):
324 repo_model = RepoModel()
320 repo_model = RepoModel()
325 c.users_array = repo_model.get_users_js()
321 c.users_array = repo_model.get_users_js()
326 c.users_groups_array = repo_model.get_users_groups_js()
322 c.users_groups_array = repo_model.get_users_groups_js()
327 c.pull_request = PullRequest.get_or_404(pull_request_id)
323 c.pull_request = PullRequest.get_or_404(pull_request_id)
328 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
324 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
329 cc_model = ChangesetCommentsModel()
325 cc_model = ChangesetCommentsModel()
330 cs_model = ChangesetStatusModel()
326 cs_model = ChangesetStatusModel()
331 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
327 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
332 pull_request=c.pull_request,
328 pull_request=c.pull_request,
333 with_revisions=True)
329 with_revisions=True)
334
330
335 cs_statuses = defaultdict(list)
331 cs_statuses = defaultdict(list)
336 for st in _cs_statuses:
332 for st in _cs_statuses:
337 cs_statuses[st.author.username] += [st]
333 cs_statuses[st.author.username] += [st]
338
334
339 c.pull_request_reviewers = []
335 c.pull_request_reviewers = []
340 c.pull_request_pending_reviewers = []
336 c.pull_request_pending_reviewers = []
341 for o in c.pull_request.reviewers:
337 for o in c.pull_request.reviewers:
342 st = cs_statuses.get(o.user.username, None)
338 st = cs_statuses.get(o.user.username, None)
343 if st:
339 if st:
344 sorter = lambda k: k.version
340 sorter = lambda k: k.version
345 st = [(x, list(y)[0])
341 st = [(x, list(y)[0])
346 for x, y in (groupby(sorted(st, key=sorter), sorter))]
342 for x, y in (groupby(sorted(st, key=sorter), sorter))]
347 else:
343 else:
348 c.pull_request_pending_reviewers.append(o.user)
344 c.pull_request_pending_reviewers.append(o.user)
349 c.pull_request_reviewers.append([o.user, st])
345 c.pull_request_reviewers.append([o.user, st])
350
346
351 # pull_requests repo_name we opened it against
347 # pull_requests repo_name we opened it against
352 # ie. other_repo must match
348 # ie. other_repo must match
353 if repo_name != c.pull_request.other_repo.repo_name:
349 if repo_name != c.pull_request.other_repo.repo_name:
354 raise HTTPNotFound
350 raise HTTPNotFound
355
351
356 # load compare data into template context
352 # load compare data into template context
357 enable_comments = not c.pull_request.is_closed()
353 enable_comments = not c.pull_request.is_closed()
358 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
354 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
359
355
360 # inline comments
356 # inline comments
361 c.inline_cnt = 0
357 c.inline_cnt = 0
362 c.inline_comments = cc_model.get_inline_comments(
358 c.inline_comments = cc_model.get_inline_comments(
363 c.rhodecode_db_repo.repo_id,
359 c.rhodecode_db_repo.repo_id,
364 pull_request=pull_request_id)
360 pull_request=pull_request_id)
365 # count inline comments
361 # count inline comments
366 for __, lines in c.inline_comments:
362 for __, lines in c.inline_comments:
367 for comments in lines.values():
363 for comments in lines.values():
368 c.inline_cnt += len(comments)
364 c.inline_cnt += len(comments)
369 # comments
365 # comments
370 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
366 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
371 pull_request=pull_request_id)
367 pull_request=pull_request_id)
372
368
373 try:
369 try:
374 cur_status = c.statuses[c.pull_request.revisions[0]][0]
370 cur_status = c.statuses[c.pull_request.revisions[0]][0]
375 except:
371 except:
376 log.error(traceback.format_exc())
372 log.error(traceback.format_exc())
377 cur_status = 'undefined'
373 cur_status = 'undefined'
378 if c.pull_request.is_closed() and 0:
374 if c.pull_request.is_closed() and 0:
379 c.current_changeset_status = cur_status
375 c.current_changeset_status = cur_status
380 else:
376 else:
381 # changeset(pull-request) status calulation based on reviewers
377 # changeset(pull-request) status calulation based on reviewers
382 c.current_changeset_status = cs_model.calculate_status(
378 c.current_changeset_status = cs_model.calculate_status(
383 c.pull_request_reviewers,
379 c.pull_request_reviewers,
384 )
380 )
385 c.changeset_statuses = ChangesetStatus.STATUSES
381 c.changeset_statuses = ChangesetStatus.STATUSES
386
382
387 c.as_form = False
383 c.as_form = False
384 c.ancestor = None # there is one - but right here we don't know which
388 return render('/pullrequests/pullrequest_show.html')
385 return render('/pullrequests/pullrequest_show.html')
389
386
390 @NotAnonymous()
387 @NotAnonymous()
391 @jsonify
388 @jsonify
392 def comment(self, repo_name, pull_request_id):
389 def comment(self, repo_name, pull_request_id):
393 pull_request = PullRequest.get_or_404(pull_request_id)
390 pull_request = PullRequest.get_or_404(pull_request_id)
394 if pull_request.is_closed():
391 if pull_request.is_closed():
395 raise HTTPForbidden()
392 raise HTTPForbidden()
396
393
397 status = request.POST.get('changeset_status')
394 status = request.POST.get('changeset_status')
398 change_status = request.POST.get('change_changeset_status')
395 change_status = request.POST.get('change_changeset_status')
399 text = request.POST.get('text')
396 text = request.POST.get('text')
400 close_pr = request.POST.get('save_close')
397 close_pr = request.POST.get('save_close')
401
398
402 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
399 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
403 if status and change_status and allowed_to_change_status:
400 if status and change_status and allowed_to_change_status:
404 _def = (_('status change -> %s')
401 _def = (_('status change -> %s')
405 % ChangesetStatus.get_status_lbl(status))
402 % ChangesetStatus.get_status_lbl(status))
406 if close_pr:
403 if close_pr:
407 _def = _('Closing with') + ' ' + _def
404 _def = _('Closing with') + ' ' + _def
408 text = text or _def
405 text = text or _def
409 comm = ChangesetCommentsModel().create(
406 comm = ChangesetCommentsModel().create(
410 text=text,
407 text=text,
411 repo=c.rhodecode_db_repo.repo_id,
408 repo=c.rhodecode_db_repo.repo_id,
412 user=c.rhodecode_user.user_id,
409 user=c.rhodecode_user.user_id,
413 pull_request=pull_request_id,
410 pull_request=pull_request_id,
414 f_path=request.POST.get('f_path'),
411 f_path=request.POST.get('f_path'),
415 line_no=request.POST.get('line'),
412 line_no=request.POST.get('line'),
416 status_change=(ChangesetStatus.get_status_lbl(status)
413 status_change=(ChangesetStatus.get_status_lbl(status)
417 if status and change_status
414 if status and change_status
418 and allowed_to_change_status else None),
415 and allowed_to_change_status else None),
419 closing_pr=close_pr
416 closing_pr=close_pr
420 )
417 )
421
418
422 action_logger(self.rhodecode_user,
419 action_logger(self.rhodecode_user,
423 'user_commented_pull_request:%s' % pull_request_id,
420 'user_commented_pull_request:%s' % pull_request_id,
424 c.rhodecode_db_repo, self.ip_addr, self.sa)
421 c.rhodecode_db_repo, self.ip_addr, self.sa)
425
422
426 if allowed_to_change_status:
423 if allowed_to_change_status:
427 # get status if set !
424 # get status if set !
428 if status and change_status:
425 if status and change_status:
429 ChangesetStatusModel().set_status(
426 ChangesetStatusModel().set_status(
430 c.rhodecode_db_repo.repo_id,
427 c.rhodecode_db_repo.repo_id,
431 status,
428 status,
432 c.rhodecode_user.user_id,
429 c.rhodecode_user.user_id,
433 comm,
430 comm,
434 pull_request=pull_request_id
431 pull_request=pull_request_id
435 )
432 )
436
433
437 if close_pr:
434 if close_pr:
438 if status in ['rejected', 'approved']:
435 if status in ['rejected', 'approved']:
439 PullRequestModel().close_pull_request(pull_request_id)
436 PullRequestModel().close_pull_request(pull_request_id)
440 action_logger(self.rhodecode_user,
437 action_logger(self.rhodecode_user,
441 'user_closed_pull_request:%s' % pull_request_id,
438 'user_closed_pull_request:%s' % pull_request_id,
442 c.rhodecode_db_repo, self.ip_addr, self.sa)
439 c.rhodecode_db_repo, self.ip_addr, self.sa)
443 else:
440 else:
444 h.flash(_('Closing pull request on other statuses than '
441 h.flash(_('Closing pull request on other statuses than '
445 'rejected or approved forbidden'),
442 'rejected or approved forbidden'),
446 category='warning')
443 category='warning')
447
444
448 Session().commit()
445 Session().commit()
449
446
450 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
447 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
451 return redirect(h.url('pullrequest_show', repo_name=repo_name,
448 return redirect(h.url('pullrequest_show', repo_name=repo_name,
452 pull_request_id=pull_request_id))
449 pull_request_id=pull_request_id))
453
450
454 data = {
451 data = {
455 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
452 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
456 }
453 }
457 if comm:
454 if comm:
458 c.co = comm
455 c.co = comm
459 data.update(comm.get_dict())
456 data.update(comm.get_dict())
460 data.update({'rendered_text':
457 data.update({'rendered_text':
461 render('changeset/changeset_comment_block.html')})
458 render('changeset/changeset_comment_block.html')})
462
459
463 return data
460 return data
464
461
465 @NotAnonymous()
462 @NotAnonymous()
466 @jsonify
463 @jsonify
467 def delete_comment(self, repo_name, comment_id):
464 def delete_comment(self, repo_name, comment_id):
468 co = ChangesetComment.get(comment_id)
465 co = ChangesetComment.get(comment_id)
469 if co.pull_request.is_closed():
466 if co.pull_request.is_closed():
470 #don't allow deleting comments on closed pull request
467 #don't allow deleting comments on closed pull request
471 raise HTTPForbidden()
468 raise HTTPForbidden()
472
469
473 owner = co.author.user_id == c.rhodecode_user.user_id
470 owner = co.author.user_id == c.rhodecode_user.user_id
474 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
471 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
475 ChangesetCommentsModel().delete(comment=co)
472 ChangesetCommentsModel().delete(comment=co)
476 Session().commit()
473 Session().commit()
477 return True
474 return True
478 else:
475 else:
479 raise HTTPForbidden()
476 raise HTTPForbidden()
@@ -1,398 +1,401 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 from formencode import All
25 from formencode import All
26
26
27 from pylons.i18n.translation import _
27 from pylons.i18n.translation import _
28
28
29 from rhodecode.model import validators as v
29 from rhodecode.model import validators as v
30 from rhodecode import BACKENDS
30 from rhodecode import BACKENDS
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class LoginForm(formencode.Schema):
35 class LoginForm(formencode.Schema):
36 allow_extra_fields = True
36 allow_extra_fields = True
37 filter_extra_fields = True
37 filter_extra_fields = True
38 username = v.UnicodeString(
38 username = v.UnicodeString(
39 strip=True,
39 strip=True,
40 min=1,
40 min=1,
41 not_empty=True,
41 not_empty=True,
42 messages={
42 messages={
43 'empty': _(u'Please enter a login'),
43 'empty': _(u'Please enter a login'),
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 )
45 )
46
46
47 password = v.UnicodeString(
47 password = v.UnicodeString(
48 strip=False,
48 strip=False,
49 min=3,
49 min=3,
50 not_empty=True,
50 not_empty=True,
51 messages={
51 messages={
52 'empty': _(u'Please enter a password'),
52 'empty': _(u'Please enter a password'),
53 'tooShort': _(u'Enter %(min)i characters or more')}
53 'tooShort': _(u'Enter %(min)i characters or more')}
54 )
54 )
55
55
56 remember = v.StringBoolean(if_missing=False)
56 remember = v.StringBoolean(if_missing=False)
57
57
58 chained_validators = [v.ValidAuth()]
58 chained_validators = [v.ValidAuth()]
59
59
60
60
61 def UserForm(edit=False, old_data={}):
61 def UserForm(edit=False, old_data={}):
62 class _UserForm(formencode.Schema):
62 class _UserForm(formencode.Schema):
63 allow_extra_fields = True
63 allow_extra_fields = True
64 filter_extra_fields = True
64 filter_extra_fields = True
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 v.ValidUsername(edit, old_data))
66 v.ValidUsername(edit, old_data))
67 if edit:
67 if edit:
68 new_password = All(
68 new_password = All(
69 v.ValidPassword(),
69 v.ValidPassword(),
70 v.UnicodeString(strip=False, min=6, not_empty=False)
70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 )
71 )
72 password_confirmation = All(
72 password_confirmation = All(
73 v.ValidPassword(),
73 v.ValidPassword(),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 )
75 )
76 admin = v.StringBoolean(if_missing=False)
76 admin = v.StringBoolean(if_missing=False)
77 else:
77 else:
78 password = All(
78 password = All(
79 v.ValidPassword(),
79 v.ValidPassword(),
80 v.UnicodeString(strip=False, min=6, not_empty=True)
80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 )
81 )
82 password_confirmation = All(
82 password_confirmation = All(
83 v.ValidPassword(),
83 v.ValidPassword(),
84 v.UnicodeString(strip=False, min=6, not_empty=False)
84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 )
85 )
86
86
87 active = v.StringBoolean(if_missing=False)
87 active = v.StringBoolean(if_missing=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91
91
92 chained_validators = [v.ValidPasswordsMatch()]
92 chained_validators = [v.ValidPasswordsMatch()]
93
93
94 return _UserForm
94 return _UserForm
95
95
96
96
97 def UserGroupForm(edit=False, old_data={}, available_members=[]):
97 def UserGroupForm(edit=False, old_data={}, available_members=[]):
98 class _UserGroupForm(formencode.Schema):
98 class _UserGroupForm(formencode.Schema):
99 allow_extra_fields = True
99 allow_extra_fields = True
100 filter_extra_fields = True
100 filter_extra_fields = True
101
101
102 users_group_name = All(
102 users_group_name = All(
103 v.UnicodeString(strip=True, min=1, not_empty=True),
103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 v.ValidUserGroup(edit, old_data)
104 v.ValidUserGroup(edit, old_data)
105 )
105 )
106
106
107 users_group_active = v.StringBoolean(if_missing=False)
107 users_group_active = v.StringBoolean(if_missing=False)
108
108
109 if edit:
109 if edit:
110 users_group_members = v.OneOf(
110 users_group_members = v.OneOf(
111 available_members, hideList=False, testValueList=True,
111 available_members, hideList=False, testValueList=True,
112 if_missing=None, not_empty=False
112 if_missing=None, not_empty=False
113 )
113 )
114
114
115 return _UserGroupForm
115 return _UserGroupForm
116
116
117
117
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[],
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[],
119 can_create_in_root=False):
119 can_create_in_root=False):
120 class _ReposGroupForm(formencode.Schema):
120 class _ReposGroupForm(formencode.Schema):
121 allow_extra_fields = True
121 allow_extra_fields = True
122 filter_extra_fields = False
122 filter_extra_fields = False
123
123
124 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
125 v.SlugifyName())
125 v.SlugifyName())
126 group_description = v.UnicodeString(strip=True, min=1,
126 group_description = v.UnicodeString(strip=True, min=1,
127 not_empty=False)
127 not_empty=False)
128 if edit:
128 if edit:
129 #FIXME: do a special check that we cannot move a group to one of
129 #FIXME: do a special check that we cannot move a group to one of
130 #it's children
130 #it's children
131 pass
131 pass
132 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
132 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
133 v.OneOf(available_groups, hideList=False,
133 v.OneOf(available_groups, hideList=False,
134 testValueList=True,
134 testValueList=True,
135 if_missing=None, not_empty=True))
135 if_missing=None, not_empty=True))
136 enable_locking = v.StringBoolean(if_missing=False)
136 enable_locking = v.StringBoolean(if_missing=False)
137 recursive = v.StringBoolean(if_missing=False)
137 recursive = v.StringBoolean(if_missing=False)
138 chained_validators = [v.ValidReposGroup(edit, old_data),
138 chained_validators = [v.ValidReposGroup(edit, old_data),
139 v.ValidPerms('group')]
139 v.ValidPerms('group')]
140
140
141 return _ReposGroupForm
141 return _ReposGroupForm
142
142
143
143
144 def RegisterForm(edit=False, old_data={}):
144 def RegisterForm(edit=False, old_data={}):
145 class _RegisterForm(formencode.Schema):
145 class _RegisterForm(formencode.Schema):
146 allow_extra_fields = True
146 allow_extra_fields = True
147 filter_extra_fields = True
147 filter_extra_fields = True
148 username = All(
148 username = All(
149 v.ValidUsername(edit, old_data),
149 v.ValidUsername(edit, old_data),
150 v.UnicodeString(strip=True, min=1, not_empty=True)
150 v.UnicodeString(strip=True, min=1, not_empty=True)
151 )
151 )
152 password = All(
152 password = All(
153 v.ValidPassword(),
153 v.ValidPassword(),
154 v.UnicodeString(strip=False, min=6, not_empty=True)
154 v.UnicodeString(strip=False, min=6, not_empty=True)
155 )
155 )
156 password_confirmation = All(
156 password_confirmation = All(
157 v.ValidPassword(),
157 v.ValidPassword(),
158 v.UnicodeString(strip=False, min=6, not_empty=True)
158 v.UnicodeString(strip=False, min=6, not_empty=True)
159 )
159 )
160 active = v.StringBoolean(if_missing=False)
160 active = v.StringBoolean(if_missing=False)
161 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
161 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
162 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
162 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
163 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
163 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
164
164
165 chained_validators = [v.ValidPasswordsMatch()]
165 chained_validators = [v.ValidPasswordsMatch()]
166
166
167 return _RegisterForm
167 return _RegisterForm
168
168
169
169
170 def PasswordResetForm():
170 def PasswordResetForm():
171 class _PasswordResetForm(formencode.Schema):
171 class _PasswordResetForm(formencode.Schema):
172 allow_extra_fields = True
172 allow_extra_fields = True
173 filter_extra_fields = True
173 filter_extra_fields = True
174 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
174 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
175 return _PasswordResetForm
175 return _PasswordResetForm
176
176
177
177
178 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
178 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
179 repo_groups=[], landing_revs=[]):
179 repo_groups=[], landing_revs=[]):
180 class _RepoForm(formencode.Schema):
180 class _RepoForm(formencode.Schema):
181 allow_extra_fields = True
181 allow_extra_fields = True
182 filter_extra_fields = False
182 filter_extra_fields = False
183 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
183 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
184 v.SlugifyName())
184 v.SlugifyName())
185 repo_group = All(v.CanWriteGroup(),
185 repo_group = All(v.CanWriteGroup(),
186 v.OneOf(repo_groups, hideList=True))
186 v.OneOf(repo_groups, hideList=True))
187 repo_type = v.OneOf(supported_backends)
187 repo_type = v.OneOf(supported_backends)
188 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
188 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
189 repo_private = v.StringBoolean(if_missing=False)
189 repo_private = v.StringBoolean(if_missing=False)
190 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
190 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
191 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
191 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
192
192
193 repo_enable_statistics = v.StringBoolean(if_missing=False)
193 repo_enable_statistics = v.StringBoolean(if_missing=False)
194 repo_enable_downloads = v.StringBoolean(if_missing=False)
194 repo_enable_downloads = v.StringBoolean(if_missing=False)
195 repo_enable_locking = v.StringBoolean(if_missing=False)
195 repo_enable_locking = v.StringBoolean(if_missing=False)
196
196
197 if edit:
197 if edit:
198 #this is repo owner
198 #this is repo owner
199 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
199 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
200
200
201 chained_validators = [v.ValidCloneUri(),
201 chained_validators = [v.ValidCloneUri(),
202 v.ValidRepoName(edit, old_data),
202 v.ValidRepoName(edit, old_data),
203 v.ValidPerms()]
203 v.ValidPerms()]
204 return _RepoForm
204 return _RepoForm
205
205
206
206
207 def RepoFieldForm():
207 def RepoFieldForm():
208 class _RepoFieldForm(formencode.Schema):
208 class _RepoFieldForm(formencode.Schema):
209 filter_extra_fields = True
209 filter_extra_fields = True
210 allow_extra_fields = True
210 allow_extra_fields = True
211
211
212 new_field_key = All(v.FieldKey(),
212 new_field_key = All(v.FieldKey(),
213 v.UnicodeString(strip=True, min=3, not_empty=True))
213 v.UnicodeString(strip=True, min=3, not_empty=True))
214 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
214 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
215 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
215 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
216 if_missing='str')
216 if_missing='str')
217 new_field_label = v.UnicodeString(not_empty=False)
217 new_field_label = v.UnicodeString(not_empty=False)
218 new_field_desc = v.UnicodeString(not_empty=False)
218 new_field_desc = v.UnicodeString(not_empty=False)
219
219
220 return _RepoFieldForm
220 return _RepoFieldForm
221
221
222
222
223 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
223 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
224 repo_groups=[], landing_revs=[]):
224 repo_groups=[], landing_revs=[]):
225 class _RepoForm(formencode.Schema):
225 class _RepoForm(formencode.Schema):
226 allow_extra_fields = True
226 allow_extra_fields = True
227 filter_extra_fields = False
227 filter_extra_fields = False
228 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
228 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
229 v.SlugifyName())
229 v.SlugifyName())
230 repo_group = All(v.CanWriteGroup(),
230 repo_group = All(v.CanWriteGroup(),
231 v.OneOf(repo_groups, hideList=True))
231 v.OneOf(repo_groups, hideList=True))
232 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
232 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
233 repo_private = v.StringBoolean(if_missing=False)
233 repo_private = v.StringBoolean(if_missing=False)
234 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
234 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
235 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
235 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
236
236
237 chained_validators = [v.ValidCloneUri(),
237 chained_validators = [v.ValidCloneUri(),
238 v.ValidRepoName(edit, old_data),
238 v.ValidRepoName(edit, old_data),
239 v.ValidPerms(),
239 v.ValidPerms(),
240 v.ValidSettings()]
240 v.ValidSettings()]
241 return _RepoForm
241 return _RepoForm
242
242
243
243
244 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
244 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
245 repo_groups=[], landing_revs=[]):
245 repo_groups=[], landing_revs=[]):
246 class _RepoForkForm(formencode.Schema):
246 class _RepoForkForm(formencode.Schema):
247 allow_extra_fields = True
247 allow_extra_fields = True
248 filter_extra_fields = False
248 filter_extra_fields = False
249 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
249 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
250 v.SlugifyName())
250 v.SlugifyName())
251 repo_group = All(v.CanWriteGroup(),
251 repo_group = All(v.CanWriteGroup(),
252 v.OneOf(repo_groups, hideList=True))
252 v.OneOf(repo_groups, hideList=True))
253 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
253 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
254 description = v.UnicodeString(strip=True, min=1, not_empty=True)
254 description = v.UnicodeString(strip=True, min=1, not_empty=True)
255 private = v.StringBoolean(if_missing=False)
255 private = v.StringBoolean(if_missing=False)
256 copy_permissions = v.StringBoolean(if_missing=False)
256 copy_permissions = v.StringBoolean(if_missing=False)
257 update_after_clone = v.StringBoolean(if_missing=False)
257 update_after_clone = v.StringBoolean(if_missing=False)
258 fork_parent_id = v.UnicodeString()
258 fork_parent_id = v.UnicodeString()
259 chained_validators = [v.ValidForkName(edit, old_data)]
259 chained_validators = [v.ValidForkName(edit, old_data)]
260 landing_rev = v.OneOf(landing_revs, hideList=True)
260 landing_rev = v.OneOf(landing_revs, hideList=True)
261
261
262 return _RepoForkForm
262 return _RepoForkForm
263
263
264
264
265 def ApplicationSettingsForm():
265 def ApplicationSettingsForm():
266 class _ApplicationSettingsForm(formencode.Schema):
266 class _ApplicationSettingsForm(formencode.Schema):
267 allow_extra_fields = True
267 allow_extra_fields = True
268 filter_extra_fields = False
268 filter_extra_fields = False
269 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
269 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
270 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
270 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
271 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
271 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
272
272
273 return _ApplicationSettingsForm
273 return _ApplicationSettingsForm
274
274
275
275
276 def ApplicationVisualisationForm():
276 def ApplicationVisualisationForm():
277 class _ApplicationVisualisationForm(formencode.Schema):
277 class _ApplicationVisualisationForm(formencode.Schema):
278 allow_extra_fields = True
278 allow_extra_fields = True
279 filter_extra_fields = False
279 filter_extra_fields = False
280 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
280 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
281 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
281 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
282 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
282 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
283
283
284 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
284 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
285 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
285 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
286 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
286 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
287
287
288 return _ApplicationVisualisationForm
288 return _ApplicationVisualisationForm
289
289
290
290
291 def ApplicationUiSettingsForm():
291 def ApplicationUiSettingsForm():
292 class _ApplicationUiSettingsForm(formencode.Schema):
292 class _ApplicationUiSettingsForm(formencode.Schema):
293 allow_extra_fields = True
293 allow_extra_fields = True
294 filter_extra_fields = False
294 filter_extra_fields = False
295 web_push_ssl = v.StringBoolean(if_missing=False)
295 web_push_ssl = v.StringBoolean(if_missing=False)
296 paths_root_path = All(
296 paths_root_path = All(
297 v.ValidPath(),
297 v.ValidPath(),
298 v.UnicodeString(strip=True, min=1, not_empty=True)
298 v.UnicodeString(strip=True, min=1, not_empty=True)
299 )
299 )
300 hooks_changegroup_update = v.StringBoolean(if_missing=False)
300 hooks_changegroup_update = v.StringBoolean(if_missing=False)
301 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
301 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
302 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
302 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
303 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
303 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
304
304
305 extensions_largefiles = v.StringBoolean(if_missing=False)
305 extensions_largefiles = v.StringBoolean(if_missing=False)
306 extensions_hgsubversion = v.StringBoolean(if_missing=False)
306 extensions_hgsubversion = v.StringBoolean(if_missing=False)
307 extensions_hggit = v.StringBoolean(if_missing=False)
307 extensions_hggit = v.StringBoolean(if_missing=False)
308
308
309 return _ApplicationUiSettingsForm
309 return _ApplicationUiSettingsForm
310
310
311
311
312 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
312 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
313 register_choices, create_choices, fork_choices):
313 register_choices, create_choices, fork_choices):
314 class _DefaultPermissionsForm(formencode.Schema):
314 class _DefaultPermissionsForm(formencode.Schema):
315 allow_extra_fields = True
315 allow_extra_fields = True
316 filter_extra_fields = True
316 filter_extra_fields = True
317 overwrite_default_repo = v.StringBoolean(if_missing=False)
317 overwrite_default_repo = v.StringBoolean(if_missing=False)
318 overwrite_default_group = v.StringBoolean(if_missing=False)
318 overwrite_default_group = v.StringBoolean(if_missing=False)
319 anonymous = v.StringBoolean(if_missing=False)
319 anonymous = v.StringBoolean(if_missing=False)
320 default_repo_perm = v.OneOf(repo_perms_choices)
320 default_repo_perm = v.OneOf(repo_perms_choices)
321 default_group_perm = v.OneOf(group_perms_choices)
321 default_group_perm = v.OneOf(group_perms_choices)
322 default_register = v.OneOf(register_choices)
322 default_register = v.OneOf(register_choices)
323 default_create = v.OneOf(create_choices)
323 default_create = v.OneOf(create_choices)
324 default_fork = v.OneOf(fork_choices)
324 default_fork = v.OneOf(fork_choices)
325
325
326 return _DefaultPermissionsForm
326 return _DefaultPermissionsForm
327
327
328
328
329 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
329 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
330 class _DefaultsForm(formencode.Schema):
330 class _DefaultsForm(formencode.Schema):
331 allow_extra_fields = True
331 allow_extra_fields = True
332 filter_extra_fields = True
332 filter_extra_fields = True
333 default_repo_type = v.OneOf(supported_backends)
333 default_repo_type = v.OneOf(supported_backends)
334 default_repo_private = v.StringBoolean(if_missing=False)
334 default_repo_private = v.StringBoolean(if_missing=False)
335 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
335 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
336 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
336 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
337 default_repo_enable_locking = v.StringBoolean(if_missing=False)
337 default_repo_enable_locking = v.StringBoolean(if_missing=False)
338
338
339 return _DefaultsForm
339 return _DefaultsForm
340
340
341
341
342 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
342 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
343 tls_kind_choices):
343 tls_kind_choices):
344 class _LdapSettingsForm(formencode.Schema):
344 class _LdapSettingsForm(formencode.Schema):
345 allow_extra_fields = True
345 allow_extra_fields = True
346 filter_extra_fields = True
346 filter_extra_fields = True
347 #pre_validators = [LdapLibValidator]
347 #pre_validators = [LdapLibValidator]
348 ldap_active = v.StringBoolean(if_missing=False)
348 ldap_active = v.StringBoolean(if_missing=False)
349 ldap_host = v.UnicodeString(strip=True,)
349 ldap_host = v.UnicodeString(strip=True,)
350 ldap_port = v.Number(strip=True,)
350 ldap_port = v.Number(strip=True,)
351 ldap_tls_kind = v.OneOf(tls_kind_choices)
351 ldap_tls_kind = v.OneOf(tls_kind_choices)
352 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
352 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
353 ldap_dn_user = v.UnicodeString(strip=True,)
353 ldap_dn_user = v.UnicodeString(strip=True,)
354 ldap_dn_pass = v.UnicodeString(strip=True,)
354 ldap_dn_pass = v.UnicodeString(strip=True,)
355 ldap_base_dn = v.UnicodeString(strip=True,)
355 ldap_base_dn = v.UnicodeString(strip=True,)
356 ldap_filter = v.UnicodeString(strip=True,)
356 ldap_filter = v.UnicodeString(strip=True,)
357 ldap_search_scope = v.OneOf(search_scope_choices)
357 ldap_search_scope = v.OneOf(search_scope_choices)
358 ldap_attr_login = All(
358 ldap_attr_login = All(
359 v.AttrLoginValidator(),
359 v.AttrLoginValidator(),
360 v.UnicodeString(strip=True,)
360 v.UnicodeString(strip=True,)
361 )
361 )
362 ldap_attr_firstname = v.UnicodeString(strip=True,)
362 ldap_attr_firstname = v.UnicodeString(strip=True,)
363 ldap_attr_lastname = v.UnicodeString(strip=True,)
363 ldap_attr_lastname = v.UnicodeString(strip=True,)
364 ldap_attr_email = v.UnicodeString(strip=True,)
364 ldap_attr_email = v.UnicodeString(strip=True,)
365
365
366 return _LdapSettingsForm
366 return _LdapSettingsForm
367
367
368
368
369 def UserExtraEmailForm():
369 def UserExtraEmailForm():
370 class _UserExtraEmailForm(formencode.Schema):
370 class _UserExtraEmailForm(formencode.Schema):
371 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
371 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
372 return _UserExtraEmailForm
372 return _UserExtraEmailForm
373
373
374
374
375 def UserExtraIpForm():
375 def UserExtraIpForm():
376 class _UserExtraIpForm(formencode.Schema):
376 class _UserExtraIpForm(formencode.Schema):
377 ip = v.ValidIp()(not_empty=True)
377 ip = v.ValidIp()(not_empty=True)
378 return _UserExtraIpForm
378 return _UserExtraIpForm
379
379
380
380
381 def PullRequestForm(repo_id):
381 def PullRequestForm(repo_id):
382 class _PullRequestForm(formencode.Schema):
382 class _PullRequestForm(formencode.Schema):
383 allow_extra_fields = True
383 allow_extra_fields = True
384 filter_extra_fields = True
384 filter_extra_fields = True
385
385
386 user = v.UnicodeString(strip=True, required=True)
386 user = v.UnicodeString(strip=True, required=True)
387 org_repo = v.UnicodeString(strip=True, required=True)
387 org_repo = v.UnicodeString(strip=True, required=True)
388 org_ref = v.UnicodeString(strip=True, required=True)
388 org_ref = v.UnicodeString(strip=True, required=True)
389 other_repo = v.UnicodeString(strip=True, required=True)
389 other_repo = v.UnicodeString(strip=True, required=True)
390 other_ref = v.UnicodeString(strip=True, required=True)
390 other_ref = v.UnicodeString(strip=True, required=True)
391 revisions = All(#v.NotReviewedRevisions(repo_id)(),
391 revisions = All(#v.NotReviewedRevisions(repo_id)(),
392 v.UniqueList(not_empty=True))
392 v.UniqueList(not_empty=True))
393 review_members = v.UniqueList(not_empty=True)
393 review_members = v.UniqueList(not_empty=True)
394
394
395 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
395 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
396 pullrequest_desc = v.UnicodeString(strip=True, required=False)
396 pullrequest_desc = v.UnicodeString(strip=True, required=False)
397
397
398 ancestor_rev = v.UnicodeString(strip=True, required=True)
399 merge_rev = v.UnicodeString(strip=True, required=True)
400
398 return _PullRequestForm
401 return _PullRequestForm
@@ -1,255 +1,261 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_request
3 rhodecode.model.pull_request
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import datetime
27 import datetime
28 import re
28 import re
29
29
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.lib import helpers as h, unionrepo
33 from rhodecode.lib import helpers as h, unionrepo
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
36 ChangesetStatus
36 ChangesetStatus
37 from rhodecode.model.notification import NotificationModel
37 from rhodecode.model.notification import NotificationModel
38 from rhodecode.lib.utils2 import safe_unicode
38 from rhodecode.lib.utils2 import safe_unicode
39
39
40 from rhodecode.lib.vcs.utils.hgcompat import scmutil
40 from rhodecode.lib.vcs.utils.hgcompat import scmutil
41 from rhodecode.lib.vcs.utils import safe_str
41 from rhodecode.lib.vcs.utils import safe_str
42 from rhodecode.lib.vcs.backends.base import EmptyChangeset
42 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class PullRequestModel(BaseModel):
47 class PullRequestModel(BaseModel):
48
48
49 cls = PullRequest
49 cls = PullRequest
50
50
51 def __get_pull_request(self, pull_request):
51 def __get_pull_request(self, pull_request):
52 return self._get_instance(PullRequest, pull_request)
52 return self._get_instance(PullRequest, pull_request)
53
53
54 def get_all(self, repo):
54 def get_all(self, repo):
55 repo = self._get_repo(repo)
55 repo = self._get_repo(repo)
56 return PullRequest.query()\
56 return PullRequest.query()\
57 .filter(PullRequest.other_repo == repo)\
57 .filter(PullRequest.other_repo == repo)\
58 .order_by(PullRequest.created_on.desc())\
58 .order_by(PullRequest.created_on.desc())\
59 .all()
59 .all()
60
60
61 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
61 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
62 revisions, reviewers, title, description=None):
62 revisions, reviewers, title, description=None):
63 from rhodecode.model.changeset_status import ChangesetStatusModel
63 from rhodecode.model.changeset_status import ChangesetStatusModel
64
64
65 created_by_user = self._get_user(created_by)
65 created_by_user = self._get_user(created_by)
66 org_repo = self._get_repo(org_repo)
66 org_repo = self._get_repo(org_repo)
67 other_repo = self._get_repo(other_repo)
67 other_repo = self._get_repo(other_repo)
68
68
69 new = PullRequest()
69 new = PullRequest()
70 new.org_repo = org_repo
70 new.org_repo = org_repo
71 new.org_ref = org_ref
71 new.org_ref = org_ref
72 new.other_repo = other_repo
72 new.other_repo = other_repo
73 new.other_ref = other_ref
73 new.other_ref = other_ref
74 new.revisions = revisions
74 new.revisions = revisions
75 new.title = title
75 new.title = title
76 new.description = description
76 new.description = description
77 new.author = created_by_user
77 new.author = created_by_user
78 Session().add(new)
78 Session().add(new)
79 Session().flush()
79 Session().flush()
80 #members
80 #members
81 for member in set(reviewers):
81 for member in set(reviewers):
82 _usr = self._get_user(member)
82 _usr = self._get_user(member)
83 reviewer = PullRequestReviewers(_usr, new)
83 reviewer = PullRequestReviewers(_usr, new)
84 Session().add(reviewer)
84 Session().add(reviewer)
85
85
86 #reset state to under-review
86 #reset state to under-review
87 ChangesetStatusModel().set_status(
87 ChangesetStatusModel().set_status(
88 repo=org_repo,
88 repo=org_repo,
89 status=ChangesetStatus.STATUS_UNDER_REVIEW,
89 status=ChangesetStatus.STATUS_UNDER_REVIEW,
90 user=created_by_user,
90 user=created_by_user,
91 pull_request=new
91 pull_request=new
92 )
92 )
93 revision_data = [(x.raw_id, x.message)
93 revision_data = [(x.raw_id, x.message)
94 for x in map(org_repo.get_changeset, revisions)]
94 for x in map(org_repo.get_changeset, revisions)]
95 #notification to reviewers
95 #notification to reviewers
96 notif = NotificationModel()
96 notif = NotificationModel()
97
97
98 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
98 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
99 pull_request_id=new.pull_request_id,
99 pull_request_id=new.pull_request_id,
100 qualified=True,
100 qualified=True,
101 )
101 )
102 subject = safe_unicode(
102 subject = safe_unicode(
103 h.link_to(
103 h.link_to(
104 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
104 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
105 {'user': created_by_user.username,
105 {'user': created_by_user.username,
106 'pr_title': new.title,
106 'pr_title': new.title,
107 'pr_id': new.pull_request_id},
107 'pr_id': new.pull_request_id},
108 pr_url
108 pr_url
109 )
109 )
110 )
110 )
111 body = description
111 body = description
112 kwargs = {
112 kwargs = {
113 'pr_title': title,
113 'pr_title': title,
114 'pr_user_created': h.person(created_by_user.email),
114 'pr_user_created': h.person(created_by_user.email),
115 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
115 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
116 qualified=True,),
116 qualified=True,),
117 'pr_url': pr_url,
117 'pr_url': pr_url,
118 'pr_revisions': revision_data
118 'pr_revisions': revision_data
119 }
119 }
120
120
121 notif.create(created_by=created_by_user, subject=subject, body=body,
121 notif.create(created_by=created_by_user, subject=subject, body=body,
122 recipients=reviewers,
122 recipients=reviewers,
123 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
123 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
124 return new
124 return new
125
125
126 def update_reviewers(self, pull_request, reviewers_ids):
126 def update_reviewers(self, pull_request, reviewers_ids):
127 reviewers_ids = set(reviewers_ids)
127 reviewers_ids = set(reviewers_ids)
128 pull_request = self.__get_pull_request(pull_request)
128 pull_request = self.__get_pull_request(pull_request)
129 current_reviewers = PullRequestReviewers.query()\
129 current_reviewers = PullRequestReviewers.query()\
130 .filter(PullRequestReviewers.pull_request==
130 .filter(PullRequestReviewers.pull_request==
131 pull_request)\
131 pull_request)\
132 .all()
132 .all()
133 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
133 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
134
134
135 to_add = reviewers_ids.difference(current_reviewers_ids)
135 to_add = reviewers_ids.difference(current_reviewers_ids)
136 to_remove = current_reviewers_ids.difference(reviewers_ids)
136 to_remove = current_reviewers_ids.difference(reviewers_ids)
137
137
138 log.debug("Adding %s reviewers" % to_add)
138 log.debug("Adding %s reviewers" % to_add)
139 log.debug("Removing %s reviewers" % to_remove)
139 log.debug("Removing %s reviewers" % to_remove)
140
140
141 for uid in to_add:
141 for uid in to_add:
142 _usr = self._get_user(uid)
142 _usr = self._get_user(uid)
143 reviewer = PullRequestReviewers(_usr, pull_request)
143 reviewer = PullRequestReviewers(_usr, pull_request)
144 Session().add(reviewer)
144 Session().add(reviewer)
145
145
146 for uid in to_remove:
146 for uid in to_remove:
147 reviewer = PullRequestReviewers.query()\
147 reviewer = PullRequestReviewers.query()\
148 .filter(PullRequestReviewers.user_id==uid,
148 .filter(PullRequestReviewers.user_id==uid,
149 PullRequestReviewers.pull_request==pull_request)\
149 PullRequestReviewers.pull_request==pull_request)\
150 .scalar()
150 .scalar()
151 if reviewer:
151 if reviewer:
152 Session().delete(reviewer)
152 Session().delete(reviewer)
153
153
154 def delete(self, pull_request):
154 def delete(self, pull_request):
155 pull_request = self.__get_pull_request(pull_request)
155 pull_request = self.__get_pull_request(pull_request)
156 Session().delete(pull_request)
156 Session().delete(pull_request)
157
157
158 def close_pull_request(self, pull_request):
158 def close_pull_request(self, pull_request):
159 pull_request = self.__get_pull_request(pull_request)
159 pull_request = self.__get_pull_request(pull_request)
160 pull_request.status = PullRequest.STATUS_CLOSED
160 pull_request.status = PullRequest.STATUS_CLOSED
161 pull_request.updated_on = datetime.datetime.now()
161 pull_request.updated_on = datetime.datetime.now()
162 Session().add(pull_request)
162 Session().add(pull_request)
163
163
164 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref):
164 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
165 """
165 """
166 Returns a list of changesets that can be merged from org_repo@org_ref
166 Returns a list of changesets that can be merged from org_repo@org_ref
167 to other_repo@other_ref ... and the ancestor that would be used for merge
167 to other_repo@other_ref ... and the ancestor that would be used for merge
168
168
169 :param org_repo:
169 :param org_repo:
170 :param org_ref:
170 :param org_ref:
171 :param other_repo:
171 :param other_repo:
172 :param other_ref:
172 :param other_ref:
173 :param tmp:
173 :param tmp:
174 """
174 """
175
175
176 ancestor = None
176 ancestor = None
177
177
178 if alias == 'hg':
178 if alias == 'hg':
179 # lookup up the exact node id
179 # lookup up the exact node id
180 _revset_predicates = {
180 _revset_predicates = {
181 'branch': 'branch',
181 'branch': 'branch',
182 'book': 'bookmark',
182 'book': 'bookmark',
183 'tag': 'tag',
183 'tag': 'tag',
184 'rev': 'id',
184 'rev': 'id',
185 }
185 }
186
186
187 org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]],
187 org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]],
188 safe_str(org_ref[1]))
188 safe_str(org_ref[1]))
189 if org_ref[1] == EmptyChangeset().raw_id:
189 if org_ref[1] == EmptyChangeset().raw_id:
190 org_rev = org_ref[1]
190 org_rev = org_ref[1]
191 else:
191 else:
192 org_rev = org_repo._repo[scmutil.revrange(org_repo._repo,
192 org_rev = org_repo._repo[scmutil.revrange(org_repo._repo,
193 [org_rev_spec])[-1]]
193 [org_rev_spec])[-1]]
194 other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]],
194 other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]],
195 safe_str(other_ref[1]))
195 safe_str(other_ref[1]))
196 if other_ref[1] == EmptyChangeset().raw_id:
196 if other_ref[1] == EmptyChangeset().raw_id:
197 other_rev = other_ref[1]
197 other_rev = other_ref[1]
198 else:
198 else:
199 other_rev = other_repo._repo[scmutil.revrange(other_repo._repo,
199 other_rev = other_repo._repo[scmutil.revrange(other_repo._repo,
200 [other_rev_spec])[-1]]
200 [other_rev_spec])[-1]]
201
201
202 #case two independent repos
202 #case two independent repos
203 if org_repo != other_repo:
203 if org_repo != other_repo:
204 hgrepo = unionrepo.unionrepository(other_repo.baseui,
204 hgrepo = unionrepo.unionrepository(other_repo.baseui,
205 other_repo.path,
205 other_repo.path,
206 org_repo.path)
206 org_repo.path)
207 # all the changesets we are looking for will be in other_repo,
207 # all the changesets we are looking for will be in other_repo,
208 # so rev numbers from hgrepo can be used in other_repo
208 # so rev numbers from hgrepo can be used in other_repo
209
209
210 #no remote compare do it on the same repository
210 #no remote compare do it on the same repository
211 else:
211 else:
212 hgrepo = other_repo._repo
212 hgrepo = other_repo._repo
213
213
214 revs = ["ancestors(id('%s')) and not ancestors(id('%s'))" %
214 if merge:
215 (other_rev, org_rev)]
215 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
216 changesets = [other_repo.get_changeset(cs)
216 (other_rev, org_rev, org_rev)]
217 for cs in scmutil.revrange(hgrepo, revs)]
218
217
219 if org_repo != other_repo:
220 ancestors = scmutil.revrange(hgrepo,
218 ancestors = scmutil.revrange(hgrepo,
221 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
219 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
222 if len(ancestors) == 1:
220 if len(ancestors) == 1:
223 ancestor = hgrepo[ancestors[0]].hex()
221 ancestor = hgrepo[ancestors[0]].hex()
222 else:
223 # TODO: have both + and - changesets
224 revs = ["id('%s') :: id('%s') - id('%s')" %
225 (org_rev, other_rev, org_rev)]
226
227 changesets = [other_repo.get_changeset(cs)
228 for cs in scmutil.revrange(hgrepo, revs)]
224
229
225 elif alias == 'git':
230 elif alias == 'git':
226 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
231 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
227 so, se = org_repo.run_git_command(
232 so, se = org_repo.run_git_command(
228 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
233 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
229 other_ref[1])
234 other_ref[1])
230 )
235 )
231 changesets = [org_repo.get_changeset(cs)
236 changesets = [org_repo.get_changeset(cs)
232 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
237 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
233
238
234 return changesets, ancestor
239 return changesets, ancestor
235
240
236 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
241 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref, merge):
237 """
242 """
238 Returns incoming changesets for mercurial repositories
243 Returns incoming changesets for mercurial repositories
239
244
240 :param org_repo:
245 :param org_repo:
241 :param org_ref:
246 :param org_ref:
242 :param other_repo:
247 :param other_repo:
243 :param other_ref:
248 :param other_ref:
244 """
249 """
245
250
246 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
251 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
247 raise Exception('org_ref must be a two element list/tuple')
252 raise Exception('org_ref must be a two element list/tuple')
248
253
249 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
254 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
250 raise Exception('other_ref must be a two element list/tuple')
255 raise Exception('other_ref must be a two element list/tuple')
251
256
252 cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
257 cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
253 org_repo.scm_instance, org_ref,
258 org_repo.scm_instance, org_ref,
254 other_repo.scm_instance, other_ref)
259 other_repo.scm_instance, other_ref,
260 merge)
255 return cs_ranges, ancestor
261 return cs_ranges, ancestor
@@ -1,296 +1,296 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 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
12 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
13 &raquo;
13 &raquo;
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
15 ${_('changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
15 ${_('changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('changelog')}
19 ${self.menu('changelog')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <!-- box / title -->
24 <!-- box / title -->
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class="table">
28 <div class="table">
29 % if c.pagination:
29 % if c.pagination:
30 <div id="graph">
30 <div id="graph">
31 <div id="graph_nodes">
31 <div id="graph_nodes">
32 <canvas id="graph_canvas"></canvas>
32 <canvas id="graph_canvas"></canvas>
33 </div>
33 </div>
34 <div id="graph_content">
34 <div id="graph_content">
35 <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
35 <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
38
38
39 %if c.rhodecode_db_repo.fork:
39 %if c.rhodecode_db_repo.fork:
40 <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default')}" class="ui-btn small">${_('Compare fork with parent')}</a>
40 <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default',merge=1)}" class="ui-btn small">${_('Compare fork with parent')}</a>
41 %endif
41 %endif
42 %if h.is_hg(c.rhodecode_repo):
42 %if h.is_hg(c.rhodecode_repo):
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
44 %endif
44 %endif
45 </div>
45 </div>
46 <div class="container_header">
46 <div class="container_header">
47 ${h.form(h.url.current(),method='get')}
47 ${h.form(h.url.current(),method='get')}
48 <div class="info_box" style="float:left">
48 <div class="info_box" style="float:left">
49 ${h.submit('set',_('Show'),class_="ui-btn")}
49 ${h.submit('set',_('Show'),class_="ui-btn")}
50 ${h.text('size',size=1,value=c.size)}
50 ${h.text('size',size=1,value=c.size)}
51 ${_('revisions')}
51 ${_('revisions')}
52 </div>
52 </div>
53 ${h.end_form()}
53 ${h.end_form()}
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
55 </div>
55 </div>
56
56
57 %for cnt,cs in enumerate(c.pagination):
57 %for cnt,cs in enumerate(c.pagination):
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
59 <div class="left">
59 <div class="left">
60 <div>
60 <div>
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
63 </div>
63 </div>
64 <div class="author">
64 <div class="author">
65 <div class="gravatar">
65 <div class="gravatar">
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
67 </div>
67 </div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
69 </div>
69 </div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
71 </div>
71 </div>
72 <div class="mid">
72 <div class="mid">
73 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
73 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
74 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
74 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
75 </div>
75 </div>
76 <div class="right">
76 <div class="right">
77 <div class="changes">
77 <div class="changes">
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
79 <div class="comments-container">
79 <div class="comments-container">
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
81 <div class="comments-cnt" title="${('comments')}">
81 <div class="comments-cnt" title="${('comments')}">
82 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
82 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
84 <img src="${h.url('/images/icons/comments.png')}">
84 <img src="${h.url('/images/icons/comments.png')}">
85 </a>
85 </a>
86 </div>
86 </div>
87 %endif
87 %endif
88 </div>
88 </div>
89 <div class="changeset-status-container">
89 <div class="changeset-status-container">
90 %if c.statuses.get(cs.raw_id):
90 %if c.statuses.get(cs.raw_id):
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
92 <div class="changeset-status-ico">
92 <div class="changeset-status-ico">
93 %if c.statuses.get(cs.raw_id)[2]:
93 %if c.statuses.get(cs.raw_id)[2]:
94 <a class="tooltip" title="${_('Click to open associated pull request #%s' % c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
94 <a class="tooltip" title="${_('Click to open associated pull request #%s' % c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
95 %else:
95 %else:
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
97 %endif
97 %endif
98 </div>
98 </div>
99 %endif
99 %endif
100 </div>
100 </div>
101 </div>
101 </div>
102 %if cs.parents:
102 %if cs.parents:
103 %for p_cs in reversed(cs.parents):
103 %for p_cs in reversed(cs.parents):
104 <div class="parent">${_('Parent')}
104 <div class="parent">${_('Parent')}
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
107 </div>
107 </div>
108 %endfor
108 %endfor
109 %else:
109 %else:
110 <div class="parent">${_('No parents')}</div>
110 <div class="parent">${_('No parents')}</div>
111 %endif
111 %endif
112
112
113 <span class="logtags">
113 <span class="logtags">
114 %if len(cs.parents)>1:
114 %if len(cs.parents)>1:
115 <span class="merge">${_('merge')}</span>
115 <span class="merge">${_('merge')}</span>
116 %endif
116 %endif
117 %if cs.branch:
117 %if cs.branch:
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
120 </span>
120 </span>
121 %endif
121 %endif
122 %if h.is_hg(c.rhodecode_repo):
122 %if h.is_hg(c.rhodecode_repo):
123 %for book in cs.bookmarks:
123 %for book in cs.bookmarks:
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
126 </span>
126 </span>
127 %endfor
127 %endfor
128 %endif
128 %endif
129 %for tag in cs.tags:
129 %for tag in cs.tags:
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
132 %endfor
132 %endfor
133 </span>
133 </span>
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 %endfor
137 %endfor
138 <div class="pagination-wh pagination-left">
138 <div class="pagination-wh pagination-left">
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
140 </div>
140 </div>
141 </div>
141 </div>
142 </div>
142 </div>
143
143
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
145 <script type="text/javascript">
145 <script type="text/javascript">
146 YAHOO.util.Event.onDOMReady(function(){
146 YAHOO.util.Event.onDOMReady(function(){
147
147
148 //Monitor range checkboxes and build a link to changesets
148 //Monitor range checkboxes and build a link to changesets
149 //ranges
149 //ranges
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
152 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
152 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
153
153
154 var checkbox_checker = function(e){
154 var checkbox_checker = function(e){
155 var clicked_cb = e.currentTarget;
155 var clicked_cb = e.currentTarget;
156 var checked_checkboxes = [];
156 var checked_checkboxes = [];
157 for (pos in checkboxes){
157 for (pos in checkboxes){
158 if(checkboxes[pos].checked){
158 if(checkboxes[pos].checked){
159 checked_checkboxes.push(checkboxes[pos]);
159 checked_checkboxes.push(checkboxes[pos]);
160 }
160 }
161 }
161 }
162 if(YUD.get('open_new_pr')){
162 if(YUD.get('open_new_pr')){
163 if(checked_checkboxes.length>1){
163 if(checked_checkboxes.length>1){
164 YUD.setStyle('open_new_pr','display','none');
164 YUD.setStyle('open_new_pr','display','none');
165 } else {
165 } else {
166 YUD.setStyle('open_new_pr','display','');
166 YUD.setStyle('open_new_pr','display','');
167 if(checked_checkboxes.length>0){
167 if(checked_checkboxes.length>0){
168 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
168 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
169 }else{
169 }else{
170 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
170 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
171 }
171 }
172 }
172 }
173 }
173 }
174
174
175 if(checked_checkboxes.length>0){
175 if(checked_checkboxes.length>0){
176 var rev_end = checked_checkboxes[0].name;
176 var rev_end = checked_checkboxes[0].name;
177 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
177 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
178 var url = url_tmpl.replace('__REVRANGE__',
178 var url = url_tmpl.replace('__REVRANGE__',
179 rev_start+'...'+rev_end);
179 rev_start+'...'+rev_end);
180
180
181 var link = (rev_start == rev_end)
181 var link = (rev_start == rev_end)
182 ? _TM['Show selected change __S']
182 ? _TM['Show selected change __S']
183 : _TM['Show selected changes __S -> __E'];
183 : _TM['Show selected changes __S -> __E'];
184
184
185 link = link.replace('__S',rev_start.substr(0,6));
185 link = link.replace('__S',rev_start.substr(0,6));
186 link = link.replace('__E',rev_end.substr(0,6));
186 link = link.replace('__E',rev_end.substr(0,6));
187 YUD.get('rev_range_container').href = url;
187 YUD.get('rev_range_container').href = url;
188 YUD.get('rev_range_container').innerHTML = link;
188 YUD.get('rev_range_container').innerHTML = link;
189 YUD.setStyle('rev_range_container','display','');
189 YUD.setStyle('rev_range_container','display','');
190 YUD.setStyle('rev_range_clear','display','');
190 YUD.setStyle('rev_range_clear','display','');
191
191
192 YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
192 YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
193 YUD.setStyle('compare_fork','display','none');
193 YUD.setStyle('compare_fork','display','none');
194 }
194 }
195 else{
195 else{
196 YUD.setStyle('rev_range_container','display','none');
196 YUD.setStyle('rev_range_container','display','none');
197 YUD.setStyle('rev_range_clear','display','none');
197 YUD.setStyle('rev_range_clear','display','none');
198 YUD.get('open_new_pr').href = pr_tmpl
198 YUD.get('open_new_pr').href = pr_tmpl
199 YUD.setStyle('compare_fork','display','');
199 YUD.setStyle('compare_fork','display','');
200 }
200 }
201 };
201 };
202 YUE.onDOMReady(checkbox_checker);
202 YUE.onDOMReady(checkbox_checker);
203 YUE.on(checkboxes,'click', checkbox_checker);
203 YUE.on(checkboxes,'click', checkbox_checker);
204
204
205 YUE.on('rev_range_clear','click',function(e){
205 YUE.on('rev_range_clear','click',function(e){
206 for (var i=0; i<checkboxes.length; i++){
206 for (var i=0; i<checkboxes.length; i++){
207 var cb = checkboxes[i];
207 var cb = checkboxes[i];
208 cb.checked = false;
208 cb.checked = false;
209 }
209 }
210 YUE.preventDefault(e);
210 YUE.preventDefault(e);
211 })
211 })
212 var msgs = YUQ('.message');
212 var msgs = YUQ('.message');
213 // get first element height
213 // get first element height
214 var el = YUQ('#graph_content .container')[0];
214 var el = YUQ('#graph_content .container')[0];
215 var row_h = el.clientHeight;
215 var row_h = el.clientHeight;
216 for(var i=0;i<msgs.length;i++){
216 for(var i=0;i<msgs.length;i++){
217 var m = msgs[i];
217 var m = msgs[i];
218
218
219 var h = m.clientHeight;
219 var h = m.clientHeight;
220 var pad = YUD.getStyle(m,'padding');
220 var pad = YUD.getStyle(m,'padding');
221 if(h > row_h){
221 if(h > row_h){
222 var offset = row_h - (h+12);
222 var offset = row_h - (h+12);
223 YUD.setStyle(m.nextElementSibling,'display','block');
223 YUD.setStyle(m.nextElementSibling,'display','block');
224 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
224 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
225 };
225 };
226 }
226 }
227 YUE.on(YUQ('.expand'),'click',function(e){
227 YUE.on(YUQ('.expand'),'click',function(e){
228 var elem = e.currentTarget.parentNode.parentNode;
228 var elem = e.currentTarget.parentNode.parentNode;
229 YUD.setStyle(e.currentTarget,'display','none');
229 YUD.setStyle(e.currentTarget,'display','none');
230 YUD.setStyle(elem,'height','auto');
230 YUD.setStyle(elem,'height','auto');
231
231
232 //redraw the graph, line_count and jsdata are global vars
232 //redraw the graph, line_count and jsdata are global vars
233 set_canvas(100);
233 set_canvas(100);
234
234
235 var r = new BranchRenderer();
235 var r = new BranchRenderer();
236 r.render(jsdata,100,line_count);
236 r.render(jsdata,100,line_count);
237
237
238 })
238 })
239
239
240 // Fetch changeset details
240 // Fetch changeset details
241 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
241 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
242 var id = e.currentTarget.id;
242 var id = e.currentTarget.id;
243 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
243 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
244 var url = url.replace('__CS__',id.replace('changed_total_',''));
244 var url = url.replace('__CS__',id.replace('changed_total_',''));
245 ypjax(url,id,function(){tooltip_activate()});
245 ypjax(url,id,function(){tooltip_activate()});
246 });
246 });
247
247
248 // change branch filter
248 // change branch filter
249 YUE.on(YUD.get('branch_filter'),'change',function(e){
249 YUE.on(YUD.get('branch_filter'),'change',function(e){
250 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
250 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
251 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
251 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
252 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
252 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
253 var url = url.replace('__BRANCH__',selected_branch);
253 var url = url.replace('__BRANCH__',selected_branch);
254 if(selected_branch != ''){
254 if(selected_branch != ''){
255 window.location = url;
255 window.location = url;
256 }else{
256 }else{
257 window.location = url_main;
257 window.location = url_main;
258 }
258 }
259
259
260 });
260 });
261
261
262 function set_canvas(width) {
262 function set_canvas(width) {
263 var c = document.getElementById('graph_nodes');
263 var c = document.getElementById('graph_nodes');
264 var t = document.getElementById('graph_content');
264 var t = document.getElementById('graph_content');
265 canvas = document.getElementById('graph_canvas');
265 canvas = document.getElementById('graph_canvas');
266 var div_h = t.clientHeight;
266 var div_h = t.clientHeight;
267 c.style.height=div_h+'px';
267 c.style.height=div_h+'px';
268 canvas.setAttribute('height',div_h);
268 canvas.setAttribute('height',div_h);
269 c.style.height=width+'px';
269 c.style.height=width+'px';
270 canvas.setAttribute('width',width);
270 canvas.setAttribute('width',width);
271 };
271 };
272 var heads = 1;
272 var heads = 1;
273 var line_count = 0;
273 var line_count = 0;
274 var jsdata = ${c.jsdata|n};
274 var jsdata = ${c.jsdata|n};
275
275
276 for (var i=0;i<jsdata.length;i++) {
276 for (var i=0;i<jsdata.length;i++) {
277 var in_l = jsdata[i][2];
277 var in_l = jsdata[i][2];
278 for (var j in in_l) {
278 for (var j in in_l) {
279 var m = in_l[j][1];
279 var m = in_l[j][1];
280 if (m > line_count)
280 if (m > line_count)
281 line_count = m;
281 line_count = m;
282 }
282 }
283 }
283 }
284 set_canvas(100);
284 set_canvas(100);
285
285
286 var r = new BranchRenderer();
286 var r = new BranchRenderer();
287 r.render(jsdata,100,line_count);
287 r.render(jsdata,100,line_count);
288
288
289 });
289 });
290 </script>
290 </script>
291 %else:
291 %else:
292 ${_('There are no changes yet')}
292 ${_('There are no changes yet')}
293 %endif
293 %endif
294 </div>
294 </div>
295 </div>
295 </div>
296 </%def>
296 </%def>
@@ -1,28 +1,36 b''
1 ## Changesets table !
1 ## Changesets table !
2 <div class="container">
2 <div class="container">
3 <table class="compare_view_commits noborder">
4 %if not c.cs_ranges:
3 %if not c.cs_ranges:
5 <span class="empty_data">${_('No changesets')}</span>
4 <span class="empty_data">${_('No changesets')}</span>
6 %else:
5 %else:
6 <table class="compare_view_commits noborder">
7 %for cnt, cs in enumerate(c.cs_ranges):
7 %for cnt, cs in enumerate(c.cs_ranges):
8 <tr>
8 <tr>
9 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),14)}"/></div></td>
9 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),14)}"/></div></td>
10 <td>
10 <td>
11 %if cs.raw_id in c.statuses:
11 %if cs.raw_id in c.statuses:
12 <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
12 <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
13 %endif
13 %endif
14 </td>
14 </td>
15 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.other_repo.repo_name,revision=cs.raw_id))}
15 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.other_repo.repo_name,revision=cs.raw_id))}
16 %if c.as_form:
16 %if c.as_form:
17 ${h.hidden('revisions',cs.raw_id)}
17 ${h.hidden('revisions',cs.raw_id)}
18 %endif
18 %endif
19 </td>
19 </td>
20 <td><div class="author">${h.person(cs.author)}</div></td>
20 <td><div class="author">${h.person(cs.author)}</div></td>
21 <td><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${cs.date}</span></td>
21 <td><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${cs.date}</span></td>
22 <td><div class="message tooltip" title="${h.tooltip(cs.message)}" style="white-space:normal">${h.urlify_commit(h.shorter(cs.message, 60),c.repo_name)}</div></td>
22 <td><div class="message tooltip" title="${h.tooltip(cs.message)}" style="white-space:normal">${h.urlify_commit(h.shorter(cs.message, 60),c.repo_name)}</div></td>
23 </tr>
23 </tr>
24 %endfor
24 %endfor
25
25 </table>
26 %if c.ancestor:
27 <span class="ancestor">${_('Ancestor')}:
28 ${h.link_to(c.ancestor,h.url('changeset_home',repo_name=c.repo_name,revision=c.ancestor))}
29 </span>
30 %endif
31 %if c.as_form:
32 ${h.hidden('ancestor_rev',c.ancestor)}
33 ${h.hidden('merge_rev',c.cs_ranges[-1].raw_id)}
34 %endif
26 %endif
35 %endif
27 </table>
28 </div>
36 </div>
@@ -1,198 +1,199 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
10 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
11 &raquo;
11 &raquo;
12 ${_('new pull request')}
12 ${_('new pull request')}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 <div style="float:left;padding:0px 30px 30px 30px">
23 <div style="float:left;padding:0px 30px 30px 30px">
24 ##ORG
24 ##ORG
25 <div style="float:left">
25 <div style="float:left">
26 <div>
26 <div>
27 <span style="font-size: 20px">
27 <span style="font-size: 20px">
28 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref',c.default_org_ref,c.org_refs,class_='refs')}
28 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref',c.default_org_ref,c.org_refs,class_='refs')}
29 </span>
29 </span>
30 <div style="padding:5px 3px 3px 20px;">${c.rhodecode_db_repo.description}</div>
30 <div style="padding:5px 3px 3px 20px;">${c.rhodecode_db_repo.description}</div>
31 </div>
31 </div>
32 <div style="clear:both;padding-top: 10px"></div>
32 <div style="clear:both;padding-top: 10px"></div>
33 </div>
33 </div>
34 <div style="float:left;font-size:24px;padding:0px 20px">
34 <div style="float:left;font-size:24px;padding:0px 20px">
35 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
35 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
36 </div>
36 </div>
37
37
38 ##OTHER, most Probably the PARENT OF THIS FORK
38 ##OTHER, most Probably the PARENT OF THIS FORK
39 <div style="float:left">
39 <div style="float:left">
40 <div>
40 <div>
41 <span style="font-size: 20px">
41 <span style="font-size: 20px">
42 ${h.select('other_repo',c.default_other_repo,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_other_ref,c.default_other_refs,class_='refs')}
42 ${h.select('other_repo',c.default_other_repo,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_other_ref,c.default_other_refs,class_='refs')}
43 </span>
43 </span>
44 <div id="other_repo_desc" style="padding:5px 3px 3px 20px;"></div>
44 <div id="other_repo_desc" style="padding:5px 3px 3px 20px;"></div>
45 </div>
45 </div>
46 <div style="clear:both;padding-top: 10px"></div>
46 <div style="clear:both;padding-top: 10px"></div>
47 </div>
47 </div>
48 <div style="clear:both;padding-top: 10px"></div>
48 <div style="clear:both;padding-top: 10px"></div>
49 ## overview pulled by ajax
49 ## overview pulled by ajax
50 <div style="float:left" id="pull_request_overview"></div>
50 <div style="float:left" id="pull_request_overview"></div>
51 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
51 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
52 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
52 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
53 </div>
53 </div>
54 </div>
54 </div>
55 <div style="float:left; border-left:1px dashed #eee">
55 <div style="float:left; border-left:1px dashed #eee">
56 <h4>${_('Pull request reviewers')}</h4>
56 <h4>${_('Pull request reviewers')}</h4>
57 <div id="reviewers" style="padding:0px 0px 0px 15px">
57 <div id="reviewers" style="padding:0px 0px 0px 15px">
58 ## members goes here !
58 ## members goes here !
59 <div class="group_members_wrap">
59 <div class="group_members_wrap">
60 <ul id="review_members" class="group_members">
60 <ul id="review_members" class="group_members">
61 %for member in c.review_members:
61 %for member in c.review_members:
62 <li id="reviewer_${member.user_id}">
62 <li id="reviewer_${member.user_id}">
63 <div class="reviewers_member">
63 <div class="reviewers_member">
64 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
64 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
65 <div style="float:left">${member.full_name} (${_('owner')})</div>
65 <div style="float:left">${member.full_name} (${_('owner')})</div>
66 <input type="hidden" value="${member.user_id}" name="review_members" />
66 <input type="hidden" value="${member.user_id}" name="review_members" />
67 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
67 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
68 </div>
68 </div>
69 </li>
69 </li>
70 %endfor
70 %endfor
71 </ul>
71 </ul>
72 </div>
72 </div>
73
73
74 <div class='ac'>
74 <div class='ac'>
75 <div class="reviewer_ac">
75 <div class="reviewer_ac">
76 ${h.text('user', class_='yui-ac-input')}
76 ${h.text('user', class_='yui-ac-input')}
77 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
77 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
78 <div id="reviewers_container"></div>
78 <div id="reviewers_container"></div>
79 </div>
79 </div>
80 </div>
80 </div>
81 </div>
81 </div>
82 </div>
82 </div>
83 <h3>${_('Create new pull request')}</h3>
83 <h3>${_('Create new pull request')}</h3>
84
84
85 <div class="form">
85 <div class="form">
86 <!-- fields -->
86 <!-- fields -->
87
87
88 <div class="fields">
88 <div class="fields">
89
89
90 <div class="field">
90 <div class="field">
91 <div class="label">
91 <div class="label">
92 <label for="pullrequest_title">${_('Title')}:</label>
92 <label for="pullrequest_title">${_('Title')}:</label>
93 </div>
93 </div>
94 <div class="input">
94 <div class="input">
95 ${h.text('pullrequest_title',size=30)}
95 ${h.text('pullrequest_title',size=30)}
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 <div class="field">
99 <div class="field">
100 <div class="label label-textarea">
100 <div class="label label-textarea">
101 <label for="pullrequest_desc">${_('description')}:</label>
101 <label for="pullrequest_desc">${_('description')}:</label>
102 </div>
102 </div>
103 <div class="textarea text-area editor">
103 <div class="textarea text-area editor">
104 ${h.textarea('pullrequest_desc',size=30)}
104 ${h.textarea('pullrequest_desc',size=30)}
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="buttons">
108 <div class="buttons">
109 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
109 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
110 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
110 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
111 </div>
111 </div>
112 </div>
112 </div>
113 </div>
113 </div>
114 ${h.end_form()}
114 ${h.end_form()}
115
115
116 </div>
116 </div>
117
117
118 <script type="text/javascript">
118 <script type="text/javascript">
119 var _USERS_AC_DATA = ${c.users_array|n};
119 var _USERS_AC_DATA = ${c.users_array|n};
120 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
120 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
121 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
121 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
122
122
123 var other_repos_info = ${c.other_repos_info|n};
123 var other_repos_info = ${c.other_repos_info|n};
124
124
125 var loadPreview = function(){
125 var loadPreview = function(){
126 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
126 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
127 //url template
127 //url template
128 var url = "${h.url('compare_url',
128 var url = "${h.url('compare_url',
129 repo_name='__other_repo__',
129 repo_name='__other_repo__',
130 org_ref_type='__other_ref_type__',
130 org_ref_type='__other_ref_type__',
131 org_ref='__other_ref__',
131 org_ref='__other_ref__',
132 other_repo='__org_repo__',
132 other_repo='__org_repo__',
133 other_ref_type='__org_ref_type__',
133 other_ref_type='__org_ref_type__',
134 other_ref='__org_ref__',
134 other_ref='__org_ref__',
135 as_form=True,
135 as_form=True,
136 merge=True,
136 )}";
137 )}";
137 var org_repo = YUQ('#pull_request_form #org_repo')[0].value;
138 var org_repo = YUQ('#pull_request_form #org_repo')[0].value;
138 var org_ref = YUQ('#pull_request_form #org_ref')[0].value.split(':');
139 var org_ref = YUQ('#pull_request_form #org_ref')[0].value.split(':');
139
140
140 var other_repo = YUQ('#pull_request_form #other_repo')[0].value;
141 var other_repo = YUQ('#pull_request_form #other_repo')[0].value;
141 var other_ref = YUQ('#pull_request_form #other_ref')[0].value.split(':');
142 var other_ref = YUQ('#pull_request_form #other_ref')[0].value.split(':');
142
143
143 var select_refs = YUQ('#pull_request_form select.refs')
144 var select_refs = YUQ('#pull_request_form select.refs')
144 var rev_data = {
145 var rev_data = {
145 'org_repo': org_repo,
146 'org_repo': org_repo,
146 'org_ref': org_ref[2],
147 'org_ref': org_ref[2],
147 'org_ref_type': 'rev',
148 'org_ref_type': 'rev',
148 'other_repo': other_repo,
149 'other_repo': other_repo,
149 'other_ref': other_ref[2],
150 'other_ref': other_ref[2],
150 'other_ref_type': 'rev',
151 'other_ref_type': 'rev',
151 }; // gather the org/other ref and repo here
152 }; // gather the org/other ref and repo here
152
153
153 for (k in rev_data){
154 for (k in rev_data){
154 url = url.replace('__'+k+'__',rev_data[k]);
155 url = url.replace('__'+k+'__',rev_data[k]);
155 }
156 }
156
157
157 YUD.get('pull_request_overview').innerHTML = "${_('Loading ...')}";
158 YUD.get('pull_request_overview').innerHTML = "${_('Loading ...')}";
158 YUD.get('pull_request_overview_url').href = url; // shouldn't have as_form ... but ...
159 YUD.get('pull_request_overview_url').href = url; // shouldn't have as_form ... but ...
159 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
160 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
160 ypjax(url,'pull_request_overview', function(data){
161 ypjax(url,'pull_request_overview', function(data){
161 var sel_box = YUQ('#pull_request_form #other_repo')[0];
162 var sel_box = YUQ('#pull_request_form #other_repo')[0];
162 var repo_name = sel_box.options[sel_box.selectedIndex].value;
163 var repo_name = sel_box.options[sel_box.selectedIndex].value;
163 var _data = other_repos_info[repo_name];
164 var _data = other_repos_info[repo_name];
164 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
165 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
165 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
166 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
166 // select back the revision that was just compared
167 // select back the revision that was just compared
167 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
168 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
168 // reset && add the reviewer based on selected repo
169 // reset && add the reviewer based on selected repo
169 YUD.get('review_members').innerHTML = '';
170 YUD.get('review_members').innerHTML = '';
170 addReviewMember(_data.user.user_id, _data.user.firstname,
171 addReviewMember(_data.user.user_id, _data.user.firstname,
171 _data.user.lastname, _data.user.username,
172 _data.user.lastname, _data.user.username,
172 _data.user.gravatar_link);
173 _data.user.gravatar_link);
173 })
174 })
174 }
175 }
175
176
176 ## refresh automatically when something changes (org_repo can't change)
177 ## refresh automatically when something changes (org_repo can't change)
177
178
178 YUE.on('org_ref', 'change', function(e){
179 YUE.on('org_ref', 'change', function(e){
179 loadPreview();
180 loadPreview();
180 });
181 });
181
182
182 YUE.on('other_repo', 'change', function(e){
183 YUE.on('other_repo', 'change', function(e){
183 var repo_name = e.currentTarget.value;
184 var repo_name = e.currentTarget.value;
184 // replace the <select> of changed repo
185 // replace the <select> of changed repo
185 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
186 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
186 loadPreview();
187 loadPreview();
187 });
188 });
188
189
189 YUE.on('other_ref', 'change', function(e){
190 YUE.on('other_ref', 'change', function(e){
190 loadPreview();
191 loadPreview();
191 });
192 });
192
193
193 //lazy load overview after 0.5s
194 //lazy load overview after 0.5s
194 setTimeout(loadPreview, 500)
195 setTimeout(loadPreview, 500)
195
196
196 </script>
197 </script>
197
198
198 </%def>
199 </%def>
@@ -1,434 +1,441 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 from rhodecode.model.repo import RepoModel
2 from rhodecode.model.repo import RepoModel
3 from rhodecode.model.meta import Session
3 from rhodecode.model.meta import Session
4 from rhodecode.model.db import Repository
4 from rhodecode.model.db import Repository
5 from rhodecode.model.scm import ScmModel
5 from rhodecode.model.scm import ScmModel
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7
7
8
8
9 def _fork_repo(fork_name, vcs_type, parent=None):
9 def _fork_repo(fork_name, vcs_type, parent=None):
10 if vcs_type =='hg':
10 if vcs_type =='hg':
11 _REPO = HG_REPO
11 _REPO = HG_REPO
12 elif vcs_type == 'git':
12 elif vcs_type == 'git':
13 _REPO = GIT_REPO
13 _REPO = GIT_REPO
14
14
15 if parent:
15 if parent:
16 _REPO = parent
16 _REPO = parent
17
17
18 form_data = dict(
18 form_data = dict(
19 repo_name=fork_name,
19 repo_name=fork_name,
20 repo_name_full=fork_name,
20 repo_name_full=fork_name,
21 repo_group=None,
21 repo_group=None,
22 repo_type=vcs_type,
22 repo_type=vcs_type,
23 description='',
23 description='',
24 private=False,
24 private=False,
25 copy_permissions=False,
25 copy_permissions=False,
26 landing_rev='tip',
26 landing_rev='tip',
27 update_after_clone=False,
27 update_after_clone=False,
28 fork_parent_id=Repository.get_by_repo_name(_REPO),
28 fork_parent_id=Repository.get_by_repo_name(_REPO),
29 )
29 )
30 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
30 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
31
31
32 Session().commit()
32 Session().commit()
33 return Repository.get_by_repo_name(fork_name)
33 return Repository.get_by_repo_name(fork_name)
34
34
35
35
36 def _commit_change(repo, filename, content, message, vcs_type, parent=None, newfile=False):
36 def _commit_change(repo, filename, content, message, vcs_type, parent=None, newfile=False):
37 repo = Repository.get_by_repo_name(repo)
37 repo = Repository.get_by_repo_name(repo)
38 _cs = parent
38 _cs = parent
39 if not parent:
39 if not parent:
40 _cs = EmptyChangeset(alias=vcs_type)
40 _cs = EmptyChangeset(alias=vcs_type)
41
41
42 if newfile:
42 if newfile:
43 cs = ScmModel().create_node(
43 cs = ScmModel().create_node(
44 repo=repo.scm_instance, repo_name=repo.repo_name,
44 repo=repo.scm_instance, repo_name=repo.repo_name,
45 cs=_cs, user=TEST_USER_ADMIN_LOGIN,
45 cs=_cs, user=TEST_USER_ADMIN_LOGIN,
46 author=TEST_USER_ADMIN_LOGIN,
46 author=TEST_USER_ADMIN_LOGIN,
47 message=message,
47 message=message,
48 content=content,
48 content=content,
49 f_path=filename
49 f_path=filename
50 )
50 )
51 else:
51 else:
52 cs = ScmModel().commit_change(
52 cs = ScmModel().commit_change(
53 repo=repo.scm_instance, repo_name=repo.repo_name,
53 repo=repo.scm_instance, repo_name=repo.repo_name,
54 cs=parent, user=TEST_USER_ADMIN_LOGIN,
54 cs=parent, user=TEST_USER_ADMIN_LOGIN,
55 author=TEST_USER_ADMIN_LOGIN,
55 author=TEST_USER_ADMIN_LOGIN,
56 message=message,
56 message=message,
57 content=content,
57 content=content,
58 f_path=filename
58 f_path=filename
59 )
59 )
60 return cs
60 return cs
61
61
62
62
63 class TestCompareController(TestController):
63 class TestCompareController(TestController):
64
64
65 def setUp(self):
65 def setUp(self):
66 self.r1_id = None
66 self.r1_id = None
67 self.r2_id = None
67 self.r2_id = None
68
68
69 def tearDown(self):
69 def tearDown(self):
70 if self.r2_id:
70 if self.r2_id:
71 RepoModel().delete(self.r2_id)
71 RepoModel().delete(self.r2_id)
72 if self.r1_id:
72 if self.r1_id:
73 RepoModel().delete(self.r1_id)
73 RepoModel().delete(self.r1_id)
74 Session().commit()
74 Session().commit()
75 Session.remove()
75 Session.remove()
76
76
77 def test_compare_forks_on_branch_extra_commits_hg(self):
77 def test_compare_forks_on_branch_extra_commits_hg(self):
78 self.log_user()
78 self.log_user()
79 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
79 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
80 description='diff-test',
80 description='diff-test',
81 owner=TEST_USER_ADMIN_LOGIN)
81 owner=TEST_USER_ADMIN_LOGIN)
82 Session().commit()
82 Session().commit()
83 self.r1_id = repo1.repo_id
83 self.r1_id = repo1.repo_id
84 #commit something !
84 #commit something !
85 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
85 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
86 message='commit1', vcs_type='hg', parent=None, newfile=True)
86 message='commit1', vcs_type='hg', parent=None, newfile=True)
87
87
88 #fork this repo
88 #fork this repo
89 repo2 = _fork_repo('one-fork', 'hg', parent='one')
89 repo2 = _fork_repo('one-fork', 'hg', parent='one')
90 self.r2_id = repo2.repo_id
90 self.r2_id = repo2.repo_id
91
91
92 #add two extra commit into fork
92 #add two extra commit into fork
93 cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
93 cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
94 message='commit2', vcs_type='hg', parent=cs0)
94 message='commit2', vcs_type='hg', parent=cs0)
95
95
96 cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
96 cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
97 message='commit3', vcs_type='hg', parent=cs1)
97 message='commit3', vcs_type='hg', parent=cs1)
98
98
99 rev1 = 'default'
99 rev1 = 'default'
100 rev2 = 'default'
100 rev2 = 'default'
101
101
102 response = self.app.get(url(controller='compare', action='index',
102 response = self.app.get(url(controller='compare', action='index',
103 repo_name=repo1.repo_name,
103 repo_name=repo1.repo_name,
104 org_ref_type="branch",
104 org_ref_type="branch",
105 org_ref=rev2,
105 org_ref=rev2,
106 other_repo=repo2.repo_name,
106 other_repo=repo2.repo_name,
107 other_ref_type="branch",
107 other_ref_type="branch",
108 other_ref=rev1,
108 other_ref=rev1,
109 merge='1',
109 ))
110 ))
110
111
111 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
112 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
112 response.mustcontain("""Showing 2 commits""")
113 response.mustcontain("""Showing 2 commits""")
113 response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
114 response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
114
115
115 response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
116 response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
116 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
117 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
117
118
118 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
119 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
119 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
120 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
120 ## files
121 ## files
121 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
122 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=1#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
122 #swap
123 #swap
123 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
124 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
124
125
125 def test_compare_forks_on_branch_extra_commits_origin_has_incomming_hg(self):
126 def test_compare_forks_on_branch_extra_commits_origin_has_incomming_hg(self):
126 self.log_user()
127 self.log_user()
127
128
128 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
129 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
129 description='diff-test',
130 description='diff-test',
130 owner=TEST_USER_ADMIN_LOGIN)
131 owner=TEST_USER_ADMIN_LOGIN)
131 Session().commit()
132 Session().commit()
132 self.r1_id = repo1.repo_id
133 self.r1_id = repo1.repo_id
133
134
134 #commit something !
135 #commit something !
135 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
136 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
136 message='commit1', vcs_type='hg', parent=None, newfile=True)
137 message='commit1', vcs_type='hg', parent=None, newfile=True)
137
138
138 #fork this repo
139 #fork this repo
139 repo2 = _fork_repo('one-fork', 'hg', parent='one')
140 repo2 = _fork_repo('one-fork', 'hg', parent='one')
140 self.r2_id = repo2.repo_id
141 self.r2_id = repo2.repo_id
141
142
142 #now commit something to origin repo
143 #now commit something to origin repo
143 cs1_prim = _commit_change(repo1.repo_name, filename='file2', content='line1file2\n',
144 cs1_prim = _commit_change(repo1.repo_name, filename='file2', content='line1file2\n',
144 message='commit2', vcs_type='hg', parent=cs0, newfile=True)
145 message='commit2', vcs_type='hg', parent=cs0, newfile=True)
145
146
146 #add two extra commit into fork
147 #add two extra commit into fork
147 cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
148 cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
148 message='commit2', vcs_type='hg', parent=cs0)
149 message='commit2', vcs_type='hg', parent=cs0)
149
150
150 cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
151 cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
151 message='commit3', vcs_type='hg', parent=cs1)
152 message='commit3', vcs_type='hg', parent=cs1)
152
153
153 rev1 = 'default'
154 rev1 = 'default'
154 rev2 = 'default'
155 rev2 = 'default'
155
156
156 response = self.app.get(url(controller='compare', action='index',
157 response = self.app.get(url(controller='compare', action='index',
157 repo_name=repo1.repo_name,
158 repo_name=repo1.repo_name,
158 org_ref_type="branch",
159 org_ref_type="branch",
159 org_ref=rev2,
160 org_ref=rev2,
160 other_repo=repo2.repo_name,
161 other_repo=repo2.repo_name,
161 other_ref_type="branch",
162 other_ref_type="branch",
162 other_ref=rev1,
163 other_ref=rev1,
164 merge='x',
163 ))
165 ))
164 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
166 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
165 response.mustcontain("""Showing 2 commits""")
167 response.mustcontain("""Showing 2 commits""")
166 response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
168 response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
167
169
168 response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
170 response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
169 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
171 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
170
172
171 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
173 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
172 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
174 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
173 ## files
175 ## files
174 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
176 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=x#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
175 #swap
177 #swap
176 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
178 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
177
179
178 def test_compare_cherry_pick_changesets_from_bottom(self):
180 def test_compare_cherry_pick_changesets_from_bottom(self):
179
181
180 # repo1:
182 # repo1:
181 # cs0:
183 # cs0:
182 # cs1:
184 # cs1:
183 # repo1-fork- in which we will cherry pick bottom changesets
185 # repo1-fork- in which we will cherry pick bottom changesets
184 # cs0:
186 # cs0:
185 # cs1:
187 # cs1:
186 # cs2: x
188 # cs2: x
187 # cs3: x
189 # cs3: x
188 # cs4: x
190 # cs4: x
189 # cs5:
191 # cs5:
190 #make repo1, and cs1+cs2
192 #make repo1, and cs1+cs2
191 self.log_user()
193 self.log_user()
192
194
193 repo1 = RepoModel().create_repo(repo_name='repo1', repo_type='hg',
195 repo1 = RepoModel().create_repo(repo_name='repo1', repo_type='hg',
194 description='diff-test',
196 description='diff-test',
195 owner=TEST_USER_ADMIN_LOGIN)
197 owner=TEST_USER_ADMIN_LOGIN)
196 Session().commit()
198 Session().commit()
197 self.r1_id = repo1.repo_id
199 self.r1_id = repo1.repo_id
198
200
199 #commit something !
201 #commit something !
200 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
202 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
201 message='commit1', vcs_type='hg', parent=None,
203 message='commit1', vcs_type='hg', parent=None,
202 newfile=True)
204 newfile=True)
203 cs1 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\n',
205 cs1 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\n',
204 message='commit2', vcs_type='hg', parent=cs0)
206 message='commit2', vcs_type='hg', parent=cs0)
205 #fork this repo
207 #fork this repo
206 repo2 = _fork_repo('repo1-fork', 'hg', parent='repo1')
208 repo2 = _fork_repo('repo1-fork', 'hg', parent='repo1')
207 self.r2_id = repo2.repo_id
209 self.r2_id = repo2.repo_id
208 #now make cs3-6
210 #now make cs3-6
209 cs2 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
211 cs2 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
210 message='commit3', vcs_type='hg', parent=cs1)
212 message='commit3', vcs_type='hg', parent=cs1)
211 cs3 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\n',
213 cs3 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\n',
212 message='commit4', vcs_type='hg', parent=cs2)
214 message='commit4', vcs_type='hg', parent=cs2)
213 cs4 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\n',
215 cs4 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\n',
214 message='commit5', vcs_type='hg', parent=cs3)
216 message='commit5', vcs_type='hg', parent=cs3)
215 cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
217 cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
216 message='commit6', vcs_type='hg', parent=cs4)
218 message='commit6', vcs_type='hg', parent=cs4)
217
219
218 response = self.app.get(url(controller='compare', action='index',
220 response = self.app.get(url(controller='compare', action='index',
219 repo_name=repo2.repo_name,
221 repo_name=repo2.repo_name,
220 org_ref_type="rev",
222 org_ref_type="rev",
221 org_ref=cs1.short_id, # parent of cs2, in repo2
223 org_ref=cs1.short_id, # parent of cs2, in repo2
222 other_repo=repo1.repo_name,
224 other_repo=repo1.repo_name,
223 other_ref_type="rev",
225 other_ref_type="rev",
224 other_ref=cs4.short_id,
226 other_ref=cs4.short_id,
227 merge='True',
225 ))
228 ))
226 response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, cs1.short_id, repo1.repo_name, cs4.short_id))
229 response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, cs1.short_id, repo1.repo_name, cs4.short_id))
227 response.mustcontain("""Showing 3 commits""")
230 response.mustcontain("""Showing 3 commits""")
228 response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
231 response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
229
232
230 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
233 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
231 response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
234 response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
232 response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
235 response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
233
236
234 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo1.repo_name, cs2.raw_id, cs2.short_id))
237 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo1.repo_name, cs2.raw_id, cs2.short_id))
235 response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
238 response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
236 response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
239 response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
237 ## files
240 ## files
238 response.mustcontain("""#C--826e8142e6ba">file1</a>""")
241 response.mustcontain("""#C--826e8142e6ba">file1</a>""")
239
242
240 def test_compare_cherry_pick_changesets_from_top(self):
243 def test_compare_cherry_pick_changesets_from_top(self):
241 # repo1:
244 # repo1:
242 # cs0:
245 # cs0:
243 # cs1:
246 # cs1:
244 # repo1-fork- in which we will cherry pick bottom changesets
247 # repo1-fork- in which we will cherry pick bottom changesets
245 # cs0:
248 # cs0:
246 # cs1:
249 # cs1:
247 # cs2:
250 # cs2:
248 # cs3: x
251 # cs3: x
249 # cs4: x
252 # cs4: x
250 # cs5: x
253 # cs5: x
251 #
254 #
252 #make repo1, and cs1+cs2
255 #make repo1, and cs1+cs2
253 self.log_user()
256 self.log_user()
254 repo1 = RepoModel().create_repo(repo_name='repo1', repo_type='hg',
257 repo1 = RepoModel().create_repo(repo_name='repo1', repo_type='hg',
255 description='diff-test',
258 description='diff-test',
256 owner=TEST_USER_ADMIN_LOGIN)
259 owner=TEST_USER_ADMIN_LOGIN)
257 Session().commit()
260 Session().commit()
258 self.r1_id = repo1.repo_id
261 self.r1_id = repo1.repo_id
259
262
260 #commit something !
263 #commit something !
261 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
264 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
262 message='commit1', vcs_type='hg', parent=None,
265 message='commit1', vcs_type='hg', parent=None,
263 newfile=True)
266 newfile=True)
264 cs1 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\n',
267 cs1 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\n',
265 message='commit2', vcs_type='hg', parent=cs0)
268 message='commit2', vcs_type='hg', parent=cs0)
266 #fork this repo
269 #fork this repo
267 repo2 = _fork_repo('repo1-fork', 'hg', parent='repo1')
270 repo2 = _fork_repo('repo1-fork', 'hg', parent='repo1')
268 self.r2_id = repo2.repo_id
271 self.r2_id = repo2.repo_id
269 #now make cs3-6
272 #now make cs3-6
270 cs2 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
273 cs2 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
271 message='commit3', vcs_type='hg', parent=cs1)
274 message='commit3', vcs_type='hg', parent=cs1)
272 cs3 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\n',
275 cs3 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\n',
273 message='commit4', vcs_type='hg', parent=cs2)
276 message='commit4', vcs_type='hg', parent=cs2)
274 cs4 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\n',
277 cs4 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\n',
275 message='commit5', vcs_type='hg', parent=cs3)
278 message='commit5', vcs_type='hg', parent=cs3)
276 cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
279 cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
277 message='commit6', vcs_type='hg', parent=cs4)
280 message='commit6', vcs_type='hg', parent=cs4)
278 response = self.app.get(url(controller='compare', action='index',
281 response = self.app.get(url(controller='compare', action='index',
279 repo_name=repo1.repo_name,
282 repo_name=repo1.repo_name,
280 org_ref_type="rev",
283 org_ref_type="rev",
281 org_ref=cs2.short_id, # parent of cs3, not in repo2
284 org_ref=cs2.short_id, # parent of cs3, not in repo2
282 other_ref_type="rev",
285 other_ref_type="rev",
283 other_ref=cs5.short_id,
286 other_ref=cs5.short_id,
287 merge='1',
284 ))
288 ))
285
289
286 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, cs2.short_id, repo1.repo_name, cs5.short_id))
290 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, cs2.short_id, repo1.repo_name, cs5.short_id))
287 response.mustcontain("""Showing 3 commits""")
291 response.mustcontain("""Showing 3 commits""")
288 response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
292 response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
289
293
290 response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
294 response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
291 response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
295 response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
292 response.mustcontain("""<div class="message tooltip" title="commit6" style="white-space:normal">commit6</div>""")
296 response.mustcontain("""<div class="message tooltip" title="commit6" style="white-space:normal">commit6</div>""")
293
297
294 response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
298 response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
295 response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
299 response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
296 response.mustcontain("""<a href="/%s/changeset/%s">r5:%s</a>""" % (repo1.repo_name, cs5.raw_id, cs5.short_id))
300 response.mustcontain("""<a href="/%s/changeset/%s">r5:%s</a>""" % (repo1.repo_name, cs5.raw_id, cs5.short_id))
297 ## files
301 ## files
298 response.mustcontain("""#C--826e8142e6ba">file1</a>""")
302 response.mustcontain("""#C--826e8142e6ba">file1</a>""")
299
303
300 def test_compare_cherry_pick_changeset_mixed_branches(self):
304 def test_compare_cherry_pick_changeset_mixed_branches(self):
301 """
305 """
302
306
303 """
307 """
304 pass
308 pass
305 #TODO write this tastecase
309 #TODO write this tastecase
306
310
307 def test_compare_remote_branches_hg(self):
311 def test_compare_remote_branches_hg(self):
308 self.log_user()
312 self.log_user()
309
313
310 repo2 = _fork_repo(HG_FORK, 'hg')
314 repo2 = _fork_repo(HG_FORK, 'hg')
311 self.r2_id = repo2.repo_id
315 self.r2_id = repo2.repo_id
312 rev1 = '56349e29c2af'
316 rev1 = '56349e29c2af'
313 rev2 = '7d4bc8ec6be5'
317 rev2 = '7d4bc8ec6be5'
314
318
315 response = self.app.get(url(controller='compare', action='index',
319 response = self.app.get(url(controller='compare', action='index',
316 repo_name=HG_REPO,
320 repo_name=HG_REPO,
317 org_ref_type="rev",
321 org_ref_type="rev",
318 org_ref=rev1,
322 org_ref=rev1,
319 other_ref_type="rev",
323 other_ref_type="rev",
320 other_ref=rev2,
324 other_ref=rev2,
321 other_repo=HG_FORK,
325 other_repo=HG_FORK,
326 merge='1',
322 ))
327 ))
323 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
328 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
324 ## outgoing changesets between those revisions
329 ## outgoing changesets between those revisions
325
330
326 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_FORK))
331 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_FORK))
327 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_FORK))
332 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_FORK))
328 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_FORK, rev2))
333 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_FORK, rev2))
329
334
330 ## files
335 ## files
331 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
336 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
332 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
337 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
333 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
338 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
334
339
335 def test_org_repo_new_commits_after_forking_simple_diff(self):
340 def test_org_repo_new_commits_after_forking_simple_diff(self):
336 self.log_user()
341 self.log_user()
337
342
338 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
343 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
339 description='diff-test',
344 description='diff-test',
340 owner=TEST_USER_ADMIN_LOGIN)
345 owner=TEST_USER_ADMIN_LOGIN)
341
346
342 Session().commit()
347 Session().commit()
343 self.r1_id = repo1.repo_id
348 self.r1_id = repo1.repo_id
344 r1_name = repo1.repo_name
349 r1_name = repo1.repo_name
345
350
346 #commit something initially !
351 #commit something initially !
347 cs0 = ScmModel().create_node(
352 cs0 = ScmModel().create_node(
348 repo=repo1.scm_instance, repo_name=r1_name,
353 repo=repo1.scm_instance, repo_name=r1_name,
349 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
354 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
350 author=TEST_USER_ADMIN_LOGIN,
355 author=TEST_USER_ADMIN_LOGIN,
351 message='commit1',
356 message='commit1',
352 content='line1',
357 content='line1',
353 f_path='file1'
358 f_path='file1'
354 )
359 )
355 Session().commit()
360 Session().commit()
356 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
361 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
357 #fork the repo1
362 #fork the repo1
358 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
363 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
359 description='compare-test',
364 description='compare-test',
360 clone_uri=repo1.repo_full_path,
365 clone_uri=repo1.repo_full_path,
361 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
366 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
362 Session().commit()
367 Session().commit()
363 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
368 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
364 self.r2_id = repo2.repo_id
369 self.r2_id = repo2.repo_id
365 r2_name = repo2.repo_name
370 r2_name = repo2.repo_name
366
371
367 #make 3 new commits in fork
372 #make 3 new commits in fork
368 cs1 = ScmModel().create_node(
373 cs1 = ScmModel().create_node(
369 repo=repo2.scm_instance, repo_name=r2_name,
374 repo=repo2.scm_instance, repo_name=r2_name,
370 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
375 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
371 author=TEST_USER_ADMIN_LOGIN,
376 author=TEST_USER_ADMIN_LOGIN,
372 message='commit1-fork',
377 message='commit1-fork',
373 content='file1-line1-from-fork',
378 content='file1-line1-from-fork',
374 f_path='file1-fork'
379 f_path='file1-fork'
375 )
380 )
376 cs2 = ScmModel().create_node(
381 cs2 = ScmModel().create_node(
377 repo=repo2.scm_instance, repo_name=r2_name,
382 repo=repo2.scm_instance, repo_name=r2_name,
378 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
383 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
379 author=TEST_USER_ADMIN_LOGIN,
384 author=TEST_USER_ADMIN_LOGIN,
380 message='commit2-fork',
385 message='commit2-fork',
381 content='file2-line1-from-fork',
386 content='file2-line1-from-fork',
382 f_path='file2-fork'
387 f_path='file2-fork'
383 )
388 )
384 cs3 = ScmModel().create_node(
389 cs3 = ScmModel().create_node(
385 repo=repo2.scm_instance, repo_name=r2_name,
390 repo=repo2.scm_instance, repo_name=r2_name,
386 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
391 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
387 author=TEST_USER_ADMIN_LOGIN,
392 author=TEST_USER_ADMIN_LOGIN,
388 message='commit3-fork',
393 message='commit3-fork',
389 content='file3-line1-from-fork',
394 content='file3-line1-from-fork',
390 f_path='file3-fork'
395 f_path='file3-fork'
391 )
396 )
392
397
393 #compare !
398 #compare !
394 rev1 = 'default'
399 rev1 = 'default'
395 rev2 = 'default'
400 rev2 = 'default'
396
401
397 response = self.app.get(url(controller='compare', action='index',
402 response = self.app.get(url(controller='compare', action='index',
398 repo_name=r2_name,
403 repo_name=r2_name,
399 org_ref_type="branch",
404 org_ref_type="branch",
400 org_ref=rev1,
405 org_ref=rev1,
401 other_ref_type="branch",
406 other_ref_type="branch",
402 other_ref=rev2,
407 other_ref=rev2,
403 other_repo=r1_name,
408 other_repo=r1_name,
409 merge='1',
404 ))
410 ))
405 response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
411 response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
406 response.mustcontain('No files')
412 response.mustcontain('No files')
407 response.mustcontain('No changesets')
413 response.mustcontain('No changesets')
408
414
409 #add new commit into parent !
415 #add new commit into parent !
410 cs0 = ScmModel().create_node(
416 cs0 = ScmModel().create_node(
411 repo=repo1.scm_instance, repo_name=r1_name,
417 repo=repo1.scm_instance, repo_name=r1_name,
412 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
418 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
413 author=TEST_USER_ADMIN_LOGIN,
419 author=TEST_USER_ADMIN_LOGIN,
414 message='commit2-parent',
420 message='commit2-parent',
415 content='line1-added-after-fork',
421 content='line1-added-after-fork',
416 f_path='file2'
422 f_path='file2'
417 )
423 )
418 #compare !
424 #compare !
419 rev1 = 'default'
425 rev1 = 'default'
420 rev2 = 'default'
426 rev2 = 'default'
421 response = self.app.get(url(controller='compare', action='index',
427 response = self.app.get(url(controller='compare', action='index',
422 repo_name=r2_name,
428 repo_name=r2_name,
423 org_ref_type="branch",
429 org_ref_type="branch",
424 org_ref=rev1,
430 org_ref=rev1,
425 other_ref_type="branch",
431 other_ref_type="branch",
426 other_ref=rev2,
432 other_ref=rev2,
427 other_repo=r1_name,
433 other_repo=r1_name,
434 merge='1',
428 ))
435 ))
429
436
430 response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
437 response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
431
438
432 response.mustcontain("""commit2-parent""")
439 response.mustcontain("""commit2-parent""")
433 response.mustcontain("""1 file changed with 1 insertions and 0 deletions""")
440 response.mustcontain("""1 file changed with 1 insertions and 0 deletions""")
434 response.mustcontain("""line1-added-after-fork""")
441 response.mustcontain("""line1-added-after-fork""")
General Comments 0
You need to be logged in to leave comments. Login now