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