##// END OF EJS Templates
files: fixes error when passing a diff without parameters and caused server crash...
marcink -
r1224:e76833cd beta
parent child Browse files
Show More
@@ -1,297 +1,297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 logging
27 import logging
28 import rhodecode.lib.helpers as h
28 import rhodecode.lib.helpers as h
29
29
30 from pylons import request, response, session, tmpl_context as c, url
30 from pylons import request, response, session, tmpl_context as c, url
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33
33
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.utils import EmptyChangeset
36 from rhodecode.lib.utils import EmptyChangeset
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38
38
39 from vcs.backends import ARCHIVE_SPECS
39 from vcs.backends import ARCHIVE_SPECS
40 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
40 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
41 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
41 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
42 from vcs.nodes import FileNode, NodeKind
42 from vcs.nodes import FileNode, NodeKind
43 from vcs.utils import diffs as differ
43 from vcs.utils import diffs as differ
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class FilesController(BaseRepoController):
48 class FilesController(BaseRepoController):
49
49
50 @LoginRequired()
50 @LoginRequired()
51 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
51 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 'repository.admin')
52 'repository.admin')
53 def __before__(self):
53 def __before__(self):
54 super(FilesController, self).__before__()
54 super(FilesController, self).__before__()
55 c.cut_off_limit = self.cut_off_limit
55 c.cut_off_limit = self.cut_off_limit
56
56
57 def __get_cs_or_redirect(self, rev, repo_name):
57 def __get_cs_or_redirect(self, rev, repo_name):
58 """
58 """
59 Safe way to get changeset if error occur it redirects to tip with
59 Safe way to get changeset if error occur it redirects to tip with
60 proper message
60 proper message
61
61
62 :param rev: revision to fetch
62 :param rev: revision to fetch
63 :param repo_name: repo name to redirect after
63 :param repo_name: repo name to redirect after
64 """
64 """
65
65
66 try:
66 try:
67 return c.rhodecode_repo.get_changeset(rev)
67 return c.rhodecode_repo.get_changeset(rev)
68 except EmptyRepositoryError, e:
68 except EmptyRepositoryError, e:
69 h.flash(_('There are no files yet'), category='warning')
69 h.flash(_('There are no files yet'), category='warning')
70 redirect(h.url('summary_home', repo_name=repo_name))
70 redirect(h.url('summary_home', repo_name=repo_name))
71
71
72 except RepositoryError, e:
72 except RepositoryError, e:
73 h.flash(str(e), category='warning')
73 h.flash(str(e), category='warning')
74 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
74 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
75
75
76 def __get_filenode_or_redirect(self, repo_name, cs, path):
76 def __get_filenode_or_redirect(self, repo_name, cs, path):
77 """
77 """
78 Returns file_node, if error occurs or given path is directory,
78 Returns file_node, if error occurs or given path is directory,
79 it'll redirect to top level path
79 it'll redirect to top level path
80
80
81 :param repo_name: repo_name
81 :param repo_name: repo_name
82 :param cs: given changeset
82 :param cs: given changeset
83 :param path: path to lookup
83 :param path: path to lookup
84 """
84 """
85
85
86 try:
86 try:
87 file_node = cs.get_node(path)
87 file_node = cs.get_node(path)
88 if file_node.is_dir():
88 if file_node.is_dir():
89 raise RepositoryError('given path is a directory')
89 raise RepositoryError('given path is a directory')
90 except RepositoryError, e:
90 except RepositoryError, e:
91 h.flash(str(e), category='warning')
91 h.flash(str(e), category='warning')
92 redirect(h.url('files_home', repo_name=repo_name,
92 redirect(h.url('files_home', repo_name=repo_name,
93 revision=cs.raw_id))
93 revision=cs.raw_id))
94
94
95 return file_node
95 return file_node
96
96
97 def index(self, repo_name, revision, f_path):
97 def index(self, repo_name, revision, f_path):
98 #reditect to given revision from form if given
98 #reditect to given revision from form if given
99 post_revision = request.POST.get('at_rev', None)
99 post_revision = request.POST.get('at_rev', None)
100 if post_revision:
100 if post_revision:
101 cs = self.__get_cs_or_redirect(revision, repo_name)
101 cs = self.__get_cs_or_redirect(post_revision, repo_name)
102 redirect(url('files_home', repo_name=c.repo_name,
102 redirect(url('files_home', repo_name=c.repo_name,
103 revision=cs.raw_id, f_path=f_path))
103 revision=cs.raw_id, f_path=f_path))
104
104
105 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
105 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
106 c.branch = request.GET.get('branch', None)
106 c.branch = request.GET.get('branch', None)
107 c.f_path = f_path
107 c.f_path = f_path
108
108
109 cur_rev = c.changeset.revision
109 cur_rev = c.changeset.revision
110
110
111 #prev link
111 #prev link
112 try:
112 try:
113 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
113 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
114 c.url_prev = url('files_home', repo_name=c.repo_name,
114 c.url_prev = url('files_home', repo_name=c.repo_name,
115 revision=prev_rev.raw_id, f_path=f_path)
115 revision=prev_rev.raw_id, f_path=f_path)
116 if c.branch:
116 if c.branch:
117 c.url_prev += '?branch=%s' % c.branch
117 c.url_prev += '?branch=%s' % c.branch
118 except (ChangesetDoesNotExistError, VCSError):
118 except (ChangesetDoesNotExistError, VCSError):
119 c.url_prev = '#'
119 c.url_prev = '#'
120
120
121 #next link
121 #next link
122 try:
122 try:
123 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
123 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
124 c.url_next = url('files_home', repo_name=c.repo_name,
124 c.url_next = url('files_home', repo_name=c.repo_name,
125 revision=next_rev.raw_id, f_path=f_path)
125 revision=next_rev.raw_id, f_path=f_path)
126 if c.branch:
126 if c.branch:
127 c.url_next += '?branch=%s' % c.branch
127 c.url_next += '?branch=%s' % c.branch
128 except (ChangesetDoesNotExistError, VCSError):
128 except (ChangesetDoesNotExistError, VCSError):
129 c.url_next = '#'
129 c.url_next = '#'
130
130
131 #files or dirs
131 #files or dirs
132 try:
132 try:
133 c.files_list = c.changeset.get_node(f_path)
133 c.files_list = c.changeset.get_node(f_path)
134
134
135 if c.files_list.is_file():
135 if c.files_list.is_file():
136 c.file_history = self._get_node_history(c.changeset, f_path)
136 c.file_history = self._get_node_history(c.changeset, f_path)
137 else:
137 else:
138 c.file_history = []
138 c.file_history = []
139 except RepositoryError, e:
139 except RepositoryError, e:
140 h.flash(str(e), category='warning')
140 h.flash(str(e), category='warning')
141 redirect(h.url('files_home', repo_name=repo_name,
141 redirect(h.url('files_home', repo_name=repo_name,
142 revision=revision))
142 revision=revision))
143
143
144 return render('files/files.html')
144 return render('files/files.html')
145
145
146 def rawfile(self, repo_name, revision, f_path):
146 def rawfile(self, repo_name, revision, f_path):
147 cs = self.__get_cs_or_redirect(revision, repo_name)
147 cs = self.__get_cs_or_redirect(revision, repo_name)
148 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
148 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
149
149
150 response.content_disposition = 'attachment; filename=%s' % \
150 response.content_disposition = 'attachment; filename=%s' % \
151 f_path.split(os.sep)[-1].encode('utf8', 'replace')
151 f_path.split(os.sep)[-1].encode('utf8', 'replace')
152
152
153 response.content_type = file_node.mimetype
153 response.content_type = file_node.mimetype
154 return file_node.content
154 return file_node.content
155
155
156 def raw(self, repo_name, revision, f_path):
156 def raw(self, repo_name, revision, f_path):
157 cs = self.__get_cs_or_redirect(revision, repo_name)
157 cs = self.__get_cs_or_redirect(revision, repo_name)
158 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
158 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
159
159
160 response.content_type = 'text/plain'
160 response.content_type = 'text/plain'
161 return file_node.content
161 return file_node.content
162
162
163 def annotate(self, repo_name, revision, f_path):
163 def annotate(self, repo_name, revision, f_path):
164 c.cs = self.__get_cs_or_redirect(revision, repo_name)
164 c.cs = self.__get_cs_or_redirect(revision, repo_name)
165 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
165 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
166
166
167 c.file_history = self._get_node_history(c.cs, f_path)
167 c.file_history = self._get_node_history(c.cs, f_path)
168 c.f_path = f_path
168 c.f_path = f_path
169 return render('files/files_annotate.html')
169 return render('files/files_annotate.html')
170
170
171 def archivefile(self, repo_name, fname):
171 def archivefile(self, repo_name, fname):
172
172
173 fileformat = None
173 fileformat = None
174 revision = None
174 revision = None
175 ext = None
175 ext = None
176
176
177 for a_type, ext_data in ARCHIVE_SPECS.items():
177 for a_type, ext_data in ARCHIVE_SPECS.items():
178 archive_spec = fname.split(ext_data[1])
178 archive_spec = fname.split(ext_data[1])
179 if len(archive_spec) == 2 and archive_spec[1] == '':
179 if len(archive_spec) == 2 and archive_spec[1] == '':
180 fileformat = a_type or ext_data[1]
180 fileformat = a_type or ext_data[1]
181 revision = archive_spec[0]
181 revision = archive_spec[0]
182 ext = ext_data[1]
182 ext = ext_data[1]
183
183
184 try:
184 try:
185 dbrepo = RepoModel().get_by_repo_name(repo_name)
185 dbrepo = RepoModel().get_by_repo_name(repo_name)
186 if dbrepo.enable_downloads is False:
186 if dbrepo.enable_downloads is False:
187 return _('downloads disabled')
187 return _('downloads disabled')
188
188
189 cs = c.rhodecode_repo.get_changeset(revision)
189 cs = c.rhodecode_repo.get_changeset(revision)
190 content_type = ARCHIVE_SPECS[fileformat][0]
190 content_type = ARCHIVE_SPECS[fileformat][0]
191 except ChangesetDoesNotExistError:
191 except ChangesetDoesNotExistError:
192 return _('Unknown revision %s') % revision
192 return _('Unknown revision %s') % revision
193 except EmptyRepositoryError:
193 except EmptyRepositoryError:
194 return _('Empty repository')
194 return _('Empty repository')
195 except (ImproperArchiveTypeError, KeyError):
195 except (ImproperArchiveTypeError, KeyError):
196 return _('Unknown archive type')
196 return _('Unknown archive type')
197
197
198 response.content_type = content_type
198 response.content_type = content_type
199 response.content_disposition = 'attachment; filename=%s-%s%s' \
199 response.content_disposition = 'attachment; filename=%s-%s%s' \
200 % (repo_name, revision, ext)
200 % (repo_name, revision, ext)
201
201
202 return cs.get_chunked_archive(stream=None, kind=fileformat)
202 return cs.get_chunked_archive(stream=None, kind=fileformat)
203
203
204 def diff(self, repo_name, f_path):
204 def diff(self, repo_name, f_path):
205 diff1 = request.GET.get('diff1')
205 diff1 = request.GET.get('diff1')
206 diff2 = request.GET.get('diff2')
206 diff2 = request.GET.get('diff2')
207 c.action = request.GET.get('diff')
207 c.action = request.GET.get('diff')
208 c.no_changes = diff1 == diff2
208 c.no_changes = diff1 == diff2
209 c.f_path = f_path
209 c.f_path = f_path
210
210
211 try:
211 try:
212 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
212 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
213 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
213 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
214 node1 = c.changeset_1.get_node(f_path)
214 node1 = c.changeset_1.get_node(f_path)
215 else:
215 else:
216 c.changeset_1 = EmptyChangeset()
216 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
217 node1 = FileNode('.', '', changeset=c.changeset_1)
217 node1 = FileNode('.', '', changeset=c.changeset_1)
218
218
219 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
219 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
220 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
220 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
221 node2 = c.changeset_2.get_node(f_path)
221 node2 = c.changeset_2.get_node(f_path)
222 else:
222 else:
223 c.changeset_2 = EmptyChangeset()
223 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
224 node2 = FileNode('.', '', changeset=c.changeset_2)
224 node2 = FileNode('.', '', changeset=c.changeset_2)
225 except RepositoryError:
225 except RepositoryError:
226 return redirect(url('files_home',
226 return redirect(url('files_home',
227 repo_name=c.repo_name, f_path=f_path))
227 repo_name=c.repo_name, f_path=f_path))
228
228
229 if c.action == 'download':
229 if c.action == 'download':
230 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
230 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
231 format='gitdiff')
231 format='gitdiff')
232
232
233 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
233 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
234 response.content_type = 'text/plain'
234 response.content_type = 'text/plain'
235 response.content_disposition = 'attachment; filename=%s' \
235 response.content_disposition = 'attachment; filename=%s' \
236 % diff_name
236 % diff_name
237 return diff.raw_diff()
237 return diff.raw_diff()
238
238
239 elif c.action == 'raw':
239 elif c.action == 'raw':
240 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
240 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
241 format='gitdiff')
241 format='gitdiff')
242 response.content_type = 'text/plain'
242 response.content_type = 'text/plain'
243 return diff.raw_diff()
243 return diff.raw_diff()
244
244
245 elif c.action == 'diff':
245 elif c.action == 'diff':
246
246
247 if node1.is_binary or node2.is_binary:
247 if node1.is_binary or node2.is_binary:
248 c.cur_diff = _('Binary file')
248 c.cur_diff = _('Binary file')
249 elif node1.size > self.cut_off_limit or \
249 elif node1.size > self.cut_off_limit or \
250 node2.size > self.cut_off_limit:
250 node2.size > self.cut_off_limit:
251 c.cur_diff = _('Diff is too big to display')
251 c.cur_diff = _('Diff is too big to display')
252 else:
252 else:
253 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
253 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
254 format='gitdiff')
254 format='gitdiff')
255 c.cur_diff = diff.as_html()
255 c.cur_diff = diff.as_html()
256 else:
256 else:
257
257
258 #default option
258 #default option
259 if node1.is_binary or node2.is_binary:
259 if node1.is_binary or node2.is_binary:
260 c.cur_diff = _('Binary file')
260 c.cur_diff = _('Binary file')
261 elif node1.size > self.cut_off_limit or \
261 elif node1.size > self.cut_off_limit or \
262 node2.size > self.cut_off_limit:
262 node2.size > self.cut_off_limit:
263 c.cur_diff = _('Diff is too big to display')
263 c.cur_diff = _('Diff is too big to display')
264 else:
264 else:
265 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
265 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
266 format='gitdiff')
266 format='gitdiff')
267 c.cur_diff = diff.as_html()
267 c.cur_diff = diff.as_html()
268
268
269 if not c.cur_diff:
269 if not c.cur_diff:
270 c.no_changes = True
270 c.no_changes = True
271 return render('files/file_diff.html')
271 return render('files/file_diff.html')
272
272
273 def _get_node_history(self, cs, f_path):
273 def _get_node_history(self, cs, f_path):
274 changesets = cs.get_file_history(f_path)
274 changesets = cs.get_file_history(f_path)
275 hist_l = []
275 hist_l = []
276
276
277 changesets_group = ([], _("Changesets"))
277 changesets_group = ([], _("Changesets"))
278 branches_group = ([], _("Branches"))
278 branches_group = ([], _("Branches"))
279 tags_group = ([], _("Tags"))
279 tags_group = ([], _("Tags"))
280
280
281 for chs in changesets:
281 for chs in changesets:
282 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
282 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
283 changesets_group[0].append((chs.raw_id, n_desc,))
283 changesets_group[0].append((chs.raw_id, n_desc,))
284
284
285 hist_l.append(changesets_group)
285 hist_l.append(changesets_group)
286
286
287 for name, chs in c.rhodecode_repo.branches.items():
287 for name, chs in c.rhodecode_repo.branches.items():
288 #chs = chs.split(':')[-1]
288 #chs = chs.split(':')[-1]
289 branches_group[0].append((chs, name),)
289 branches_group[0].append((chs, name),)
290 hist_l.append(branches_group)
290 hist_l.append(branches_group)
291
291
292 for name, chs in c.rhodecode_repo.tags.items():
292 for name, chs in c.rhodecode_repo.tags.items():
293 #chs = chs.split(':')[-1]
293 #chs = chs.split(':')[-1]
294 tags_group[0].append((chs, name),)
294 tags_group[0].append((chs, name),)
295 hist_l.append(tags_group)
295 hist_l.append(tags_group)
296
296
297 return hist_l
297 return hist_l
@@ -1,688 +1,707 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) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import paste
30 import paste
31 import beaker
31 import beaker
32
32
33 from paste.script.command import Command, BadCommand
33 from paste.script.command import Command, BadCommand
34
34
35 from UserDict import DictMixin
35 from UserDict import DictMixin
36
36
37 from mercurial import ui, config, hg
37 from mercurial import ui, config, hg
38 from mercurial.error import RepoError
38 from mercurial.error import RepoError
39
39
40 from webhelpers.text import collapse, remove_formatting, strip_tags
40 from webhelpers.text import collapse, remove_formatting, strip_tags
41
41
42 from vcs.backends.base import BaseChangeset
42 from vcs.backends.base import BaseChangeset
43 from vcs.utils.lazy import LazyProperty
43 from vcs.utils.lazy import LazyProperty
44
44
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.model.caching_query import FromCache
46 from rhodecode.model.caching_query import FromCache
47 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group
47 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def recursive_replace(str, replace=' '):
54 def recursive_replace(str, replace=' '):
55 """Recursive replace of given sign to just one instance
55 """Recursive replace of given sign to just one instance
56
56
57 :param str: given string
57 :param str: given string
58 :param replace: char to find and replace multiple instances
58 :param replace: char to find and replace multiple instances
59
59
60 Examples::
60 Examples::
61 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
61 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
62 'Mighty-Mighty-Bo-sstones'
62 'Mighty-Mighty-Bo-sstones'
63 """
63 """
64
64
65 if str.find(replace * 2) == -1:
65 if str.find(replace * 2) == -1:
66 return str
66 return str
67 else:
67 else:
68 str = str.replace(replace * 2, replace)
68 str = str.replace(replace * 2, replace)
69 return recursive_replace(str, replace)
69 return recursive_replace(str, replace)
70
70
71
71 def repo_name_slug(value):
72 def repo_name_slug(value):
72 """Return slug of name of repository
73 """Return slug of name of repository
73 This function is called on each creation/modification
74 This function is called on each creation/modification
74 of repository to prevent bad names in repo
75 of repository to prevent bad names in repo
75 """
76 """
76
77
77 slug = remove_formatting(value)
78 slug = remove_formatting(value)
78 slug = strip_tags(slug)
79 slug = strip_tags(slug)
79
80
80 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
81 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
81 slug = slug.replace(c, '-')
82 slug = slug.replace(c, '-')
82 slug = recursive_replace(slug, '-')
83 slug = recursive_replace(slug, '-')
83 slug = collapse(slug, '-')
84 slug = collapse(slug, '-')
84 return slug
85 return slug
85
86
87
86 def get_repo_slug(request):
88 def get_repo_slug(request):
87 return request.environ['pylons.routes_dict'].get('repo_name')
89 return request.environ['pylons.routes_dict'].get('repo_name')
88
90
91
89 def action_logger(user, action, repo, ipaddr='', sa=None):
92 def action_logger(user, action, repo, ipaddr='', sa=None):
90 """
93 """
91 Action logger for various actions made by users
94 Action logger for various actions made by users
92
95
93 :param user: user that made this action, can be a unique username string or
96 :param user: user that made this action, can be a unique username string or
94 object containing user_id attribute
97 object containing user_id attribute
95 :param action: action to log, should be on of predefined unique actions for
98 :param action: action to log, should be on of predefined unique actions for
96 easy translations
99 easy translations
97 :param repo: string name of repository or object containing repo_id,
100 :param repo: string name of repository or object containing repo_id,
98 that action was made on
101 that action was made on
99 :param ipaddr: optional ip address from what the action was made
102 :param ipaddr: optional ip address from what the action was made
100 :param sa: optional sqlalchemy session
103 :param sa: optional sqlalchemy session
101
104
102 """
105 """
103
106
104 if not sa:
107 if not sa:
105 sa = meta.Session()
108 sa = meta.Session()
106
109
107 try:
110 try:
108 um = UserModel()
111 um = UserModel()
109 if hasattr(user, 'user_id'):
112 if hasattr(user, 'user_id'):
110 user_obj = user
113 user_obj = user
111 elif isinstance(user, basestring):
114 elif isinstance(user, basestring):
112 user_obj = um.get_by_username(user, cache=False)
115 user_obj = um.get_by_username(user, cache=False)
113 else:
116 else:
114 raise Exception('You have to provide user object or username')
117 raise Exception('You have to provide user object or username')
115
118
116
117 rm = RepoModel()
119 rm = RepoModel()
118 if hasattr(repo, 'repo_id'):
120 if hasattr(repo, 'repo_id'):
119 repo_obj = rm.get(repo.repo_id, cache=False)
121 repo_obj = rm.get(repo.repo_id, cache=False)
120 repo_name = repo_obj.repo_name
122 repo_name = repo_obj.repo_name
121 elif isinstance(repo, basestring):
123 elif isinstance(repo, basestring):
122 repo_name = repo.lstrip('/')
124 repo_name = repo.lstrip('/')
123 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
125 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
124 else:
126 else:
125 raise Exception('You have to provide repository to action logger')
127 raise Exception('You have to provide repository to action logger')
126
128
127
128 user_log = UserLog()
129 user_log = UserLog()
129 user_log.user_id = user_obj.user_id
130 user_log.user_id = user_obj.user_id
130 user_log.action = action
131 user_log.action = action
131
132
132 user_log.repository_id = repo_obj.repo_id
133 user_log.repository_id = repo_obj.repo_id
133 user_log.repository_name = repo_name
134 user_log.repository_name = repo_name
134
135
135 user_log.action_date = datetime.datetime.now()
136 user_log.action_date = datetime.datetime.now()
136 user_log.user_ip = ipaddr
137 user_log.user_ip = ipaddr
137 sa.add(user_log)
138 sa.add(user_log)
138 sa.commit()
139 sa.commit()
139
140
140 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
141 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
141 except:
142 except:
142 log.error(traceback.format_exc())
143 log.error(traceback.format_exc())
143 sa.rollback()
144 sa.rollback()
144
145
146
145 def get_repos(path, recursive=False):
147 def get_repos(path, recursive=False):
146 """
148 """
147 Scans given path for repos and return (name,(type,path)) tuple
149 Scans given path for repos and return (name,(type,path)) tuple
148
150
149 :param path: path to scann for repositories
151 :param path: path to scann for repositories
150 :param recursive: recursive search and return names with subdirs in front
152 :param recursive: recursive search and return names with subdirs in front
151 """
153 """
152 from vcs.utils.helpers import get_scm
154 from vcs.utils.helpers import get_scm
153 from vcs.exceptions import VCSError
155 from vcs.exceptions import VCSError
154
156
155 if path.endswith(os.sep):
157 if path.endswith(os.sep):
156 #remove ending slash for better results
158 #remove ending slash for better results
157 path = path[:-1]
159 path = path[:-1]
158
160
159 def _get_repos(p):
161 def _get_repos(p):
160 if not os.access(p, os.W_OK):
162 if not os.access(p, os.W_OK):
161 return
163 return
162 for dirpath in os.listdir(p):
164 for dirpath in os.listdir(p):
163 print dirpath
165 print dirpath
164 if os.path.isfile(os.path.join(p, dirpath)):
166 if os.path.isfile(os.path.join(p, dirpath)):
165 continue
167 continue
166 cur_path = os.path.join(p, dirpath)
168 cur_path = os.path.join(p, dirpath)
167 try:
169 try:
168 scm_info = get_scm(cur_path)
170 scm_info = get_scm(cur_path)
169 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
171 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
170 except VCSError:
172 except VCSError:
171 if not recursive:
173 if not recursive:
172 continue
174 continue
173 #check if this dir containts other repos for recursive scan
175 #check if this dir containts other repos for recursive scan
174 rec_path = os.path.join(p, dirpath)
176 rec_path = os.path.join(p, dirpath)
175 if os.path.isdir(rec_path):
177 if os.path.isdir(rec_path):
176 for inner_scm in _get_repos(rec_path):
178 for inner_scm in _get_repos(rec_path):
177 yield inner_scm
179 yield inner_scm
178
180
179 return _get_repos(path)
181 return _get_repos(path)
180
182
183
181 def check_repo_fast(repo_name, base_path):
184 def check_repo_fast(repo_name, base_path):
182 """
185 """
183 Check given path for existence of directory
186 Check given path for existence of directory
184 :param repo_name:
187 :param repo_name:
185 :param base_path:
188 :param base_path:
186
189
187 :return False: if this directory is present
190 :return False: if this directory is present
188 """
191 """
189 if os.path.isdir(os.path.join(base_path, repo_name)):return False
192 if os.path.isdir(os.path.join(base_path, repo_name)):
193 return False
190 return True
194 return True
191
195
196
192 def check_repo(repo_name, base_path, verify=True):
197 def check_repo(repo_name, base_path, verify=True):
193
198
194 repo_path = os.path.join(base_path, repo_name)
199 repo_path = os.path.join(base_path, repo_name)
195
200
196 try:
201 try:
197 if not check_repo_fast(repo_name, base_path):
202 if not check_repo_fast(repo_name, base_path):
198 return False
203 return False
199 r = hg.repository(ui.ui(), repo_path)
204 r = hg.repository(ui.ui(), repo_path)
200 if verify:
205 if verify:
201 hg.verify(r)
206 hg.verify(r)
202 #here we hnow that repo exists it was verified
207 #here we hnow that repo exists it was verified
203 log.info('%s repo is already created', repo_name)
208 log.info('%s repo is already created', repo_name)
204 return False
209 return False
205 except RepoError:
210 except RepoError:
206 #it means that there is no valid repo there...
211 #it means that there is no valid repo there...
207 log.info('%s repo is free for creation', repo_name)
212 log.info('%s repo is free for creation', repo_name)
208 return True
213 return True
209
214
215
210 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
216 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
211 while True:
217 while True:
212 ok = raw_input(prompt)
218 ok = raw_input(prompt)
213 if ok in ('y', 'ye', 'yes'): return True
219 if ok in ('y', 'ye', 'yes'):
214 if ok in ('n', 'no', 'nop', 'nope'): return False
220 return True
221 if ok in ('n', 'no', 'nop', 'nope'):
222 return False
215 retries = retries - 1
223 retries = retries - 1
216 if retries < 0: raise IOError
224 if retries < 0:
225 raise IOError
217 print complaint
226 print complaint
218
227
219 #propagated from mercurial documentation
228 #propagated from mercurial documentation
220 ui_sections = ['alias', 'auth',
229 ui_sections = ['alias', 'auth',
221 'decode/encode', 'defaults',
230 'decode/encode', 'defaults',
222 'diff', 'email',
231 'diff', 'email',
223 'extensions', 'format',
232 'extensions', 'format',
224 'merge-patterns', 'merge-tools',
233 'merge-patterns', 'merge-tools',
225 'hooks', 'http_proxy',
234 'hooks', 'http_proxy',
226 'smtp', 'patch',
235 'smtp', 'patch',
227 'paths', 'profiling',
236 'paths', 'profiling',
228 'server', 'trusted',
237 'server', 'trusted',
229 'ui', 'web', ]
238 'ui', 'web', ]
230
239
240
231 def make_ui(read_from='file', path=None, checkpaths=True):
241 def make_ui(read_from='file', path=None, checkpaths=True):
232 """A function that will read python rc files or database
242 """A function that will read python rc files or database
233 and make an mercurial ui object from read options
243 and make an mercurial ui object from read options
234
244
235 :param path: path to mercurial config file
245 :param path: path to mercurial config file
236 :param checkpaths: check the path
246 :param checkpaths: check the path
237 :param read_from: read from 'file' or 'db'
247 :param read_from: read from 'file' or 'db'
238 """
248 """
239
249
240 baseui = ui.ui()
250 baseui = ui.ui()
241
251
242 #clean the baseui object
252 #clean the baseui object
243 baseui._ocfg = config.config()
253 baseui._ocfg = config.config()
244 baseui._ucfg = config.config()
254 baseui._ucfg = config.config()
245 baseui._tcfg = config.config()
255 baseui._tcfg = config.config()
246
256
247 if read_from == 'file':
257 if read_from == 'file':
248 if not os.path.isfile(path):
258 if not os.path.isfile(path):
249 log.warning('Unable to read config file %s' % path)
259 log.warning('Unable to read config file %s' % path)
250 return False
260 return False
251 log.debug('reading hgrc from %s', path)
261 log.debug('reading hgrc from %s', path)
252 cfg = config.config()
262 cfg = config.config()
253 cfg.read(path)
263 cfg.read(path)
254 for section in ui_sections:
264 for section in ui_sections:
255 for k, v in cfg.items(section):
265 for k, v in cfg.items(section):
256 log.debug('settings ui from file[%s]%s:%s', section, k, v)
266 log.debug('settings ui from file[%s]%s:%s', section, k, v)
257 baseui.setconfig(section, k, v)
267 baseui.setconfig(section, k, v)
258
268
259
260 elif read_from == 'db':
269 elif read_from == 'db':
261 sa = meta.Session()
270 sa = meta.Session()
262 ret = sa.query(RhodeCodeUi)\
271 ret = sa.query(RhodeCodeUi)\
263 .options(FromCache("sql_cache_short",
272 .options(FromCache("sql_cache_short",
264 "get_hg_ui_settings")).all()
273 "get_hg_ui_settings")).all()
265
274
266 hg_ui = ret
275 hg_ui = ret
267 for ui_ in hg_ui:
276 for ui_ in hg_ui:
268 if ui_.ui_active:
277 if ui_.ui_active:
269 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
278 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
270 ui_.ui_key, ui_.ui_value)
279 ui_.ui_key, ui_.ui_value)
271 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
280 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
272
281
273 meta.Session.remove()
282 meta.Session.remove()
274 return baseui
283 return baseui
275
284
276
285
277 def set_rhodecode_config(config):
286 def set_rhodecode_config(config):
278 """Updates pylons config with new settings from database
287 """Updates pylons config with new settings from database
279
288
280 :param config:
289 :param config:
281 """
290 """
282 from rhodecode.model.settings import SettingsModel
291 from rhodecode.model.settings import SettingsModel
283 hgsettings = SettingsModel().get_app_settings()
292 hgsettings = SettingsModel().get_app_settings()
284
293
285 for k, v in hgsettings.items():
294 for k, v in hgsettings.items():
286 config[k] = v
295 config[k] = v
287
296
297
288 def invalidate_cache(cache_key, *args):
298 def invalidate_cache(cache_key, *args):
289 """Puts cache invalidation task into db for
299 """Puts cache invalidation task into db for
290 further global cache invalidation
300 further global cache invalidation
291 """
301 """
292
302
293 from rhodecode.model.scm import ScmModel
303 from rhodecode.model.scm import ScmModel
294
304
295 if cache_key.startswith('get_repo_cached_'):
305 if cache_key.startswith('get_repo_cached_'):
296 name = cache_key.split('get_repo_cached_')[-1]
306 name = cache_key.split('get_repo_cached_')[-1]
297 ScmModel().mark_for_invalidation(name)
307 ScmModel().mark_for_invalidation(name)
298
308
309
299 class EmptyChangeset(BaseChangeset):
310 class EmptyChangeset(BaseChangeset):
300 """
311 """
301 An dummy empty changeset. It's possible to pass hash when creating
312 An dummy empty changeset. It's possible to pass hash when creating
302 an EmptyChangeset
313 an EmptyChangeset
303 """
314 """
304
315
305 def __init__(self, cs='0' * 40):
316 def __init__(self, cs='0' * 40, repo=None):
306 self._empty_cs = cs
317 self._empty_cs = cs
307 self.revision = -1
318 self.revision = -1
308 self.message = ''
319 self.message = ''
309 self.author = ''
320 self.author = ''
310 self.date = ''
321 self.date = ''
322 self.repository = repo
311
323
312 @LazyProperty
324 @LazyProperty
313 def raw_id(self):
325 def raw_id(self):
314 """Returns raw string identifying this changeset, useful for web
326 """Returns raw string identifying this changeset, useful for web
315 representation.
327 representation.
316 """
328 """
317
329
318 return self._empty_cs
330 return self._empty_cs
319
331
320 @LazyProperty
332 @LazyProperty
321 def short_id(self):
333 def short_id(self):
322 return self.raw_id[:12]
334 return self.raw_id[:12]
323
335
324 def get_file_changeset(self, path):
336 def get_file_changeset(self, path):
325 return self
337 return self
326
338
327 def get_file_content(self, path):
339 def get_file_content(self, path):
328 return u''
340 return u''
329
341
330 def get_file_size(self, path):
342 def get_file_size(self, path):
331 return 0
343 return 0
332
344
345
333 def map_groups(groups):
346 def map_groups(groups):
334 """Checks for groups existence, and creates groups structures.
347 """Checks for groups existence, and creates groups structures.
335 It returns last group in structure
348 It returns last group in structure
336
349
337 :param groups: list of groups structure
350 :param groups: list of groups structure
338 """
351 """
339 sa = meta.Session()
352 sa = meta.Session()
340
353
341 parent = None
354 parent = None
342 group = None
355 group = None
343 for lvl, group_name in enumerate(groups[:-1]):
356 for lvl, group_name in enumerate(groups[:-1]):
344 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
357 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
345
358
346 if group is None:
359 if group is None:
347 group = Group(group_name, parent)
360 group = Group(group_name, parent)
348 sa.add(group)
361 sa.add(group)
349 sa.commit()
362 sa.commit()
350
363
351 parent = group
364 parent = group
352
365
353 return group
366 return group
354
367
368
355 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
369 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
356 """maps all repos given in initial_repo_list, non existing repositories
370 """maps all repos given in initial_repo_list, non existing repositories
357 are created, if remove_obsolete is True it also check for db entries
371 are created, if remove_obsolete is True it also check for db entries
358 that are not in initial_repo_list and removes them.
372 that are not in initial_repo_list and removes them.
359
373
360 :param initial_repo_list: list of repositories found by scanning methods
374 :param initial_repo_list: list of repositories found by scanning methods
361 :param remove_obsolete: check for obsolete entries in database
375 :param remove_obsolete: check for obsolete entries in database
362 """
376 """
363
377
364 sa = meta.Session()
378 sa = meta.Session()
365 rm = RepoModel()
379 rm = RepoModel()
366 user = sa.query(User).filter(User.admin == True).first()
380 user = sa.query(User).filter(User.admin == True).first()
367 added = []
381 added = []
368 for name, repo in initial_repo_list.items():
382 for name, repo in initial_repo_list.items():
369 group = map_groups(name.split('/'))
383 group = map_groups(name.split('/'))
370 if not rm.get_by_repo_name(name, cache=False):
384 if not rm.get_by_repo_name(name, cache=False):
371 log.info('repository %s not found creating default', name)
385 log.info('repository %s not found creating default', name)
372 added.append(name)
386 added.append(name)
373 form_data = {
387 form_data = {
374 'repo_name':name,
388 'repo_name': name,
375 'repo_type':repo.alias,
389 'repo_type': repo.alias,
376 'description':repo.description \
390 'description': repo.description \
377 if repo.description != 'unknown' else \
391 if repo.description != 'unknown' else \
378 '%s repository' % name,
392 '%s repository' % name,
379 'private':False,
393 'private': False,
380 'group_id':getattr(group, 'group_id', None)
394 'group_id': getattr(group, 'group_id', None)
381 }
395 }
382 rm.create(form_data, user, just_db=True)
396 rm.create(form_data, user, just_db=True)
383
397
384 removed = []
398 removed = []
385 if remove_obsolete:
399 if remove_obsolete:
386 #remove from database those repositories that are not in the filesystem
400 #remove from database those repositories that are not in the filesystem
387 for repo in sa.query(Repository).all():
401 for repo in sa.query(Repository).all():
388 if repo.repo_name not in initial_repo_list.keys():
402 if repo.repo_name not in initial_repo_list.keys():
389 removed.append(repo.repo_name)
403 removed.append(repo.repo_name)
390 sa.delete(repo)
404 sa.delete(repo)
391 sa.commit()
405 sa.commit()
392
406
393 return added, removed
407 return added, removed
408
409
394 class OrderedDict(dict, DictMixin):
410 class OrderedDict(dict, DictMixin):
395
411
396 def __init__(self, *args, **kwds):
412 def __init__(self, *args, **kwds):
397 if len(args) > 1:
413 if len(args) > 1:
398 raise TypeError('expected at most 1 arguments, got %d' % len(args))
414 raise TypeError('expected at most 1 arguments, got %d' % len(args))
399 try:
415 try:
400 self.__end
416 self.__end
401 except AttributeError:
417 except AttributeError:
402 self.clear()
418 self.clear()
403 self.update(*args, **kwds)
419 self.update(*args, **kwds)
404
420
405 def clear(self):
421 def clear(self):
406 self.__end = end = []
422 self.__end = end = []
407 end += [None, end, end] # sentinel node for doubly linked list
423 end += [None, end, end] # sentinel node for doubly linked list
408 self.__map = {} # key --> [key, prev, next]
424 self.__map = {} # key --> [key, prev, next]
409 dict.clear(self)
425 dict.clear(self)
410
426
411 def __setitem__(self, key, value):
427 def __setitem__(self, key, value):
412 if key not in self:
428 if key not in self:
413 end = self.__end
429 end = self.__end
414 curr = end[1]
430 curr = end[1]
415 curr[2] = end[1] = self.__map[key] = [key, curr, end]
431 curr[2] = end[1] = self.__map[key] = [key, curr, end]
416 dict.__setitem__(self, key, value)
432 dict.__setitem__(self, key, value)
417
433
418 def __delitem__(self, key):
434 def __delitem__(self, key):
419 dict.__delitem__(self, key)
435 dict.__delitem__(self, key)
420 key, prev, next = self.__map.pop(key)
436 key, prev, next = self.__map.pop(key)
421 prev[2] = next
437 prev[2] = next
422 next[1] = prev
438 next[1] = prev
423
439
424 def __iter__(self):
440 def __iter__(self):
425 end = self.__end
441 end = self.__end
426 curr = end[2]
442 curr = end[2]
427 while curr is not end:
443 while curr is not end:
428 yield curr[0]
444 yield curr[0]
429 curr = curr[2]
445 curr = curr[2]
430
446
431 def __reversed__(self):
447 def __reversed__(self):
432 end = self.__end
448 end = self.__end
433 curr = end[1]
449 curr = end[1]
434 while curr is not end:
450 while curr is not end:
435 yield curr[0]
451 yield curr[0]
436 curr = curr[1]
452 curr = curr[1]
437
453
438 def popitem(self, last=True):
454 def popitem(self, last=True):
439 if not self:
455 if not self:
440 raise KeyError('dictionary is empty')
456 raise KeyError('dictionary is empty')
441 if last:
457 if last:
442 key = reversed(self).next()
458 key = reversed(self).next()
443 else:
459 else:
444 key = iter(self).next()
460 key = iter(self).next()
445 value = self.pop(key)
461 value = self.pop(key)
446 return key, value
462 return key, value
447
463
448 def __reduce__(self):
464 def __reduce__(self):
449 items = [[k, self[k]] for k in self]
465 items = [[k, self[k]] for k in self]
450 tmp = self.__map, self.__end
466 tmp = self.__map, self.__end
451 del self.__map, self.__end
467 del self.__map, self.__end
452 inst_dict = vars(self).copy()
468 inst_dict = vars(self).copy()
453 self.__map, self.__end = tmp
469 self.__map, self.__end = tmp
454 if inst_dict:
470 if inst_dict:
455 return (self.__class__, (items,), inst_dict)
471 return (self.__class__, (items,), inst_dict)
456 return self.__class__, (items,)
472 return self.__class__, (items,)
457
473
458 def keys(self):
474 def keys(self):
459 return list(self)
475 return list(self)
460
476
461 setdefault = DictMixin.setdefault
477 setdefault = DictMixin.setdefault
462 update = DictMixin.update
478 update = DictMixin.update
463 pop = DictMixin.pop
479 pop = DictMixin.pop
464 values = DictMixin.values
480 values = DictMixin.values
465 items = DictMixin.items
481 items = DictMixin.items
466 iterkeys = DictMixin.iterkeys
482 iterkeys = DictMixin.iterkeys
467 itervalues = DictMixin.itervalues
483 itervalues = DictMixin.itervalues
468 iteritems = DictMixin.iteritems
484 iteritems = DictMixin.iteritems
469
485
470 def __repr__(self):
486 def __repr__(self):
471 if not self:
487 if not self:
472 return '%s()' % (self.__class__.__name__,)
488 return '%s()' % (self.__class__.__name__,)
473 return '%s(%r)' % (self.__class__.__name__, self.items())
489 return '%s(%r)' % (self.__class__.__name__, self.items())
474
490
475 def copy(self):
491 def copy(self):
476 return self.__class__(self)
492 return self.__class__(self)
477
493
478 @classmethod
494 @classmethod
479 def fromkeys(cls, iterable, value=None):
495 def fromkeys(cls, iterable, value=None):
480 d = cls()
496 d = cls()
481 for key in iterable:
497 for key in iterable:
482 d[key] = value
498 d[key] = value
483 return d
499 return d
484
500
485 def __eq__(self, other):
501 def __eq__(self, other):
486 if isinstance(other, OrderedDict):
502 if isinstance(other, OrderedDict):
487 return len(self) == len(other) and self.items() == other.items()
503 return len(self) == len(other) and self.items() == other.items()
488 return dict.__eq__(self, other)
504 return dict.__eq__(self, other)
489
505
490 def __ne__(self, other):
506 def __ne__(self, other):
491 return not self == other
507 return not self == other
492
508
493
509
494 #set cache regions for beaker so celery can utilise it
510 #set cache regions for beaker so celery can utilise it
495 def add_cache(settings):
511 def add_cache(settings):
496 cache_settings = {'regions':None}
512 cache_settings = {'regions': None}
497 for key in settings.keys():
513 for key in settings.keys():
498 for prefix in ['beaker.cache.', 'cache.']:
514 for prefix in ['beaker.cache.', 'cache.']:
499 if key.startswith(prefix):
515 if key.startswith(prefix):
500 name = key.split(prefix)[1].strip()
516 name = key.split(prefix)[1].strip()
501 cache_settings[name] = settings[key].strip()
517 cache_settings[name] = settings[key].strip()
502 if cache_settings['regions']:
518 if cache_settings['regions']:
503 for region in cache_settings['regions'].split(','):
519 for region in cache_settings['regions'].split(','):
504 region = region.strip()
520 region = region.strip()
505 region_settings = {}
521 region_settings = {}
506 for key, value in cache_settings.items():
522 for key, value in cache_settings.items():
507 if key.startswith(region):
523 if key.startswith(region):
508 region_settings[key.split('.')[1]] = value
524 region_settings[key.split('.')[1]] = value
509 region_settings['expire'] = int(region_settings.get('expire',
525 region_settings['expire'] = int(region_settings.get('expire',
510 60))
526 60))
511 region_settings.setdefault('lock_dir',
527 region_settings.setdefault('lock_dir',
512 cache_settings.get('lock_dir'))
528 cache_settings.get('lock_dir'))
513 region_settings.setdefault('data_dir',
529 region_settings.setdefault('data_dir',
514 cache_settings.get('data_dir'))
530 cache_settings.get('data_dir'))
515
531
516 if 'type' not in region_settings:
532 if 'type' not in region_settings:
517 region_settings['type'] = cache_settings.get('type',
533 region_settings['type'] = cache_settings.get('type',
518 'memory')
534 'memory')
519 beaker.cache.cache_regions[region] = region_settings
535 beaker.cache.cache_regions[region] = region_settings
520
536
537
521 def get_current_revision():
538 def get_current_revision():
522 """Returns tuple of (number, id) from repository containing this package
539 """Returns tuple of (number, id) from repository containing this package
523 or None if repository could not be found.
540 or None if repository could not be found.
524 """
541 """
525
542
526 try:
543 try:
527 from vcs import get_repo
544 from vcs import get_repo
528 from vcs.utils.helpers import get_scm
545 from vcs.utils.helpers import get_scm
529 from vcs.exceptions import RepositoryError, VCSError
546 from vcs.exceptions import RepositoryError, VCSError
530 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
547 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
531 scm = get_scm(repopath)[0]
548 scm = get_scm(repopath)[0]
532 repo = get_repo(path=repopath, alias=scm)
549 repo = get_repo(path=repopath, alias=scm)
533 tip = repo.get_changeset()
550 tip = repo.get_changeset()
534 return (tip.revision, tip.short_id)
551 return (tip.revision, tip.short_id)
535 except (ImportError, RepositoryError, VCSError), err:
552 except (ImportError, RepositoryError, VCSError), err:
536 logging.debug("Cannot retrieve rhodecode's revision. Original error "
553 logging.debug("Cannot retrieve rhodecode's revision. Original error "
537 "was: %s" % err)
554 "was: %s" % err)
538 return None
555 return None
539
556
540 #===============================================================================
557
558 #==============================================================================
541 # TEST FUNCTIONS AND CREATORS
559 # TEST FUNCTIONS AND CREATORS
542 #===============================================================================
560 #==============================================================================
543 def create_test_index(repo_location, full_index):
561 def create_test_index(repo_location, full_index):
544 """Makes default test index
562 """Makes default test index
545 :param repo_location:
563 :param repo_location:
546 :param full_index:
564 :param full_index:
547 """
565 """
548 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
566 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
549 from rhodecode.lib.pidlock import DaemonLock, LockHeld
567 from rhodecode.lib.pidlock import DaemonLock, LockHeld
550 import shutil
568 import shutil
551
569
552 index_location = os.path.join(repo_location, 'index')
570 index_location = os.path.join(repo_location, 'index')
553 if os.path.exists(index_location):
571 if os.path.exists(index_location):
554 shutil.rmtree(index_location)
572 shutil.rmtree(index_location)
555
573
556 try:
574 try:
557 l = DaemonLock()
575 l = DaemonLock()
558 WhooshIndexingDaemon(index_location=index_location,
576 WhooshIndexingDaemon(index_location=index_location,
559 repo_location=repo_location)\
577 repo_location=repo_location)\
560 .run(full_index=full_index)
578 .run(full_index=full_index)
561 l.release()
579 l.release()
562 except LockHeld:
580 except LockHeld:
563 pass
581 pass
564
582
583
565 def create_test_env(repos_test_path, config):
584 def create_test_env(repos_test_path, config):
566 """Makes a fresh database and
585 """Makes a fresh database and
567 install test repository into tmp dir
586 install test repository into tmp dir
568 """
587 """
569 from rhodecode.lib.db_manage import DbManage
588 from rhodecode.lib.db_manage import DbManage
570 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
589 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
571 HG_FORK, GIT_FORK, TESTS_TMP_PATH
590 HG_FORK, GIT_FORK, TESTS_TMP_PATH
572 import tarfile
591 import tarfile
573 import shutil
592 import shutil
574 from os.path import dirname as dn, join as jn, abspath
593 from os.path import dirname as dn, join as jn, abspath
575
594
576 log = logging.getLogger('TestEnvCreator')
595 log = logging.getLogger('TestEnvCreator')
577 # create logger
596 # create logger
578 log.setLevel(logging.DEBUG)
597 log.setLevel(logging.DEBUG)
579 log.propagate = True
598 log.propagate = True
580 # create console handler and set level to debug
599 # create console handler and set level to debug
581 ch = logging.StreamHandler()
600 ch = logging.StreamHandler()
582 ch.setLevel(logging.DEBUG)
601 ch.setLevel(logging.DEBUG)
583
602
584 # create formatter
603 # create formatter
585 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
604 formatter = logging.Formatter("%(asctime)s - %(name)s -"
605 " %(levelname)s - %(message)s")
586
606
587 # add formatter to ch
607 # add formatter to ch
588 ch.setFormatter(formatter)
608 ch.setFormatter(formatter)
589
609
590 # add ch to logger
610 # add ch to logger
591 log.addHandler(ch)
611 log.addHandler(ch)
592
612
593 #PART ONE create db
613 #PART ONE create db
594 dbconf = config['sqlalchemy.db1.url']
614 dbconf = config['sqlalchemy.db1.url']
595 log.debug('making test db %s', dbconf)
615 log.debug('making test db %s', dbconf)
596
616
597 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
617 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
598 tests=True)
618 tests=True)
599 dbmanage.create_tables(override=True)
619 dbmanage.create_tables(override=True)
600 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
620 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
601 dbmanage.create_default_user()
621 dbmanage.create_default_user()
602 dbmanage.admin_prompt()
622 dbmanage.admin_prompt()
603 dbmanage.create_permissions()
623 dbmanage.create_permissions()
604 dbmanage.populate_default_permissions()
624 dbmanage.populate_default_permissions()
605
625
606 #PART TWO make test repo
626 #PART TWO make test repo
607 log.debug('making test vcs repositories')
627 log.debug('making test vcs repositories')
608
628
609 #remove old one from previos tests
629 #remove old one from previos tests
610 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
630 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
611
631
612 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
632 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
613 log.debug('removing %s', r)
633 log.debug('removing %s', r)
614 shutil.rmtree(jn(TESTS_TMP_PATH, r))
634 shutil.rmtree(jn(TESTS_TMP_PATH, r))
615
635
616 #CREATE DEFAULT HG REPOSITORY
636 #CREATE DEFAULT HG REPOSITORY
617 cur_dir = dn(dn(abspath(__file__)))
637 cur_dir = dn(dn(abspath(__file__)))
618 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
638 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
619 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
639 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
620 tar.close()
640 tar.close()
621
641
642
622 #==============================================================================
643 #==============================================================================
623 # PASTER COMMANDS
644 # PASTER COMMANDS
624 #==============================================================================
645 #==============================================================================
625
626 class BasePasterCommand(Command):
646 class BasePasterCommand(Command):
627 """
647 """
628 Abstract Base Class for paster commands.
648 Abstract Base Class for paster commands.
629
649
630 The celery commands are somewhat aggressive about loading
650 The celery commands are somewhat aggressive about loading
631 celery.conf, and since our module sets the `CELERY_LOADER`
651 celery.conf, and since our module sets the `CELERY_LOADER`
632 environment variable to our loader, we have to bootstrap a bit and
652 environment variable to our loader, we have to bootstrap a bit and
633 make sure we've had a chance to load the pylons config off of the
653 make sure we've had a chance to load the pylons config off of the
634 command line, otherwise everything fails.
654 command line, otherwise everything fails.
635 """
655 """
636 min_args = 1
656 min_args = 1
637 min_args_error = "Please provide a paster config file as an argument."
657 min_args_error = "Please provide a paster config file as an argument."
638 takes_config_file = 1
658 takes_config_file = 1
639 requires_config_file = True
659 requires_config_file = True
640
660
641 def notify_msg(self, msg, log=False):
661 def notify_msg(self, msg, log=False):
642 """Make a notification to user, additionally if logger is passed
662 """Make a notification to user, additionally if logger is passed
643 it logs this action using given logger
663 it logs this action using given logger
644
664
645 :param msg: message that will be printed to user
665 :param msg: message that will be printed to user
646 :param log: logging instance, to use to additionally log this message
666 :param log: logging instance, to use to additionally log this message
647
667
648 """
668 """
649 if log and isinstance(log, logging):
669 if log and isinstance(log, logging):
650 log(msg)
670 log(msg)
651
671
652
653 def run(self, args):
672 def run(self, args):
654 """
673 """
655 Overrides Command.run
674 Overrides Command.run
656
675
657 Checks for a config file argument and loads it.
676 Checks for a config file argument and loads it.
658 """
677 """
659 if len(args) < self.min_args:
678 if len(args) < self.min_args:
660 raise BadCommand(
679 raise BadCommand(
661 self.min_args_error % {'min_args': self.min_args,
680 self.min_args_error % {'min_args': self.min_args,
662 'actual_args': len(args)})
681 'actual_args': len(args)})
663
682
664 # Decrement because we're going to lob off the first argument.
683 # Decrement because we're going to lob off the first argument.
665 # @@ This is hacky
684 # @@ This is hacky
666 self.min_args -= 1
685 self.min_args -= 1
667 self.bootstrap_config(args[0])
686 self.bootstrap_config(args[0])
668 self.update_parser()
687 self.update_parser()
669 return super(BasePasterCommand, self).run(args[1:])
688 return super(BasePasterCommand, self).run(args[1:])
670
689
671 def update_parser(self):
690 def update_parser(self):
672 """
691 """
673 Abstract method. Allows for the class's parser to be updated
692 Abstract method. Allows for the class's parser to be updated
674 before the superclass's `run` method is called. Necessary to
693 before the superclass's `run` method is called. Necessary to
675 allow options/arguments to be passed through to the underlying
694 allow options/arguments to be passed through to the underlying
676 celery command.
695 celery command.
677 """
696 """
678 raise NotImplementedError("Abstract Method.")
697 raise NotImplementedError("Abstract Method.")
679
698
680 def bootstrap_config(self, conf):
699 def bootstrap_config(self, conf):
681 """
700 """
682 Loads the pylons configuration.
701 Loads the pylons configuration.
683 """
702 """
684 from pylons import config as pylonsconfig
703 from pylons import config as pylonsconfig
685
704
686 path_to_ini_file = os.path.realpath(conf)
705 path_to_ini_file = os.path.realpath(conf)
687 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
706 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
688 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
707 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,116 +1,117 b''
1 import sys
1 import sys
2 from rhodecode import get_version
2 from rhodecode import get_version
3 from rhodecode import __platform__
3 from rhodecode import __platform__
4 from rhodecode import __license__
4 from rhodecode import __license__
5 from rhodecode import PLATFORM_OTHERS
5 from rhodecode import PLATFORM_OTHERS
6
6
7 py_version = sys.version_info
7 py_version = sys.version_info
8
8
9 if py_version < (2, 5):
9 if py_version < (2, 5):
10 raise Exception('RhodeCode requires python 2.5 or later')
10 raise Exception('RhodeCode requires python 2.5 or later')
11
11
12 requirements = [
12 requirements = [
13 "Pylons==1.0.0",
13 "Pylons==1.0.0",
14 "WebHelpers>=1.2",
14 "WebHelpers>=1.2",
15 "SQLAlchemy>=0.6.6",
15 "SQLAlchemy>=0.6.6",
16 "Mako>=0.4.0",
16 "Mako>=0.4.0",
17 "vcs>=0.2.0",
17 "vcs>=0.2.0",
18 "pygments>=1.4",
18 "pygments>=1.4",
19 "mercurial>=1.8.1",
19 "mercurial>=1.8.1",
20 "whoosh>=1.8.0",
20 "whoosh>=1.8.0",
21 "celery>=2.2.5",
21 "celery>=2.2.5",
22 "babel",
22 "babel",
23 "python-dateutil>=1.5.0,<2.0.0",
23 "python-dateutil>=1.5.0,<2.0.0",
24 "dulwich>=0.7.0"
24 ]
25 ]
25
26
26 classifiers = ['Development Status :: 4 - Beta',
27 classifiers = ['Development Status :: 4 - Beta',
27 'Environment :: Web Environment',
28 'Environment :: Web Environment',
28 'Framework :: Pylons',
29 'Framework :: Pylons',
29 'Intended Audience :: Developers',
30 'Intended Audience :: Developers',
30 'Operating System :: OS Independent',
31 'Operating System :: OS Independent',
31 'Programming Language :: Python',
32 'Programming Language :: Python',
32 'Programming Language :: Python :: 2.5',
33 'Programming Language :: Python :: 2.5',
33 'Programming Language :: Python :: 2.6',
34 'Programming Language :: Python :: 2.6',
34 'Programming Language :: Python :: 2.7', ]
35 'Programming Language :: Python :: 2.7', ]
35
36
36 if py_version < (2, 6):
37 if py_version < (2, 6):
37 requirements.append("simplejson")
38 requirements.append("simplejson")
38 requirements.append("pysqlite")
39 requirements.append("pysqlite")
39
40
40 if __platform__ in PLATFORM_OTHERS:
41 if __platform__ in PLATFORM_OTHERS:
41 requirements.append("py-bcrypt")
42 requirements.append("py-bcrypt")
42
43
43
44
44 #additional files from project that goes somewhere in the filesystem
45 #additional files from project that goes somewhere in the filesystem
45 #relative to sys.prefix
46 #relative to sys.prefix
46 data_files = []
47 data_files = []
47
48
48 #additional files that goes into package itself
49 #additional files that goes into package itself
49 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
50 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
50
51
51 description = ('Mercurial repository browser/management with '
52 description = ('Mercurial repository browser/management with '
52 'build in push/pull server and full text search')
53 'build in push/pull server and full text search')
53 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
54 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
54 'repository management', 'hgweb replacement'
55 'repository management', 'hgweb replacement'
55 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
56 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
56 #long description
57 #long description
57 try:
58 try:
58 readme_file = 'README.rst'
59 readme_file = 'README.rst'
59 changelog_file = 'docs/changelog.rst'
60 changelog_file = 'docs/changelog.rst'
60 long_description = open(readme_file).read() + '\n\n' + \
61 long_description = open(readme_file).read() + '\n\n' + \
61 open(changelog_file).read()
62 open(changelog_file).read()
62
63
63 except IOError, err:
64 except IOError, err:
64 sys.stderr.write("[WARNING] Cannot find file specified as "
65 sys.stderr.write("[WARNING] Cannot find file specified as "
65 "long_description (%s)\n or changelog (%s) skipping that file" \
66 "long_description (%s)\n or changelog (%s) skipping that file" \
66 % (readme_file, changelog_file))
67 % (readme_file, changelog_file))
67 long_description = description
68 long_description = description
68
69
69
70
70 try:
71 try:
71 from setuptools import setup, find_packages
72 from setuptools import setup, find_packages
72 except ImportError:
73 except ImportError:
73 from ez_setup import use_setuptools
74 from ez_setup import use_setuptools
74 use_setuptools()
75 use_setuptools()
75 from setuptools import setup, find_packages
76 from setuptools import setup, find_packages
76 #packages
77 #packages
77 packages = find_packages(exclude=['ez_setup'])
78 packages = find_packages(exclude=['ez_setup'])
78
79
79 setup(
80 setup(
80 name='RhodeCode',
81 name='RhodeCode',
81 version=get_version(),
82 version=get_version(),
82 description=description,
83 description=description,
83 long_description=long_description,
84 long_description=long_description,
84 keywords=keywords,
85 keywords=keywords,
85 license=__license__,
86 license=__license__,
86 author='Marcin Kuzminski',
87 author='Marcin Kuzminski',
87 author_email='marcin@python-works.com',
88 author_email='marcin@python-works.com',
88 url='http://rhodecode.org',
89 url='http://rhodecode.org',
89 install_requires=requirements,
90 install_requires=requirements,
90 classifiers=classifiers,
91 classifiers=classifiers,
91 setup_requires=["PasteScript>=1.6.3"],
92 setup_requires=["PasteScript>=1.6.3"],
92 data_files=data_files,
93 data_files=data_files,
93 packages=packages,
94 packages=packages,
94 include_package_data=True,
95 include_package_data=True,
95 test_suite='nose.collector',
96 test_suite='nose.collector',
96 package_data=package_data,
97 package_data=package_data,
97 message_extractors={'rhodecode': [
98 message_extractors={'rhodecode': [
98 ('**.py', 'python', None),
99 ('**.py', 'python', None),
99 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
100 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
100 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
101 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
101 ('public/**', 'ignore', None)]},
102 ('public/**', 'ignore', None)]},
102 zip_safe=False,
103 zip_safe=False,
103 paster_plugins=['PasteScript', 'Pylons'],
104 paster_plugins=['PasteScript', 'Pylons'],
104 entry_points="""
105 entry_points="""
105 [paste.app_factory]
106 [paste.app_factory]
106 main = rhodecode.config.middleware:make_app
107 main = rhodecode.config.middleware:make_app
107
108
108 [paste.app_install]
109 [paste.app_install]
109 main = pylons.util:PylonsInstaller
110 main = pylons.util:PylonsInstaller
110
111
111 [paste.global_paster_command]
112 [paste.global_paster_command]
112 make-index = rhodecode.lib.indexers:MakeIndex
113 make-index = rhodecode.lib.indexers:MakeIndex
113 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
114 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
114 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
115 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
115 """,
116 """,
116 )
117 )
General Comments 0
You need to be logged in to leave comments. Login now