##// END OF EJS Templates
Moved all Mercurial imports into hgcompat from vcs
marcink -
r3941:3208aaef beta
parent child Browse files
Show More
@@ -1,559 +1,556 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, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import 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.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
47 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
48 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
52 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
54 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
55 from rhodecode.lib.utils2 import safe_int
59
56
60 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
61
58
62
59
63 class PullrequestsController(BaseRepoController):
60 class PullrequestsController(BaseRepoController):
64
61
65 def __before__(self):
62 def __before__(self):
66 super(PullrequestsController, self).__before__()
63 super(PullrequestsController, self).__before__()
67 repo_model = RepoModel()
64 repo_model = RepoModel()
68 c.users_array = repo_model.get_users_js()
65 c.users_array = repo_model.get_users_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
66 c.users_groups_array = repo_model.get_users_groups_js()
70
67
71 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
68 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
72 """return a structure with repo's interesting changesets, suitable for
69 """return a structure with repo's interesting changesets, suitable for
73 the selectors in pullrequest.html
70 the selectors in pullrequest.html
74
71
75 rev: a revision that must be in the list somehow and selected by default
72 rev: a revision that must be in the list somehow and selected by default
76 branch: a branch that must be in the list and selected by default - even if closed
73 branch: a branch that must be in the list and selected by default - even if closed
77 branch_rev: a revision of which peers should be preferred and available."""
74 branch_rev: a revision of which peers should be preferred and available."""
78 # list named branches that has been merged to this named branch - it should probably merge back
75 # list named branches that has been merged to this named branch - it should probably merge back
79 peers = []
76 peers = []
80
77
81 if rev:
78 if rev:
82 rev = safe_str(rev)
79 rev = safe_str(rev)
83
80
84 if branch:
81 if branch:
85 branch = safe_str(branch)
82 branch = safe_str(branch)
86
83
87 if branch_rev:
84 if branch_rev:
88 branch_rev = safe_str(branch_rev)
85 branch_rev = safe_str(branch_rev)
89 # not restricting to merge() would also get branch point and be better
86 # not restricting to merge() would also get branch point and be better
90 # (especially because it would get the branch point) ... but is currently too expensive
87 # (especially because it would get the branch point) ... but is currently too expensive
91 otherbranches = {}
88 otherbranches = {}
92 for i in repo._repo.revs(
89 for i in repo._repo.revs(
93 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
90 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
94 branch_rev, branch_rev):
91 branch_rev, branch_rev):
95 cs = repo.get_changeset(i)
92 cs = repo.get_changeset(i)
96 otherbranches[cs.branch] = cs.raw_id
93 otherbranches[cs.branch] = cs.raw_id
97 for abranch, node in otherbranches.iteritems():
94 for abranch, node in otherbranches.iteritems():
98 selected = 'branch:%s:%s' % (abranch, node)
95 selected = 'branch:%s:%s' % (abranch, node)
99 peers.append((selected, abranch))
96 peers.append((selected, abranch))
100
97
101 selected = None
98 selected = None
102
99
103 branches = []
100 branches = []
104 for abranch, branchrev in repo.branches.iteritems():
101 for abranch, branchrev in repo.branches.iteritems():
105 n = 'branch:%s:%s' % (abranch, branchrev)
102 n = 'branch:%s:%s' % (abranch, branchrev)
106 branches.append((n, abranch))
103 branches.append((n, abranch))
107 if rev == branchrev:
104 if rev == branchrev:
108 selected = n
105 selected = n
109 if branch == abranch:
106 if branch == abranch:
110 selected = n
107 selected = n
111 branch = None
108 branch = None
112 if branch: # branch not in list - it is probably closed
109 if branch: # branch not in list - it is probably closed
113 revs = repo._repo.revs('max(branch(%s))', branch)
110 revs = repo._repo.revs('max(branch(%s))', branch)
114 if revs:
111 if revs:
115 cs = repo.get_changeset(revs[0])
112 cs = repo.get_changeset(revs[0])
116 selected = 'branch:%s:%s' % (branch, cs.raw_id)
113 selected = 'branch:%s:%s' % (branch, cs.raw_id)
117 branches.append((selected, branch))
114 branches.append((selected, branch))
118
115
119 bookmarks = []
116 bookmarks = []
120 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
117 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
121 n = 'book:%s:%s' % (bookmark, bookmarkrev)
118 n = 'book:%s:%s' % (bookmark, bookmarkrev)
122 bookmarks.append((n, bookmark))
119 bookmarks.append((n, bookmark))
123 if rev == bookmarkrev:
120 if rev == bookmarkrev:
124 selected = n
121 selected = n
125
122
126 tags = []
123 tags = []
127 for tag, tagrev in repo.tags.iteritems():
124 for tag, tagrev in repo.tags.iteritems():
128 n = 'tag:%s:%s' % (tag, tagrev)
125 n = 'tag:%s:%s' % (tag, tagrev)
129 tags.append((n, tag))
126 tags.append((n, tag))
130 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
127 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
131 selected = n
128 selected = n
132
129
133 # prio 1: rev was selected as existing entry above
130 # prio 1: rev was selected as existing entry above
134
131
135 # prio 2: create special entry for rev; rev _must_ be used
132 # prio 2: create special entry for rev; rev _must_ be used
136 specials = []
133 specials = []
137 if rev and selected is None:
134 if rev and selected is None:
138 selected = 'rev:%s:%s' % (rev, rev)
135 selected = 'rev:%s:%s' % (rev, rev)
139 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
136 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
140
137
141 # prio 3: most recent peer branch
138 # prio 3: most recent peer branch
142 if peers and not selected:
139 if peers and not selected:
143 selected = peers[0][0][0]
140 selected = peers[0][0][0]
144
141
145 # prio 4: tip revision
142 # prio 4: tip revision
146 if not selected:
143 if not selected:
147 selected = 'tag:tip:%s' % repo.tags['tip']
144 selected = 'tag:tip:%s' % repo.tags['tip']
148
145
149 groups = [(specials, _("Special")),
146 groups = [(specials, _("Special")),
150 (peers, _("Peer branches")),
147 (peers, _("Peer branches")),
151 (bookmarks, _("Bookmarks")),
148 (bookmarks, _("Bookmarks")),
152 (branches, _("Branches")),
149 (branches, _("Branches")),
153 (tags, _("Tags")),
150 (tags, _("Tags")),
154 ]
151 ]
155 return [g for g in groups if g[0]], selected
152 return [g for g in groups if g[0]], selected
156
153
157 def _get_is_allowed_change_status(self, pull_request):
154 def _get_is_allowed_change_status(self, pull_request):
158 owner = self.rhodecode_user.user_id == pull_request.user_id
155 owner = self.rhodecode_user.user_id == pull_request.user_id
159 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
156 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
160 pull_request.reviewers]
157 pull_request.reviewers]
161 return (self.rhodecode_user.admin or owner or reviewer)
158 return (self.rhodecode_user.admin or owner or reviewer)
162
159
163 def _load_compare_data(self, pull_request, enable_comments=True):
160 def _load_compare_data(self, pull_request, enable_comments=True):
164 """
161 """
165 Load context data needed for generating compare diff
162 Load context data needed for generating compare diff
166
163
167 :param pull_request:
164 :param pull_request:
168 """
165 """
169 org_repo = pull_request.org_repo
166 org_repo = pull_request.org_repo
170 (org_ref_type,
167 (org_ref_type,
171 org_ref_name,
168 org_ref_name,
172 org_ref_rev) = pull_request.org_ref.split(':')
169 org_ref_rev) = pull_request.org_ref.split(':')
173
170
174 other_repo = org_repo
171 other_repo = org_repo
175 (other_ref_type,
172 (other_ref_type,
176 other_ref_name,
173 other_ref_name,
177 other_ref_rev) = pull_request.other_ref.split(':')
174 other_ref_rev) = pull_request.other_ref.split(':')
178
175
179 # despite opening revisions for bookmarks/branches/tags, we always
176 # despite opening revisions for bookmarks/branches/tags, we always
180 # convert this to rev to prevent changes after bookmark or branch change
177 # convert this to rev to prevent changes after bookmark or branch change
181 org_ref = ('rev', org_ref_rev)
178 org_ref = ('rev', org_ref_rev)
182 other_ref = ('rev', other_ref_rev)
179 other_ref = ('rev', other_ref_rev)
183
180
184 c.org_repo = org_repo
181 c.org_repo = org_repo
185 c.other_repo = other_repo
182 c.other_repo = other_repo
186
183
187 c.fulldiff = fulldiff = request.GET.get('fulldiff')
184 c.fulldiff = fulldiff = request.GET.get('fulldiff')
188
185
189 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
186 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
190
187
191 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
188 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
192
189
193 c.org_ref = org_ref[1]
190 c.org_ref = org_ref[1]
194 c.org_ref_type = org_ref[0]
191 c.org_ref_type = org_ref[0]
195 c.other_ref = other_ref[1]
192 c.other_ref = other_ref[1]
196 c.other_ref_type = other_ref[0]
193 c.other_ref_type = other_ref[0]
197
194
198 diff_limit = self.cut_off_limit if not fulldiff else None
195 diff_limit = self.cut_off_limit if not fulldiff else None
199
196
200 # we swap org/other ref since we run a simple diff on one repo
197 # we swap org/other ref since we run a simple diff on one repo
201 log.debug('running diff between %s and %s in %s'
198 log.debug('running diff between %s and %s in %s'
202 % (other_ref, org_ref, org_repo.scm_instance.path))
199 % (other_ref, org_ref, org_repo.scm_instance.path))
203 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
200 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
204
201
205 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
202 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
206 diff_limit=diff_limit)
203 diff_limit=diff_limit)
207 _parsed = diff_processor.prepare()
204 _parsed = diff_processor.prepare()
208
205
209 c.limited_diff = False
206 c.limited_diff = False
210 if isinstance(_parsed, LimitedDiffContainer):
207 if isinstance(_parsed, LimitedDiffContainer):
211 c.limited_diff = True
208 c.limited_diff = True
212
209
213 c.files = []
210 c.files = []
214 c.changes = {}
211 c.changes = {}
215 c.lines_added = 0
212 c.lines_added = 0
216 c.lines_deleted = 0
213 c.lines_deleted = 0
217
214
218 for f in _parsed:
215 for f in _parsed:
219 st = f['stats']
216 st = f['stats']
220 c.lines_added += st['added']
217 c.lines_added += st['added']
221 c.lines_deleted += st['deleted']
218 c.lines_deleted += st['deleted']
222 fid = h.FID('', f['filename'])
219 fid = h.FID('', f['filename'])
223 c.files.append([fid, f['operation'], f['filename'], f['stats']])
220 c.files.append([fid, f['operation'], f['filename'], f['stats']])
224 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
221 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
225 parsed_lines=[f])
222 parsed_lines=[f])
226 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
223 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
227
224
228 @LoginRequired()
225 @LoginRequired()
229 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
226 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
230 'repository.admin')
227 'repository.admin')
231 def show_all(self, repo_name):
228 def show_all(self, repo_name):
232 c.pull_requests = PullRequestModel().get_all(repo_name)
229 c.pull_requests = PullRequestModel().get_all(repo_name)
233 c.repo_name = repo_name
230 c.repo_name = repo_name
234 p = safe_int(request.GET.get('page', 1), 1)
231 p = safe_int(request.GET.get('page', 1), 1)
235
232
236 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
233 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
237
234
238 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
235 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
239
236
240 if request.environ.get('HTTP_X_PARTIAL_XHR'):
237 if request.environ.get('HTTP_X_PARTIAL_XHR'):
241 return c.pullrequest_data
238 return c.pullrequest_data
242
239
243 return render('/pullrequests/pullrequest_show_all.html')
240 return render('/pullrequests/pullrequest_show_all.html')
244
241
245 @LoginRequired()
242 @LoginRequired()
246 @NotAnonymous()
243 @NotAnonymous()
247 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
244 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
248 'repository.admin')
245 'repository.admin')
249 def index(self):
246 def index(self):
250 org_repo = c.rhodecode_db_repo
247 org_repo = c.rhodecode_db_repo
251
248
252 if org_repo.scm_instance.alias != 'hg':
249 if org_repo.scm_instance.alias != 'hg':
253 log.error('Review not available for GIT REPOS')
250 log.error('Review not available for GIT REPOS')
254 raise HTTPNotFound
251 raise HTTPNotFound
255
252
256 try:
253 try:
257 org_repo.scm_instance.get_changeset()
254 org_repo.scm_instance.get_changeset()
258 except EmptyRepositoryError, e:
255 except EmptyRepositoryError, e:
259 h.flash(h.literal(_('There are no changesets yet')),
256 h.flash(h.literal(_('There are no changesets yet')),
260 category='warning')
257 category='warning')
261 redirect(url('summary_home', repo_name=org_repo.repo_name))
258 redirect(url('summary_home', repo_name=org_repo.repo_name))
262
259
263 org_rev = request.GET.get('rev_end')
260 org_rev = request.GET.get('rev_end')
264 # rev_start is not directly useful - its parent could however be used
261 # rev_start is not directly useful - its parent could however be used
265 # as default for other and thus give a simple compare view
262 # as default for other and thus give a simple compare view
266 #other_rev = request.POST.get('rev_start')
263 #other_rev = request.POST.get('rev_start')
267 branch = request.GET.get('branch')
264 branch = request.GET.get('branch')
268
265
269 c.org_repos = []
266 c.org_repos = []
270 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
267 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
271 c.default_org_repo = org_repo.repo_name
268 c.default_org_repo = org_repo.repo_name
272 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
269 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
273
270
274 c.other_repos = []
271 c.other_repos = []
275 other_repos_info = {}
272 other_repos_info = {}
276
273
277 def add_other_repo(repo, branch_rev=None):
274 def add_other_repo(repo, branch_rev=None):
278 if repo.repo_name in other_repos_info: # shouldn't happen
275 if repo.repo_name in other_repos_info: # shouldn't happen
279 return
276 return
280 c.other_repos.append((repo.repo_name, repo.repo_name))
277 c.other_repos.append((repo.repo_name, repo.repo_name))
281 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
278 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
282 other_repos_info[repo.repo_name] = {
279 other_repos_info[repo.repo_name] = {
283 'user': dict(user_id=repo.user.user_id,
280 'user': dict(user_id=repo.user.user_id,
284 username=repo.user.username,
281 username=repo.user.username,
285 firstname=repo.user.firstname,
282 firstname=repo.user.firstname,
286 lastname=repo.user.lastname,
283 lastname=repo.user.lastname,
287 gravatar_link=h.gravatar_url(repo.user.email, 14)),
284 gravatar_link=h.gravatar_url(repo.user.email, 14)),
288 'description': repo.description.split('\n', 1)[0],
285 'description': repo.description.split('\n', 1)[0],
289 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
286 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
290 }
287 }
291
288
292 # add org repo to other so we can open pull request against peer branches on itself
289 # add org repo to other so we can open pull request against peer branches on itself
293 add_other_repo(org_repo, branch_rev=org_rev)
290 add_other_repo(org_repo, branch_rev=org_rev)
294 c.default_other_repo = org_repo.repo_name
291 c.default_other_repo = org_repo.repo_name
295
292
296 # gather forks and add to this list ... even though it is rare to
293 # gather forks and add to this list ... even though it is rare to
297 # request forks to pull from their parent
294 # request forks to pull from their parent
298 for fork in org_repo.forks:
295 for fork in org_repo.forks:
299 add_other_repo(fork)
296 add_other_repo(fork)
300
297
301 # add parents of this fork also, but only if it's not empty
298 # add parents of this fork also, but only if it's not empty
302 if org_repo.parent and org_repo.parent.scm_instance.revisions:
299 if org_repo.parent and org_repo.parent.scm_instance.revisions:
303 add_other_repo(org_repo.parent)
300 add_other_repo(org_repo.parent)
304 c.default_other_repo = org_repo.parent.repo_name
301 c.default_other_repo = org_repo.parent.repo_name
305
302
306 c.default_other_repo_info = other_repos_info[c.default_other_repo]
303 c.default_other_repo_info = other_repos_info[c.default_other_repo]
307 c.other_repos_info = json.dumps(other_repos_info)
304 c.other_repos_info = json.dumps(other_repos_info)
308
305
309 return render('/pullrequests/pullrequest.html')
306 return render('/pullrequests/pullrequest.html')
310
307
311 @LoginRequired()
308 @LoginRequired()
312 @NotAnonymous()
309 @NotAnonymous()
313 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
310 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
314 'repository.admin')
311 'repository.admin')
315 def create(self, repo_name):
312 def create(self, repo_name):
316 repo = RepoModel()._get_repo(repo_name)
313 repo = RepoModel()._get_repo(repo_name)
317 try:
314 try:
318 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
315 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
319 except formencode.Invalid, errors:
316 except formencode.Invalid, errors:
320 log.error(traceback.format_exc())
317 log.error(traceback.format_exc())
321 if errors.error_dict.get('revisions'):
318 if errors.error_dict.get('revisions'):
322 msg = 'Revisions: %s' % errors.error_dict['revisions']
319 msg = 'Revisions: %s' % errors.error_dict['revisions']
323 elif errors.error_dict.get('pullrequest_title'):
320 elif errors.error_dict.get('pullrequest_title'):
324 msg = _('Pull request requires a title with min. 3 chars')
321 msg = _('Pull request requires a title with min. 3 chars')
325 else:
322 else:
326 msg = _('Error creating pull request')
323 msg = _('Error creating pull request')
327
324
328 h.flash(msg, 'error')
325 h.flash(msg, 'error')
329 return redirect(url('pullrequest_home', repo_name=repo_name))
326 return redirect(url('pullrequest_home', repo_name=repo_name))
330
327
331 org_repo = _form['org_repo']
328 org_repo = _form['org_repo']
332 org_ref = 'rev:merge:%s' % _form['merge_rev']
329 org_ref = 'rev:merge:%s' % _form['merge_rev']
333 other_repo = _form['other_repo']
330 other_repo = _form['other_repo']
334 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
331 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
335 revisions = [x for x in reversed(_form['revisions'])]
332 revisions = [x for x in reversed(_form['revisions'])]
336 reviewers = _form['review_members']
333 reviewers = _form['review_members']
337
334
338 title = _form['pullrequest_title']
335 title = _form['pullrequest_title']
339 description = _form['pullrequest_desc']
336 description = _form['pullrequest_desc']
340 try:
337 try:
341 pull_request = PullRequestModel().create(
338 pull_request = PullRequestModel().create(
342 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
339 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
343 other_ref, revisions, reviewers, title, description
340 other_ref, revisions, reviewers, title, description
344 )
341 )
345 Session().commit()
342 Session().commit()
346 h.flash(_('Successfully opened new pull request'),
343 h.flash(_('Successfully opened new pull request'),
347 category='success')
344 category='success')
348 except Exception:
345 except Exception:
349 h.flash(_('Error occurred during sending pull request'),
346 h.flash(_('Error occurred during sending pull request'),
350 category='error')
347 category='error')
351 log.error(traceback.format_exc())
348 log.error(traceback.format_exc())
352 return redirect(url('pullrequest_home', repo_name=repo_name))
349 return redirect(url('pullrequest_home', repo_name=repo_name))
353
350
354 return redirect(url('pullrequest_show', repo_name=other_repo,
351 return redirect(url('pullrequest_show', repo_name=other_repo,
355 pull_request_id=pull_request.pull_request_id))
352 pull_request_id=pull_request.pull_request_id))
356
353
357 @LoginRequired()
354 @LoginRequired()
358 @NotAnonymous()
355 @NotAnonymous()
359 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
356 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
360 'repository.admin')
357 'repository.admin')
361 @jsonify
358 @jsonify
362 def update(self, repo_name, pull_request_id):
359 def update(self, repo_name, pull_request_id):
363 pull_request = PullRequest.get_or_404(pull_request_id)
360 pull_request = PullRequest.get_or_404(pull_request_id)
364 if pull_request.is_closed():
361 if pull_request.is_closed():
365 raise HTTPForbidden()
362 raise HTTPForbidden()
366 #only owner or admin can update it
363 #only owner or admin can update it
367 owner = pull_request.author.user_id == c.rhodecode_user.user_id
364 owner = pull_request.author.user_id == c.rhodecode_user.user_id
368 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
365 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
369 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
366 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
370 request.POST.get('reviewers_ids', '').split(',')))
367 request.POST.get('reviewers_ids', '').split(',')))
371
368
372 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
369 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
373 Session().commit()
370 Session().commit()
374 return True
371 return True
375 raise HTTPForbidden()
372 raise HTTPForbidden()
376
373
377 @LoginRequired()
374 @LoginRequired()
378 @NotAnonymous()
375 @NotAnonymous()
379 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
376 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
380 'repository.admin')
377 'repository.admin')
381 @jsonify
378 @jsonify
382 def delete(self, repo_name, pull_request_id):
379 def delete(self, repo_name, pull_request_id):
383 pull_request = PullRequest.get_or_404(pull_request_id)
380 pull_request = PullRequest.get_or_404(pull_request_id)
384 #only owner can delete it !
381 #only owner can delete it !
385 if pull_request.author.user_id == c.rhodecode_user.user_id:
382 if pull_request.author.user_id == c.rhodecode_user.user_id:
386 PullRequestModel().delete(pull_request)
383 PullRequestModel().delete(pull_request)
387 Session().commit()
384 Session().commit()
388 h.flash(_('Successfully deleted pull request'),
385 h.flash(_('Successfully deleted pull request'),
389 category='success')
386 category='success')
390 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
387 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
391 raise HTTPForbidden()
388 raise HTTPForbidden()
392
389
393 @LoginRequired()
390 @LoginRequired()
394 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
391 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
395 'repository.admin')
392 'repository.admin')
396 def show(self, repo_name, pull_request_id):
393 def show(self, repo_name, pull_request_id):
397 repo_model = RepoModel()
394 repo_model = RepoModel()
398 c.users_array = repo_model.get_users_js()
395 c.users_array = repo_model.get_users_js()
399 c.users_groups_array = repo_model.get_users_groups_js()
396 c.users_groups_array = repo_model.get_users_groups_js()
400 c.pull_request = PullRequest.get_or_404(pull_request_id)
397 c.pull_request = PullRequest.get_or_404(pull_request_id)
401 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
398 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
402 cc_model = ChangesetCommentsModel()
399 cc_model = ChangesetCommentsModel()
403 cs_model = ChangesetStatusModel()
400 cs_model = ChangesetStatusModel()
404 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
401 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
405 pull_request=c.pull_request,
402 pull_request=c.pull_request,
406 with_revisions=True)
403 with_revisions=True)
407
404
408 cs_statuses = defaultdict(list)
405 cs_statuses = defaultdict(list)
409 for st in _cs_statuses:
406 for st in _cs_statuses:
410 cs_statuses[st.author.username] += [st]
407 cs_statuses[st.author.username] += [st]
411
408
412 c.pull_request_reviewers = []
409 c.pull_request_reviewers = []
413 c.pull_request_pending_reviewers = []
410 c.pull_request_pending_reviewers = []
414 for o in c.pull_request.reviewers:
411 for o in c.pull_request.reviewers:
415 st = cs_statuses.get(o.user.username, None)
412 st = cs_statuses.get(o.user.username, None)
416 if st:
413 if st:
417 sorter = lambda k: k.version
414 sorter = lambda k: k.version
418 st = [(x, list(y)[0])
415 st = [(x, list(y)[0])
419 for x, y in (groupby(sorted(st, key=sorter), sorter))]
416 for x, y in (groupby(sorted(st, key=sorter), sorter))]
420 else:
417 else:
421 c.pull_request_pending_reviewers.append(o.user)
418 c.pull_request_pending_reviewers.append(o.user)
422 c.pull_request_reviewers.append([o.user, st])
419 c.pull_request_reviewers.append([o.user, st])
423
420
424 # pull_requests repo_name we opened it against
421 # pull_requests repo_name we opened it against
425 # ie. other_repo must match
422 # ie. other_repo must match
426 if repo_name != c.pull_request.other_repo.repo_name:
423 if repo_name != c.pull_request.other_repo.repo_name:
427 raise HTTPNotFound
424 raise HTTPNotFound
428
425
429 # load compare data into template context
426 # load compare data into template context
430 enable_comments = not c.pull_request.is_closed()
427 enable_comments = not c.pull_request.is_closed()
431 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
428 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
432
429
433 # inline comments
430 # inline comments
434 c.inline_cnt = 0
431 c.inline_cnt = 0
435 c.inline_comments = cc_model.get_inline_comments(
432 c.inline_comments = cc_model.get_inline_comments(
436 c.rhodecode_db_repo.repo_id,
433 c.rhodecode_db_repo.repo_id,
437 pull_request=pull_request_id)
434 pull_request=pull_request_id)
438 # count inline comments
435 # count inline comments
439 for __, lines in c.inline_comments:
436 for __, lines in c.inline_comments:
440 for comments in lines.values():
437 for comments in lines.values():
441 c.inline_cnt += len(comments)
438 c.inline_cnt += len(comments)
442 # comments
439 # comments
443 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
440 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
444 pull_request=pull_request_id)
441 pull_request=pull_request_id)
445
442
446 try:
443 try:
447 cur_status = c.statuses[c.pull_request.revisions[0]][0]
444 cur_status = c.statuses[c.pull_request.revisions[0]][0]
448 except Exception:
445 except Exception:
449 log.error(traceback.format_exc())
446 log.error(traceback.format_exc())
450 cur_status = 'undefined'
447 cur_status = 'undefined'
451 if c.pull_request.is_closed() and 0:
448 if c.pull_request.is_closed() and 0:
452 c.current_changeset_status = cur_status
449 c.current_changeset_status = cur_status
453 else:
450 else:
454 # changeset(pull-request) status calulation based on reviewers
451 # changeset(pull-request) status calulation based on reviewers
455 c.current_changeset_status = cs_model.calculate_status(
452 c.current_changeset_status = cs_model.calculate_status(
456 c.pull_request_reviewers,
453 c.pull_request_reviewers,
457 )
454 )
458 c.changeset_statuses = ChangesetStatus.STATUSES
455 c.changeset_statuses = ChangesetStatus.STATUSES
459
456
460 c.as_form = False
457 c.as_form = False
461 c.ancestor = None # there is one - but right here we don't know which
458 c.ancestor = None # there is one - but right here we don't know which
462 return render('/pullrequests/pullrequest_show.html')
459 return render('/pullrequests/pullrequest_show.html')
463
460
464 @LoginRequired()
461 @LoginRequired()
465 @NotAnonymous()
462 @NotAnonymous()
466 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
463 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
467 'repository.admin')
464 'repository.admin')
468 @jsonify
465 @jsonify
469 def comment(self, repo_name, pull_request_id):
466 def comment(self, repo_name, pull_request_id):
470 pull_request = PullRequest.get_or_404(pull_request_id)
467 pull_request = PullRequest.get_or_404(pull_request_id)
471 if pull_request.is_closed():
468 if pull_request.is_closed():
472 raise HTTPForbidden()
469 raise HTTPForbidden()
473
470
474 status = request.POST.get('changeset_status')
471 status = request.POST.get('changeset_status')
475 change_status = request.POST.get('change_changeset_status')
472 change_status = request.POST.get('change_changeset_status')
476 text = request.POST.get('text')
473 text = request.POST.get('text')
477 close_pr = request.POST.get('save_close')
474 close_pr = request.POST.get('save_close')
478
475
479 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
476 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
480 if status and change_status and allowed_to_change_status:
477 if status and change_status and allowed_to_change_status:
481 _def = (_('Status change -> %s')
478 _def = (_('Status change -> %s')
482 % ChangesetStatus.get_status_lbl(status))
479 % ChangesetStatus.get_status_lbl(status))
483 if close_pr:
480 if close_pr:
484 _def = _('Closing with') + ' ' + _def
481 _def = _('Closing with') + ' ' + _def
485 text = text or _def
482 text = text or _def
486 comm = ChangesetCommentsModel().create(
483 comm = ChangesetCommentsModel().create(
487 text=text,
484 text=text,
488 repo=c.rhodecode_db_repo.repo_id,
485 repo=c.rhodecode_db_repo.repo_id,
489 user=c.rhodecode_user.user_id,
486 user=c.rhodecode_user.user_id,
490 pull_request=pull_request_id,
487 pull_request=pull_request_id,
491 f_path=request.POST.get('f_path'),
488 f_path=request.POST.get('f_path'),
492 line_no=request.POST.get('line'),
489 line_no=request.POST.get('line'),
493 status_change=(ChangesetStatus.get_status_lbl(status)
490 status_change=(ChangesetStatus.get_status_lbl(status)
494 if status and change_status
491 if status and change_status
495 and allowed_to_change_status else None),
492 and allowed_to_change_status else None),
496 closing_pr=close_pr
493 closing_pr=close_pr
497 )
494 )
498
495
499 action_logger(self.rhodecode_user,
496 action_logger(self.rhodecode_user,
500 'user_commented_pull_request:%s' % pull_request_id,
497 'user_commented_pull_request:%s' % pull_request_id,
501 c.rhodecode_db_repo, self.ip_addr, self.sa)
498 c.rhodecode_db_repo, self.ip_addr, self.sa)
502
499
503 if allowed_to_change_status:
500 if allowed_to_change_status:
504 # get status if set !
501 # get status if set !
505 if status and change_status:
502 if status and change_status:
506 ChangesetStatusModel().set_status(
503 ChangesetStatusModel().set_status(
507 c.rhodecode_db_repo.repo_id,
504 c.rhodecode_db_repo.repo_id,
508 status,
505 status,
509 c.rhodecode_user.user_id,
506 c.rhodecode_user.user_id,
510 comm,
507 comm,
511 pull_request=pull_request_id
508 pull_request=pull_request_id
512 )
509 )
513
510
514 if close_pr:
511 if close_pr:
515 if status in ['rejected', 'approved']:
512 if status in ['rejected', 'approved']:
516 PullRequestModel().close_pull_request(pull_request_id)
513 PullRequestModel().close_pull_request(pull_request_id)
517 action_logger(self.rhodecode_user,
514 action_logger(self.rhodecode_user,
518 'user_closed_pull_request:%s' % pull_request_id,
515 'user_closed_pull_request:%s' % pull_request_id,
519 c.rhodecode_db_repo, self.ip_addr, self.sa)
516 c.rhodecode_db_repo, self.ip_addr, self.sa)
520 else:
517 else:
521 h.flash(_('Closing pull request on other statuses than '
518 h.flash(_('Closing pull request on other statuses than '
522 'rejected or approved forbidden'),
519 'rejected or approved forbidden'),
523 category='warning')
520 category='warning')
524
521
525 Session().commit()
522 Session().commit()
526
523
527 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
524 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
528 return redirect(h.url('pullrequest_show', repo_name=repo_name,
525 return redirect(h.url('pullrequest_show', repo_name=repo_name,
529 pull_request_id=pull_request_id))
526 pull_request_id=pull_request_id))
530
527
531 data = {
528 data = {
532 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
529 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
533 }
530 }
534 if comm:
531 if comm:
535 c.co = comm
532 c.co = comm
536 data.update(comm.get_dict())
533 data.update(comm.get_dict())
537 data.update({'rendered_text':
534 data.update({'rendered_text':
538 render('changeset/changeset_comment_block.html')})
535 render('changeset/changeset_comment_block.html')})
539
536
540 return data
537 return data
541
538
542 @LoginRequired()
539 @LoginRequired()
543 @NotAnonymous()
540 @NotAnonymous()
544 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
541 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
545 'repository.admin')
542 'repository.admin')
546 @jsonify
543 @jsonify
547 def delete_comment(self, repo_name, comment_id):
544 def delete_comment(self, repo_name, comment_id):
548 co = ChangesetComment.get(comment_id)
545 co = ChangesetComment.get(comment_id)
549 if co.pull_request.is_closed():
546 if co.pull_request.is_closed():
550 #don't allow deleting comments on closed pull request
547 #don't allow deleting comments on closed pull request
551 raise HTTPForbidden()
548 raise HTTPForbidden()
552
549
553 owner = co.author.user_id == c.rhodecode_user.user_id
550 owner = co.author.user_id == c.rhodecode_user.user_id
554 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
551 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
555 ChangesetCommentsModel().delete(comment=co)
552 ChangesetCommentsModel().delete(comment=co)
556 Session().commit()
553 Session().commit()
557 return True
554 return True
558 else:
555 else:
559 raise HTTPForbidden()
556 raise HTTPForbidden()
@@ -1,387 +1,385 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.hooks
3 rhodecode.lib.hooks
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Hooks runned by rhodecode
6 Hooks runned by rhodecode
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
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 os
25 import os
26 import sys
26 import sys
27 import time
27 import time
28 import binascii
28 import binascii
29 import traceback
29 import traceback
30 from inspect import isfunction
30 from inspect import isfunction
31
31
32 from mercurial.scmutil import revrange
32 from rhodecode.lib.vcs.utils.hgcompat import nullrev, revrange
33 from mercurial.node import nullrev
34
35 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import action_logger
34 from rhodecode.lib.utils import action_logger
37 from rhodecode.lib.vcs.backends.base import EmptyChangeset
35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
38 from rhodecode.lib.compat import json
36 from rhodecode.lib.compat import json
39 from rhodecode.lib.exceptions import HTTPLockedRC
37 from rhodecode.lib.exceptions import HTTPLockedRC
40 from rhodecode.lib.utils2 import safe_str, _extract_extras
38 from rhodecode.lib.utils2 import safe_str, _extract_extras
41 from rhodecode.model.db import Repository, User
39 from rhodecode.model.db import Repository, User
42
40
43
41
44 def _get_scm_size(alias, root_path):
42 def _get_scm_size(alias, root_path):
45
43
46 if not alias.startswith('.'):
44 if not alias.startswith('.'):
47 alias += '.'
45 alias += '.'
48
46
49 size_scm, size_root = 0, 0
47 size_scm, size_root = 0, 0
50 for path, dirs, files in os.walk(safe_str(root_path)):
48 for path, dirs, files in os.walk(safe_str(root_path)):
51 if path.find(alias) != -1:
49 if path.find(alias) != -1:
52 for f in files:
50 for f in files:
53 try:
51 try:
54 size_scm += os.path.getsize(os.path.join(path, f))
52 size_scm += os.path.getsize(os.path.join(path, f))
55 except OSError:
53 except OSError:
56 pass
54 pass
57 else:
55 else:
58 for f in files:
56 for f in files:
59 try:
57 try:
60 size_root += os.path.getsize(os.path.join(path, f))
58 size_root += os.path.getsize(os.path.join(path, f))
61 except OSError:
59 except OSError:
62 pass
60 pass
63
61
64 size_scm_f = h.format_byte_size(size_scm)
62 size_scm_f = h.format_byte_size(size_scm)
65 size_root_f = h.format_byte_size(size_root)
63 size_root_f = h.format_byte_size(size_root)
66 size_total_f = h.format_byte_size(size_root + size_scm)
64 size_total_f = h.format_byte_size(size_root + size_scm)
67
65
68 return size_scm_f, size_root_f, size_total_f
66 return size_scm_f, size_root_f, size_total_f
69
67
70
68
71 def repo_size(ui, repo, hooktype=None, **kwargs):
69 def repo_size(ui, repo, hooktype=None, **kwargs):
72 """
70 """
73 Presents size of repository after push
71 Presents size of repository after push
74
72
75 :param ui:
73 :param ui:
76 :param repo:
74 :param repo:
77 :param hooktype:
75 :param hooktype:
78 """
76 """
79
77
80 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
78 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
81
79
82 last_cs = repo[len(repo) - 1]
80 last_cs = repo[len(repo) - 1]
83
81
84 msg = ('Repository size .hg:%s repo:%s total:%s\n'
82 msg = ('Repository size .hg:%s repo:%s total:%s\n'
85 'Last revision is now r%s:%s\n') % (
83 'Last revision is now r%s:%s\n') % (
86 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
84 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
87 )
85 )
88
86
89 sys.stdout.write(msg)
87 sys.stdout.write(msg)
90
88
91
89
92 def pre_push(ui, repo, **kwargs):
90 def pre_push(ui, repo, **kwargs):
93 # pre push function, currently used to ban pushing when
91 # pre push function, currently used to ban pushing when
94 # repository is locked
92 # repository is locked
95 ex = _extract_extras()
93 ex = _extract_extras()
96
94
97 usr = User.get_by_username(ex.username)
95 usr = User.get_by_username(ex.username)
98 if ex.locked_by[0] and usr.user_id != int(ex.locked_by[0]):
96 if ex.locked_by[0] and usr.user_id != int(ex.locked_by[0]):
99 locked_by = User.get(ex.locked_by[0]).username
97 locked_by = User.get(ex.locked_by[0]).username
100 # this exception is interpreted in git/hg middlewares and based
98 # this exception is interpreted in git/hg middlewares and based
101 # on that proper return code is server to client
99 # on that proper return code is server to client
102 _http_ret = HTTPLockedRC(ex.repository, locked_by)
100 _http_ret = HTTPLockedRC(ex.repository, locked_by)
103 if str(_http_ret.code).startswith('2'):
101 if str(_http_ret.code).startswith('2'):
104 #2xx Codes don't raise exceptions
102 #2xx Codes don't raise exceptions
105 sys.stdout.write(_http_ret.title)
103 sys.stdout.write(_http_ret.title)
106 else:
104 else:
107 raise _http_ret
105 raise _http_ret
108
106
109
107
110 def pre_pull(ui, repo, **kwargs):
108 def pre_pull(ui, repo, **kwargs):
111 # pre push function, currently used to ban pushing when
109 # pre push function, currently used to ban pushing when
112 # repository is locked
110 # repository is locked
113 ex = _extract_extras()
111 ex = _extract_extras()
114 if ex.locked_by[0]:
112 if ex.locked_by[0]:
115 locked_by = User.get(ex.locked_by[0]).username
113 locked_by = User.get(ex.locked_by[0]).username
116 # this exception is interpreted in git/hg middlewares and based
114 # this exception is interpreted in git/hg middlewares and based
117 # on that proper return code is server to client
115 # on that proper return code is server to client
118 _http_ret = HTTPLockedRC(ex.repository, locked_by)
116 _http_ret = HTTPLockedRC(ex.repository, locked_by)
119 if str(_http_ret.code).startswith('2'):
117 if str(_http_ret.code).startswith('2'):
120 #2xx Codes don't raise exceptions
118 #2xx Codes don't raise exceptions
121 sys.stdout.write(_http_ret.title)
119 sys.stdout.write(_http_ret.title)
122 else:
120 else:
123 raise _http_ret
121 raise _http_ret
124
122
125
123
126 def log_pull_action(ui, repo, **kwargs):
124 def log_pull_action(ui, repo, **kwargs):
127 """
125 """
128 Logs user last pull action
126 Logs user last pull action
129
127
130 :param ui:
128 :param ui:
131 :param repo:
129 :param repo:
132 """
130 """
133 ex = _extract_extras()
131 ex = _extract_extras()
134
132
135 user = User.get_by_username(ex.username)
133 user = User.get_by_username(ex.username)
136 action = 'pull'
134 action = 'pull'
137 action_logger(user, action, ex.repository, ex.ip, commit=True)
135 action_logger(user, action, ex.repository, ex.ip, commit=True)
138 # extension hook call
136 # extension hook call
139 from rhodecode import EXTENSIONS
137 from rhodecode import EXTENSIONS
140 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
138 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
141 if isfunction(callback):
139 if isfunction(callback):
142 kw = {}
140 kw = {}
143 kw.update(ex)
141 kw.update(ex)
144 callback(**kw)
142 callback(**kw)
145
143
146 if ex.make_lock is not None and ex.make_lock:
144 if ex.make_lock is not None and ex.make_lock:
147 Repository.lock(Repository.get_by_repo_name(ex.repository), user.user_id)
145 Repository.lock(Repository.get_by_repo_name(ex.repository), user.user_id)
148 #msg = 'Made lock on repo `%s`' % repository
146 #msg = 'Made lock on repo `%s`' % repository
149 #sys.stdout.write(msg)
147 #sys.stdout.write(msg)
150
148
151 if ex.locked_by[0]:
149 if ex.locked_by[0]:
152 locked_by = User.get(ex.locked_by[0]).username
150 locked_by = User.get(ex.locked_by[0]).username
153 _http_ret = HTTPLockedRC(ex.repository, locked_by)
151 _http_ret = HTTPLockedRC(ex.repository, locked_by)
154 if str(_http_ret.code).startswith('2'):
152 if str(_http_ret.code).startswith('2'):
155 #2xx Codes don't raise exceptions
153 #2xx Codes don't raise exceptions
156 sys.stdout.write(_http_ret.title)
154 sys.stdout.write(_http_ret.title)
157 return 0
155 return 0
158
156
159
157
160 def log_push_action(ui, repo, **kwargs):
158 def log_push_action(ui, repo, **kwargs):
161 """
159 """
162 Maps user last push action to new changeset id, from mercurial
160 Maps user last push action to new changeset id, from mercurial
163
161
164 :param ui:
162 :param ui:
165 :param repo: repo object containing the `ui` object
163 :param repo: repo object containing the `ui` object
166 """
164 """
167
165
168 ex = _extract_extras()
166 ex = _extract_extras()
169
167
170 action = ex.action + ':%s'
168 action = ex.action + ':%s'
171
169
172 if ex.scm == 'hg':
170 if ex.scm == 'hg':
173 node = kwargs['node']
171 node = kwargs['node']
174
172
175 def get_revs(repo, rev_opt):
173 def get_revs(repo, rev_opt):
176 if rev_opt:
174 if rev_opt:
177 revs = revrange(repo, rev_opt)
175 revs = revrange(repo, rev_opt)
178
176
179 if len(revs) == 0:
177 if len(revs) == 0:
180 return (nullrev, nullrev)
178 return (nullrev, nullrev)
181 return (max(revs), min(revs))
179 return (max(revs), min(revs))
182 else:
180 else:
183 return (len(repo) - 1, 0)
181 return (len(repo) - 1, 0)
184
182
185 stop, start = get_revs(repo, [node + ':'])
183 stop, start = get_revs(repo, [node + ':'])
186 h = binascii.hexlify
184 h = binascii.hexlify
187 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
185 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
188 elif ex.scm == 'git':
186 elif ex.scm == 'git':
189 revs = kwargs.get('_git_revs', [])
187 revs = kwargs.get('_git_revs', [])
190 if '_git_revs' in kwargs:
188 if '_git_revs' in kwargs:
191 kwargs.pop('_git_revs')
189 kwargs.pop('_git_revs')
192
190
193 action = action % ','.join(revs)
191 action = action % ','.join(revs)
194
192
195 action_logger(ex.username, action, ex.repository, ex.ip, commit=True)
193 action_logger(ex.username, action, ex.repository, ex.ip, commit=True)
196
194
197 # extension hook call
195 # extension hook call
198 from rhodecode import EXTENSIONS
196 from rhodecode import EXTENSIONS
199 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
197 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
200 if isfunction(callback):
198 if isfunction(callback):
201 kw = {'pushed_revs': revs}
199 kw = {'pushed_revs': revs}
202 kw.update(ex)
200 kw.update(ex)
203 callback(**kw)
201 callback(**kw)
204
202
205 if ex.make_lock is not None and not ex.make_lock:
203 if ex.make_lock is not None and not ex.make_lock:
206 Repository.unlock(Repository.get_by_repo_name(ex.repository))
204 Repository.unlock(Repository.get_by_repo_name(ex.repository))
207 msg = 'Released lock on repo `%s`\n' % ex.repository
205 msg = 'Released lock on repo `%s`\n' % ex.repository
208 sys.stdout.write(msg)
206 sys.stdout.write(msg)
209
207
210 if ex.locked_by[0]:
208 if ex.locked_by[0]:
211 locked_by = User.get(ex.locked_by[0]).username
209 locked_by = User.get(ex.locked_by[0]).username
212 _http_ret = HTTPLockedRC(ex.repository, locked_by)
210 _http_ret = HTTPLockedRC(ex.repository, locked_by)
213 if str(_http_ret.code).startswith('2'):
211 if str(_http_ret.code).startswith('2'):
214 #2xx Codes don't raise exceptions
212 #2xx Codes don't raise exceptions
215 sys.stdout.write(_http_ret.title)
213 sys.stdout.write(_http_ret.title)
216
214
217 return 0
215 return 0
218
216
219
217
220 def log_create_repository(repository_dict, created_by, **kwargs):
218 def log_create_repository(repository_dict, created_by, **kwargs):
221 """
219 """
222 Post create repository Hook. This is a dummy function for admins to re-use
220 Post create repository Hook. This is a dummy function for admins to re-use
223 if needed. It's taken from rhodecode-extensions module and executed
221 if needed. It's taken from rhodecode-extensions module and executed
224 if present
222 if present
225
223
226 :param repository: dict dump of repository object
224 :param repository: dict dump of repository object
227 :param created_by: username who created repository
225 :param created_by: username who created repository
228
226
229 available keys of repository_dict:
227 available keys of repository_dict:
230
228
231 'repo_type',
229 'repo_type',
232 'description',
230 'description',
233 'private',
231 'private',
234 'created_on',
232 'created_on',
235 'enable_downloads',
233 'enable_downloads',
236 'repo_id',
234 'repo_id',
237 'user_id',
235 'user_id',
238 'enable_statistics',
236 'enable_statistics',
239 'clone_uri',
237 'clone_uri',
240 'fork_id',
238 'fork_id',
241 'group_id',
239 'group_id',
242 'repo_name'
240 'repo_name'
243
241
244 """
242 """
245 from rhodecode import EXTENSIONS
243 from rhodecode import EXTENSIONS
246 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
244 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
247 if isfunction(callback):
245 if isfunction(callback):
248 kw = {}
246 kw = {}
249 kw.update(repository_dict)
247 kw.update(repository_dict)
250 kw.update({'created_by': created_by})
248 kw.update({'created_by': created_by})
251 kw.update(kwargs)
249 kw.update(kwargs)
252 return callback(**kw)
250 return callback(**kw)
253
251
254 return 0
252 return 0
255
253
256
254
257 def log_delete_repository(repository_dict, deleted_by, **kwargs):
255 def log_delete_repository(repository_dict, deleted_by, **kwargs):
258 """
256 """
259 Post delete repository Hook. This is a dummy function for admins to re-use
257 Post delete repository Hook. This is a dummy function for admins to re-use
260 if needed. It's taken from rhodecode-extensions module and executed
258 if needed. It's taken from rhodecode-extensions module and executed
261 if present
259 if present
262
260
263 :param repository: dict dump of repository object
261 :param repository: dict dump of repository object
264 :param deleted_by: username who deleted the repository
262 :param deleted_by: username who deleted the repository
265
263
266 available keys of repository_dict:
264 available keys of repository_dict:
267
265
268 'repo_type',
266 'repo_type',
269 'description',
267 'description',
270 'private',
268 'private',
271 'created_on',
269 'created_on',
272 'enable_downloads',
270 'enable_downloads',
273 'repo_id',
271 'repo_id',
274 'user_id',
272 'user_id',
275 'enable_statistics',
273 'enable_statistics',
276 'clone_uri',
274 'clone_uri',
277 'fork_id',
275 'fork_id',
278 'group_id',
276 'group_id',
279 'repo_name'
277 'repo_name'
280
278
281 """
279 """
282 from rhodecode import EXTENSIONS
280 from rhodecode import EXTENSIONS
283 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
281 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
284 if isfunction(callback):
282 if isfunction(callback):
285 kw = {}
283 kw = {}
286 kw.update(repository_dict)
284 kw.update(repository_dict)
287 kw.update({'deleted_by': deleted_by,
285 kw.update({'deleted_by': deleted_by,
288 'deleted_on': time.time()})
286 'deleted_on': time.time()})
289 kw.update(kwargs)
287 kw.update(kwargs)
290 return callback(**kw)
288 return callback(**kw)
291
289
292 return 0
290 return 0
293
291
294
292
295 handle_git_pre_receive = (lambda repo_path, revs, env:
293 handle_git_pre_receive = (lambda repo_path, revs, env:
296 handle_git_receive(repo_path, revs, env, hook_type='pre'))
294 handle_git_receive(repo_path, revs, env, hook_type='pre'))
297 handle_git_post_receive = (lambda repo_path, revs, env:
295 handle_git_post_receive = (lambda repo_path, revs, env:
298 handle_git_receive(repo_path, revs, env, hook_type='post'))
296 handle_git_receive(repo_path, revs, env, hook_type='post'))
299
297
300
298
301 def handle_git_receive(repo_path, revs, env, hook_type='post'):
299 def handle_git_receive(repo_path, revs, env, hook_type='post'):
302 """
300 """
303 A really hacky method that is runned by git post-receive hook and logs
301 A really hacky method that is runned by git post-receive hook and logs
304 an push action together with pushed revisions. It's executed by subprocess
302 an push action together with pushed revisions. It's executed by subprocess
305 thus needs all info to be able to create a on the fly pylons enviroment,
303 thus needs all info to be able to create a on the fly pylons enviroment,
306 connect to database and run the logging code. Hacky as sh*t but works.
304 connect to database and run the logging code. Hacky as sh*t but works.
307
305
308 :param repo_path:
306 :param repo_path:
309 :param revs:
307 :param revs:
310 :param env:
308 :param env:
311 """
309 """
312 from paste.deploy import appconfig
310 from paste.deploy import appconfig
313 from sqlalchemy import engine_from_config
311 from sqlalchemy import engine_from_config
314 from rhodecode.config.environment import load_environment
312 from rhodecode.config.environment import load_environment
315 from rhodecode.model import init_model
313 from rhodecode.model import init_model
316 from rhodecode.model.db import RhodeCodeUi
314 from rhodecode.model.db import RhodeCodeUi
317 from rhodecode.lib.utils import make_ui
315 from rhodecode.lib.utils import make_ui
318 extras = _extract_extras(env)
316 extras = _extract_extras(env)
319
317
320 path, ini_name = os.path.split(extras['config'])
318 path, ini_name = os.path.split(extras['config'])
321 conf = appconfig('config:%s' % ini_name, relative_to=path)
319 conf = appconfig('config:%s' % ini_name, relative_to=path)
322 load_environment(conf.global_conf, conf.local_conf)
320 load_environment(conf.global_conf, conf.local_conf)
323
321
324 engine = engine_from_config(conf, 'sqlalchemy.db1.')
322 engine = engine_from_config(conf, 'sqlalchemy.db1.')
325 init_model(engine)
323 init_model(engine)
326
324
327 baseui = make_ui('db')
325 baseui = make_ui('db')
328 # fix if it's not a bare repo
326 # fix if it's not a bare repo
329 if repo_path.endswith(os.sep + '.git'):
327 if repo_path.endswith(os.sep + '.git'):
330 repo_path = repo_path[:-5]
328 repo_path = repo_path[:-5]
331
329
332 repo = Repository.get_by_full_path(repo_path)
330 repo = Repository.get_by_full_path(repo_path)
333 if not repo:
331 if not repo:
334 raise OSError('Repository %s not found in database'
332 raise OSError('Repository %s not found in database'
335 % (safe_str(repo_path)))
333 % (safe_str(repo_path)))
336
334
337 _hooks = dict(baseui.configitems('hooks')) or {}
335 _hooks = dict(baseui.configitems('hooks')) or {}
338
336
339 if hook_type == 'pre':
337 if hook_type == 'pre':
340 repo = repo.scm_instance
338 repo = repo.scm_instance
341 else:
339 else:
342 #post push shouldn't use the cached instance never
340 #post push shouldn't use the cached instance never
343 repo = repo.scm_instance_no_cache()
341 repo = repo.scm_instance_no_cache()
344
342
345 if hook_type == 'pre':
343 if hook_type == 'pre':
346 pre_push(baseui, repo)
344 pre_push(baseui, repo)
347
345
348 # if push hook is enabled via web interface
346 # if push hook is enabled via web interface
349 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
347 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
350
348
351 rev_data = []
349 rev_data = []
352 for l in revs:
350 for l in revs:
353 old_rev, new_rev, ref = l.split(' ')
351 old_rev, new_rev, ref = l.split(' ')
354 _ref_data = ref.split('/')
352 _ref_data = ref.split('/')
355 if _ref_data[1] in ['tags', 'heads']:
353 if _ref_data[1] in ['tags', 'heads']:
356 rev_data.append({'old_rev': old_rev,
354 rev_data.append({'old_rev': old_rev,
357 'new_rev': new_rev,
355 'new_rev': new_rev,
358 'ref': ref,
356 'ref': ref,
359 'type': _ref_data[1],
357 'type': _ref_data[1],
360 'name': _ref_data[2].strip()})
358 'name': _ref_data[2].strip()})
361
359
362 git_revs = []
360 git_revs = []
363 for push_ref in rev_data:
361 for push_ref in rev_data:
364 _type = push_ref['type']
362 _type = push_ref['type']
365 if _type == 'heads':
363 if _type == 'heads':
366 if push_ref['old_rev'] == EmptyChangeset().raw_id:
364 if push_ref['old_rev'] == EmptyChangeset().raw_id:
367 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
365 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
368 heads = repo.run_git_command(cmd)[0]
366 heads = repo.run_git_command(cmd)[0]
369 heads = heads.replace(push_ref['ref'], '')
367 heads = heads.replace(push_ref['ref'], '')
370 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
368 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
371 heads.splitlines()))
369 heads.splitlines()))
372 cmd = (('log %(new_rev)s' % push_ref) +
370 cmd = (('log %(new_rev)s' % push_ref) +
373 ' --reverse --pretty=format:"%H" --not ' + heads)
371 ' --reverse --pretty=format:"%H" --not ' + heads)
374 git_revs += repo.run_git_command(cmd)[0].splitlines()
372 git_revs += repo.run_git_command(cmd)[0].splitlines()
375
373
376 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
374 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
377 #delete branch case
375 #delete branch case
378 git_revs += ['delete_branch=>%s' % push_ref['name']]
376 git_revs += ['delete_branch=>%s' % push_ref['name']]
379 else:
377 else:
380 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
378 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
381 ' --reverse --pretty=format:"%H"')
379 ' --reverse --pretty=format:"%H"')
382 git_revs += repo.run_git_command(cmd)[0].splitlines()
380 git_revs += repo.run_git_command(cmd)[0].splitlines()
383
381
384 elif _type == 'tags':
382 elif _type == 'tags':
385 git_revs += ['tag=>%s' % push_ref['name']]
383 git_revs += ['tag=>%s' % push_ref['name']]
386
384
387 log_push_action(baseui, repo, _git_revs=git_revs)
385 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,286 +1,285 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
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
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb_mod
33
31
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
33 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
36 HTTPBadRequest, HTTPNotAcceptable
34 HTTPBadRequest, HTTPNotAcceptable
37
35
38 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\
36 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\
39 _set_extras
37 _set_extras
40 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.base import BaseVCSController
41 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.auth import get_container_username
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
43 from rhodecode.lib.compat import json
41 from rhodecode.lib.compat import json
42 from rhodecode.lib.vcs.utils.hgcompat import RepoError, hgweb_mod
44 from rhodecode.model.db import User
43 from rhodecode.model.db import User
45 from rhodecode.lib.exceptions import HTTPLockedRC
44 from rhodecode.lib.exceptions import HTTPLockedRC
46
45
47
46
48 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
49
48
50
49
51 def is_mercurial(environ):
50 def is_mercurial(environ):
52 """
51 """
53 Returns True if request's target is mercurial server - header
52 Returns True if request's target is mercurial server - header
54 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
55 """
54 """
56 http_accept = environ.get('HTTP_ACCEPT')
55 http_accept = environ.get('HTTP_ACCEPT')
57 path_info = environ['PATH_INFO']
56 path_info = environ['PATH_INFO']
58 if http_accept and http_accept.startswith('application/mercurial'):
57 if http_accept and http_accept.startswith('application/mercurial'):
59 ishg_path = True
58 ishg_path = True
60 else:
59 else:
61 ishg_path = False
60 ishg_path = False
62
61
63 log.debug('pathinfo: %s detected as HG %s' % (
62 log.debug('pathinfo: %s detected as HG %s' % (
64 path_info, ishg_path)
63 path_info, ishg_path)
65 )
64 )
66 return ishg_path
65 return ishg_path
67
66
68
67
69 class SimpleHg(BaseVCSController):
68 class SimpleHg(BaseVCSController):
70
69
71 def _handle_request(self, environ, start_response):
70 def _handle_request(self, environ, start_response):
72 if not is_mercurial(environ):
71 if not is_mercurial(environ):
73 return self.application(environ, start_response)
72 return self.application(environ, start_response)
74 if not self._check_ssl(environ, start_response):
73 if not self._check_ssl(environ, start_response):
75 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
74 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
76
75
77 ip_addr = self._get_ip_addr(environ)
76 ip_addr = self._get_ip_addr(environ)
78 username = None
77 username = None
79 # skip passing error to error controller
78 # skip passing error to error controller
80 environ['pylons.status_code_redirect'] = True
79 environ['pylons.status_code_redirect'] = True
81
80
82 #======================================================================
81 #======================================================================
83 # EXTRACT REPOSITORY NAME FROM ENV
82 # EXTRACT REPOSITORY NAME FROM ENV
84 #======================================================================
83 #======================================================================
85 try:
84 try:
86 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
85 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
87 log.debug('Extracted repo name is %s' % repo_name)
86 log.debug('Extracted repo name is %s' % repo_name)
88 except Exception:
87 except Exception:
89 return HTTPInternalServerError()(environ, start_response)
88 return HTTPInternalServerError()(environ, start_response)
90
89
91 # quick check if that dir exists...
90 # quick check if that dir exists...
92 if not is_valid_repo(repo_name, self.basepath, 'hg'):
91 if not is_valid_repo(repo_name, self.basepath, 'hg'):
93 return HTTPNotFound()(environ, start_response)
92 return HTTPNotFound()(environ, start_response)
94
93
95 #======================================================================
94 #======================================================================
96 # GET ACTION PULL or PUSH
95 # GET ACTION PULL or PUSH
97 #======================================================================
96 #======================================================================
98 action = self.__get_action(environ)
97 action = self.__get_action(environ)
99
98
100 #======================================================================
99 #======================================================================
101 # CHECK ANONYMOUS PERMISSION
100 # CHECK ANONYMOUS PERMISSION
102 #======================================================================
101 #======================================================================
103 if action in ['pull', 'push']:
102 if action in ['pull', 'push']:
104 anonymous_user = self.__get_user('default')
103 anonymous_user = self.__get_user('default')
105 username = anonymous_user.username
104 username = anonymous_user.username
106 anonymous_perm = self._check_permission(action, anonymous_user,
105 anonymous_perm = self._check_permission(action, anonymous_user,
107 repo_name, ip_addr)
106 repo_name, ip_addr)
108
107
109 if not anonymous_perm or not anonymous_user.active:
108 if not anonymous_perm or not anonymous_user.active:
110 if not anonymous_perm:
109 if not anonymous_perm:
111 log.debug('Not enough credentials to access this '
110 log.debug('Not enough credentials to access this '
112 'repository as anonymous user')
111 'repository as anonymous user')
113 if not anonymous_user.active:
112 if not anonymous_user.active:
114 log.debug('Anonymous access is disabled, running '
113 log.debug('Anonymous access is disabled, running '
115 'authentication')
114 'authentication')
116 #==============================================================
115 #==============================================================
117 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
116 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
118 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
117 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
119 #==============================================================
118 #==============================================================
120
119
121 # Attempting to retrieve username from the container
120 # Attempting to retrieve username from the container
122 username = get_container_username(environ, self.config)
121 username = get_container_username(environ, self.config)
123
122
124 # If not authenticated by the container, running basic auth
123 # If not authenticated by the container, running basic auth
125 if not username:
124 if not username:
126 self.authenticate.realm = \
125 self.authenticate.realm = \
127 safe_str(self.config['rhodecode_realm'])
126 safe_str(self.config['rhodecode_realm'])
128 result = self.authenticate(environ)
127 result = self.authenticate(environ)
129 if isinstance(result, str):
128 if isinstance(result, str):
130 AUTH_TYPE.update(environ, 'basic')
129 AUTH_TYPE.update(environ, 'basic')
131 REMOTE_USER.update(environ, result)
130 REMOTE_USER.update(environ, result)
132 username = result
131 username = result
133 else:
132 else:
134 return result.wsgi_application(environ, start_response)
133 return result.wsgi_application(environ, start_response)
135
134
136 #==============================================================
135 #==============================================================
137 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
136 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
138 #==============================================================
137 #==============================================================
139 try:
138 try:
140 user = self.__get_user(username)
139 user = self.__get_user(username)
141 if user is None or not user.active:
140 if user is None or not user.active:
142 return HTTPForbidden()(environ, start_response)
141 return HTTPForbidden()(environ, start_response)
143 username = user.username
142 username = user.username
144 except Exception:
143 except Exception:
145 log.error(traceback.format_exc())
144 log.error(traceback.format_exc())
146 return HTTPInternalServerError()(environ, start_response)
145 return HTTPInternalServerError()(environ, start_response)
147
146
148 #check permissions for this repository
147 #check permissions for this repository
149 perm = self._check_permission(action, user, repo_name, ip_addr)
148 perm = self._check_permission(action, user, repo_name, ip_addr)
150 if not perm:
149 if not perm:
151 return HTTPForbidden()(environ, start_response)
150 return HTTPForbidden()(environ, start_response)
152
151
153 # extras are injected into mercurial UI object and later available
152 # extras are injected into mercurial UI object and later available
154 # in hg hooks executed by rhodecode
153 # in hg hooks executed by rhodecode
155 from rhodecode import CONFIG
154 from rhodecode import CONFIG
156 server_url = get_server_url(environ)
155 server_url = get_server_url(environ)
157 extras = {
156 extras = {
158 'ip': ip_addr,
157 'ip': ip_addr,
159 'username': username,
158 'username': username,
160 'action': action,
159 'action': action,
161 'repository': repo_name,
160 'repository': repo_name,
162 'scm': 'hg',
161 'scm': 'hg',
163 'config': CONFIG['__file__'],
162 'config': CONFIG['__file__'],
164 'server_url': server_url,
163 'server_url': server_url,
165 'make_lock': None,
164 'make_lock': None,
166 'locked_by': [None, None]
165 'locked_by': [None, None]
167 }
166 }
168 #======================================================================
167 #======================================================================
169 # MERCURIAL REQUEST HANDLING
168 # MERCURIAL REQUEST HANDLING
170 #======================================================================
169 #======================================================================
171 str_repo_name = safe_str(repo_name)
170 str_repo_name = safe_str(repo_name)
172 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
171 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
173 log.debug('Repository path is %s' % repo_path)
172 log.debug('Repository path is %s' % repo_path)
174
173
175 # CHECK LOCKING only if it's not ANONYMOUS USER
174 # CHECK LOCKING only if it's not ANONYMOUS USER
176 if username != User.DEFAULT_USER:
175 if username != User.DEFAULT_USER:
177 log.debug('Checking locking on repository')
176 log.debug('Checking locking on repository')
178 (make_lock,
177 (make_lock,
179 locked,
178 locked,
180 locked_by) = self._check_locking_state(
179 locked_by) = self._check_locking_state(
181 environ=environ, action=action,
180 environ=environ, action=action,
182 repo=repo_name, user_id=user.user_id
181 repo=repo_name, user_id=user.user_id
183 )
182 )
184 # store the make_lock for later evaluation in hooks
183 # store the make_lock for later evaluation in hooks
185 extras.update({'make_lock': make_lock,
184 extras.update({'make_lock': make_lock,
186 'locked_by': locked_by})
185 'locked_by': locked_by})
187
186
188 fix_PATH()
187 fix_PATH()
189 log.debug('HOOKS extras is %s' % extras)
188 log.debug('HOOKS extras is %s' % extras)
190 baseui = make_ui('db')
189 baseui = make_ui('db')
191 self.__inject_extras(repo_path, baseui, extras)
190 self.__inject_extras(repo_path, baseui, extras)
192
191
193 try:
192 try:
194 log.info('%s action on HG repo "%s" by "%s" from %s' %
193 log.info('%s action on HG repo "%s" by "%s" from %s' %
195 (action, str_repo_name, safe_str(username), ip_addr))
194 (action, str_repo_name, safe_str(username), ip_addr))
196 app = self.__make_app(repo_path, baseui, extras)
195 app = self.__make_app(repo_path, baseui, extras)
197 return app(environ, start_response)
196 return app(environ, start_response)
198 except RepoError, e:
197 except RepoError, e:
199 if str(e).find('not found') != -1:
198 if str(e).find('not found') != -1:
200 return HTTPNotFound()(environ, start_response)
199 return HTTPNotFound()(environ, start_response)
201 except HTTPLockedRC, e:
200 except HTTPLockedRC, e:
202 _code = CONFIG.get('lock_ret_code')
201 _code = CONFIG.get('lock_ret_code')
203 log.debug('Repository LOCKED ret code %s!' % (_code))
202 log.debug('Repository LOCKED ret code %s!' % (_code))
204 return e(environ, start_response)
203 return e(environ, start_response)
205 except Exception:
204 except Exception:
206 log.error(traceback.format_exc())
205 log.error(traceback.format_exc())
207 return HTTPInternalServerError()(environ, start_response)
206 return HTTPInternalServerError()(environ, start_response)
208 finally:
207 finally:
209 # invalidate cache on push
208 # invalidate cache on push
210 if action == 'push':
209 if action == 'push':
211 self._invalidate_cache(repo_name)
210 self._invalidate_cache(repo_name)
212
211
213 def __make_app(self, repo_name, baseui, extras):
212 def __make_app(self, repo_name, baseui, extras):
214 """
213 """
215 Make an wsgi application using hgweb, and inject generated baseui
214 Make an wsgi application using hgweb, and inject generated baseui
216 instance, additionally inject some extras into ui object
215 instance, additionally inject some extras into ui object
217 """
216 """
218 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
217 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
219
218
220 def __get_repository(self, environ):
219 def __get_repository(self, environ):
221 """
220 """
222 Get's repository name out of PATH_INFO header
221 Get's repository name out of PATH_INFO header
223
222
224 :param environ: environ where PATH_INFO is stored
223 :param environ: environ where PATH_INFO is stored
225 """
224 """
226 try:
225 try:
227 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
226 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
228 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
227 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
229 if repo_name.endswith('/'):
228 if repo_name.endswith('/'):
230 repo_name = repo_name.rstrip('/')
229 repo_name = repo_name.rstrip('/')
231 except Exception:
230 except Exception:
232 log.error(traceback.format_exc())
231 log.error(traceback.format_exc())
233 raise
232 raise
234
233
235 return repo_name
234 return repo_name
236
235
237 def __get_user(self, username):
236 def __get_user(self, username):
238 return User.get_by_username(username)
237 return User.get_by_username(username)
239
238
240 def __get_action(self, environ):
239 def __get_action(self, environ):
241 """
240 """
242 Maps mercurial request commands into a clone,pull or push command.
241 Maps mercurial request commands into a clone,pull or push command.
243 This should always return a valid command string
242 This should always return a valid command string
244
243
245 :param environ:
244 :param environ:
246 """
245 """
247 mapping = {'changegroup': 'pull',
246 mapping = {'changegroup': 'pull',
248 'changegroupsubset': 'pull',
247 'changegroupsubset': 'pull',
249 'stream_out': 'pull',
248 'stream_out': 'pull',
250 'listkeys': 'pull',
249 'listkeys': 'pull',
251 'unbundle': 'push',
250 'unbundle': 'push',
252 'pushkey': 'push', }
251 'pushkey': 'push', }
253 for qry in environ['QUERY_STRING'].split('&'):
252 for qry in environ['QUERY_STRING'].split('&'):
254 if qry.startswith('cmd'):
253 if qry.startswith('cmd'):
255 cmd = qry.split('=')[-1]
254 cmd = qry.split('=')[-1]
256 if cmd in mapping:
255 if cmd in mapping:
257 return mapping[cmd]
256 return mapping[cmd]
258
257
259 return 'pull'
258 return 'pull'
260
259
261 raise Exception('Unable to detect pull/push action !!'
260 raise Exception('Unable to detect pull/push action !!'
262 'Are you using non standard command or client ?')
261 'Are you using non standard command or client ?')
263
262
264 def __inject_extras(self, repo_path, baseui, extras={}):
263 def __inject_extras(self, repo_path, baseui, extras={}):
265 """
264 """
266 Injects some extra params into baseui instance
265 Injects some extra params into baseui instance
267
266
268 also overwrites global settings with those takes from local hgrc file
267 also overwrites global settings with those takes from local hgrc file
269
268
270 :param baseui: baseui instance
269 :param baseui: baseui instance
271 :param extras: dict with extra params to put into baseui
270 :param extras: dict with extra params to put into baseui
272 """
271 """
273
272
274 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
273 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
275
274
276 # make our hgweb quiet so it doesn't print output
275 # make our hgweb quiet so it doesn't print output
277 baseui.setconfig('ui', 'quiet', 'true')
276 baseui.setconfig('ui', 'quiet', 'true')
278
277
279 repoui = make_ui('file', hgrc, False)
278 repoui = make_ui('file', hgrc, False)
280
279
281 if repoui:
280 if repoui:
282 #overwrite our ui instance with the section from hgrc file
281 #overwrite our ui instance with the section from hgrc file
283 for section in ui_sections:
282 for section in ui_sections:
284 for k, v in repoui.configitems(section):
283 for k, v in repoui.configitems(section):
285 baseui.setconfig(section, k, v)
284 baseui.setconfig(section, k, v)
286 _set_extras(extras)
285 _set_extras(extras)
@@ -1,816 +1,815 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
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
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
35 import decorator
36 import warnings
36 import warnings
37 from os.path import abspath
37 from os.path import abspath
38 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
39
39
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41
41
42 from mercurial import ui, config
43
44 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
45
43
46 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs import get_backend
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.utils.hgcompat import ui, config
49 from rhodecode.lib.vcs.utils.helpers import get_scm
48 from rhodecode.lib.vcs.utils.helpers import get_scm
50 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
51
50
52 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
53
52
54 from rhodecode.model import meta
53 from rhodecode.model import meta
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation, UserGroup
55 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation, UserGroup
57 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
58 from rhodecode.model.repos_group import ReposGroupModel
57 from rhodecode.model.repos_group import ReposGroupModel
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 from rhodecode.lib.vcs.utils.fakemod import create_module
59 from rhodecode.lib.vcs.utils.fakemod import create_module
61 from rhodecode.model.users_group import UserGroupModel
60 from rhodecode.model.users_group import UserGroupModel
62
61
63 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
64
63
65 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
66
65
67
66
68 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
69 """
68 """
70 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
71
70
72 :param str_: given string
71 :param str_: given string
73 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
74
73
75 Examples::
74 Examples::
76 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
77 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
78 """
77 """
79
78
80 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
81 return str_
80 return str_
82 else:
81 else:
83 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
84 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
85
84
86
85
87 def repo_name_slug(value):
86 def repo_name_slug(value):
88 """
87 """
89 Return slug of name of repository
88 Return slug of name of repository
90 This function is called on each creation/modification
89 This function is called on each creation/modification
91 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
92 """
91 """
93
92
94 slug = remove_formatting(value)
93 slug = remove_formatting(value)
95 slug = strip_tags(slug)
94 slug = strip_tags(slug)
96
95
97 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
98 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
99 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
100 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
101 return slug
100 return slug
102
101
103
102
104 #==============================================================================
103 #==============================================================================
105 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
104 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
106 #==============================================================================
105 #==============================================================================
107 def get_repo_slug(request):
106 def get_repo_slug(request):
108 _repo = request.environ['pylons.routes_dict'].get('repo_name')
107 _repo = request.environ['pylons.routes_dict'].get('repo_name')
109 if _repo:
108 if _repo:
110 _repo = _repo.rstrip('/')
109 _repo = _repo.rstrip('/')
111 return _repo
110 return _repo
112
111
113
112
114 def get_repos_group_slug(request):
113 def get_repos_group_slug(request):
115 _group = request.environ['pylons.routes_dict'].get('group_name')
114 _group = request.environ['pylons.routes_dict'].get('group_name')
116 if _group:
115 if _group:
117 _group = _group.rstrip('/')
116 _group = _group.rstrip('/')
118 return _group
117 return _group
119
118
120
119
121 def get_user_group_slug(request):
120 def get_user_group_slug(request):
122 _group = request.environ['pylons.routes_dict'].get('id')
121 _group = request.environ['pylons.routes_dict'].get('id')
123 try:
122 try:
124 _group = UserGroup.get(_group)
123 _group = UserGroup.get(_group)
125 if _group:
124 if _group:
126 _group = _group.users_group_name
125 _group = _group.users_group_name
127 except Exception:
126 except Exception:
128 log.debug(traceback.format_exc())
127 log.debug(traceback.format_exc())
129 #catch all failures here
128 #catch all failures here
130 pass
129 pass
131
130
132 return _group
131 return _group
133
132
134
133
135 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
134 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
136 """
135 """
137 Action logger for various actions made by users
136 Action logger for various actions made by users
138
137
139 :param user: user that made this action, can be a unique username string or
138 :param user: user that made this action, can be a unique username string or
140 object containing user_id attribute
139 object containing user_id attribute
141 :param action: action to log, should be on of predefined unique actions for
140 :param action: action to log, should be on of predefined unique actions for
142 easy translations
141 easy translations
143 :param repo: string name of repository or object containing repo_id,
142 :param repo: string name of repository or object containing repo_id,
144 that action was made on
143 that action was made on
145 :param ipaddr: optional ip address from what the action was made
144 :param ipaddr: optional ip address from what the action was made
146 :param sa: optional sqlalchemy session
145 :param sa: optional sqlalchemy session
147
146
148 """
147 """
149
148
150 if not sa:
149 if not sa:
151 sa = meta.Session()
150 sa = meta.Session()
152
151
153 try:
152 try:
154 if hasattr(user, 'user_id'):
153 if hasattr(user, 'user_id'):
155 user_obj = User.get(user.user_id)
154 user_obj = User.get(user.user_id)
156 elif isinstance(user, basestring):
155 elif isinstance(user, basestring):
157 user_obj = User.get_by_username(user)
156 user_obj = User.get_by_username(user)
158 else:
157 else:
159 raise Exception('You have to provide a user object or a username')
158 raise Exception('You have to provide a user object or a username')
160
159
161 if hasattr(repo, 'repo_id'):
160 if hasattr(repo, 'repo_id'):
162 repo_obj = Repository.get(repo.repo_id)
161 repo_obj = Repository.get(repo.repo_id)
163 repo_name = repo_obj.repo_name
162 repo_name = repo_obj.repo_name
164 elif isinstance(repo, basestring):
163 elif isinstance(repo, basestring):
165 repo_name = repo.lstrip('/')
164 repo_name = repo.lstrip('/')
166 repo_obj = Repository.get_by_repo_name(repo_name)
165 repo_obj = Repository.get_by_repo_name(repo_name)
167 else:
166 else:
168 repo_obj = None
167 repo_obj = None
169 repo_name = ''
168 repo_name = ''
170
169
171 user_log = UserLog()
170 user_log = UserLog()
172 user_log.user_id = user_obj.user_id
171 user_log.user_id = user_obj.user_id
173 user_log.username = user_obj.username
172 user_log.username = user_obj.username
174 user_log.action = safe_unicode(action)
173 user_log.action = safe_unicode(action)
175
174
176 user_log.repository = repo_obj
175 user_log.repository = repo_obj
177 user_log.repository_name = repo_name
176 user_log.repository_name = repo_name
178
177
179 user_log.action_date = datetime.datetime.now()
178 user_log.action_date = datetime.datetime.now()
180 user_log.user_ip = ipaddr
179 user_log.user_ip = ipaddr
181 sa.add(user_log)
180 sa.add(user_log)
182
181
183 log.info('Logging action:%s on %s by user:%s ip:%s' %
182 log.info('Logging action:%s on %s by user:%s ip:%s' %
184 (action, safe_unicode(repo), user_obj, ipaddr))
183 (action, safe_unicode(repo), user_obj, ipaddr))
185 if commit:
184 if commit:
186 sa.commit()
185 sa.commit()
187 except Exception:
186 except Exception:
188 log.error(traceback.format_exc())
187 log.error(traceback.format_exc())
189 raise
188 raise
190
189
191
190
192 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
191 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
193 """
192 """
194 Scans given path for repos and return (name,(type,path)) tuple
193 Scans given path for repos and return (name,(type,path)) tuple
195
194
196 :param path: path to scan for repositories
195 :param path: path to scan for repositories
197 :param recursive: recursive search and return names with subdirs in front
196 :param recursive: recursive search and return names with subdirs in front
198 """
197 """
199
198
200 # remove ending slash for better results
199 # remove ending slash for better results
201 path = path.rstrip(os.sep)
200 path = path.rstrip(os.sep)
202 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
201 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
203
202
204 def _get_repos(p):
203 def _get_repos(p):
205 if not os.access(p, os.W_OK):
204 if not os.access(p, os.W_OK):
206 log.warn('ignoring repo path without write access: %s', p)
205 log.warn('ignoring repo path without write access: %s', p)
207 return
206 return
208 for dirpath in os.listdir(p):
207 for dirpath in os.listdir(p):
209 if os.path.isfile(os.path.join(p, dirpath)):
208 if os.path.isfile(os.path.join(p, dirpath)):
210 continue
209 continue
211 cur_path = os.path.join(p, dirpath)
210 cur_path = os.path.join(p, dirpath)
212
211
213 # skip removed repos
212 # skip removed repos
214 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
213 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
215 continue
214 continue
216
215
217 #skip .<somethin> dirs
216 #skip .<somethin> dirs
218 if dirpath.startswith('.'):
217 if dirpath.startswith('.'):
219 continue
218 continue
220
219
221 try:
220 try:
222 scm_info = get_scm(cur_path)
221 scm_info = get_scm(cur_path)
223 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
222 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
224 except VCSError:
223 except VCSError:
225 if not recursive:
224 if not recursive:
226 continue
225 continue
227 #check if this dir containts other repos for recursive scan
226 #check if this dir containts other repos for recursive scan
228 rec_path = os.path.join(p, dirpath)
227 rec_path = os.path.join(p, dirpath)
229 if os.path.isdir(rec_path):
228 if os.path.isdir(rec_path):
230 for inner_scm in _get_repos(rec_path):
229 for inner_scm in _get_repos(rec_path):
231 yield inner_scm
230 yield inner_scm
232
231
233 return _get_repos(path)
232 return _get_repos(path)
234
233
235
234
236 def is_valid_repo(repo_name, base_path, scm=None):
235 def is_valid_repo(repo_name, base_path, scm=None):
237 """
236 """
238 Returns True if given path is a valid repository False otherwise.
237 Returns True if given path is a valid repository False otherwise.
239 If scm param is given also compare if given scm is the same as expected
238 If scm param is given also compare if given scm is the same as expected
240 from scm parameter
239 from scm parameter
241
240
242 :param repo_name:
241 :param repo_name:
243 :param base_path:
242 :param base_path:
244 :param scm:
243 :param scm:
245
244
246 :return True: if given path is a valid repository
245 :return True: if given path is a valid repository
247 """
246 """
248 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
247 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
249
248
250 try:
249 try:
251 scm_ = get_scm(full_path)
250 scm_ = get_scm(full_path)
252 if scm:
251 if scm:
253 return scm_[0] == scm
252 return scm_[0] == scm
254 return True
253 return True
255 except VCSError:
254 except VCSError:
256 return False
255 return False
257
256
258
257
259 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
258 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
260 """
259 """
261 Returns True if given path is a repository group False otherwise
260 Returns True if given path is a repository group False otherwise
262
261
263 :param repo_name:
262 :param repo_name:
264 :param base_path:
263 :param base_path:
265 """
264 """
266 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
265 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
267
266
268 # check if it's not a repo
267 # check if it's not a repo
269 if is_valid_repo(repos_group_name, base_path):
268 if is_valid_repo(repos_group_name, base_path):
270 return False
269 return False
271
270
272 try:
271 try:
273 # we need to check bare git repos at higher level
272 # we need to check bare git repos at higher level
274 # since we might match branches/hooks/info/objects or possible
273 # since we might match branches/hooks/info/objects or possible
275 # other things inside bare git repo
274 # other things inside bare git repo
276 get_scm(os.path.dirname(full_path))
275 get_scm(os.path.dirname(full_path))
277 return False
276 return False
278 except VCSError:
277 except VCSError:
279 pass
278 pass
280
279
281 # check if it's a valid path
280 # check if it's a valid path
282 if skip_path_check or os.path.isdir(full_path):
281 if skip_path_check or os.path.isdir(full_path):
283 return True
282 return True
284
283
285 return False
284 return False
286
285
287
286
288 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
287 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
289 while True:
288 while True:
290 ok = raw_input(prompt)
289 ok = raw_input(prompt)
291 if ok in ('y', 'ye', 'yes'):
290 if ok in ('y', 'ye', 'yes'):
292 return True
291 return True
293 if ok in ('n', 'no', 'nop', 'nope'):
292 if ok in ('n', 'no', 'nop', 'nope'):
294 return False
293 return False
295 retries = retries - 1
294 retries = retries - 1
296 if retries < 0:
295 if retries < 0:
297 raise IOError
296 raise IOError
298 print complaint
297 print complaint
299
298
300 #propagated from mercurial documentation
299 #propagated from mercurial documentation
301 ui_sections = ['alias', 'auth',
300 ui_sections = ['alias', 'auth',
302 'decode/encode', 'defaults',
301 'decode/encode', 'defaults',
303 'diff', 'email',
302 'diff', 'email',
304 'extensions', 'format',
303 'extensions', 'format',
305 'merge-patterns', 'merge-tools',
304 'merge-patterns', 'merge-tools',
306 'hooks', 'http_proxy',
305 'hooks', 'http_proxy',
307 'smtp', 'patch',
306 'smtp', 'patch',
308 'paths', 'profiling',
307 'paths', 'profiling',
309 'server', 'trusted',
308 'server', 'trusted',
310 'ui', 'web', ]
309 'ui', 'web', ]
311
310
312
311
313 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
312 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
314 """
313 """
315 A function that will read python rc files or database
314 A function that will read python rc files or database
316 and make an mercurial ui object from read options
315 and make an mercurial ui object from read options
317
316
318 :param path: path to mercurial config file
317 :param path: path to mercurial config file
319 :param checkpaths: check the path
318 :param checkpaths: check the path
320 :param read_from: read from 'file' or 'db'
319 :param read_from: read from 'file' or 'db'
321 """
320 """
322
321
323 baseui = ui.ui()
322 baseui = ui.ui()
324
323
325 # clean the baseui object
324 # clean the baseui object
326 baseui._ocfg = config.config()
325 baseui._ocfg = config.config()
327 baseui._ucfg = config.config()
326 baseui._ucfg = config.config()
328 baseui._tcfg = config.config()
327 baseui._tcfg = config.config()
329
328
330 if read_from == 'file':
329 if read_from == 'file':
331 if not os.path.isfile(path):
330 if not os.path.isfile(path):
332 log.debug('hgrc file is not present at %s, skipping...' % path)
331 log.debug('hgrc file is not present at %s, skipping...' % path)
333 return False
332 return False
334 log.debug('reading hgrc from %s' % path)
333 log.debug('reading hgrc from %s' % path)
335 cfg = config.config()
334 cfg = config.config()
336 cfg.read(path)
335 cfg.read(path)
337 for section in ui_sections:
336 for section in ui_sections:
338 for k, v in cfg.items(section):
337 for k, v in cfg.items(section):
339 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
338 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
340 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
339 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
341
340
342 elif read_from == 'db':
341 elif read_from == 'db':
343 sa = meta.Session()
342 sa = meta.Session()
344 ret = sa.query(RhodeCodeUi)\
343 ret = sa.query(RhodeCodeUi)\
345 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
344 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
346 .all()
345 .all()
347
346
348 hg_ui = ret
347 hg_ui = ret
349 for ui_ in hg_ui:
348 for ui_ in hg_ui:
350 if ui_.ui_active:
349 if ui_.ui_active:
351 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
350 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
352 ui_.ui_key, ui_.ui_value)
351 ui_.ui_key, ui_.ui_value)
353 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
352 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
354 safe_str(ui_.ui_value))
353 safe_str(ui_.ui_value))
355 if ui_.ui_key == 'push_ssl':
354 if ui_.ui_key == 'push_ssl':
356 # force set push_ssl requirement to False, rhodecode
355 # force set push_ssl requirement to False, rhodecode
357 # handles that
356 # handles that
358 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
357 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
359 False)
358 False)
360 if clear_session:
359 if clear_session:
361 meta.Session.remove()
360 meta.Session.remove()
362 return baseui
361 return baseui
363
362
364
363
365 def set_rhodecode_config(config):
364 def set_rhodecode_config(config):
366 """
365 """
367 Updates pylons config with new settings from database
366 Updates pylons config with new settings from database
368
367
369 :param config:
368 :param config:
370 """
369 """
371 hgsettings = RhodeCodeSetting.get_app_settings()
370 hgsettings = RhodeCodeSetting.get_app_settings()
372
371
373 for k, v in hgsettings.items():
372 for k, v in hgsettings.items():
374 config[k] = v
373 config[k] = v
375
374
376
375
377 def set_vcs_config(config):
376 def set_vcs_config(config):
378 """
377 """
379 Patch VCS config with some RhodeCode specific stuff
378 Patch VCS config with some RhodeCode specific stuff
380
379
381 :param config: rhodecode.CONFIG
380 :param config: rhodecode.CONFIG
382 """
381 """
383 import rhodecode
382 import rhodecode
384 from rhodecode.lib.vcs import conf
383 from rhodecode.lib.vcs import conf
385 from rhodecode.lib.utils2 import aslist
384 from rhodecode.lib.utils2 import aslist
386 conf.settings.BACKENDS = {
385 conf.settings.BACKENDS = {
387 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
386 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
388 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
387 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
389 }
388 }
390
389
391 conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
390 conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
392 conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
391 conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
393 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
392 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
394 'utf8'), sep=',')
393 'utf8'), sep=',')
395
394
396
395
397 def map_groups(path):
396 def map_groups(path):
398 """
397 """
399 Given a full path to a repository, create all nested groups that this
398 Given a full path to a repository, create all nested groups that this
400 repo is inside. This function creates parent-child relationships between
399 repo is inside. This function creates parent-child relationships between
401 groups and creates default perms for all new groups.
400 groups and creates default perms for all new groups.
402
401
403 :param paths: full path to repository
402 :param paths: full path to repository
404 """
403 """
405 sa = meta.Session()
404 sa = meta.Session()
406 groups = path.split(Repository.url_sep())
405 groups = path.split(Repository.url_sep())
407 parent = None
406 parent = None
408 group = None
407 group = None
409
408
410 # last element is repo in nested groups structure
409 # last element is repo in nested groups structure
411 groups = groups[:-1]
410 groups = groups[:-1]
412 rgm = ReposGroupModel(sa)
411 rgm = ReposGroupModel(sa)
413 owner = User.get_first_admin()
412 owner = User.get_first_admin()
414 for lvl, group_name in enumerate(groups):
413 for lvl, group_name in enumerate(groups):
415 group_name = '/'.join(groups[:lvl] + [group_name])
414 group_name = '/'.join(groups[:lvl] + [group_name])
416 group = RepoGroup.get_by_group_name(group_name)
415 group = RepoGroup.get_by_group_name(group_name)
417 desc = '%s group' % group_name
416 desc = '%s group' % group_name
418
417
419 # skip folders that are now removed repos
418 # skip folders that are now removed repos
420 if REMOVED_REPO_PAT.match(group_name):
419 if REMOVED_REPO_PAT.match(group_name):
421 break
420 break
422
421
423 if group is None:
422 if group is None:
424 log.debug('creating group level: %s group_name: %s'
423 log.debug('creating group level: %s group_name: %s'
425 % (lvl, group_name))
424 % (lvl, group_name))
426 group = RepoGroup(group_name, parent)
425 group = RepoGroup(group_name, parent)
427 group.group_description = desc
426 group.group_description = desc
428 group.user = owner
427 group.user = owner
429 sa.add(group)
428 sa.add(group)
430 perm_obj = rgm._create_default_perms(group)
429 perm_obj = rgm._create_default_perms(group)
431 sa.add(perm_obj)
430 sa.add(perm_obj)
432 sa.flush()
431 sa.flush()
433
432
434 parent = group
433 parent = group
435 return group
434 return group
436
435
437
436
438 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
437 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
439 install_git_hook=False):
438 install_git_hook=False):
440 """
439 """
441 maps all repos given in initial_repo_list, non existing repositories
440 maps all repos given in initial_repo_list, non existing repositories
442 are created, if remove_obsolete is True it also check for db entries
441 are created, if remove_obsolete is True it also check for db entries
443 that are not in initial_repo_list and removes them.
442 that are not in initial_repo_list and removes them.
444
443
445 :param initial_repo_list: list of repositories found by scanning methods
444 :param initial_repo_list: list of repositories found by scanning methods
446 :param remove_obsolete: check for obsolete entries in database
445 :param remove_obsolete: check for obsolete entries in database
447 :param install_git_hook: if this is True, also check and install githook
446 :param install_git_hook: if this is True, also check and install githook
448 for a repo if missing
447 for a repo if missing
449 """
448 """
450 from rhodecode.model.repo import RepoModel
449 from rhodecode.model.repo import RepoModel
451 from rhodecode.model.scm import ScmModel
450 from rhodecode.model.scm import ScmModel
452 sa = meta.Session()
451 sa = meta.Session()
453 rm = RepoModel()
452 rm = RepoModel()
454 user = User.get_first_admin()
453 user = User.get_first_admin()
455 added = []
454 added = []
456
455
457 ##creation defaults
456 ##creation defaults
458 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
457 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
459 enable_statistics = defs.get('repo_enable_statistics')
458 enable_statistics = defs.get('repo_enable_statistics')
460 enable_locking = defs.get('repo_enable_locking')
459 enable_locking = defs.get('repo_enable_locking')
461 enable_downloads = defs.get('repo_enable_downloads')
460 enable_downloads = defs.get('repo_enable_downloads')
462 private = defs.get('repo_private')
461 private = defs.get('repo_private')
463
462
464 for name, repo in initial_repo_list.items():
463 for name, repo in initial_repo_list.items():
465 group = map_groups(name)
464 group = map_groups(name)
466 db_repo = rm.get_by_repo_name(name)
465 db_repo = rm.get_by_repo_name(name)
467 # found repo that is on filesystem not in RhodeCode database
466 # found repo that is on filesystem not in RhodeCode database
468 if not db_repo:
467 if not db_repo:
469 log.info('repository %s not found, creating now' % name)
468 log.info('repository %s not found, creating now' % name)
470 added.append(name)
469 added.append(name)
471 desc = (repo.description
470 desc = (repo.description
472 if repo.description != 'unknown'
471 if repo.description != 'unknown'
473 else '%s repository' % name)
472 else '%s repository' % name)
474
473
475 new_repo = rm.create_repo(
474 new_repo = rm.create_repo(
476 repo_name=name,
475 repo_name=name,
477 repo_type=repo.alias,
476 repo_type=repo.alias,
478 description=desc,
477 description=desc,
479 repos_group=getattr(group, 'group_id', None),
478 repos_group=getattr(group, 'group_id', None),
480 owner=user,
479 owner=user,
481 just_db=True,
480 just_db=True,
482 enable_locking=enable_locking,
481 enable_locking=enable_locking,
483 enable_downloads=enable_downloads,
482 enable_downloads=enable_downloads,
484 enable_statistics=enable_statistics,
483 enable_statistics=enable_statistics,
485 private=private
484 private=private
486 )
485 )
487 # we added that repo just now, and make sure it has githook
486 # we added that repo just now, and make sure it has githook
488 # installed
487 # installed
489 if new_repo.repo_type == 'git':
488 if new_repo.repo_type == 'git':
490 ScmModel().install_git_hook(new_repo.scm_instance)
489 ScmModel().install_git_hook(new_repo.scm_instance)
491 new_repo.update_changeset_cache()
490 new_repo.update_changeset_cache()
492 elif install_git_hook:
491 elif install_git_hook:
493 if db_repo.repo_type == 'git':
492 if db_repo.repo_type == 'git':
494 ScmModel().install_git_hook(db_repo.scm_instance)
493 ScmModel().install_git_hook(db_repo.scm_instance)
495
494
496 sa.commit()
495 sa.commit()
497 removed = []
496 removed = []
498 if remove_obsolete:
497 if remove_obsolete:
499 # remove from database those repositories that are not in the filesystem
498 # remove from database those repositories that are not in the filesystem
500 for repo in sa.query(Repository).all():
499 for repo in sa.query(Repository).all():
501 if repo.repo_name not in initial_repo_list.keys():
500 if repo.repo_name not in initial_repo_list.keys():
502 log.debug("Removing non-existing repository found in db `%s`" %
501 log.debug("Removing non-existing repository found in db `%s`" %
503 repo.repo_name)
502 repo.repo_name)
504 try:
503 try:
505 removed.append(repo.repo_name)
504 removed.append(repo.repo_name)
506 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
505 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
507 sa.commit()
506 sa.commit()
508 except Exception:
507 except Exception:
509 #don't hold further removals on error
508 #don't hold further removals on error
510 log.error(traceback.format_exc())
509 log.error(traceback.format_exc())
511 sa.rollback()
510 sa.rollback()
512 return added, removed
511 return added, removed
513
512
514
513
515 # set cache regions for beaker so celery can utilise it
514 # set cache regions for beaker so celery can utilise it
516 def add_cache(settings):
515 def add_cache(settings):
517 cache_settings = {'regions': None}
516 cache_settings = {'regions': None}
518 for key in settings.keys():
517 for key in settings.keys():
519 for prefix in ['beaker.cache.', 'cache.']:
518 for prefix in ['beaker.cache.', 'cache.']:
520 if key.startswith(prefix):
519 if key.startswith(prefix):
521 name = key.split(prefix)[1].strip()
520 name = key.split(prefix)[1].strip()
522 cache_settings[name] = settings[key].strip()
521 cache_settings[name] = settings[key].strip()
523 if cache_settings['regions']:
522 if cache_settings['regions']:
524 for region in cache_settings['regions'].split(','):
523 for region in cache_settings['regions'].split(','):
525 region = region.strip()
524 region = region.strip()
526 region_settings = {}
525 region_settings = {}
527 for key, value in cache_settings.items():
526 for key, value in cache_settings.items():
528 if key.startswith(region):
527 if key.startswith(region):
529 region_settings[key.split('.')[1]] = value
528 region_settings[key.split('.')[1]] = value
530 region_settings['expire'] = int(region_settings.get('expire',
529 region_settings['expire'] = int(region_settings.get('expire',
531 60))
530 60))
532 region_settings.setdefault('lock_dir',
531 region_settings.setdefault('lock_dir',
533 cache_settings.get('lock_dir'))
532 cache_settings.get('lock_dir'))
534 region_settings.setdefault('data_dir',
533 region_settings.setdefault('data_dir',
535 cache_settings.get('data_dir'))
534 cache_settings.get('data_dir'))
536
535
537 if 'type' not in region_settings:
536 if 'type' not in region_settings:
538 region_settings['type'] = cache_settings.get('type',
537 region_settings['type'] = cache_settings.get('type',
539 'memory')
538 'memory')
540 beaker.cache.cache_regions[region] = region_settings
539 beaker.cache.cache_regions[region] = region_settings
541
540
542
541
543 def load_rcextensions(root_path):
542 def load_rcextensions(root_path):
544 import rhodecode
543 import rhodecode
545 from rhodecode.config import conf
544 from rhodecode.config import conf
546
545
547 path = os.path.join(root_path, 'rcextensions', '__init__.py')
546 path = os.path.join(root_path, 'rcextensions', '__init__.py')
548 if os.path.isfile(path):
547 if os.path.isfile(path):
549 rcext = create_module('rc', path)
548 rcext = create_module('rc', path)
550 EXT = rhodecode.EXTENSIONS = rcext
549 EXT = rhodecode.EXTENSIONS = rcext
551 log.debug('Found rcextensions now loading %s...' % rcext)
550 log.debug('Found rcextensions now loading %s...' % rcext)
552
551
553 # Additional mappings that are not present in the pygments lexers
552 # Additional mappings that are not present in the pygments lexers
554 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
553 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
555
554
556 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
555 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
557
556
558 if getattr(EXT, 'INDEX_EXTENSIONS', []):
557 if getattr(EXT, 'INDEX_EXTENSIONS', []):
559 log.debug('settings custom INDEX_EXTENSIONS')
558 log.debug('settings custom INDEX_EXTENSIONS')
560 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
559 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
561
560
562 #ADDITIONAL MAPPINGS
561 #ADDITIONAL MAPPINGS
563 log.debug('adding extra into INDEX_EXTENSIONS')
562 log.debug('adding extra into INDEX_EXTENSIONS')
564 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
563 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
565
564
566 # auto check if the module is not missing any data, set to default if is
565 # auto check if the module is not missing any data, set to default if is
567 # this will help autoupdate new feature of rcext module
566 # this will help autoupdate new feature of rcext module
568 from rhodecode.config import rcextensions
567 from rhodecode.config import rcextensions
569 for k in dir(rcextensions):
568 for k in dir(rcextensions):
570 if not k.startswith('_') and not hasattr(EXT, k):
569 if not k.startswith('_') and not hasattr(EXT, k):
571 setattr(EXT, k, getattr(rcextensions, k))
570 setattr(EXT, k, getattr(rcextensions, k))
572
571
573
572
574 def get_custom_lexer(extension):
573 def get_custom_lexer(extension):
575 """
574 """
576 returns a custom lexer if it's defined in rcextensions module, or None
575 returns a custom lexer if it's defined in rcextensions module, or None
577 if there's no custom lexer defined
576 if there's no custom lexer defined
578 """
577 """
579 import rhodecode
578 import rhodecode
580 from pygments import lexers
579 from pygments import lexers
581 #check if we didn't define this extension as other lexer
580 #check if we didn't define this extension as other lexer
582 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
581 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
583 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
582 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
584 return lexers.get_lexer_by_name(_lexer_name)
583 return lexers.get_lexer_by_name(_lexer_name)
585
584
586
585
587 #==============================================================================
586 #==============================================================================
588 # TEST FUNCTIONS AND CREATORS
587 # TEST FUNCTIONS AND CREATORS
589 #==============================================================================
588 #==============================================================================
590 def create_test_index(repo_location, config, full_index):
589 def create_test_index(repo_location, config, full_index):
591 """
590 """
592 Makes default test index
591 Makes default test index
593
592
594 :param config: test config
593 :param config: test config
595 :param full_index:
594 :param full_index:
596 """
595 """
597
596
598 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
597 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
599 from rhodecode.lib.pidlock import DaemonLock, LockHeld
598 from rhodecode.lib.pidlock import DaemonLock, LockHeld
600
599
601 repo_location = repo_location
600 repo_location = repo_location
602
601
603 index_location = os.path.join(config['app_conf']['index_dir'])
602 index_location = os.path.join(config['app_conf']['index_dir'])
604 if not os.path.exists(index_location):
603 if not os.path.exists(index_location):
605 os.makedirs(index_location)
604 os.makedirs(index_location)
606
605
607 try:
606 try:
608 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
607 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
609 WhooshIndexingDaemon(index_location=index_location,
608 WhooshIndexingDaemon(index_location=index_location,
610 repo_location=repo_location)\
609 repo_location=repo_location)\
611 .run(full_index=full_index)
610 .run(full_index=full_index)
612 l.release()
611 l.release()
613 except LockHeld:
612 except LockHeld:
614 pass
613 pass
615
614
616
615
617 def create_test_env(repos_test_path, config):
616 def create_test_env(repos_test_path, config):
618 """
617 """
619 Makes a fresh database and
618 Makes a fresh database and
620 install test repository into tmp dir
619 install test repository into tmp dir
621 """
620 """
622 from rhodecode.lib.db_manage import DbManage
621 from rhodecode.lib.db_manage import DbManage
623 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
622 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
624
623
625 # PART ONE create db
624 # PART ONE create db
626 dbconf = config['sqlalchemy.db1.url']
625 dbconf = config['sqlalchemy.db1.url']
627 log.debug('making test db %s' % dbconf)
626 log.debug('making test db %s' % dbconf)
628
627
629 # create test dir if it doesn't exist
628 # create test dir if it doesn't exist
630 if not os.path.isdir(repos_test_path):
629 if not os.path.isdir(repos_test_path):
631 log.debug('Creating testdir %s' % repos_test_path)
630 log.debug('Creating testdir %s' % repos_test_path)
632 os.makedirs(repos_test_path)
631 os.makedirs(repos_test_path)
633
632
634 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
633 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
635 tests=True)
634 tests=True)
636 dbmanage.create_tables(override=True)
635 dbmanage.create_tables(override=True)
637 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
636 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
638 dbmanage.create_default_user()
637 dbmanage.create_default_user()
639 dbmanage.admin_prompt()
638 dbmanage.admin_prompt()
640 dbmanage.create_permissions()
639 dbmanage.create_permissions()
641 dbmanage.populate_default_permissions()
640 dbmanage.populate_default_permissions()
642 Session().commit()
641 Session().commit()
643 # PART TWO make test repo
642 # PART TWO make test repo
644 log.debug('making test vcs repositories')
643 log.debug('making test vcs repositories')
645
644
646 idx_path = config['app_conf']['index_dir']
645 idx_path = config['app_conf']['index_dir']
647 data_path = config['app_conf']['cache_dir']
646 data_path = config['app_conf']['cache_dir']
648
647
649 #clean index and data
648 #clean index and data
650 if idx_path and os.path.exists(idx_path):
649 if idx_path and os.path.exists(idx_path):
651 log.debug('remove %s' % idx_path)
650 log.debug('remove %s' % idx_path)
652 shutil.rmtree(idx_path)
651 shutil.rmtree(idx_path)
653
652
654 if data_path and os.path.exists(data_path):
653 if data_path and os.path.exists(data_path):
655 log.debug('remove %s' % data_path)
654 log.debug('remove %s' % data_path)
656 shutil.rmtree(data_path)
655 shutil.rmtree(data_path)
657
656
658 #CREATE DEFAULT TEST REPOS
657 #CREATE DEFAULT TEST REPOS
659 cur_dir = dn(dn(abspath(__file__)))
658 cur_dir = dn(dn(abspath(__file__)))
660 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_hg.tar.gz"))
659 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_hg.tar.gz"))
661 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
660 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
662 tar.close()
661 tar.close()
663
662
664 cur_dir = dn(dn(abspath(__file__)))
663 cur_dir = dn(dn(abspath(__file__)))
665 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_git.tar.gz"))
664 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_git.tar.gz"))
666 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
665 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
667 tar.close()
666 tar.close()
668
667
669 #LOAD VCS test stuff
668 #LOAD VCS test stuff
670 from rhodecode.tests.vcs import setup_package
669 from rhodecode.tests.vcs import setup_package
671 setup_package()
670 setup_package()
672
671
673
672
674 #==============================================================================
673 #==============================================================================
675 # PASTER COMMANDS
674 # PASTER COMMANDS
676 #==============================================================================
675 #==============================================================================
677 class BasePasterCommand(Command):
676 class BasePasterCommand(Command):
678 """
677 """
679 Abstract Base Class for paster commands.
678 Abstract Base Class for paster commands.
680
679
681 The celery commands are somewhat aggressive about loading
680 The celery commands are somewhat aggressive about loading
682 celery.conf, and since our module sets the `CELERY_LOADER`
681 celery.conf, and since our module sets the `CELERY_LOADER`
683 environment variable to our loader, we have to bootstrap a bit and
682 environment variable to our loader, we have to bootstrap a bit and
684 make sure we've had a chance to load the pylons config off of the
683 make sure we've had a chance to load the pylons config off of the
685 command line, otherwise everything fails.
684 command line, otherwise everything fails.
686 """
685 """
687 min_args = 1
686 min_args = 1
688 min_args_error = "Please provide a paster config file as an argument."
687 min_args_error = "Please provide a paster config file as an argument."
689 takes_config_file = 1
688 takes_config_file = 1
690 requires_config_file = True
689 requires_config_file = True
691
690
692 def notify_msg(self, msg, log=False):
691 def notify_msg(self, msg, log=False):
693 """Make a notification to user, additionally if logger is passed
692 """Make a notification to user, additionally if logger is passed
694 it logs this action using given logger
693 it logs this action using given logger
695
694
696 :param msg: message that will be printed to user
695 :param msg: message that will be printed to user
697 :param log: logging instance, to use to additionally log this message
696 :param log: logging instance, to use to additionally log this message
698
697
699 """
698 """
700 if log and isinstance(log, logging):
699 if log and isinstance(log, logging):
701 log(msg)
700 log(msg)
702
701
703 def run(self, args):
702 def run(self, args):
704 """
703 """
705 Overrides Command.run
704 Overrides Command.run
706
705
707 Checks for a config file argument and loads it.
706 Checks for a config file argument and loads it.
708 """
707 """
709 if len(args) < self.min_args:
708 if len(args) < self.min_args:
710 raise BadCommand(
709 raise BadCommand(
711 self.min_args_error % {'min_args': self.min_args,
710 self.min_args_error % {'min_args': self.min_args,
712 'actual_args': len(args)})
711 'actual_args': len(args)})
713
712
714 # Decrement because we're going to lob off the first argument.
713 # Decrement because we're going to lob off the first argument.
715 # @@ This is hacky
714 # @@ This is hacky
716 self.min_args -= 1
715 self.min_args -= 1
717 self.bootstrap_config(args[0])
716 self.bootstrap_config(args[0])
718 self.update_parser()
717 self.update_parser()
719 return super(BasePasterCommand, self).run(args[1:])
718 return super(BasePasterCommand, self).run(args[1:])
720
719
721 def update_parser(self):
720 def update_parser(self):
722 """
721 """
723 Abstract method. Allows for the class's parser to be updated
722 Abstract method. Allows for the class's parser to be updated
724 before the superclass's `run` method is called. Necessary to
723 before the superclass's `run` method is called. Necessary to
725 allow options/arguments to be passed through to the underlying
724 allow options/arguments to be passed through to the underlying
726 celery command.
725 celery command.
727 """
726 """
728 raise NotImplementedError("Abstract Method.")
727 raise NotImplementedError("Abstract Method.")
729
728
730 def bootstrap_config(self, conf):
729 def bootstrap_config(self, conf):
731 """
730 """
732 Loads the pylons configuration.
731 Loads the pylons configuration.
733 """
732 """
734 from pylons import config as pylonsconfig
733 from pylons import config as pylonsconfig
735
734
736 self.path_to_ini_file = os.path.realpath(conf)
735 self.path_to_ini_file = os.path.realpath(conf)
737 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
736 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
738 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
737 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
739
738
740 def _init_session(self):
739 def _init_session(self):
741 """
740 """
742 Inits SqlAlchemy Session
741 Inits SqlAlchemy Session
743 """
742 """
744 logging.config.fileConfig(self.path_to_ini_file)
743 logging.config.fileConfig(self.path_to_ini_file)
745 from pylons import config
744 from pylons import config
746 from rhodecode.model import init_model
745 from rhodecode.model import init_model
747 from rhodecode.lib.utils2 import engine_from_config
746 from rhodecode.lib.utils2 import engine_from_config
748
747
749 #get to remove repos !!
748 #get to remove repos !!
750 add_cache(config)
749 add_cache(config)
751 engine = engine_from_config(config, 'sqlalchemy.db1.')
750 engine = engine_from_config(config, 'sqlalchemy.db1.')
752 init_model(engine)
751 init_model(engine)
753
752
754
753
755 def check_git_version():
754 def check_git_version():
756 """
755 """
757 Checks what version of git is installed in system, and issues a warning
756 Checks what version of git is installed in system, and issues a warning
758 if it's too old for RhodeCode to properly work.
757 if it's too old for RhodeCode to properly work.
759 """
758 """
760 from rhodecode import BACKENDS
759 from rhodecode import BACKENDS
761 from rhodecode.lib.vcs.backends.git.repository import GitRepository
760 from rhodecode.lib.vcs.backends.git.repository import GitRepository
762 from rhodecode.lib.vcs.conf import settings
761 from rhodecode.lib.vcs.conf import settings
763 from distutils.version import StrictVersion
762 from distutils.version import StrictVersion
764
763
765 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
764 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
766 _safe=True)
765 _safe=True)
767
766
768 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
767 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
769 if len(ver.split('.')) > 3:
768 if len(ver.split('.')) > 3:
770 #StrictVersion needs to be only 3 element type
769 #StrictVersion needs to be only 3 element type
771 ver = '.'.join(ver.split('.')[:3])
770 ver = '.'.join(ver.split('.')[:3])
772 try:
771 try:
773 _ver = StrictVersion(ver)
772 _ver = StrictVersion(ver)
774 except Exception:
773 except Exception:
775 _ver = StrictVersion('0.0.0')
774 _ver = StrictVersion('0.0.0')
776 stderr = traceback.format_exc()
775 stderr = traceback.format_exc()
777
776
778 req_ver = '1.7.4'
777 req_ver = '1.7.4'
779 to_old_git = False
778 to_old_git = False
780 if _ver < StrictVersion(req_ver):
779 if _ver < StrictVersion(req_ver):
781 to_old_git = True
780 to_old_git = True
782
781
783 if 'git' in BACKENDS:
782 if 'git' in BACKENDS:
784 log.debug('GIT executable: "%s" version detected: %s'
783 log.debug('GIT executable: "%s" version detected: %s'
785 % (settings.GIT_EXECUTABLE_PATH, stdout))
784 % (settings.GIT_EXECUTABLE_PATH, stdout))
786 if stderr:
785 if stderr:
787 log.warning('Unable to detect git version, org error was: %r' % stderr)
786 log.warning('Unable to detect git version, org error was: %r' % stderr)
788 elif to_old_git:
787 elif to_old_git:
789 log.warning('RhodeCode detected git version %s, which is too old '
788 log.warning('RhodeCode detected git version %s, which is too old '
790 'for the system to function properly. Make sure '
789 'for the system to function properly. Make sure '
791 'its version is at least %s' % (ver, req_ver))
790 'its version is at least %s' % (ver, req_ver))
792 return _ver
791 return _ver
793
792
794
793
795 @decorator.decorator
794 @decorator.decorator
796 def jsonify(func, *args, **kwargs):
795 def jsonify(func, *args, **kwargs):
797 """Action decorator that formats output for JSON
796 """Action decorator that formats output for JSON
798
797
799 Given a function that will return content, this decorator will turn
798 Given a function that will return content, this decorator will turn
800 the result into JSON, with a content-type of 'application/json' and
799 the result into JSON, with a content-type of 'application/json' and
801 output it.
800 output it.
802
801
803 """
802 """
804 from pylons.decorators.util import get_pylons
803 from pylons.decorators.util import get_pylons
805 from rhodecode.lib.compat import json
804 from rhodecode.lib.compat import json
806 pylons = get_pylons(args)
805 pylons = get_pylons(args)
807 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
806 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
808 data = func(*args, **kwargs)
807 data = func(*args, **kwargs)
809 if isinstance(data, (list, tuple)):
808 if isinstance(data, (list, tuple)):
810 msg = "JSON responses with Array envelopes are susceptible to " \
809 msg = "JSON responses with Array envelopes are susceptible to " \
811 "cross-site data leak attacks, see " \
810 "cross-site data leak attacks, see " \
812 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
811 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
813 warnings.warn(msg, Warning, 2)
812 warnings.warn(msg, Warning, 2)
814 log.warning(msg)
813 log.warning(msg)
815 log.debug("Returning JSON wrapped action output")
814 log.debug("Returning JSON wrapped action output")
816 return json.dumps(data, encoding='utf-8')
815 return json.dumps(data, encoding='utf-8')
@@ -1,47 +1,47 b''
1 from mercurial import ui, config
1 from rhodecode.lib.vcs.utils.hgcompat import ui, config
2
2
3
3
4 def make_ui(self, path='hgwebdir.config'):
4 def make_ui(self, path='hgwebdir.config'):
5 """
5 """
6 A funcion that will read python rc files and make an ui from read options
6 A funcion that will read python rc files and make an ui from read options
7
7
8 :param path: path to mercurial config file
8 :param path: path to mercurial config file
9 """
9 """
10 #propagated from mercurial documentation
10 #propagated from mercurial documentation
11 sections = [
11 sections = [
12 'alias',
12 'alias',
13 'auth',
13 'auth',
14 'decode/encode',
14 'decode/encode',
15 'defaults',
15 'defaults',
16 'diff',
16 'diff',
17 'email',
17 'email',
18 'extensions',
18 'extensions',
19 'format',
19 'format',
20 'merge-patterns',
20 'merge-patterns',
21 'merge-tools',
21 'merge-tools',
22 'hooks',
22 'hooks',
23 'http_proxy',
23 'http_proxy',
24 'smtp',
24 'smtp',
25 'patch',
25 'patch',
26 'paths',
26 'paths',
27 'profiling',
27 'profiling',
28 'server',
28 'server',
29 'trusted',
29 'trusted',
30 'ui',
30 'ui',
31 'web',
31 'web',
32 ]
32 ]
33
33
34 repos = path
34 repos = path
35 baseui = ui.ui()
35 baseui = ui.ui()
36 cfg = config.config()
36 cfg = config.config()
37 cfg.read(repos)
37 cfg.read(repos)
38 self.paths = cfg.items('paths')
38 self.paths = cfg.items('paths')
39 self.base_path = self.paths[0][1].replace('*', '')
39 self.base_path = self.paths[0][1].replace('*', '')
40 self.check_repo_dir(self.paths)
40 self.check_repo_dir(self.paths)
41 self.set_statics(cfg)
41 self.set_statics(cfg)
42
42
43 for section in sections:
43 for section in sections:
44 for k, v in cfg.items(section):
44 for k, v in cfg.items(section):
45 baseui.setconfig(section, k, v)
45 baseui.setconfig(section, k, v)
46
46
47 return baseui
47 return baseui
@@ -1,25 +1,30 b''
1 """
1 """
2 Mercurial libs compatibility
2 Mercurial libs compatibility
3 """
3 """
4
4
5 import mercurial
5 from mercurial import archival, merge as hg_merge, patch, ui
6 from mercurial import archival, merge as hg_merge, patch, ui
7 from mercurial import discovery
8 from mercurial import localrepo
9 from mercurial import scmutil
10 from mercurial import config
6 from mercurial.commands import clone, nullid, pull
11 from mercurial.commands import clone, nullid, pull
7 from mercurial.context import memctx, memfilectx
12 from mercurial.context import memctx, memfilectx
8 from mercurial.error import RepoError, RepoLookupError, Abort
13 from mercurial.error import RepoError, RepoLookupError, Abort
14 from mercurial.hgweb import hgweb_mod
9 from mercurial.hgweb.common import get_contact
15 from mercurial.hgweb.common import get_contact
10 from mercurial.localrepo import localrepository
16 from mercurial.localrepo import localrepository
11 from mercurial.match import match
17 from mercurial.match import match
12 from mercurial.mdiff import diffopts
18 from mercurial.mdiff import diffopts
13 from mercurial.node import hex
19 from mercurial.node import hex
14 from mercurial.encoding import tolocal
20 from mercurial.encoding import tolocal
15 from mercurial import discovery
16 from mercurial import localrepo
17 from mercurial import scmutil
18 from mercurial.discovery import findcommonoutgoing
21 from mercurial.discovery import findcommonoutgoing
19 from mercurial.hg import peer
22 from mercurial.hg import peer
20
23 from mercurial.httppeer import httppeer
21 from mercurial.util import url as hg_url
24 from mercurial.util import url as hg_url
25 from mercurial.scmutil import revrange
26 from mercurial.node import nullrev
22
27
23 # those authnadlers are patched for python 2.6.5 bug an
28 # those authnadlers are patched for python 2.6.5 bug an
24 # infinit looping when given invalid resources
29 # infinit looping when given invalid resources
25 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
30 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
@@ -1,827 +1,827 b''
1 """
1 """
2 Set of generic validators
2 Set of generic validators
3 """
3 """
4 import os
4 import os
5 import re
5 import re
6 import formencode
6 import formencode
7 import logging
7 import logging
8 from collections import defaultdict
8 from collections import defaultdict
9 from pylons.i18n.translation import _
9 from pylons.i18n.translation import _
10 from webhelpers.pylonslib.secure_form import authentication_token
10 from webhelpers.pylonslib.secure_form import authentication_token
11
11
12 from formencode.validators import (
12 from formencode.validators import (
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
14 NotEmpty, IPAddress, CIDR, String, FancyValidator
14 NotEmpty, IPAddress, CIDR, String, FancyValidator
15 )
15 )
16 from rhodecode.lib.compat import OrderedSet
16 from rhodecode.lib.compat import OrderedSet
17 from rhodecode.lib import ipaddr
17 from rhodecode.lib import ipaddr
18 from rhodecode.lib.utils import repo_name_slug
18 from rhodecode.lib.utils import repo_name_slug
19 from rhodecode.lib.utils2 import safe_int, str2bool
19 from rhodecode.lib.utils2 import safe_int, str2bool
20 from rhodecode.model.db import RepoGroup, Repository, UserGroup, User,\
20 from rhodecode.model.db import RepoGroup, Repository, UserGroup, User,\
21 ChangesetStatus
21 ChangesetStatus
22 from rhodecode.lib.exceptions import LdapImportError
22 from rhodecode.lib.exceptions import LdapImportError
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny
24 from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny
25
25
26 # silence warnings and pylint
26 # silence warnings and pylint
27 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
27 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
28 NotEmpty, IPAddress, CIDR, String, FancyValidator
28 NotEmpty, IPAddress, CIDR, String, FancyValidator
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class UniqueList(formencode.FancyValidator):
33 class UniqueList(formencode.FancyValidator):
34 """
34 """
35 Unique List !
35 Unique List !
36 """
36 """
37 messages = dict(
37 messages = dict(
38 empty=_('Value cannot be an empty list'),
38 empty=_('Value cannot be an empty list'),
39 missing_value=_('Value cannot be an empty list'),
39 missing_value=_('Value cannot be an empty list'),
40 )
40 )
41
41
42 def _to_python(self, value, state):
42 def _to_python(self, value, state):
43 if isinstance(value, list):
43 if isinstance(value, list):
44 return value
44 return value
45 elif isinstance(value, set):
45 elif isinstance(value, set):
46 return list(value)
46 return list(value)
47 elif isinstance(value, tuple):
47 elif isinstance(value, tuple):
48 return list(value)
48 return list(value)
49 elif value is None:
49 elif value is None:
50 return []
50 return []
51 else:
51 else:
52 return [value]
52 return [value]
53
53
54 def empty_value(self, value):
54 def empty_value(self, value):
55 return []
55 return []
56
56
57
57
58 class StateObj(object):
58 class StateObj(object):
59 """
59 """
60 this is needed to translate the messages using _() in validators
60 this is needed to translate the messages using _() in validators
61 """
61 """
62 _ = staticmethod(_)
62 _ = staticmethod(_)
63
63
64
64
65 def M(self, key, state=None, **kwargs):
65 def M(self, key, state=None, **kwargs):
66 """
66 """
67 returns string from self.message based on given key,
67 returns string from self.message based on given key,
68 passed kw params are used to substitute %(named)s params inside
68 passed kw params are used to substitute %(named)s params inside
69 translated strings
69 translated strings
70
70
71 :param msg:
71 :param msg:
72 :param state:
72 :param state:
73 """
73 """
74 if state is None:
74 if state is None:
75 state = StateObj()
75 state = StateObj()
76 else:
76 else:
77 state._ = staticmethod(_)
77 state._ = staticmethod(_)
78 #inject validator into state object
78 #inject validator into state object
79 return self.message(key, state, **kwargs)
79 return self.message(key, state, **kwargs)
80
80
81
81
82 def ValidUsername(edit=False, old_data={}):
82 def ValidUsername(edit=False, old_data={}):
83 class _validator(formencode.validators.FancyValidator):
83 class _validator(formencode.validators.FancyValidator):
84 messages = {
84 messages = {
85 'username_exists': _(u'Username "%(username)s" already exists'),
85 'username_exists': _(u'Username "%(username)s" already exists'),
86 'system_invalid_username':
86 'system_invalid_username':
87 _(u'Username "%(username)s" is forbidden'),
87 _(u'Username "%(username)s" is forbidden'),
88 'invalid_username':
88 'invalid_username':
89 _(u'Username may only contain alphanumeric characters '
89 _(u'Username may only contain alphanumeric characters '
90 'underscores, periods or dashes and must begin with '
90 'underscores, periods or dashes and must begin with '
91 'alphanumeric character or underscore')
91 'alphanumeric character or underscore')
92 }
92 }
93
93
94 def validate_python(self, value, state):
94 def validate_python(self, value, state):
95 if value in ['default', 'new_user']:
95 if value in ['default', 'new_user']:
96 msg = M(self, 'system_invalid_username', state, username=value)
96 msg = M(self, 'system_invalid_username', state, username=value)
97 raise formencode.Invalid(msg, value, state)
97 raise formencode.Invalid(msg, value, state)
98 #check if user is unique
98 #check if user is unique
99 old_un = None
99 old_un = None
100 if edit:
100 if edit:
101 old_un = User.get(old_data.get('user_id')).username
101 old_un = User.get(old_data.get('user_id')).username
102
102
103 if old_un != value or not edit:
103 if old_un != value or not edit:
104 if User.get_by_username(value, case_insensitive=True):
104 if User.get_by_username(value, case_insensitive=True):
105 msg = M(self, 'username_exists', state, username=value)
105 msg = M(self, 'username_exists', state, username=value)
106 raise formencode.Invalid(msg, value, state)
106 raise formencode.Invalid(msg, value, state)
107
107
108 if re.match(r'^[a-zA-Z0-9\_]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
108 if re.match(r'^[a-zA-Z0-9\_]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
109 msg = M(self, 'invalid_username', state)
109 msg = M(self, 'invalid_username', state)
110 raise formencode.Invalid(msg, value, state)
110 raise formencode.Invalid(msg, value, state)
111 return _validator
111 return _validator
112
112
113
113
114 def ValidRepoUser():
114 def ValidRepoUser():
115 class _validator(formencode.validators.FancyValidator):
115 class _validator(formencode.validators.FancyValidator):
116 messages = {
116 messages = {
117 'invalid_username': _(u'Username %(username)s is not valid')
117 'invalid_username': _(u'Username %(username)s is not valid')
118 }
118 }
119
119
120 def validate_python(self, value, state):
120 def validate_python(self, value, state):
121 try:
121 try:
122 User.query().filter(User.active == True)\
122 User.query().filter(User.active == True)\
123 .filter(User.username == value).one()
123 .filter(User.username == value).one()
124 except Exception:
124 except Exception:
125 msg = M(self, 'invalid_username', state, username=value)
125 msg = M(self, 'invalid_username', state, username=value)
126 raise formencode.Invalid(msg, value, state,
126 raise formencode.Invalid(msg, value, state,
127 error_dict=dict(username=msg)
127 error_dict=dict(username=msg)
128 )
128 )
129
129
130 return _validator
130 return _validator
131
131
132
132
133 def ValidUserGroup(edit=False, old_data={}):
133 def ValidUserGroup(edit=False, old_data={}):
134 class _validator(formencode.validators.FancyValidator):
134 class _validator(formencode.validators.FancyValidator):
135 messages = {
135 messages = {
136 'invalid_group': _(u'Invalid user group name'),
136 'invalid_group': _(u'Invalid user group name'),
137 'group_exist': _(u'User group "%(usergroup)s" already exists'),
137 'group_exist': _(u'User group "%(usergroup)s" already exists'),
138 'invalid_usergroup_name':
138 'invalid_usergroup_name':
139 _(u'user group name may only contain alphanumeric '
139 _(u'user group name may only contain alphanumeric '
140 'characters underscores, periods or dashes and must begin '
140 'characters underscores, periods or dashes and must begin '
141 'with alphanumeric character')
141 'with alphanumeric character')
142 }
142 }
143
143
144 def validate_python(self, value, state):
144 def validate_python(self, value, state):
145 if value in ['default']:
145 if value in ['default']:
146 msg = M(self, 'invalid_group', state)
146 msg = M(self, 'invalid_group', state)
147 raise formencode.Invalid(msg, value, state,
147 raise formencode.Invalid(msg, value, state,
148 error_dict=dict(users_group_name=msg)
148 error_dict=dict(users_group_name=msg)
149 )
149 )
150 #check if group is unique
150 #check if group is unique
151 old_ugname = None
151 old_ugname = None
152 if edit:
152 if edit:
153 old_id = old_data.get('users_group_id')
153 old_id = old_data.get('users_group_id')
154 old_ugname = UserGroup.get(old_id).users_group_name
154 old_ugname = UserGroup.get(old_id).users_group_name
155
155
156 if old_ugname != value or not edit:
156 if old_ugname != value or not edit:
157 is_existing_group = UserGroup.get_by_group_name(value,
157 is_existing_group = UserGroup.get_by_group_name(value,
158 case_insensitive=True)
158 case_insensitive=True)
159 if is_existing_group:
159 if is_existing_group:
160 msg = M(self, 'group_exist', state, usergroup=value)
160 msg = M(self, 'group_exist', state, usergroup=value)
161 raise formencode.Invalid(msg, value, state,
161 raise formencode.Invalid(msg, value, state,
162 error_dict=dict(users_group_name=msg)
162 error_dict=dict(users_group_name=msg)
163 )
163 )
164
164
165 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
165 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
166 msg = M(self, 'invalid_usergroup_name', state)
166 msg = M(self, 'invalid_usergroup_name', state)
167 raise formencode.Invalid(msg, value, state,
167 raise formencode.Invalid(msg, value, state,
168 error_dict=dict(users_group_name=msg)
168 error_dict=dict(users_group_name=msg)
169 )
169 )
170
170
171 return _validator
171 return _validator
172
172
173
173
174 def ValidReposGroup(edit=False, old_data={}):
174 def ValidReposGroup(edit=False, old_data={}):
175 class _validator(formencode.validators.FancyValidator):
175 class _validator(formencode.validators.FancyValidator):
176 messages = {
176 messages = {
177 'group_parent_id': _(u'Cannot assign this group as parent'),
177 'group_parent_id': _(u'Cannot assign this group as parent'),
178 'group_exists': _(u'Group "%(group_name)s" already exists'),
178 'group_exists': _(u'Group "%(group_name)s" already exists'),
179 'repo_exists':
179 'repo_exists':
180 _(u'Repository with name "%(group_name)s" already exists')
180 _(u'Repository with name "%(group_name)s" already exists')
181 }
181 }
182
182
183 def validate_python(self, value, state):
183 def validate_python(self, value, state):
184 # TODO WRITE VALIDATIONS
184 # TODO WRITE VALIDATIONS
185 group_name = value.get('group_name')
185 group_name = value.get('group_name')
186 group_parent_id = value.get('group_parent_id')
186 group_parent_id = value.get('group_parent_id')
187
187
188 # slugify repo group just in case :)
188 # slugify repo group just in case :)
189 slug = repo_name_slug(group_name)
189 slug = repo_name_slug(group_name)
190
190
191 # check for parent of self
191 # check for parent of self
192 parent_of_self = lambda: (
192 parent_of_self = lambda: (
193 old_data['group_id'] == int(group_parent_id)
193 old_data['group_id'] == int(group_parent_id)
194 if group_parent_id else False
194 if group_parent_id else False
195 )
195 )
196 if edit and parent_of_self():
196 if edit and parent_of_self():
197 msg = M(self, 'group_parent_id', state)
197 msg = M(self, 'group_parent_id', state)
198 raise formencode.Invalid(msg, value, state,
198 raise formencode.Invalid(msg, value, state,
199 error_dict=dict(group_parent_id=msg)
199 error_dict=dict(group_parent_id=msg)
200 )
200 )
201
201
202 old_gname = None
202 old_gname = None
203 if edit:
203 if edit:
204 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
204 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
205
205
206 if old_gname != group_name or not edit:
206 if old_gname != group_name or not edit:
207
207
208 # check group
208 # check group
209 gr = RepoGroup.query()\
209 gr = RepoGroup.query()\
210 .filter(RepoGroup.group_name == slug)\
210 .filter(RepoGroup.group_name == slug)\
211 .filter(RepoGroup.group_parent_id == group_parent_id)\
211 .filter(RepoGroup.group_parent_id == group_parent_id)\
212 .scalar()
212 .scalar()
213
213
214 if gr:
214 if gr:
215 msg = M(self, 'group_exists', state, group_name=slug)
215 msg = M(self, 'group_exists', state, group_name=slug)
216 raise formencode.Invalid(msg, value, state,
216 raise formencode.Invalid(msg, value, state,
217 error_dict=dict(group_name=msg)
217 error_dict=dict(group_name=msg)
218 )
218 )
219
219
220 # check for same repo
220 # check for same repo
221 repo = Repository.query()\
221 repo = Repository.query()\
222 .filter(Repository.repo_name == slug)\
222 .filter(Repository.repo_name == slug)\
223 .scalar()
223 .scalar()
224
224
225 if repo:
225 if repo:
226 msg = M(self, 'repo_exists', state, group_name=slug)
226 msg = M(self, 'repo_exists', state, group_name=slug)
227 raise formencode.Invalid(msg, value, state,
227 raise formencode.Invalid(msg, value, state,
228 error_dict=dict(group_name=msg)
228 error_dict=dict(group_name=msg)
229 )
229 )
230
230
231 return _validator
231 return _validator
232
232
233
233
234 def ValidPassword():
234 def ValidPassword():
235 class _validator(formencode.validators.FancyValidator):
235 class _validator(formencode.validators.FancyValidator):
236 messages = {
236 messages = {
237 'invalid_password':
237 'invalid_password':
238 _(u'Invalid characters (non-ascii) in password')
238 _(u'Invalid characters (non-ascii) in password')
239 }
239 }
240
240
241 def validate_python(self, value, state):
241 def validate_python(self, value, state):
242 try:
242 try:
243 (value or '').decode('ascii')
243 (value or '').decode('ascii')
244 except UnicodeError:
244 except UnicodeError:
245 msg = M(self, 'invalid_password', state)
245 msg = M(self, 'invalid_password', state)
246 raise formencode.Invalid(msg, value, state,)
246 raise formencode.Invalid(msg, value, state,)
247 return _validator
247 return _validator
248
248
249
249
250 def ValidPasswordsMatch():
250 def ValidPasswordsMatch():
251 class _validator(formencode.validators.FancyValidator):
251 class _validator(formencode.validators.FancyValidator):
252 messages = {
252 messages = {
253 'password_mismatch': _(u'Passwords do not match'),
253 'password_mismatch': _(u'Passwords do not match'),
254 }
254 }
255
255
256 def validate_python(self, value, state):
256 def validate_python(self, value, state):
257
257
258 pass_val = value.get('password') or value.get('new_password')
258 pass_val = value.get('password') or value.get('new_password')
259 if pass_val != value['password_confirmation']:
259 if pass_val != value['password_confirmation']:
260 msg = M(self, 'password_mismatch', state)
260 msg = M(self, 'password_mismatch', state)
261 raise formencode.Invalid(msg, value, state,
261 raise formencode.Invalid(msg, value, state,
262 error_dict=dict(password_confirmation=msg)
262 error_dict=dict(password_confirmation=msg)
263 )
263 )
264 return _validator
264 return _validator
265
265
266
266
267 def ValidAuth():
267 def ValidAuth():
268 class _validator(formencode.validators.FancyValidator):
268 class _validator(formencode.validators.FancyValidator):
269 messages = {
269 messages = {
270 'invalid_password': _(u'invalid password'),
270 'invalid_password': _(u'invalid password'),
271 'invalid_username': _(u'invalid user name'),
271 'invalid_username': _(u'invalid user name'),
272 'disabled_account': _(u'Your account is disabled')
272 'disabled_account': _(u'Your account is disabled')
273 }
273 }
274
274
275 def validate_python(self, value, state):
275 def validate_python(self, value, state):
276 from rhodecode.lib.auth import authenticate
276 from rhodecode.lib.auth import authenticate
277
277
278 password = value['password']
278 password = value['password']
279 username = value['username']
279 username = value['username']
280
280
281 if not authenticate(username, password):
281 if not authenticate(username, password):
282 user = User.get_by_username(username)
282 user = User.get_by_username(username)
283 if user and not user.active:
283 if user and not user.active:
284 log.warning('user %s is disabled' % username)
284 log.warning('user %s is disabled' % username)
285 msg = M(self, 'disabled_account', state)
285 msg = M(self, 'disabled_account', state)
286 raise formencode.Invalid(msg, value, state,
286 raise formencode.Invalid(msg, value, state,
287 error_dict=dict(username=msg)
287 error_dict=dict(username=msg)
288 )
288 )
289 else:
289 else:
290 log.warning('user %s failed to authenticate' % username)
290 log.warning('user %s failed to authenticate' % username)
291 msg = M(self, 'invalid_username', state)
291 msg = M(self, 'invalid_username', state)
292 msg2 = M(self, 'invalid_password', state)
292 msg2 = M(self, 'invalid_password', state)
293 raise formencode.Invalid(msg, value, state,
293 raise formencode.Invalid(msg, value, state,
294 error_dict=dict(username=msg, password=msg2)
294 error_dict=dict(username=msg, password=msg2)
295 )
295 )
296 return _validator
296 return _validator
297
297
298
298
299 def ValidAuthToken():
299 def ValidAuthToken():
300 class _validator(formencode.validators.FancyValidator):
300 class _validator(formencode.validators.FancyValidator):
301 messages = {
301 messages = {
302 'invalid_token': _(u'Token mismatch')
302 'invalid_token': _(u'Token mismatch')
303 }
303 }
304
304
305 def validate_python(self, value, state):
305 def validate_python(self, value, state):
306 if value != authentication_token():
306 if value != authentication_token():
307 msg = M(self, 'invalid_token', state)
307 msg = M(self, 'invalid_token', state)
308 raise formencode.Invalid(msg, value, state)
308 raise formencode.Invalid(msg, value, state)
309 return _validator
309 return _validator
310
310
311
311
312 def ValidRepoName(edit=False, old_data={}):
312 def ValidRepoName(edit=False, old_data={}):
313 class _validator(formencode.validators.FancyValidator):
313 class _validator(formencode.validators.FancyValidator):
314 messages = {
314 messages = {
315 'invalid_repo_name':
315 'invalid_repo_name':
316 _(u'Repository name %(repo)s is disallowed'),
316 _(u'Repository name %(repo)s is disallowed'),
317 'repository_exists':
317 'repository_exists':
318 _(u'Repository named %(repo)s already exists'),
318 _(u'Repository named %(repo)s already exists'),
319 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
319 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
320 'exists in group "%(group)s"'),
320 'exists in group "%(group)s"'),
321 'same_group_exists': _(u'Repository group with name "%(repo)s" '
321 'same_group_exists': _(u'Repository group with name "%(repo)s" '
322 'already exists')
322 'already exists')
323 }
323 }
324
324
325 def _to_python(self, value, state):
325 def _to_python(self, value, state):
326 repo_name = repo_name_slug(value.get('repo_name', ''))
326 repo_name = repo_name_slug(value.get('repo_name', ''))
327 repo_group = value.get('repo_group')
327 repo_group = value.get('repo_group')
328 if repo_group:
328 if repo_group:
329 gr = RepoGroup.get(repo_group)
329 gr = RepoGroup.get(repo_group)
330 group_path = gr.full_path
330 group_path = gr.full_path
331 group_name = gr.group_name
331 group_name = gr.group_name
332 # value needs to be aware of group name in order to check
332 # value needs to be aware of group name in order to check
333 # db key This is an actual just the name to store in the
333 # db key This is an actual just the name to store in the
334 # database
334 # database
335 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
335 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
336 else:
336 else:
337 group_name = group_path = ''
337 group_name = group_path = ''
338 repo_name_full = repo_name
338 repo_name_full = repo_name
339
339
340 value['repo_name'] = repo_name
340 value['repo_name'] = repo_name
341 value['repo_name_full'] = repo_name_full
341 value['repo_name_full'] = repo_name_full
342 value['group_path'] = group_path
342 value['group_path'] = group_path
343 value['group_name'] = group_name
343 value['group_name'] = group_name
344 return value
344 return value
345
345
346 def validate_python(self, value, state):
346 def validate_python(self, value, state):
347
347
348 repo_name = value.get('repo_name')
348 repo_name = value.get('repo_name')
349 repo_name_full = value.get('repo_name_full')
349 repo_name_full = value.get('repo_name_full')
350 group_path = value.get('group_path')
350 group_path = value.get('group_path')
351 group_name = value.get('group_name')
351 group_name = value.get('group_name')
352
352
353 if repo_name in [ADMIN_PREFIX, '']:
353 if repo_name in [ADMIN_PREFIX, '']:
354 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
354 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
355 raise formencode.Invalid(msg, value, state,
355 raise formencode.Invalid(msg, value, state,
356 error_dict=dict(repo_name=msg)
356 error_dict=dict(repo_name=msg)
357 )
357 )
358
358
359 rename = old_data.get('repo_name') != repo_name_full
359 rename = old_data.get('repo_name') != repo_name_full
360 create = not edit
360 create = not edit
361 if rename or create:
361 if rename or create:
362
362
363 if group_path != '':
363 if group_path != '':
364 if Repository.get_by_repo_name(repo_name_full):
364 if Repository.get_by_repo_name(repo_name_full):
365 msg = M(self, 'repository_in_group_exists', state,
365 msg = M(self, 'repository_in_group_exists', state,
366 repo=repo_name, group=group_name)
366 repo=repo_name, group=group_name)
367 raise formencode.Invalid(msg, value, state,
367 raise formencode.Invalid(msg, value, state,
368 error_dict=dict(repo_name=msg)
368 error_dict=dict(repo_name=msg)
369 )
369 )
370 elif RepoGroup.get_by_group_name(repo_name_full):
370 elif RepoGroup.get_by_group_name(repo_name_full):
371 msg = M(self, 'same_group_exists', state,
371 msg = M(self, 'same_group_exists', state,
372 repo=repo_name)
372 repo=repo_name)
373 raise formencode.Invalid(msg, value, state,
373 raise formencode.Invalid(msg, value, state,
374 error_dict=dict(repo_name=msg)
374 error_dict=dict(repo_name=msg)
375 )
375 )
376
376
377 elif Repository.get_by_repo_name(repo_name_full):
377 elif Repository.get_by_repo_name(repo_name_full):
378 msg = M(self, 'repository_exists', state,
378 msg = M(self, 'repository_exists', state,
379 repo=repo_name)
379 repo=repo_name)
380 raise formencode.Invalid(msg, value, state,
380 raise formencode.Invalid(msg, value, state,
381 error_dict=dict(repo_name=msg)
381 error_dict=dict(repo_name=msg)
382 )
382 )
383 return value
383 return value
384 return _validator
384 return _validator
385
385
386
386
387 def ValidForkName(*args, **kwargs):
387 def ValidForkName(*args, **kwargs):
388 return ValidRepoName(*args, **kwargs)
388 return ValidRepoName(*args, **kwargs)
389
389
390
390
391 def SlugifyName():
391 def SlugifyName():
392 class _validator(formencode.validators.FancyValidator):
392 class _validator(formencode.validators.FancyValidator):
393
393
394 def _to_python(self, value, state):
394 def _to_python(self, value, state):
395 return repo_name_slug(value)
395 return repo_name_slug(value)
396
396
397 def validate_python(self, value, state):
397 def validate_python(self, value, state):
398 pass
398 pass
399
399
400 return _validator
400 return _validator
401
401
402
402
403 def ValidCloneUri():
403 def ValidCloneUri():
404 from rhodecode.lib.utils import make_ui
404 from rhodecode.lib.utils import make_ui
405
405
406 def url_handler(repo_type, url, ui=None):
406 def url_handler(repo_type, url, ui=None):
407 if repo_type == 'hg':
407 if repo_type == 'hg':
408 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
408 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
409 from mercurial.httppeer import httppeer
409 from rhodecode.lib.vcs.utils.hgcompat import httppeer
410 if url.startswith('http'):
410 if url.startswith('http'):
411 ## initially check if it's at least the proper URL
411 ## initially check if it's at least the proper URL
412 ## or does it pass basic auth
412 ## or does it pass basic auth
413 MercurialRepository._check_url(url)
413 MercurialRepository._check_url(url)
414 httppeer(ui, url)._capabilities()
414 httppeer(ui, url)._capabilities()
415 elif url.startswith('svn+http'):
415 elif url.startswith('svn+http'):
416 from hgsubversion.svnrepo import svnremoterepo
416 from hgsubversion.svnrepo import svnremoterepo
417 svnremoterepo(ui, url).capabilities
417 svnremoterepo(ui, url).capabilities
418 elif url.startswith('git+http'):
418 elif url.startswith('git+http'):
419 raise NotImplementedError()
419 raise NotImplementedError()
420 else:
420 else:
421 raise Exception('clone from URI %s not allowed' % (url))
421 raise Exception('clone from URI %s not allowed' % (url,))
422
422
423 elif repo_type == 'git':
423 elif repo_type == 'git':
424 from rhodecode.lib.vcs.backends.git.repository import GitRepository
424 from rhodecode.lib.vcs.backends.git.repository import GitRepository
425 if url.startswith('http'):
425 if url.startswith('http'):
426 ## initially check if it's at least the proper URL
426 ## initially check if it's at least the proper URL
427 ## or does it pass basic auth
427 ## or does it pass basic auth
428 GitRepository._check_url(url)
428 GitRepository._check_url(url)
429 elif url.startswith('svn+http'):
429 elif url.startswith('svn+http'):
430 raise NotImplementedError()
430 raise NotImplementedError()
431 elif url.startswith('hg+http'):
431 elif url.startswith('hg+http'):
432 raise NotImplementedError()
432 raise NotImplementedError()
433 else:
433 else:
434 raise Exception('clone from URI %s not allowed' % (url))
434 raise Exception('clone from URI %s not allowed' % (url))
435
435
436 class _validator(formencode.validators.FancyValidator):
436 class _validator(formencode.validators.FancyValidator):
437 messages = {
437 messages = {
438 'clone_uri': _(u'invalid clone url'),
438 'clone_uri': _(u'invalid clone url'),
439 'invalid_clone_uri': _(u'Invalid clone url, provide a '
439 'invalid_clone_uri': _(u'Invalid clone url, provide a '
440 'valid clone http(s)/svn+http(s) url')
440 'valid clone http(s)/svn+http(s) url')
441 }
441 }
442
442
443 def validate_python(self, value, state):
443 def validate_python(self, value, state):
444 repo_type = value.get('repo_type')
444 repo_type = value.get('repo_type')
445 url = value.get('clone_uri')
445 url = value.get('clone_uri')
446
446
447 if not url:
447 if not url:
448 pass
448 pass
449 else:
449 else:
450 try:
450 try:
451 url_handler(repo_type, url, make_ui('db', clear_session=False))
451 url_handler(repo_type, url, make_ui('db', clear_session=False))
452 except Exception:
452 except Exception:
453 log.exception('Url validation failed')
453 log.exception('Url validation failed')
454 msg = M(self, 'clone_uri')
454 msg = M(self, 'clone_uri')
455 raise formencode.Invalid(msg, value, state,
455 raise formencode.Invalid(msg, value, state,
456 error_dict=dict(clone_uri=msg)
456 error_dict=dict(clone_uri=msg)
457 )
457 )
458 return _validator
458 return _validator
459
459
460
460
461 def ValidForkType(old_data={}):
461 def ValidForkType(old_data={}):
462 class _validator(formencode.validators.FancyValidator):
462 class _validator(formencode.validators.FancyValidator):
463 messages = {
463 messages = {
464 'invalid_fork_type': _(u'Fork have to be the same type as parent')
464 'invalid_fork_type': _(u'Fork have to be the same type as parent')
465 }
465 }
466
466
467 def validate_python(self, value, state):
467 def validate_python(self, value, state):
468 if old_data['repo_type'] != value:
468 if old_data['repo_type'] != value:
469 msg = M(self, 'invalid_fork_type', state)
469 msg = M(self, 'invalid_fork_type', state)
470 raise formencode.Invalid(msg, value, state,
470 raise formencode.Invalid(msg, value, state,
471 error_dict=dict(repo_type=msg)
471 error_dict=dict(repo_type=msg)
472 )
472 )
473 return _validator
473 return _validator
474
474
475
475
476 def CanWriteGroup(old_data=None):
476 def CanWriteGroup(old_data=None):
477 class _validator(formencode.validators.FancyValidator):
477 class _validator(formencode.validators.FancyValidator):
478 messages = {
478 messages = {
479 'permission_denied': _(u"You don't have permissions "
479 'permission_denied': _(u"You don't have permissions "
480 "to create repository in this group"),
480 "to create repository in this group"),
481 'permission_denied_root': _(u"no permission to create repository "
481 'permission_denied_root': _(u"no permission to create repository "
482 "in root location")
482 "in root location")
483 }
483 }
484
484
485 def _to_python(self, value, state):
485 def _to_python(self, value, state):
486 #root location
486 #root location
487 if value in [-1, "-1"]:
487 if value in [-1, "-1"]:
488 return None
488 return None
489 return value
489 return value
490
490
491 def validate_python(self, value, state):
491 def validate_python(self, value, state):
492 gr = RepoGroup.get(value)
492 gr = RepoGroup.get(value)
493 gr_name = gr.group_name if gr else None # None means ROOT location
493 gr_name = gr.group_name if gr else None # None means ROOT location
494 val = HasReposGroupPermissionAny('group.write', 'group.admin')
494 val = HasReposGroupPermissionAny('group.write', 'group.admin')
495 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
495 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
496 forbidden = not val(gr_name, 'can write into group validator')
496 forbidden = not val(gr_name, 'can write into group validator')
497 value_changed = True # old_data['repo_group'].get('group_id') != safe_int(value)
497 value_changed = True # old_data['repo_group'].get('group_id') != safe_int(value)
498 if value_changed: # do check if we changed the value
498 if value_changed: # do check if we changed the value
499 #parent group need to be existing
499 #parent group need to be existing
500 if gr and forbidden:
500 if gr and forbidden:
501 msg = M(self, 'permission_denied', state)
501 msg = M(self, 'permission_denied', state)
502 raise formencode.Invalid(msg, value, state,
502 raise formencode.Invalid(msg, value, state,
503 error_dict=dict(repo_type=msg)
503 error_dict=dict(repo_type=msg)
504 )
504 )
505 ## check if we can write to root location !
505 ## check if we can write to root location !
506 elif gr is None and not can_create_repos():
506 elif gr is None and not can_create_repos():
507 msg = M(self, 'permission_denied_root', state)
507 msg = M(self, 'permission_denied_root', state)
508 raise formencode.Invalid(msg, value, state,
508 raise formencode.Invalid(msg, value, state,
509 error_dict=dict(repo_type=msg)
509 error_dict=dict(repo_type=msg)
510 )
510 )
511
511
512 return _validator
512 return _validator
513
513
514
514
515 def CanCreateGroup(can_create_in_root=False):
515 def CanCreateGroup(can_create_in_root=False):
516 class _validator(formencode.validators.FancyValidator):
516 class _validator(formencode.validators.FancyValidator):
517 messages = {
517 messages = {
518 'permission_denied': _(u"You don't have permissions "
518 'permission_denied': _(u"You don't have permissions "
519 "to create a group in this location")
519 "to create a group in this location")
520 }
520 }
521
521
522 def to_python(self, value, state):
522 def to_python(self, value, state):
523 #root location
523 #root location
524 if value in [-1, "-1"]:
524 if value in [-1, "-1"]:
525 return None
525 return None
526 return value
526 return value
527
527
528 def validate_python(self, value, state):
528 def validate_python(self, value, state):
529 gr = RepoGroup.get(value)
529 gr = RepoGroup.get(value)
530 gr_name = gr.group_name if gr else None # None means ROOT location
530 gr_name = gr.group_name if gr else None # None means ROOT location
531
531
532 if can_create_in_root and gr is None:
532 if can_create_in_root and gr is None:
533 #we can create in root, we're fine no validations required
533 #we can create in root, we're fine no validations required
534 return
534 return
535
535
536 forbidden_in_root = gr is None and not can_create_in_root
536 forbidden_in_root = gr is None and not can_create_in_root
537 val = HasReposGroupPermissionAny('group.admin')
537 val = HasReposGroupPermissionAny('group.admin')
538 forbidden = not val(gr_name, 'can create group validator')
538 forbidden = not val(gr_name, 'can create group validator')
539 if forbidden_in_root or forbidden:
539 if forbidden_in_root or forbidden:
540 msg = M(self, 'permission_denied', state)
540 msg = M(self, 'permission_denied', state)
541 raise formencode.Invalid(msg, value, state,
541 raise formencode.Invalid(msg, value, state,
542 error_dict=dict(group_parent_id=msg)
542 error_dict=dict(group_parent_id=msg)
543 )
543 )
544
544
545 return _validator
545 return _validator
546
546
547
547
548 def ValidPerms(type_='repo'):
548 def ValidPerms(type_='repo'):
549 if type_ == 'repo_group':
549 if type_ == 'repo_group':
550 EMPTY_PERM = 'group.none'
550 EMPTY_PERM = 'group.none'
551 elif type_ == 'repo':
551 elif type_ == 'repo':
552 EMPTY_PERM = 'repository.none'
552 EMPTY_PERM = 'repository.none'
553 elif type_ == 'user_group':
553 elif type_ == 'user_group':
554 EMPTY_PERM = 'usergroup.none'
554 EMPTY_PERM = 'usergroup.none'
555
555
556 class _validator(formencode.validators.FancyValidator):
556 class _validator(formencode.validators.FancyValidator):
557 messages = {
557 messages = {
558 'perm_new_member_name':
558 'perm_new_member_name':
559 _(u'This username or user group name is not valid')
559 _(u'This username or user group name is not valid')
560 }
560 }
561
561
562 def to_python(self, value, state):
562 def to_python(self, value, state):
563 perms_update = OrderedSet()
563 perms_update = OrderedSet()
564 perms_new = OrderedSet()
564 perms_new = OrderedSet()
565 # build a list of permission to update and new permission to create
565 # build a list of permission to update and new permission to create
566
566
567 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
567 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
568 new_perms_group = defaultdict(dict)
568 new_perms_group = defaultdict(dict)
569 for k, v in value.copy().iteritems():
569 for k, v in value.copy().iteritems():
570 if k.startswith('perm_new_member'):
570 if k.startswith('perm_new_member'):
571 del value[k]
571 del value[k]
572 _type, part = k.split('perm_new_member_')
572 _type, part = k.split('perm_new_member_')
573 args = part.split('_')
573 args = part.split('_')
574 if len(args) == 1:
574 if len(args) == 1:
575 new_perms_group[args[0]]['perm'] = v
575 new_perms_group[args[0]]['perm'] = v
576 elif len(args) == 2:
576 elif len(args) == 2:
577 _key, pos = args
577 _key, pos = args
578 new_perms_group[pos][_key] = v
578 new_perms_group[pos][_key] = v
579
579
580 # fill new permissions in order of how they were added
580 # fill new permissions in order of how they were added
581 for k in sorted(map(int, new_perms_group.keys())):
581 for k in sorted(map(int, new_perms_group.keys())):
582 perm_dict = new_perms_group[str(k)]
582 perm_dict = new_perms_group[str(k)]
583 new_member = perm_dict.get('name')
583 new_member = perm_dict.get('name')
584 new_perm = perm_dict.get('perm')
584 new_perm = perm_dict.get('perm')
585 new_type = perm_dict.get('type')
585 new_type = perm_dict.get('type')
586 if new_member and new_perm and new_type:
586 if new_member and new_perm and new_type:
587 perms_new.add((new_member, new_perm, new_type))
587 perms_new.add((new_member, new_perm, new_type))
588
588
589 for k, v in value.iteritems():
589 for k, v in value.iteritems():
590 if k.startswith('u_perm_') or k.startswith('g_perm_'):
590 if k.startswith('u_perm_') or k.startswith('g_perm_'):
591 member = k[7:]
591 member = k[7:]
592 t = {'u': 'user',
592 t = {'u': 'user',
593 'g': 'users_group'
593 'g': 'users_group'
594 }[k[0]]
594 }[k[0]]
595 if member == 'default':
595 if member == 'default':
596 if str2bool(value.get('repo_private')):
596 if str2bool(value.get('repo_private')):
597 # set none for default when updating to
597 # set none for default when updating to
598 # private repo protects agains form manipulation
598 # private repo protects agains form manipulation
599 v = EMPTY_PERM
599 v = EMPTY_PERM
600 perms_update.add((member, v, t))
600 perms_update.add((member, v, t))
601
601
602 value['perms_updates'] = list(perms_update)
602 value['perms_updates'] = list(perms_update)
603 value['perms_new'] = list(perms_new)
603 value['perms_new'] = list(perms_new)
604
604
605 # update permissions
605 # update permissions
606 for k, v, t in perms_new:
606 for k, v, t in perms_new:
607 try:
607 try:
608 if t is 'user':
608 if t is 'user':
609 self.user_db = User.query()\
609 self.user_db = User.query()\
610 .filter(User.active == True)\
610 .filter(User.active == True)\
611 .filter(User.username == k).one()
611 .filter(User.username == k).one()
612 if t is 'users_group':
612 if t is 'users_group':
613 self.user_db = UserGroup.query()\
613 self.user_db = UserGroup.query()\
614 .filter(UserGroup.users_group_active == True)\
614 .filter(UserGroup.users_group_active == True)\
615 .filter(UserGroup.users_group_name == k).one()
615 .filter(UserGroup.users_group_name == k).one()
616
616
617 except Exception:
617 except Exception:
618 log.exception('Updated permission failed')
618 log.exception('Updated permission failed')
619 msg = M(self, 'perm_new_member_type', state)
619 msg = M(self, 'perm_new_member_type', state)
620 raise formencode.Invalid(msg, value, state,
620 raise formencode.Invalid(msg, value, state,
621 error_dict=dict(perm_new_member_name=msg)
621 error_dict=dict(perm_new_member_name=msg)
622 )
622 )
623 return value
623 return value
624 return _validator
624 return _validator
625
625
626
626
627 def ValidSettings():
627 def ValidSettings():
628 class _validator(formencode.validators.FancyValidator):
628 class _validator(formencode.validators.FancyValidator):
629 def _to_python(self, value, state):
629 def _to_python(self, value, state):
630 # settings form for users that are not admin
630 # settings form for users that are not admin
631 # can't edit certain parameters, it's extra backup if they mangle
631 # can't edit certain parameters, it's extra backup if they mangle
632 # with forms
632 # with forms
633
633
634 forbidden_params = [
634 forbidden_params = [
635 'user', 'repo_type', 'repo_enable_locking',
635 'user', 'repo_type', 'repo_enable_locking',
636 'repo_enable_downloads', 'repo_enable_statistics'
636 'repo_enable_downloads', 'repo_enable_statistics'
637 ]
637 ]
638
638
639 for param in forbidden_params:
639 for param in forbidden_params:
640 if param in value:
640 if param in value:
641 del value[param]
641 del value[param]
642 return value
642 return value
643
643
644 def validate_python(self, value, state):
644 def validate_python(self, value, state):
645 pass
645 pass
646 return _validator
646 return _validator
647
647
648
648
649 def ValidPath():
649 def ValidPath():
650 class _validator(formencode.validators.FancyValidator):
650 class _validator(formencode.validators.FancyValidator):
651 messages = {
651 messages = {
652 'invalid_path': _(u'This is not a valid path')
652 'invalid_path': _(u'This is not a valid path')
653 }
653 }
654
654
655 def validate_python(self, value, state):
655 def validate_python(self, value, state):
656 if not os.path.isdir(value):
656 if not os.path.isdir(value):
657 msg = M(self, 'invalid_path', state)
657 msg = M(self, 'invalid_path', state)
658 raise formencode.Invalid(msg, value, state,
658 raise formencode.Invalid(msg, value, state,
659 error_dict=dict(paths_root_path=msg)
659 error_dict=dict(paths_root_path=msg)
660 )
660 )
661 return _validator
661 return _validator
662
662
663
663
664 def UniqSystemEmail(old_data={}):
664 def UniqSystemEmail(old_data={}):
665 class _validator(formencode.validators.FancyValidator):
665 class _validator(formencode.validators.FancyValidator):
666 messages = {
666 messages = {
667 'email_taken': _(u'This e-mail address is already taken')
667 'email_taken': _(u'This e-mail address is already taken')
668 }
668 }
669
669
670 def _to_python(self, value, state):
670 def _to_python(self, value, state):
671 return value.lower()
671 return value.lower()
672
672
673 def validate_python(self, value, state):
673 def validate_python(self, value, state):
674 if (old_data.get('email') or '').lower() != value:
674 if (old_data.get('email') or '').lower() != value:
675 user = User.get_by_email(value, case_insensitive=True)
675 user = User.get_by_email(value, case_insensitive=True)
676 if user:
676 if user:
677 msg = M(self, 'email_taken', state)
677 msg = M(self, 'email_taken', state)
678 raise formencode.Invalid(msg, value, state,
678 raise formencode.Invalid(msg, value, state,
679 error_dict=dict(email=msg)
679 error_dict=dict(email=msg)
680 )
680 )
681 return _validator
681 return _validator
682
682
683
683
684 def ValidSystemEmail():
684 def ValidSystemEmail():
685 class _validator(formencode.validators.FancyValidator):
685 class _validator(formencode.validators.FancyValidator):
686 messages = {
686 messages = {
687 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
687 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
688 }
688 }
689
689
690 def _to_python(self, value, state):
690 def _to_python(self, value, state):
691 return value.lower()
691 return value.lower()
692
692
693 def validate_python(self, value, state):
693 def validate_python(self, value, state):
694 user = User.get_by_email(value, case_insensitive=True)
694 user = User.get_by_email(value, case_insensitive=True)
695 if user is None:
695 if user is None:
696 msg = M(self, 'non_existing_email', state, email=value)
696 msg = M(self, 'non_existing_email', state, email=value)
697 raise formencode.Invalid(msg, value, state,
697 raise formencode.Invalid(msg, value, state,
698 error_dict=dict(email=msg)
698 error_dict=dict(email=msg)
699 )
699 )
700
700
701 return _validator
701 return _validator
702
702
703
703
704 def LdapLibValidator():
704 def LdapLibValidator():
705 class _validator(formencode.validators.FancyValidator):
705 class _validator(formencode.validators.FancyValidator):
706 messages = {
706 messages = {
707
707
708 }
708 }
709
709
710 def validate_python(self, value, state):
710 def validate_python(self, value, state):
711 try:
711 try:
712 import ldap
712 import ldap
713 ldap # pyflakes silence !
713 ldap # pyflakes silence !
714 except ImportError:
714 except ImportError:
715 raise LdapImportError()
715 raise LdapImportError()
716
716
717 return _validator
717 return _validator
718
718
719
719
720 def AttrLoginValidator():
720 def AttrLoginValidator():
721 class _validator(formencode.validators.FancyValidator):
721 class _validator(formencode.validators.FancyValidator):
722 messages = {
722 messages = {
723 'invalid_cn':
723 'invalid_cn':
724 _(u'The LDAP Login attribute of the CN must be specified - '
724 _(u'The LDAP Login attribute of the CN must be specified - '
725 'this is the name of the attribute that is equivalent '
725 'this is the name of the attribute that is equivalent '
726 'to "username"')
726 'to "username"')
727 }
727 }
728 messages['empty'] = messages['invalid_cn']
728 messages['empty'] = messages['invalid_cn']
729
729
730 return _validator
730 return _validator
731
731
732
732
733 def NotReviewedRevisions(repo_id):
733 def NotReviewedRevisions(repo_id):
734 class _validator(formencode.validators.FancyValidator):
734 class _validator(formencode.validators.FancyValidator):
735 messages = {
735 messages = {
736 'rev_already_reviewed':
736 'rev_already_reviewed':
737 _(u'Revisions %(revs)s are already part of pull request '
737 _(u'Revisions %(revs)s are already part of pull request '
738 'or have set status')
738 'or have set status')
739 }
739 }
740
740
741 def validate_python(self, value, state):
741 def validate_python(self, value, state):
742 # check revisions if they are not reviewed, or a part of another
742 # check revisions if they are not reviewed, or a part of another
743 # pull request
743 # pull request
744 statuses = ChangesetStatus.query()\
744 statuses = ChangesetStatus.query()\
745 .filter(ChangesetStatus.revision.in_(value))\
745 .filter(ChangesetStatus.revision.in_(value))\
746 .filter(ChangesetStatus.repo_id == repo_id)\
746 .filter(ChangesetStatus.repo_id == repo_id)\
747 .all()
747 .all()
748
748
749 errors = []
749 errors = []
750 for cs in statuses:
750 for cs in statuses:
751 if cs.pull_request_id:
751 if cs.pull_request_id:
752 errors.append(['pull_req', cs.revision[:12]])
752 errors.append(['pull_req', cs.revision[:12]])
753 elif cs.status:
753 elif cs.status:
754 errors.append(['status', cs.revision[:12]])
754 errors.append(['status', cs.revision[:12]])
755
755
756 if errors:
756 if errors:
757 revs = ','.join([x[1] for x in errors])
757 revs = ','.join([x[1] for x in errors])
758 msg = M(self, 'rev_already_reviewed', state, revs=revs)
758 msg = M(self, 'rev_already_reviewed', state, revs=revs)
759 raise formencode.Invalid(msg, value, state,
759 raise formencode.Invalid(msg, value, state,
760 error_dict=dict(revisions=revs)
760 error_dict=dict(revisions=revs)
761 )
761 )
762
762
763 return _validator
763 return _validator
764
764
765
765
766 def ValidIp():
766 def ValidIp():
767 class _validator(CIDR):
767 class _validator(CIDR):
768 messages = dict(
768 messages = dict(
769 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
769 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
770 illegalBits=_('The network size (bits) must be within the range'
770 illegalBits=_('The network size (bits) must be within the range'
771 ' of 0-32 (not %(bits)r)')
771 ' of 0-32 (not %(bits)r)')
772 )
772 )
773
773
774 def to_python(self, value, state):
774 def to_python(self, value, state):
775 v = super(_validator, self).to_python(value, state)
775 v = super(_validator, self).to_python(value, state)
776 v = v.strip()
776 v = v.strip()
777 net = ipaddr.IPNetwork(address=v)
777 net = ipaddr.IPNetwork(address=v)
778 if isinstance(net, ipaddr.IPv4Network):
778 if isinstance(net, ipaddr.IPv4Network):
779 #if IPv4 doesn't end with a mask, add /32
779 #if IPv4 doesn't end with a mask, add /32
780 if '/' not in value:
780 if '/' not in value:
781 v += '/32'
781 v += '/32'
782 if isinstance(net, ipaddr.IPv6Network):
782 if isinstance(net, ipaddr.IPv6Network):
783 #if IPv6 doesn't end with a mask, add /128
783 #if IPv6 doesn't end with a mask, add /128
784 if '/' not in value:
784 if '/' not in value:
785 v += '/128'
785 v += '/128'
786 return v
786 return v
787
787
788 def validate_python(self, value, state):
788 def validate_python(self, value, state):
789 try:
789 try:
790 addr = value.strip()
790 addr = value.strip()
791 #this raises an ValueError if address is not IpV4 or IpV6
791 #this raises an ValueError if address is not IpV4 or IpV6
792 ipaddr.IPNetwork(address=addr)
792 ipaddr.IPNetwork(address=addr)
793 except ValueError:
793 except ValueError:
794 raise formencode.Invalid(self.message('badFormat', state),
794 raise formencode.Invalid(self.message('badFormat', state),
795 value, state)
795 value, state)
796
796
797 return _validator
797 return _validator
798
798
799
799
800 def FieldKey():
800 def FieldKey():
801 class _validator(formencode.validators.FancyValidator):
801 class _validator(formencode.validators.FancyValidator):
802 messages = dict(
802 messages = dict(
803 badFormat=_('Key name can only consist of letters, '
803 badFormat=_('Key name can only consist of letters, '
804 'underscore, dash or numbers')
804 'underscore, dash or numbers')
805 )
805 )
806
806
807 def validate_python(self, value, state):
807 def validate_python(self, value, state):
808 if not re.match('[a-zA-Z0-9_-]+$', value):
808 if not re.match('[a-zA-Z0-9_-]+$', value):
809 raise formencode.Invalid(self.message('badFormat', state),
809 raise formencode.Invalid(self.message('badFormat', state),
810 value, state)
810 value, state)
811 return _validator
811 return _validator
812
812
813
813
814 def BasePath():
814 def BasePath():
815 class _validator(formencode.validators.FancyValidator):
815 class _validator(formencode.validators.FancyValidator):
816 messages = dict(
816 messages = dict(
817 badPath=_('Filename cannot be inside a directory')
817 badPath=_('Filename cannot be inside a directory')
818 )
818 )
819
819
820 def _to_python(self, value, state):
820 def _to_python(self, value, state):
821 return value
821 return value
822
822
823 def validate_python(self, value, state):
823 def validate_python(self, value, state):
824 if value != os.path.basename(value):
824 if value != os.path.basename(value):
825 raise formencode.Invalid(self.message('badPath', state),
825 raise formencode.Invalid(self.message('badPath', state),
826 value, state)
826 value, state)
827 return _validator
827 return _validator
@@ -1,558 +1,558 b''
1 from __future__ import with_statement
1 from __future__ import with_statement
2
2
3 import os
3 import os
4 from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset
4 from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset
5 from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
5 from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
6 from rhodecode.lib.vcs.nodes import NodeKind, NodeState
6 from rhodecode.lib.vcs.nodes import NodeKind, NodeState
7 from rhodecode.tests.vcs.conf import PACKAGE_DIR, TEST_HG_REPO, TEST_HG_REPO_CLONE, \
7 from rhodecode.tests.vcs.conf import PACKAGE_DIR, TEST_HG_REPO, TEST_HG_REPO_CLONE, \
8 TEST_HG_REPO_PULL
8 TEST_HG_REPO_PULL
9 from rhodecode.lib.vcs.utils.compat import unittest
9 from rhodecode.lib.vcs.utils.compat import unittest
10
10
11
11
12 # Use only clean mercurial's ui
12 # Use only clean mercurial's ui
13 import mercurial.scmutil
13 from rhodecode.lib.vcs.utils.hgcompat import mercurial
14 mercurial.scmutil.rcpath()
14 mercurial.scmutil.rcpath()
15 if mercurial.scmutil._rcpath:
15 if mercurial.scmutil._rcpath:
16 mercurial.scmutil._rcpath = mercurial.scmutil._rcpath[:1]
16 mercurial.scmutil._rcpath = mercurial.scmutil._rcpath[:1]
17
17
18
18
19 class MercurialRepositoryTest(unittest.TestCase):
19 class MercurialRepositoryTest(unittest.TestCase):
20
20
21 def __check_for_existing_repo(self):
21 def __check_for_existing_repo(self):
22 if os.path.exists(TEST_HG_REPO_CLONE):
22 if os.path.exists(TEST_HG_REPO_CLONE):
23 self.fail('Cannot test mercurial clone repo as location %s already '
23 self.fail('Cannot test mercurial clone repo as location %s already '
24 'exists. You should manually remove it first.'
24 'exists. You should manually remove it first.'
25 % TEST_HG_REPO_CLONE)
25 % TEST_HG_REPO_CLONE)
26
26
27 def setUp(self):
27 def setUp(self):
28 self.repo = MercurialRepository(TEST_HG_REPO)
28 self.repo = MercurialRepository(TEST_HG_REPO)
29
29
30 def test_wrong_repo_path(self):
30 def test_wrong_repo_path(self):
31 wrong_repo_path = '/tmp/errorrepo'
31 wrong_repo_path = '/tmp/errorrepo'
32 self.assertRaises(RepositoryError, MercurialRepository, wrong_repo_path)
32 self.assertRaises(RepositoryError, MercurialRepository, wrong_repo_path)
33
33
34 def test_unicode_path_repo(self):
34 def test_unicode_path_repo(self):
35 self.assertRaises(VCSError,lambda:MercurialRepository(u'iShouldFail'))
35 self.assertRaises(VCSError,lambda:MercurialRepository(u'iShouldFail'))
36
36
37 def test_repo_clone(self):
37 def test_repo_clone(self):
38 self.__check_for_existing_repo()
38 self.__check_for_existing_repo()
39 repo = MercurialRepository(TEST_HG_REPO)
39 repo = MercurialRepository(TEST_HG_REPO)
40 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
40 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
41 src_url=TEST_HG_REPO, update_after_clone=True)
41 src_url=TEST_HG_REPO, update_after_clone=True)
42 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
42 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
43 # Checking hashes of changesets should be enough
43 # Checking hashes of changesets should be enough
44 for changeset in repo.get_changesets():
44 for changeset in repo.get_changesets():
45 raw_id = changeset.raw_id
45 raw_id = changeset.raw_id
46 self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
46 self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
47
47
48 def test_repo_clone_with_update(self):
48 def test_repo_clone_with_update(self):
49 repo = MercurialRepository(TEST_HG_REPO)
49 repo = MercurialRepository(TEST_HG_REPO)
50 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update',
50 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update',
51 src_url=TEST_HG_REPO, update_after_clone=True)
51 src_url=TEST_HG_REPO, update_after_clone=True)
52 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
52 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
53
53
54 #check if current workdir was updated
54 #check if current workdir was updated
55 self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
55 self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
56 + '_w_update',
56 + '_w_update',
57 'MANIFEST.in')), True,)
57 'MANIFEST.in')), True,)
58
58
59 def test_repo_clone_without_update(self):
59 def test_repo_clone_without_update(self):
60 repo = MercurialRepository(TEST_HG_REPO)
60 repo = MercurialRepository(TEST_HG_REPO)
61 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update',
61 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update',
62 src_url=TEST_HG_REPO, update_after_clone=False)
62 src_url=TEST_HG_REPO, update_after_clone=False)
63 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
63 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
64 self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
64 self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
65 + '_wo_update',
65 + '_wo_update',
66 'MANIFEST.in')), False,)
66 'MANIFEST.in')), False,)
67
67
68 def test_pull(self):
68 def test_pull(self):
69 if os.path.exists(TEST_HG_REPO_PULL):
69 if os.path.exists(TEST_HG_REPO_PULL):
70 self.fail('Cannot test mercurial pull command as location %s '
70 self.fail('Cannot test mercurial pull command as location %s '
71 'already exists. You should manually remove it first'
71 'already exists. You should manually remove it first'
72 % TEST_HG_REPO_PULL)
72 % TEST_HG_REPO_PULL)
73 repo_new = MercurialRepository(TEST_HG_REPO_PULL, create=True)
73 repo_new = MercurialRepository(TEST_HG_REPO_PULL, create=True)
74 self.assertTrue(len(self.repo.revisions) > len(repo_new.revisions))
74 self.assertTrue(len(self.repo.revisions) > len(repo_new.revisions))
75
75
76 repo_new.pull(self.repo.path)
76 repo_new.pull(self.repo.path)
77 repo_new = MercurialRepository(TEST_HG_REPO_PULL)
77 repo_new = MercurialRepository(TEST_HG_REPO_PULL)
78 self.assertTrue(len(self.repo.revisions) == len(repo_new.revisions))
78 self.assertTrue(len(self.repo.revisions) == len(repo_new.revisions))
79
79
80 def test_revisions(self):
80 def test_revisions(self):
81 # there are 21 revisions at bitbucket now
81 # there are 21 revisions at bitbucket now
82 # so we can assume they would be available from now on
82 # so we can assume they would be available from now on
83 subset = set(['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
83 subset = set(['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
84 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
84 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
85 '6cba7170863a2411822803fa77a0a264f1310b35',
85 '6cba7170863a2411822803fa77a0a264f1310b35',
86 '56349e29c2af3ac913b28bde9a2c6154436e615b',
86 '56349e29c2af3ac913b28bde9a2c6154436e615b',
87 '2dda4e345facb0ccff1a191052dd1606dba6781d',
87 '2dda4e345facb0ccff1a191052dd1606dba6781d',
88 '6fff84722075f1607a30f436523403845f84cd9e',
88 '6fff84722075f1607a30f436523403845f84cd9e',
89 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
89 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
90 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
90 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
91 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
91 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
92 'be90031137367893f1c406e0a8683010fd115b79',
92 'be90031137367893f1c406e0a8683010fd115b79',
93 'db8e58be770518cbb2b1cdfa69146e47cd481481',
93 'db8e58be770518cbb2b1cdfa69146e47cd481481',
94 '84478366594b424af694a6c784cb991a16b87c21',
94 '84478366594b424af694a6c784cb991a16b87c21',
95 '17f8e105dddb9f339600389c6dc7175d395a535c',
95 '17f8e105dddb9f339600389c6dc7175d395a535c',
96 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
96 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
97 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
97 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
98 '786facd2c61deb9cf91e9534735124fb8fc11842',
98 '786facd2c61deb9cf91e9534735124fb8fc11842',
99 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
99 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
100 'aa6a0de05b7612707db567078e130a6cd114a9a7',
100 'aa6a0de05b7612707db567078e130a6cd114a9a7',
101 'eada5a770da98ab0dd7325e29d00e0714f228d09'
101 'eada5a770da98ab0dd7325e29d00e0714f228d09'
102 ])
102 ])
103 self.assertTrue(subset.issubset(set(self.repo.revisions)))
103 self.assertTrue(subset.issubset(set(self.repo.revisions)))
104
104
105
105
106 # check if we have the proper order of revisions
106 # check if we have the proper order of revisions
107 org = ['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
107 org = ['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
108 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
108 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
109 '6cba7170863a2411822803fa77a0a264f1310b35',
109 '6cba7170863a2411822803fa77a0a264f1310b35',
110 '56349e29c2af3ac913b28bde9a2c6154436e615b',
110 '56349e29c2af3ac913b28bde9a2c6154436e615b',
111 '2dda4e345facb0ccff1a191052dd1606dba6781d',
111 '2dda4e345facb0ccff1a191052dd1606dba6781d',
112 '6fff84722075f1607a30f436523403845f84cd9e',
112 '6fff84722075f1607a30f436523403845f84cd9e',
113 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
113 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
114 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
114 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
115 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
115 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
116 'be90031137367893f1c406e0a8683010fd115b79',
116 'be90031137367893f1c406e0a8683010fd115b79',
117 'db8e58be770518cbb2b1cdfa69146e47cd481481',
117 'db8e58be770518cbb2b1cdfa69146e47cd481481',
118 '84478366594b424af694a6c784cb991a16b87c21',
118 '84478366594b424af694a6c784cb991a16b87c21',
119 '17f8e105dddb9f339600389c6dc7175d395a535c',
119 '17f8e105dddb9f339600389c6dc7175d395a535c',
120 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
120 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
121 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
121 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
122 '786facd2c61deb9cf91e9534735124fb8fc11842',
122 '786facd2c61deb9cf91e9534735124fb8fc11842',
123 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
123 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
124 'aa6a0de05b7612707db567078e130a6cd114a9a7',
124 'aa6a0de05b7612707db567078e130a6cd114a9a7',
125 'eada5a770da98ab0dd7325e29d00e0714f228d09',
125 'eada5a770da98ab0dd7325e29d00e0714f228d09',
126 '2c1885c735575ca478bf9e17b0029dca68824458',
126 '2c1885c735575ca478bf9e17b0029dca68824458',
127 'd9bcd465040bf869799b09ad732c04e0eea99fe9',
127 'd9bcd465040bf869799b09ad732c04e0eea99fe9',
128 '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7',
128 '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7',
129 '4fb8326d78e5120da2c7468dcf7098997be385da',
129 '4fb8326d78e5120da2c7468dcf7098997be385da',
130 '62b4a097164940bd66030c4db51687f3ec035eed',
130 '62b4a097164940bd66030c4db51687f3ec035eed',
131 '536c1a19428381cfea92ac44985304f6a8049569',
131 '536c1a19428381cfea92ac44985304f6a8049569',
132 '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4',
132 '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4',
133 '9bb326a04ae5d98d437dece54be04f830cf1edd9',
133 '9bb326a04ae5d98d437dece54be04f830cf1edd9',
134 'f8940bcb890a98c4702319fbe36db75ea309b475',
134 'f8940bcb890a98c4702319fbe36db75ea309b475',
135 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
135 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
136 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
136 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
137 'ee87846a61c12153b51543bf860e1026c6d3dcba', ]
137 'ee87846a61c12153b51543bf860e1026c6d3dcba', ]
138 self.assertEqual(org, self.repo.revisions[:31])
138 self.assertEqual(org, self.repo.revisions[:31])
139
139
140 def test_iter_slice(self):
140 def test_iter_slice(self):
141 sliced = list(self.repo[:10])
141 sliced = list(self.repo[:10])
142 itered = list(self.repo)[:10]
142 itered = list(self.repo)[:10]
143 self.assertEqual(sliced, itered)
143 self.assertEqual(sliced, itered)
144
144
145 def test_slicing(self):
145 def test_slicing(self):
146 #4 1 5 10 95
146 #4 1 5 10 95
147 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
147 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
148 (10, 20, 10), (5, 100, 95)]:
148 (10, 20, 10), (5, 100, 95)]:
149 revs = list(self.repo[sfrom:sto])
149 revs = list(self.repo[sfrom:sto])
150 self.assertEqual(len(revs), size)
150 self.assertEqual(len(revs), size)
151 self.assertEqual(revs[0], self.repo.get_changeset(sfrom))
151 self.assertEqual(revs[0], self.repo.get_changeset(sfrom))
152 self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1))
152 self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1))
153
153
154 def test_branches(self):
154 def test_branches(self):
155 # TODO: Need more tests here
155 # TODO: Need more tests here
156
156
157 #active branches
157 #active branches
158 self.assertTrue('default' in self.repo.branches)
158 self.assertTrue('default' in self.repo.branches)
159 self.assertTrue('stable' in self.repo.branches)
159 self.assertTrue('stable' in self.repo.branches)
160
160
161 # closed
161 # closed
162 self.assertTrue('git' in self.repo._get_branches(closed=True))
162 self.assertTrue('git' in self.repo._get_branches(closed=True))
163 self.assertTrue('web' in self.repo._get_branches(closed=True))
163 self.assertTrue('web' in self.repo._get_branches(closed=True))
164
164
165 for name, id in self.repo.branches.items():
165 for name, id in self.repo.branches.items():
166 self.assertTrue(isinstance(
166 self.assertTrue(isinstance(
167 self.repo.get_changeset(id), MercurialChangeset))
167 self.repo.get_changeset(id), MercurialChangeset))
168
168
169 def test_tip_in_tags(self):
169 def test_tip_in_tags(self):
170 # tip is always a tag
170 # tip is always a tag
171 self.assertIn('tip', self.repo.tags)
171 self.assertIn('tip', self.repo.tags)
172
172
173 def test_tip_changeset_in_tags(self):
173 def test_tip_changeset_in_tags(self):
174 tip = self.repo.get_changeset()
174 tip = self.repo.get_changeset()
175 self.assertEqual(self.repo.tags['tip'], tip.raw_id)
175 self.assertEqual(self.repo.tags['tip'], tip.raw_id)
176
176
177 def test_initial_changeset(self):
177 def test_initial_changeset(self):
178
178
179 init_chset = self.repo.get_changeset(0)
179 init_chset = self.repo.get_changeset(0)
180 self.assertEqual(init_chset.message, 'initial import')
180 self.assertEqual(init_chset.message, 'initial import')
181 self.assertEqual(init_chset.author,
181 self.assertEqual(init_chset.author,
182 'Marcin Kuzminski <marcin@python-blog.com>')
182 'Marcin Kuzminski <marcin@python-blog.com>')
183 self.assertEqual(sorted(init_chset._file_paths),
183 self.assertEqual(sorted(init_chset._file_paths),
184 sorted([
184 sorted([
185 'vcs/__init__.py',
185 'vcs/__init__.py',
186 'vcs/backends/BaseRepository.py',
186 'vcs/backends/BaseRepository.py',
187 'vcs/backends/__init__.py',
187 'vcs/backends/__init__.py',
188 ])
188 ])
189 )
189 )
190 self.assertEqual(sorted(init_chset._dir_paths),
190 self.assertEqual(sorted(init_chset._dir_paths),
191 sorted(['', 'vcs', 'vcs/backends']))
191 sorted(['', 'vcs', 'vcs/backends']))
192
192
193 self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar')
193 self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar')
194
194
195 node = init_chset.get_node('vcs/')
195 node = init_chset.get_node('vcs/')
196 self.assertTrue(hasattr(node, 'kind'))
196 self.assertTrue(hasattr(node, 'kind'))
197 self.assertEqual(node.kind, NodeKind.DIR)
197 self.assertEqual(node.kind, NodeKind.DIR)
198
198
199 node = init_chset.get_node('vcs')
199 node = init_chset.get_node('vcs')
200 self.assertTrue(hasattr(node, 'kind'))
200 self.assertTrue(hasattr(node, 'kind'))
201 self.assertEqual(node.kind, NodeKind.DIR)
201 self.assertEqual(node.kind, NodeKind.DIR)
202
202
203 node = init_chset.get_node('vcs/__init__.py')
203 node = init_chset.get_node('vcs/__init__.py')
204 self.assertTrue(hasattr(node, 'kind'))
204 self.assertTrue(hasattr(node, 'kind'))
205 self.assertEqual(node.kind, NodeKind.FILE)
205 self.assertEqual(node.kind, NodeKind.FILE)
206
206
207 def test_not_existing_changeset(self):
207 def test_not_existing_changeset(self):
208 #rawid
208 #rawid
209 self.assertRaises(RepositoryError, self.repo.get_changeset,
209 self.assertRaises(RepositoryError, self.repo.get_changeset,
210 'abcd' * 10)
210 'abcd' * 10)
211 #shortid
211 #shortid
212 self.assertRaises(RepositoryError, self.repo.get_changeset,
212 self.assertRaises(RepositoryError, self.repo.get_changeset,
213 'erro' * 4)
213 'erro' * 4)
214 #numeric
214 #numeric
215 self.assertRaises(RepositoryError, self.repo.get_changeset,
215 self.assertRaises(RepositoryError, self.repo.get_changeset,
216 self.repo.count() + 1)
216 self.repo.count() + 1)
217
217
218
218
219 # Small chance we ever get to this one
219 # Small chance we ever get to this one
220 revision = pow(2, 30)
220 revision = pow(2, 30)
221 self.assertRaises(RepositoryError, self.repo.get_changeset, revision)
221 self.assertRaises(RepositoryError, self.repo.get_changeset, revision)
222
222
223 def test_changeset10(self):
223 def test_changeset10(self):
224
224
225 chset10 = self.repo.get_changeset(10)
225 chset10 = self.repo.get_changeset(10)
226 README = """===
226 README = """===
227 VCS
227 VCS
228 ===
228 ===
229
229
230 Various Version Control System management abstraction layer for Python.
230 Various Version Control System management abstraction layer for Python.
231
231
232 Introduction
232 Introduction
233 ------------
233 ------------
234
234
235 TODO: To be written...
235 TODO: To be written...
236
236
237 """
237 """
238 node = chset10.get_node('README.rst')
238 node = chset10.get_node('README.rst')
239 self.assertEqual(node.kind, NodeKind.FILE)
239 self.assertEqual(node.kind, NodeKind.FILE)
240 self.assertEqual(node.content, README)
240 self.assertEqual(node.content, README)
241
241
242
242
243 class MercurialChangesetTest(unittest.TestCase):
243 class MercurialChangesetTest(unittest.TestCase):
244
244
245 def setUp(self):
245 def setUp(self):
246 self.repo = MercurialRepository(TEST_HG_REPO)
246 self.repo = MercurialRepository(TEST_HG_REPO)
247
247
248 def _test_equality(self, changeset):
248 def _test_equality(self, changeset):
249 revision = changeset.revision
249 revision = changeset.revision
250 self.assertEqual(changeset, self.repo.get_changeset(revision))
250 self.assertEqual(changeset, self.repo.get_changeset(revision))
251
251
252 def test_equality(self):
252 def test_equality(self):
253 self.setUp()
253 self.setUp()
254 revs = [0, 10, 20]
254 revs = [0, 10, 20]
255 changesets = [self.repo.get_changeset(rev) for rev in revs]
255 changesets = [self.repo.get_changeset(rev) for rev in revs]
256 for changeset in changesets:
256 for changeset in changesets:
257 self._test_equality(changeset)
257 self._test_equality(changeset)
258
258
259 def test_default_changeset(self):
259 def test_default_changeset(self):
260 tip = self.repo.get_changeset('tip')
260 tip = self.repo.get_changeset('tip')
261 self.assertEqual(tip, self.repo.get_changeset())
261 self.assertEqual(tip, self.repo.get_changeset())
262 self.assertEqual(tip, self.repo.get_changeset(revision=None))
262 self.assertEqual(tip, self.repo.get_changeset(revision=None))
263 self.assertEqual(tip, list(self.repo[-1:])[0])
263 self.assertEqual(tip, list(self.repo[-1:])[0])
264
264
265 def test_root_node(self):
265 def test_root_node(self):
266 tip = self.repo.get_changeset('tip')
266 tip = self.repo.get_changeset('tip')
267 self.assertTrue(tip.root is tip.get_node(''))
267 self.assertTrue(tip.root is tip.get_node(''))
268
268
269 def test_lazy_fetch(self):
269 def test_lazy_fetch(self):
270 """
270 """
271 Test if changeset's nodes expands and are cached as we walk through
271 Test if changeset's nodes expands and are cached as we walk through
272 the revision. This test is somewhat hard to write as order of tests
272 the revision. This test is somewhat hard to write as order of tests
273 is a key here. Written by running command after command in a shell.
273 is a key here. Written by running command after command in a shell.
274 """
274 """
275 self.setUp()
275 self.setUp()
276 chset = self.repo.get_changeset(45)
276 chset = self.repo.get_changeset(45)
277 self.assertTrue(len(chset.nodes) == 0)
277 self.assertTrue(len(chset.nodes) == 0)
278 root = chset.root
278 root = chset.root
279 self.assertTrue(len(chset.nodes) == 1)
279 self.assertTrue(len(chset.nodes) == 1)
280 self.assertTrue(len(root.nodes) == 8)
280 self.assertTrue(len(root.nodes) == 8)
281 # accessing root.nodes updates chset.nodes
281 # accessing root.nodes updates chset.nodes
282 self.assertTrue(len(chset.nodes) == 9)
282 self.assertTrue(len(chset.nodes) == 9)
283
283
284 docs = root.get_node('docs')
284 docs = root.get_node('docs')
285 # we haven't yet accessed anything new as docs dir was already cached
285 # we haven't yet accessed anything new as docs dir was already cached
286 self.assertTrue(len(chset.nodes) == 9)
286 self.assertTrue(len(chset.nodes) == 9)
287 self.assertTrue(len(docs.nodes) == 8)
287 self.assertTrue(len(docs.nodes) == 8)
288 # accessing docs.nodes updates chset.nodes
288 # accessing docs.nodes updates chset.nodes
289 self.assertTrue(len(chset.nodes) == 17)
289 self.assertTrue(len(chset.nodes) == 17)
290
290
291 self.assertTrue(docs is chset.get_node('docs'))
291 self.assertTrue(docs is chset.get_node('docs'))
292 self.assertTrue(docs is root.nodes[0])
292 self.assertTrue(docs is root.nodes[0])
293 self.assertTrue(docs is root.dirs[0])
293 self.assertTrue(docs is root.dirs[0])
294 self.assertTrue(docs is chset.get_node('docs'))
294 self.assertTrue(docs is chset.get_node('docs'))
295
295
296 def test_nodes_with_changeset(self):
296 def test_nodes_with_changeset(self):
297 self.setUp()
297 self.setUp()
298 chset = self.repo.get_changeset(45)
298 chset = self.repo.get_changeset(45)
299 root = chset.root
299 root = chset.root
300 docs = root.get_node('docs')
300 docs = root.get_node('docs')
301 self.assertTrue(docs is chset.get_node('docs'))
301 self.assertTrue(docs is chset.get_node('docs'))
302 api = docs.get_node('api')
302 api = docs.get_node('api')
303 self.assertTrue(api is chset.get_node('docs/api'))
303 self.assertTrue(api is chset.get_node('docs/api'))
304 index = api.get_node('index.rst')
304 index = api.get_node('index.rst')
305 self.assertTrue(index is chset.get_node('docs/api/index.rst'))
305 self.assertTrue(index is chset.get_node('docs/api/index.rst'))
306 self.assertTrue(index is chset.get_node('docs')\
306 self.assertTrue(index is chset.get_node('docs')\
307 .get_node('api')\
307 .get_node('api')\
308 .get_node('index.rst'))
308 .get_node('index.rst'))
309
309
310 def test_branch_and_tags(self):
310 def test_branch_and_tags(self):
311 chset0 = self.repo.get_changeset(0)
311 chset0 = self.repo.get_changeset(0)
312 self.assertEqual(chset0.branch, 'default')
312 self.assertEqual(chset0.branch, 'default')
313 self.assertEqual(chset0.tags, [])
313 self.assertEqual(chset0.tags, [])
314
314
315 chset10 = self.repo.get_changeset(10)
315 chset10 = self.repo.get_changeset(10)
316 self.assertEqual(chset10.branch, 'default')
316 self.assertEqual(chset10.branch, 'default')
317 self.assertEqual(chset10.tags, [])
317 self.assertEqual(chset10.tags, [])
318
318
319 chset44 = self.repo.get_changeset(44)
319 chset44 = self.repo.get_changeset(44)
320 self.assertEqual(chset44.branch, 'web')
320 self.assertEqual(chset44.branch, 'web')
321
321
322 tip = self.repo.get_changeset('tip')
322 tip = self.repo.get_changeset('tip')
323 self.assertTrue('tip' in tip.tags)
323 self.assertTrue('tip' in tip.tags)
324
324
325 def _test_file_size(self, revision, path, size):
325 def _test_file_size(self, revision, path, size):
326 node = self.repo.get_changeset(revision).get_node(path)
326 node = self.repo.get_changeset(revision).get_node(path)
327 self.assertTrue(node.is_file())
327 self.assertTrue(node.is_file())
328 self.assertEqual(node.size, size)
328 self.assertEqual(node.size, size)
329
329
330 def test_file_size(self):
330 def test_file_size(self):
331 to_check = (
331 to_check = (
332 (10, 'setup.py', 1068),
332 (10, 'setup.py', 1068),
333 (20, 'setup.py', 1106),
333 (20, 'setup.py', 1106),
334 (60, 'setup.py', 1074),
334 (60, 'setup.py', 1074),
335
335
336 (10, 'vcs/backends/base.py', 2921),
336 (10, 'vcs/backends/base.py', 2921),
337 (20, 'vcs/backends/base.py', 3936),
337 (20, 'vcs/backends/base.py', 3936),
338 (60, 'vcs/backends/base.py', 6189),
338 (60, 'vcs/backends/base.py', 6189),
339 )
339 )
340 for revision, path, size in to_check:
340 for revision, path, size in to_check:
341 self._test_file_size(revision, path, size)
341 self._test_file_size(revision, path, size)
342
342
343 def test_file_history(self):
343 def test_file_history(self):
344 # we can only check if those revisions are present in the history
344 # we can only check if those revisions are present in the history
345 # as we cannot update this test every time file is changed
345 # as we cannot update this test every time file is changed
346 files = {
346 files = {
347 'setup.py': [7, 18, 45, 46, 47, 69, 77],
347 'setup.py': [7, 18, 45, 46, 47, 69, 77],
348 'vcs/nodes.py': [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60,
348 'vcs/nodes.py': [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60,
349 61, 73, 76],
349 61, 73, 76],
350 'vcs/backends/hg.py': [4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23,
350 'vcs/backends/hg.py': [4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23,
351 26, 27, 28, 30, 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47,
351 26, 27, 28, 30, 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47,
352 48, 49, 53, 54, 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79,
352 48, 49, 53, 54, 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79,
353 82],
353 82],
354 }
354 }
355 for path, revs in files.items():
355 for path, revs in files.items():
356 tip = self.repo.get_changeset(revs[-1])
356 tip = self.repo.get_changeset(revs[-1])
357 node = tip.get_node(path)
357 node = tip.get_node(path)
358 node_revs = [chset.revision for chset in node.history]
358 node_revs = [chset.revision for chset in node.history]
359 self.assertTrue(set(revs).issubset(set(node_revs)),
359 self.assertTrue(set(revs).issubset(set(node_revs)),
360 "We assumed that %s is subset of revisions for which file %s "
360 "We assumed that %s is subset of revisions for which file %s "
361 "has been changed, and history of that node returned: %s"
361 "has been changed, and history of that node returned: %s"
362 % (revs, path, node_revs))
362 % (revs, path, node_revs))
363
363
364 def test_file_annotate(self):
364 def test_file_annotate(self):
365 files = {
365 files = {
366 'vcs/backends/__init__.py':
366 'vcs/backends/__init__.py':
367 {89: {'lines_no': 31,
367 {89: {'lines_no': 31,
368 'changesets': [32, 32, 61, 32, 32, 37, 32, 32, 32, 44,
368 'changesets': [32, 32, 61, 32, 32, 37, 32, 32, 32, 44,
369 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
369 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
370 32, 32, 32, 32, 37, 32, 37, 37, 32,
370 32, 32, 32, 32, 37, 32, 37, 37, 32,
371 32, 32]},
371 32, 32]},
372 20: {'lines_no': 1,
372 20: {'lines_no': 1,
373 'changesets': [4]},
373 'changesets': [4]},
374 55: {'lines_no': 31,
374 55: {'lines_no': 31,
375 'changesets': [32, 32, 45, 32, 32, 37, 32, 32, 32, 44,
375 'changesets': [32, 32, 45, 32, 32, 37, 32, 32, 32, 44,
376 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
376 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
377 32, 32, 32, 32, 37, 32, 37, 37, 32,
377 32, 32, 32, 32, 37, 32, 37, 37, 32,
378 32, 32]}},
378 32, 32]}},
379 'vcs/exceptions.py':
379 'vcs/exceptions.py':
380 {89: {'lines_no': 18,
380 {89: {'lines_no': 18,
381 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
381 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
382 16, 16, 17, 16, 16, 18, 18, 18]},
382 16, 16, 17, 16, 16, 18, 18, 18]},
383 20: {'lines_no': 18,
383 20: {'lines_no': 18,
384 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
384 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
385 16, 16, 17, 16, 16, 18, 18, 18]},
385 16, 16, 17, 16, 16, 18, 18, 18]},
386 55: {'lines_no': 18, 'changesets': [16, 16, 16, 16, 16, 16,
386 55: {'lines_no': 18, 'changesets': [16, 16, 16, 16, 16, 16,
387 16, 16, 16, 16, 16, 16,
387 16, 16, 16, 16, 16, 16,
388 17, 16, 16, 18, 18, 18]}},
388 17, 16, 16, 18, 18, 18]}},
389 'MANIFEST.in': {89: {'lines_no': 5,
389 'MANIFEST.in': {89: {'lines_no': 5,
390 'changesets': [7, 7, 7, 71, 71]},
390 'changesets': [7, 7, 7, 71, 71]},
391 20: {'lines_no': 3,
391 20: {'lines_no': 3,
392 'changesets': [7, 7, 7]},
392 'changesets': [7, 7, 7]},
393 55: {'lines_no': 3,
393 55: {'lines_no': 3,
394 'changesets': [7, 7, 7]}}}
394 'changesets': [7, 7, 7]}}}
395
395
396 for fname, revision_dict in files.items():
396 for fname, revision_dict in files.items():
397 for rev, data in revision_dict.items():
397 for rev, data in revision_dict.items():
398 cs = self.repo.get_changeset(rev)
398 cs = self.repo.get_changeset(rev)
399 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
399 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
400 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
400 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
401 self.assertEqual(l1_1, l1_2)
401 self.assertEqual(l1_1, l1_2)
402 l1 = l1_2 = [x[2]().revision for x in cs.get_file_annotate(fname)]
402 l1 = l1_2 = [x[2]().revision for x in cs.get_file_annotate(fname)]
403 l2 = files[fname][rev]['changesets']
403 l2 = files[fname][rev]['changesets']
404 self.assertTrue(l1 == l2 , "The lists of revision for %s@rev%s"
404 self.assertTrue(l1 == l2 , "The lists of revision for %s@rev%s"
405 "from annotation list should match each other,"
405 "from annotation list should match each other,"
406 "got \n%s \nvs \n%s " % (fname, rev, l1, l2))
406 "got \n%s \nvs \n%s " % (fname, rev, l1, l2))
407
407
408 def test_changeset_state(self):
408 def test_changeset_state(self):
409 """
409 """
410 Tests which files have been added/changed/removed at particular revision
410 Tests which files have been added/changed/removed at particular revision
411 """
411 """
412
412
413 # rev 46ad32a4f974:
413 # rev 46ad32a4f974:
414 # hg st --rev 46ad32a4f974
414 # hg st --rev 46ad32a4f974
415 # changed: 13
415 # changed: 13
416 # added: 20
416 # added: 20
417 # removed: 1
417 # removed: 1
418 changed = set(['.hgignore'
418 changed = set(['.hgignore'
419 , 'README.rst' , 'docs/conf.py' , 'docs/index.rst' , 'setup.py'
419 , 'README.rst' , 'docs/conf.py' , 'docs/index.rst' , 'setup.py'
420 , 'tests/test_hg.py' , 'tests/test_nodes.py' , 'vcs/__init__.py'
420 , 'tests/test_hg.py' , 'tests/test_nodes.py' , 'vcs/__init__.py'
421 , 'vcs/backends/__init__.py' , 'vcs/backends/base.py'
421 , 'vcs/backends/__init__.py' , 'vcs/backends/base.py'
422 , 'vcs/backends/hg.py' , 'vcs/nodes.py' , 'vcs/utils/__init__.py'])
422 , 'vcs/backends/hg.py' , 'vcs/nodes.py' , 'vcs/utils/__init__.py'])
423
423
424 added = set(['docs/api/backends/hg.rst'
424 added = set(['docs/api/backends/hg.rst'
425 , 'docs/api/backends/index.rst' , 'docs/api/index.rst'
425 , 'docs/api/backends/index.rst' , 'docs/api/index.rst'
426 , 'docs/api/nodes.rst' , 'docs/api/web/index.rst'
426 , 'docs/api/nodes.rst' , 'docs/api/web/index.rst'
427 , 'docs/api/web/simplevcs.rst' , 'docs/installation.rst'
427 , 'docs/api/web/simplevcs.rst' , 'docs/installation.rst'
428 , 'docs/quickstart.rst' , 'setup.cfg' , 'vcs/utils/baseui_config.py'
428 , 'docs/quickstart.rst' , 'setup.cfg' , 'vcs/utils/baseui_config.py'
429 , 'vcs/utils/web.py' , 'vcs/web/__init__.py' , 'vcs/web/exceptions.py'
429 , 'vcs/utils/web.py' , 'vcs/web/__init__.py' , 'vcs/web/exceptions.py'
430 , 'vcs/web/simplevcs/__init__.py' , 'vcs/web/simplevcs/exceptions.py'
430 , 'vcs/web/simplevcs/__init__.py' , 'vcs/web/simplevcs/exceptions.py'
431 , 'vcs/web/simplevcs/middleware.py' , 'vcs/web/simplevcs/models.py'
431 , 'vcs/web/simplevcs/middleware.py' , 'vcs/web/simplevcs/models.py'
432 , 'vcs/web/simplevcs/settings.py' , 'vcs/web/simplevcs/utils.py'
432 , 'vcs/web/simplevcs/settings.py' , 'vcs/web/simplevcs/utils.py'
433 , 'vcs/web/simplevcs/views.py'])
433 , 'vcs/web/simplevcs/views.py'])
434
434
435 removed = set(['docs/api.rst'])
435 removed = set(['docs/api.rst'])
436
436
437 chset64 = self.repo.get_changeset('46ad32a4f974')
437 chset64 = self.repo.get_changeset('46ad32a4f974')
438 self.assertEqual(set((node.path for node in chset64.added)), added)
438 self.assertEqual(set((node.path for node in chset64.added)), added)
439 self.assertEqual(set((node.path for node in chset64.changed)), changed)
439 self.assertEqual(set((node.path for node in chset64.changed)), changed)
440 self.assertEqual(set((node.path for node in chset64.removed)), removed)
440 self.assertEqual(set((node.path for node in chset64.removed)), removed)
441
441
442 # rev b090f22d27d6:
442 # rev b090f22d27d6:
443 # hg st --rev b090f22d27d6
443 # hg st --rev b090f22d27d6
444 # changed: 13
444 # changed: 13
445 # added: 20
445 # added: 20
446 # removed: 1
446 # removed: 1
447 chset88 = self.repo.get_changeset('b090f22d27d6')
447 chset88 = self.repo.get_changeset('b090f22d27d6')
448 self.assertEqual(set((node.path for node in chset88.added)), set())
448 self.assertEqual(set((node.path for node in chset88.added)), set())
449 self.assertEqual(set((node.path for node in chset88.changed)),
449 self.assertEqual(set((node.path for node in chset88.changed)),
450 set(['.hgignore']))
450 set(['.hgignore']))
451 self.assertEqual(set((node.path for node in chset88.removed)), set())
451 self.assertEqual(set((node.path for node in chset88.removed)), set())
452 #
452 #
453 # 85:
453 # 85:
454 # added: 2 ['vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
454 # added: 2 ['vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
455 # changed: 4 ['vcs/web/simplevcs/models.py', ...]
455 # changed: 4 ['vcs/web/simplevcs/models.py', ...]
456 # removed: 1 ['vcs/utils/web.py']
456 # removed: 1 ['vcs/utils/web.py']
457 chset85 = self.repo.get_changeset(85)
457 chset85 = self.repo.get_changeset(85)
458 self.assertEqual(set((node.path for node in chset85.added)), set([
458 self.assertEqual(set((node.path for node in chset85.added)), set([
459 'vcs/utils/diffs.py',
459 'vcs/utils/diffs.py',
460 'vcs/web/simplevcs/views/diffs.py']))
460 'vcs/web/simplevcs/views/diffs.py']))
461 self.assertEqual(set((node.path for node in chset85.changed)), set([
461 self.assertEqual(set((node.path for node in chset85.changed)), set([
462 'vcs/web/simplevcs/models.py',
462 'vcs/web/simplevcs/models.py',
463 'vcs/web/simplevcs/utils.py',
463 'vcs/web/simplevcs/utils.py',
464 'vcs/web/simplevcs/views/__init__.py',
464 'vcs/web/simplevcs/views/__init__.py',
465 'vcs/web/simplevcs/views/repository.py',
465 'vcs/web/simplevcs/views/repository.py',
466 ]))
466 ]))
467 self.assertEqual(set((node.path for node in chset85.removed)),
467 self.assertEqual(set((node.path for node in chset85.removed)),
468 set(['vcs/utils/web.py']))
468 set(['vcs/utils/web.py']))
469
469
470
470
471 def test_files_state(self):
471 def test_files_state(self):
472 """
472 """
473 Tests state of FileNodes.
473 Tests state of FileNodes.
474 """
474 """
475 chset = self.repo.get_changeset(85)
475 chset = self.repo.get_changeset(85)
476 node = chset.get_node('vcs/utils/diffs.py')
476 node = chset.get_node('vcs/utils/diffs.py')
477 self.assertTrue(node.state, NodeState.ADDED)
477 self.assertTrue(node.state, NodeState.ADDED)
478 self.assertTrue(node.added)
478 self.assertTrue(node.added)
479 self.assertFalse(node.changed)
479 self.assertFalse(node.changed)
480 self.assertFalse(node.not_changed)
480 self.assertFalse(node.not_changed)
481 self.assertFalse(node.removed)
481 self.assertFalse(node.removed)
482
482
483 chset = self.repo.get_changeset(88)
483 chset = self.repo.get_changeset(88)
484 node = chset.get_node('.hgignore')
484 node = chset.get_node('.hgignore')
485 self.assertTrue(node.state, NodeState.CHANGED)
485 self.assertTrue(node.state, NodeState.CHANGED)
486 self.assertFalse(node.added)
486 self.assertFalse(node.added)
487 self.assertTrue(node.changed)
487 self.assertTrue(node.changed)
488 self.assertFalse(node.not_changed)
488 self.assertFalse(node.not_changed)
489 self.assertFalse(node.removed)
489 self.assertFalse(node.removed)
490
490
491 chset = self.repo.get_changeset(85)
491 chset = self.repo.get_changeset(85)
492 node = chset.get_node('setup.py')
492 node = chset.get_node('setup.py')
493 self.assertTrue(node.state, NodeState.NOT_CHANGED)
493 self.assertTrue(node.state, NodeState.NOT_CHANGED)
494 self.assertFalse(node.added)
494 self.assertFalse(node.added)
495 self.assertFalse(node.changed)
495 self.assertFalse(node.changed)
496 self.assertTrue(node.not_changed)
496 self.assertTrue(node.not_changed)
497 self.assertFalse(node.removed)
497 self.assertFalse(node.removed)
498
498
499 # If node has REMOVED state then trying to fetch it would raise
499 # If node has REMOVED state then trying to fetch it would raise
500 # ChangesetError exception
500 # ChangesetError exception
501 chset = self.repo.get_changeset(2)
501 chset = self.repo.get_changeset(2)
502 path = 'vcs/backends/BaseRepository.py'
502 path = 'vcs/backends/BaseRepository.py'
503 self.assertRaises(NodeDoesNotExistError, chset.get_node, path)
503 self.assertRaises(NodeDoesNotExistError, chset.get_node, path)
504 # but it would be one of ``removed`` (changeset's attribute)
504 # but it would be one of ``removed`` (changeset's attribute)
505 self.assertTrue(path in [rf.path for rf in chset.removed])
505 self.assertTrue(path in [rf.path for rf in chset.removed])
506
506
507 def test_commit_message_is_unicode(self):
507 def test_commit_message_is_unicode(self):
508 for cm in self.repo:
508 for cm in self.repo:
509 self.assertEqual(type(cm.message), unicode)
509 self.assertEqual(type(cm.message), unicode)
510
510
511 def test_changeset_author_is_unicode(self):
511 def test_changeset_author_is_unicode(self):
512 for cm in self.repo:
512 for cm in self.repo:
513 self.assertEqual(type(cm.author), unicode)
513 self.assertEqual(type(cm.author), unicode)
514
514
515 def test_repo_files_content_is_unicode(self):
515 def test_repo_files_content_is_unicode(self):
516 test_changeset = self.repo.get_changeset(100)
516 test_changeset = self.repo.get_changeset(100)
517 for node in test_changeset.get_node('/'):
517 for node in test_changeset.get_node('/'):
518 if node.is_file():
518 if node.is_file():
519 self.assertEqual(type(node.content), unicode)
519 self.assertEqual(type(node.content), unicode)
520
520
521 def test_wrong_path(self):
521 def test_wrong_path(self):
522 # There is 'setup.py' in the root dir but not there:
522 # There is 'setup.py' in the root dir but not there:
523 path = 'foo/bar/setup.py'
523 path = 'foo/bar/setup.py'
524 self.assertRaises(VCSError, self.repo.get_changeset().get_node, path)
524 self.assertRaises(VCSError, self.repo.get_changeset().get_node, path)
525
525
526
526
527 def test_archival_file(self):
527 def test_archival_file(self):
528 #TODO:
528 #TODO:
529 pass
529 pass
530
530
531 def test_archival_as_generator(self):
531 def test_archival_as_generator(self):
532 #TODO:
532 #TODO:
533 pass
533 pass
534
534
535 def test_archival_wrong_kind(self):
535 def test_archival_wrong_kind(self):
536 tip = self.repo.get_changeset()
536 tip = self.repo.get_changeset()
537 self.assertRaises(VCSError, tip.fill_archive, kind='error')
537 self.assertRaises(VCSError, tip.fill_archive, kind='error')
538
538
539 def test_archival_empty_prefix(self):
539 def test_archival_empty_prefix(self):
540 #TODO:
540 #TODO:
541 pass
541 pass
542
542
543
543
544 def test_author_email(self):
544 def test_author_email(self):
545 self.assertEqual('marcin@python-blog.com',
545 self.assertEqual('marcin@python-blog.com',
546 self.repo.get_changeset('b986218ba1c9').author_email)
546 self.repo.get_changeset('b986218ba1c9').author_email)
547 self.assertEqual('lukasz.balcerzak@python-center.pl',
547 self.assertEqual('lukasz.balcerzak@python-center.pl',
548 self.repo.get_changeset('3803844fdbd3').author_email)
548 self.repo.get_changeset('3803844fdbd3').author_email)
549 self.assertEqual('',
549 self.assertEqual('',
550 self.repo.get_changeset('84478366594b').author_email)
550 self.repo.get_changeset('84478366594b').author_email)
551
551
552 def test_author_username(self):
552 def test_author_username(self):
553 self.assertEqual('Marcin Kuzminski',
553 self.assertEqual('Marcin Kuzminski',
554 self.repo.get_changeset('b986218ba1c9').author_name)
554 self.repo.get_changeset('b986218ba1c9').author_name)
555 self.assertEqual('Lukasz Balcerzak',
555 self.assertEqual('Lukasz Balcerzak',
556 self.repo.get_changeset('3803844fdbd3').author_name)
556 self.repo.get_changeset('3803844fdbd3').author_name)
557 self.assertEqual('marcink',
557 self.assertEqual('marcink',
558 self.repo.get_changeset('84478366594b').author_name)
558 self.repo.get_changeset('84478366594b').author_name)
General Comments 0
You need to be logged in to leave comments. Login now