##// END OF EJS Templates
merge with beta
marcink -
r2270:c9dc3cd9 merge default
parent child Browse files
Show More
@@ -1,174 +1,174
1 1 =========
2 2 RhodeCode
3 3 =========
4 4
5 5 About
6 6 -----
7 7
8 8 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
9 9 with a built in push/pull server and full text search and code-review.
10 10 It works on http/https and has a built in permission/authentication system with
11 11 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
12 12 simple API so it's easy integrable with existing external systems.
13 13
14 14 RhodeCode is similar in some respects to github_ or bitbucket_,
15 15 however RhodeCode can be run as standalone hosted application on your own server.
16 16 It is open source and donation ware and focuses more on providing a customized,
17 17 self administered interface for Mercurial_ and GIT_ repositories.
18 RhodeCode works on *nix systems and Windows it is powered by a vcs_ library
18 RhodeCode works on \*nix systems and Windows it is powered by a vcs_ library
19 19 that Lukasz Balcerzak and Marcin Kuzminski created to handle multiple
20 20 different version control systems.
21 21
22 22 RhodeCode uses `PEP386 versioning <http://www.python.org/dev/peps/pep-0386/>`_
23 23
24 24 Installation
25 25 ------------
26 26 Stable releases of RhodeCode are best installed via::
27 27
28 28 easy_install rhodecode
29 29
30 30 Or::
31 31
32 32 pip install rhodecode
33 33
34 34 Detailed instructions and links may be found on the Installation page.
35 35
36 36 Please visit http://packages.python.org/RhodeCode/installation.html for
37 37 more details
38 38
39 39 RhodeCode demo
40 40 --------------
41 41
42 42 http://demo.rhodecode.org
43 43
44 44 The default access is anonymous but you can login to an administrative account
45 45 using the following credentials:
46 46
47 47 - username: demo
48 48 - password: demo12
49 49
50 50 Source code
51 51 -----------
52 52
53 53 The latest sources can be obtained from official RhodeCode instance
54 54 https://secure.rhodecode.org
55 55
56 56
57 57 MIRRORS:
58 58
59 59 Issue tracker and sources at bitbucket_
60 60
61 61 http://bitbucket.org/marcinkuzminski/rhodecode
62 62
63 63 Sources at github_
64 64
65 65 https://github.com/marcinkuzminski/rhodecode
66 66
67 67
68 68 RhodeCode Features
69 69 ------------------
70 70
71 71 - Has its own middleware to handle mercurial_ protocol requests.
72 72 Each request can be logged and authenticated.
73 73 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
74 74 Supports http/https and LDAP
75 75 - Full permissions (private/read/write/admin) and authentication per project.
76 76 One account for web interface and mercurial_ push/pull/clone operations.
77 77 - Have built in users groups for easier permission management
78 78 - Repository groups let you group repos and manage them easier.
79 79 - Users can fork other users repo. RhodeCode have also compare view to see
80 80 combined changeset for all changeset made within single push.
81 81 - Build in commit-api let's you add, edit and commit files right from RhodeCode
82 82 interface using simple editor or upload form for binaries.
83 83 - Mako templates let's you customize the look and feel of the application.
84 84 - Beautiful diffs, annotations and source code browsing all colored by pygments.
85 85 Raw diffs are made in git-diff format, including git_ binary-patches
86 86 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
87 87 - Admin interface with user/permission management. Admin activity journal, logs
88 88 pulls, pushes, forks, registrations and other actions made by all users.
89 89 - Server side forks. It is possible to fork a project and modify it freely
90 90 without breaking the main repository. You can even write Your own hooks
91 91 and install them
92 92 - code review with notification system, inline commenting, all parsed using
93 93 rst syntax
94 94 - rst and markdown README support for repositories
95 95 - Full text search powered by Whoosh on the source files, and file names.
96 96 Build in indexing daemons, with optional incremental index build
97 97 (no external search servers required all in one application)
98 98 - Setup project descriptions and info inside built in db for easy, non
99 99 file-system operations
100 100 - Intelligent cache with invalidation after push or project change, provides
101 101 high performance and always up to date data.
102 102 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
103 103 - Optional async tasks for speed and performance using celery_
104 104 - Backup scripts can do backup of whole app and send it over scp to desired
105 105 location
106 106 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
107 107
108 108
109 109 Incoming / Plans
110 110 ----------------
111 111
112 112 - Finer granular permissions per branch, repo group or subrepo
113 113 - pull requests and web based merges
114 114 - per line file history
115 115 - SSH based authentication with server side key management
116 116 - Commit based built in wiki system
117 117 - More statistics and graph (global annotation + some more statistics)
118 118 - Other advancements as development continues (or you can of course make
119 119 additions and or requests)
120 120
121 121 License
122 122 -------
123 123
124 124 ``RhodeCode`` is released under the GPLv3 license.
125 125
126 126
127 127 Getting help
128 128 ------------
129 129
130 130 Listed bellow are various support resources that should help.
131 131
132 132 .. note::
133 133
134 134 Please try to read the documentation before posting any issues
135 135
136 136 - Join the `Google group <http://groups.google.com/group/rhodecode>`_ and ask
137 137 any questions.
138 138
139 139 - Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
140 140
141 141
142 142 - Join #rhodecode on FreeNode (irc.freenode.net)
143 143 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
144 144
145 145 - You can also follow me on twitter **@marcinkuzminski** where i often post some
146 146 news about RhodeCode
147 147
148 148
149 149 Online documentation
150 150 --------------------
151 151
152 152 Online documentation for the current version of RhodeCode is available at
153 153 - http://packages.python.org/RhodeCode/
154 154 - http://rhodecode.readthedocs.org/en/latest/index.html
155 155
156 156 You may also build the documentation for yourself - go into ``docs/`` and run::
157 157
158 158 make html
159 159
160 160 (You need to have sphinx_ installed to build the documentation. If you don't
161 161 have sphinx_ installed you can install it via the command:
162 162 ``easy_install sphinx``)
163 163
164 164 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
165 165 .. _python: http://www.python.org/
166 166 .. _sphinx: http://sphinx.pocoo.org/
167 167 .. _mercurial: http://mercurial.selenic.com/
168 168 .. _bitbucket: http://bitbucket.org/
169 169 .. _github: http://github.com/
170 170 .. _subversion: http://subversion.tigris.org/
171 171 .. _git: http://git-scm.com/
172 172 .. _celery: http://celeryproject.org/
173 173 .. _Sphinx: http://sphinx.pocoo.org/
174 174 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
@@ -1,487 +1,485
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) 2010-2012 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 traceback
29 import tempfile
29 30
30 31 from pylons import request, response, tmpl_context as c, url
31 32 from pylons.i18n.translation import _
32 33 from pylons.controllers.util import redirect
33 34 from pylons.decorators import jsonify
34 35
35 36 from rhodecode.lib import diffs
36 37 from rhodecode.lib import helpers as h
37 38
38 39 from rhodecode.lib.compat import OrderedDict
39 40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
40 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 42 from rhodecode.lib.base import BaseRepoController, render
42 43 from rhodecode.lib.utils import EmptyChangeset
43 44 from rhodecode.lib.vcs.conf import settings
44 45 from rhodecode.lib.vcs.exceptions import RepositoryError, \
45 46 ChangesetDoesNotExistError, EmptyRepositoryError, \
46 47 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
47 48 from rhodecode.lib.vcs.nodes import FileNode
48 49
49 50 from rhodecode.model.repo import RepoModel
50 51 from rhodecode.model.scm import ScmModel
51 52 from rhodecode.model.db import Repository
52 53
53 54 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
54 55 _context_url, get_line_ctx, get_ignore_ws
55 56
56 57
57 58 log = logging.getLogger(__name__)
58 59
59 60
60 61 class FilesController(BaseRepoController):
61 62
62 63 @LoginRequired()
63 64 def __before__(self):
64 65 super(FilesController, self).__before__()
65 66 c.cut_off_limit = self.cut_off_limit
66 67
67 68 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
68 69 """
69 70 Safe way to get changeset if error occur it redirects to tip with
70 71 proper message
71 72
72 73 :param rev: revision to fetch
73 74 :param repo_name: repo name to redirect after
74 75 """
75 76
76 77 try:
77 78 return c.rhodecode_repo.get_changeset(rev)
78 79 except EmptyRepositoryError, e:
79 80 if not redirect_after:
80 81 return None
81 82 url_ = url('files_add_home',
82 83 repo_name=c.repo_name,
83 84 revision=0, f_path='')
84 85 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
85 86 h.flash(h.literal(_('There are no files yet %s' % add_new)),
86 87 category='warning')
87 88 redirect(h.url('summary_home', repo_name=repo_name))
88 89
89 90 except RepositoryError, e:
90 91 h.flash(str(e), category='warning')
91 92 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
92 93
93 94 def __get_filenode_or_redirect(self, repo_name, cs, path):
94 95 """
95 96 Returns file_node, if error occurs or given path is directory,
96 97 it'll redirect to top level path
97 98
98 99 :param repo_name: repo_name
99 100 :param cs: given changeset
100 101 :param path: path to lookup
101 102 """
102 103
103 104 try:
104 105 file_node = cs.get_node(path)
105 106 if file_node.is_dir():
106 107 raise RepositoryError('given path is a directory')
107 108 except RepositoryError, e:
108 109 h.flash(str(e), category='warning')
109 110 redirect(h.url('files_home', repo_name=repo_name,
110 111 revision=cs.raw_id))
111 112
112 113 return file_node
113 114
114 115 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
115 116 'repository.admin')
116 117 def index(self, repo_name, revision, f_path, annotate=False):
117 118 # redirect to given revision from form if given
118 119 post_revision = request.POST.get('at_rev', None)
119 120 if post_revision:
120 121 cs = self.__get_cs_or_redirect(post_revision, repo_name)
121 122 redirect(url('files_home', repo_name=c.repo_name,
122 123 revision=cs.raw_id, f_path=f_path))
123 124
124 125 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
125 126 c.branch = request.GET.get('branch', None)
126 127 c.f_path = f_path
127 128 c.annotate = annotate
128 129 cur_rev = c.changeset.revision
129 130
130 131 # prev link
131 132 try:
132 133 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
133 134 c.url_prev = url('files_home', repo_name=c.repo_name,
134 135 revision=prev_rev.raw_id, f_path=f_path)
135 136 if c.branch:
136 137 c.url_prev += '?branch=%s' % c.branch
137 138 except (ChangesetDoesNotExistError, VCSError):
138 139 c.url_prev = '#'
139 140
140 141 # next link
141 142 try:
142 143 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
143 144 c.url_next = url('files_home', repo_name=c.repo_name,
144 145 revision=next_rev.raw_id, f_path=f_path)
145 146 if c.branch:
146 147 c.url_next += '?branch=%s' % c.branch
147 148 except (ChangesetDoesNotExistError, VCSError):
148 149 c.url_next = '#'
149 150
150 151 # files or dirs
151 152 try:
152 153 c.file = c.changeset.get_node(f_path)
153 154
154 155 if c.file.is_file():
155 156 c.file_history = self._get_node_history(c.changeset, f_path)
156 157 else:
157 158 c.file_history = []
158 159 except RepositoryError, e:
159 160 h.flash(str(e), category='warning')
160 161 redirect(h.url('files_home', repo_name=repo_name,
161 162 revision=revision))
162 163
163 164 return render('files/files.html')
164 165
165 166 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 167 'repository.admin')
167 168 def rawfile(self, repo_name, revision, f_path):
168 169 cs = self.__get_cs_or_redirect(revision, repo_name)
169 170 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
170 171
171 172 response.content_disposition = 'attachment; filename=%s' % \
172 173 safe_str(f_path.split(Repository.url_sep())[-1])
173 174
174 175 response.content_type = file_node.mimetype
175 176 return file_node.content
176 177
177 178 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
178 179 'repository.admin')
179 180 def raw(self, repo_name, revision, f_path):
180 181 cs = self.__get_cs_or_redirect(revision, repo_name)
181 182 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
182 183
183 184 raw_mimetype_mapping = {
184 185 # map original mimetype to a mimetype used for "show as raw"
185 186 # you can also provide a content-disposition to override the
186 187 # default "attachment" disposition.
187 188 # orig_type: (new_type, new_dispo)
188 189
189 190 # show images inline:
190 191 'image/x-icon': ('image/x-icon', 'inline'),
191 192 'image/png': ('image/png', 'inline'),
192 193 'image/gif': ('image/gif', 'inline'),
193 194 'image/jpeg': ('image/jpeg', 'inline'),
194 195 'image/svg+xml': ('image/svg+xml', 'inline'),
195 196 }
196 197
197 198 mimetype = file_node.mimetype
198 199 try:
199 200 mimetype, dispo = raw_mimetype_mapping[mimetype]
200 201 except KeyError:
201 202 # we don't know anything special about this, handle it safely
202 203 if file_node.is_binary:
203 204 # do same as download raw for binary files
204 205 mimetype, dispo = 'application/octet-stream', 'attachment'
205 206 else:
206 207 # do not just use the original mimetype, but force text/plain,
207 208 # otherwise it would serve text/html and that might be unsafe.
208 209 # Note: underlying vcs library fakes text/plain mimetype if the
209 210 # mimetype can not be determined and it thinks it is not
210 211 # binary.This might lead to erroneous text display in some
211 212 # cases, but helps in other cases, like with text files
212 213 # without extension.
213 214 mimetype, dispo = 'text/plain', 'inline'
214 215
215 216 if dispo == 'attachment':
216 217 dispo = 'attachment; filename=%s' % \
217 218 safe_str(f_path.split(os.sep)[-1])
218 219
219 220 response.content_disposition = dispo
220 221 response.content_type = mimetype
221 222 return file_node.content
222 223
223 224 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
224 225 def edit(self, repo_name, revision, f_path):
225 226 r_post = request.POST
226 227
227 228 c.cs = self.__get_cs_or_redirect(revision, repo_name)
228 229 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
229 230
230 231 if c.file.is_binary:
231 232 return redirect(url('files_home', repo_name=c.repo_name,
232 233 revision=c.cs.raw_id, f_path=f_path))
233 234
234 235 c.f_path = f_path
235 236
236 237 if r_post:
237 238
238 239 old_content = c.file.content
239 240 sl = old_content.splitlines(1)
240 241 first_line = sl[0] if sl else ''
241 242 # modes: 0 - Unix, 1 - Mac, 2 - DOS
242 243 mode = detect_mode(first_line, 0)
243 244 content = convert_line_endings(r_post.get('content'), mode)
244 245
245 246 message = r_post.get('message') or (_('Edited %s via RhodeCode')
246 247 % (f_path))
247 248 author = self.rhodecode_user.full_contact
248 249
249 250 if content == old_content:
250 251 h.flash(_('No changes'),
251 252 category='warning')
252 253 return redirect(url('changeset_home', repo_name=c.repo_name,
253 254 revision='tip'))
254 255
255 256 try:
256 257 self.scm_model.commit_change(repo=c.rhodecode_repo,
257 258 repo_name=repo_name, cs=c.cs,
258 259 user=self.rhodecode_user,
259 260 author=author, message=message,
260 261 content=content, f_path=f_path)
261 262 h.flash(_('Successfully committed to %s' % f_path),
262 263 category='success')
263 264
264 265 except Exception:
265 266 log.error(traceback.format_exc())
266 267 h.flash(_('Error occurred during commit'), category='error')
267 268 return redirect(url('changeset_home',
268 269 repo_name=c.repo_name, revision='tip'))
269 270
270 271 return render('files/files_edit.html')
271 272
272 273 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
273 274 def add(self, repo_name, revision, f_path):
274 275 r_post = request.POST
275 276 c.cs = self.__get_cs_or_redirect(revision, repo_name,
276 277 redirect_after=False)
277 278 if c.cs is None:
278 279 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
279 280
280 281 c.f_path = f_path
281 282
282 283 if r_post:
283 284 unix_mode = 0
284 285 content = convert_line_endings(r_post.get('content'), unix_mode)
285 286
286 287 message = r_post.get('message') or (_('Added %s via RhodeCode')
287 288 % (f_path))
288 289 location = r_post.get('location')
289 290 filename = r_post.get('filename')
290 291 file_obj = r_post.get('upload_file', None)
291 292
292 293 if file_obj is not None and hasattr(file_obj, 'filename'):
293 294 filename = file_obj.filename
294 295 content = file_obj.file
295 296
296 297 node_path = os.path.join(location, filename)
297 298 author = self.rhodecode_user.full_contact
298 299
299 300 if not content:
300 301 h.flash(_('No content'), category='warning')
301 302 return redirect(url('changeset_home', repo_name=c.repo_name,
302 303 revision='tip'))
303 304 if not filename:
304 305 h.flash(_('No filename'), category='warning')
305 306 return redirect(url('changeset_home', repo_name=c.repo_name,
306 307 revision='tip'))
307 308
308 309 try:
309 310 self.scm_model.create_node(repo=c.rhodecode_repo,
310 311 repo_name=repo_name, cs=c.cs,
311 312 user=self.rhodecode_user,
312 313 author=author, message=message,
313 314 content=content, f_path=node_path)
314 315 h.flash(_('Successfully committed to %s' % node_path),
315 316 category='success')
316 317 except NodeAlreadyExistsError, e:
317 318 h.flash(_(e), category='error')
318 319 except Exception:
319 320 log.error(traceback.format_exc())
320 321 h.flash(_('Error occurred during commit'), category='error')
321 322 return redirect(url('changeset_home',
322 323 repo_name=c.repo_name, revision='tip'))
323 324
324 325 return render('files/files_add.html')
325 326
326 327 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 328 'repository.admin')
328 329 def archivefile(self, repo_name, fname):
329 330
330 331 fileformat = None
331 332 revision = None
332 333 ext = None
333 334 subrepos = request.GET.get('subrepos') == 'true'
334 335
335 336 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
336 337 archive_spec = fname.split(ext_data[1])
337 338 if len(archive_spec) == 2 and archive_spec[1] == '':
338 339 fileformat = a_type or ext_data[1]
339 340 revision = archive_spec[0]
340 341 ext = ext_data[1]
341 342
342 343 try:
343 344 dbrepo = RepoModel().get_by_repo_name(repo_name)
344 345 if dbrepo.enable_downloads is False:
345 346 return _('downloads disabled')
346 347
347 348 if c.rhodecode_repo.alias == 'hg':
348 349 # patch and reset hooks section of UI config to not run any
349 350 # hooks on fetching archives with subrepos
350 351 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
351 352 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
352 353
353 354 cs = c.rhodecode_repo.get_changeset(revision)
354 355 content_type = settings.ARCHIVE_SPECS[fileformat][0]
355 356 except ChangesetDoesNotExistError:
356 357 return _('Unknown revision %s') % revision
357 358 except EmptyRepositoryError:
358 359 return _('Empty repository')
359 360 except (ImproperArchiveTypeError, KeyError):
360 361 return _('Unknown archive type')
361 362
363 archive = tempfile.NamedTemporaryFile(mode='w+r+b')
364 cs.fill_archive(stream=archive, kind=fileformat, subrepos=subrepos)
365
362 366 response.content_type = content_type
363 367 response.content_disposition = 'attachment; filename=%s-%s%s' \
364 % (repo_name, revision, ext)
365
366 import tempfile
367 archive = tempfile.mkstemp()[1]
368 t = open(archive, 'wb')
369 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
368 % (repo_name, revision[:12], ext)
369 response.content_length = str(os.path.getsize(archive.name))
370 370
371 def get_chunked_archive(archive):
372 stream = open(archive, 'rb')
371 def get_chunked_archive(tmpfile):
373 372 while True:
374 data = stream.read(4096)
373 data = tmpfile.read(16 * 1024)
375 374 if not data:
376 os.remove(archive)
375 tmpfile.close()
377 376 break
378 377 yield data
379
380 return get_chunked_archive(archive)
378 return get_chunked_archive(tmpfile=archive)
381 379
382 380 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
383 381 'repository.admin')
384 382 def diff(self, repo_name, f_path):
385 383 ignore_whitespace = request.GET.get('ignorews') == '1'
386 384 line_context = request.GET.get('context', 3)
387 385 diff1 = request.GET.get('diff1', '')
388 386 diff2 = request.GET.get('diff2', '')
389 387 c.action = request.GET.get('diff')
390 388 c.no_changes = diff1 == diff2
391 389 c.f_path = f_path
392 390 c.big_diff = False
393 391 c.anchor_url = anchor_url
394 392 c.ignorews_url = _ignorews_url
395 393 c.context_url = _context_url
396 394 c.changes = OrderedDict()
397 395 c.changes[diff2] = []
398 396 try:
399 397 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
400 398 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
401 399 node1 = c.changeset_1.get_node(f_path)
402 400 else:
403 401 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
404 402 node1 = FileNode('.', '', changeset=c.changeset_1)
405 403
406 404 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
407 405 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
408 406 node2 = c.changeset_2.get_node(f_path)
409 407 else:
410 408 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
411 409 node2 = FileNode('.', '', changeset=c.changeset_2)
412 410 except RepositoryError:
413 411 return redirect(url('files_home', repo_name=c.repo_name,
414 412 f_path=f_path))
415 413
416 414 if c.action == 'download':
417 415 _diff = diffs.get_gitdiff(node1, node2,
418 416 ignore_whitespace=ignore_whitespace,
419 417 context=line_context)
420 418 diff = diffs.DiffProcessor(_diff, format='gitdiff')
421 419
422 420 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
423 421 response.content_type = 'text/plain'
424 422 response.content_disposition = (
425 423 'attachment; filename=%s' % diff_name
426 424 )
427 425 return diff.raw_diff()
428 426
429 427 elif c.action == 'raw':
430 428 _diff = diffs.get_gitdiff(node1, node2,
431 429 ignore_whitespace=ignore_whitespace,
432 430 context=line_context)
433 431 diff = diffs.DiffProcessor(_diff, format='gitdiff')
434 432 response.content_type = 'text/plain'
435 433 return diff.raw_diff()
436 434
437 435 else:
438 436 fid = h.FID(diff2, node2.path)
439 437 line_context_lcl = get_line_ctx(fid, request.GET)
440 438 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
441 439
442 440 lim = request.GET.get('fulldiff') or self.cut_off_limit
443 441 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
444 442 filenode_new=node2,
445 443 cut_off_limit=lim,
446 444 ignore_whitespace=ign_whitespace_lcl,
447 445 line_context=line_context_lcl,
448 446 enable_comments=False)
449 447
450 448 c.changes = [('', node2, diff, cs1, cs2, st,)]
451 449
452 450 return render('files/file_diff.html')
453 451
454 452 def _get_node_history(self, cs, f_path):
455 453 changesets = cs.get_file_history(f_path)
456 454 hist_l = []
457 455
458 456 changesets_group = ([], _("Changesets"))
459 457 branches_group = ([], _("Branches"))
460 458 tags_group = ([], _("Tags"))
461 459 _hg = cs.repository.alias == 'hg'
462 460 for chs in changesets:
463 461 _branch = '(%s)' % chs.branch if _hg else ''
464 462 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
465 463 changesets_group[0].append((chs.raw_id, n_desc,))
466 464
467 465 hist_l.append(changesets_group)
468 466
469 467 for name, chs in c.rhodecode_repo.branches.items():
470 468 branches_group[0].append((chs, name),)
471 469 hist_l.append(branches_group)
472 470
473 471 for name, chs in c.rhodecode_repo.tags.items():
474 472 tags_group[0].append((chs, name),)
475 473 hist_l.append(tags_group)
476 474
477 475 return hist_l
478 476
479 477 @jsonify
480 478 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
481 479 'repository.admin')
482 480 def nodelist(self, repo_name, revision, f_path):
483 481 if request.environ.get('HTTP_X_PARTIAL_XHR'):
484 482 cs = self.__get_cs_or_redirect(revision, repo_name)
485 483 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
486 484 flat=False)
487 485 return _d + _f
@@ -1,359 +1,357
1 1 import os
2 2 import posixpath
3 3
4 4 from rhodecode.lib.vcs.backends.base import BaseChangeset
5 5 from rhodecode.lib.vcs.conf import settings
6 6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
7 7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
8 8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
9 9 ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
10 10 RemovedFileNodesGenerator, RootNode, SubModuleNode
11 11
12 12 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
13 13 from rhodecode.lib.vcs.utils.lazy import LazyProperty
14 14 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
15 15
16 16 from ...utils.hgcompat import archival, hex
17 17
18 18
19 19 class MercurialChangeset(BaseChangeset):
20 20 """
21 21 Represents state of the repository at the single revision.
22 22 """
23 23
24 24 def __init__(self, repository, revision):
25 25 self.repository = repository
26 26 self.raw_id = revision
27 27 self._ctx = repository._repo[revision]
28 28 self.revision = self._ctx._rev
29 29 self.nodes = {}
30 30
31 31 @LazyProperty
32 32 def tags(self):
33 33 return map(safe_unicode, self._ctx.tags())
34 34
35 35 @LazyProperty
36 36 def branch(self):
37 37 return safe_unicode(self._ctx.branch())
38 38
39 39 @LazyProperty
40 40 def bookmarks(self):
41 41 return map(safe_unicode, self._ctx.bookmarks())
42 42
43 43 @LazyProperty
44 44 def message(self):
45 45 return safe_unicode(self._ctx.description())
46 46
47 47 @LazyProperty
48 48 def author(self):
49 49 return safe_unicode(self._ctx.user())
50 50
51 51 @LazyProperty
52 52 def date(self):
53 53 return date_fromtimestamp(*self._ctx.date())
54 54
55 55 @LazyProperty
56 56 def status(self):
57 57 """
58 58 Returns modified, added, removed, deleted files for current changeset
59 59 """
60 60 return self.repository._repo.status(self._ctx.p1().node(),
61 61 self._ctx.node())
62 62
63 63 @LazyProperty
64 64 def _file_paths(self):
65 65 return list(self._ctx)
66 66
67 67 @LazyProperty
68 68 def _dir_paths(self):
69 69 p = list(set(get_dirs_for_path(*self._file_paths)))
70 70 p.insert(0, '')
71 71 return p
72 72
73 73 @LazyProperty
74 74 def _paths(self):
75 75 return self._dir_paths + self._file_paths
76 76
77 77 @LazyProperty
78 78 def id(self):
79 79 if self.last:
80 80 return u'tip'
81 81 return self.short_id
82 82
83 83 @LazyProperty
84 84 def short_id(self):
85 85 return self.raw_id[:12]
86 86
87 87 @LazyProperty
88 88 def parents(self):
89 89 """
90 90 Returns list of parents changesets.
91 91 """
92 92 return [self.repository.get_changeset(parent.rev())
93 93 for parent in self._ctx.parents() if parent.rev() >= 0]
94 94
95 95 def next(self, branch=None):
96 96
97 97 if branch and self.branch != branch:
98 98 raise VCSError('Branch option used on changeset not belonging '
99 99 'to that branch')
100 100
101 101 def _next(changeset, branch):
102 102 try:
103 103 next_ = changeset.revision + 1
104 104 next_rev = changeset.repository.revisions[next_]
105 105 except IndexError:
106 106 raise ChangesetDoesNotExistError
107 107 cs = changeset.repository.get_changeset(next_rev)
108 108
109 109 if branch and branch != cs.branch:
110 110 return _next(cs, branch)
111 111
112 112 return cs
113 113
114 114 return _next(self, branch)
115 115
116 116 def prev(self, branch=None):
117 117 if branch and self.branch != branch:
118 118 raise VCSError('Branch option used on changeset not belonging '
119 119 'to that branch')
120 120
121 121 def _prev(changeset, branch):
122 122 try:
123 123 prev_ = changeset.revision - 1
124 124 if prev_ < 0:
125 125 raise IndexError
126 126 prev_rev = changeset.repository.revisions[prev_]
127 127 except IndexError:
128 128 raise ChangesetDoesNotExistError
129 129
130 130 cs = changeset.repository.get_changeset(prev_rev)
131 131
132 132 if branch and branch != cs.branch:
133 133 return _prev(cs, branch)
134 134
135 135 return cs
136 136
137 137 return _prev(self, branch)
138 138
139 139 def _fix_path(self, path):
140 140 """
141 141 Paths are stored without trailing slash so we need to get rid off it if
142 142 needed. Also mercurial keeps filenodes as str so we need to decode
143 143 from unicode to str
144 144 """
145 145 if path.endswith('/'):
146 146 path = path.rstrip('/')
147 147
148 148 return safe_str(path)
149 149
150 150 def _get_kind(self, path):
151 151 path = self._fix_path(path)
152 152 if path in self._file_paths:
153 153 return NodeKind.FILE
154 154 elif path in self._dir_paths:
155 155 return NodeKind.DIR
156 156 else:
157 157 raise ChangesetError("Node does not exist at the given path %r"
158 158 % (path))
159 159
160 160 def _get_filectx(self, path):
161 161 path = self._fix_path(path)
162 162 if self._get_kind(path) != NodeKind.FILE:
163 163 raise ChangesetError("File does not exist for revision %r at "
164 164 " %r" % (self.revision, path))
165 165 return self._ctx.filectx(path)
166 166
167 167 def _extract_submodules(self):
168 168 """
169 169 returns a dictionary with submodule information from substate file
170 170 of hg repository
171 171 """
172 172 return self._ctx.substate
173 173
174 174 def get_file_mode(self, path):
175 175 """
176 176 Returns stat mode of the file at the given ``path``.
177 177 """
178 178 fctx = self._get_filectx(path)
179 179 if 'x' in fctx.flags():
180 180 return 0100755
181 181 else:
182 182 return 0100644
183 183
184 184 def get_file_content(self, path):
185 185 """
186 186 Returns content of the file at given ``path``.
187 187 """
188 188 fctx = self._get_filectx(path)
189 189 return fctx.data()
190 190
191 191 def get_file_size(self, path):
192 192 """
193 193 Returns size of the file at given ``path``.
194 194 """
195 195 fctx = self._get_filectx(path)
196 196 return fctx.size()
197 197
198 198 def get_file_changeset(self, path):
199 199 """
200 200 Returns last commit of the file at the given ``path``.
201 201 """
202 202 node = self.get_node(path)
203 203 return node.history[0]
204 204
205 205 def get_file_history(self, path):
206 206 """
207 207 Returns history of file as reversed list of ``Changeset`` objects for
208 208 which file at given ``path`` has been modified.
209 209 """
210 210 fctx = self._get_filectx(path)
211 211 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
212 212 changesets = [self.repository.get_changeset(hex(node))
213 213 for node in reversed(nodes)]
214 214 return changesets
215 215
216 216 def get_file_annotate(self, path):
217 217 """
218 218 Returns a list of three element tuples with lineno,changeset and line
219 219 """
220 220 fctx = self._get_filectx(path)
221 221 annotate = []
222 222 for i, annotate_data in enumerate(fctx.annotate()):
223 223 ln_no = i + 1
224 224 annotate.append((ln_no, self.repository\
225 225 .get_changeset(hex(annotate_data[0].node())),
226 226 annotate_data[1],))
227 227
228 228 return annotate
229 229
230 230 def fill_archive(self, stream=None, kind='tgz', prefix=None,
231 231 subrepos=False):
232 232 """
233 233 Fills up given stream.
234 234
235 235 :param stream: file like object.
236 236 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
237 237 Default: ``tgz``.
238 238 :param prefix: name of root directory in archive.
239 239 Default is repository name and changeset's raw_id joined with dash
240 240 (``repo-tip.<KIND>``).
241 241 :param subrepos: include subrepos in this archive.
242 242
243 243 :raise ImproperArchiveTypeError: If given kind is wrong.
244 244 :raise VcsError: If given stream is None
245 245 """
246 246
247 247 allowed_kinds = settings.ARCHIVE_SPECS.keys()
248 248 if kind not in allowed_kinds:
249 249 raise ImproperArchiveTypeError('Archive kind not supported use one'
250 250 'of %s', allowed_kinds)
251 251
252 252 if stream is None:
253 253 raise VCSError('You need to pass in a valid stream for filling'
254 254 ' with archival data')
255 255
256 256 if prefix is None:
257 257 prefix = '%s-%s' % (self.repository.name, self.short_id)
258 258 elif prefix.startswith('/'):
259 259 raise VCSError("Prefix cannot start with leading slash")
260 260 elif prefix.strip() == '':
261 261 raise VCSError("Prefix cannot be empty")
262 262
263 263 archival.archive(self.repository._repo, stream, self.raw_id,
264 264 kind, prefix=prefix, subrepos=subrepos)
265 265
266 #stream.close()
267
268 266 if stream.closed and hasattr(stream, 'name'):
269 267 stream = open(stream.name, 'rb')
270 268 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
271 269 stream = open(stream.name, 'rb')
272 270 else:
273 271 stream.seek(0)
274 272
275 273 def get_nodes(self, path):
276 274 """
277 275 Returns combined ``DirNode`` and ``FileNode`` objects list representing
278 276 state of changeset at the given ``path``. If node at the given ``path``
279 277 is not instance of ``DirNode``, ChangesetError would be raised.
280 278 """
281 279
282 280 if self._get_kind(path) != NodeKind.DIR:
283 281 raise ChangesetError("Directory does not exist for revision %r at "
284 282 " %r" % (self.revision, path))
285 283 path = self._fix_path(path)
286 284
287 285 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
288 286 if os.path.dirname(f) == path]
289 287 dirs = path == '' and '' or [d for d in self._dir_paths
290 288 if d and posixpath.dirname(d) == path]
291 289 dirnodes = [DirNode(d, changeset=self) for d in dirs
292 290 if os.path.dirname(d) == path]
293 291
294 292 als = self.repository.alias
295 293 for k, vals in self._extract_submodules().iteritems():
296 294 #vals = url,rev,type
297 295 loc = vals[0]
298 296 cs = vals[1]
299 297 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
300 298 alias=als))
301 299 nodes = dirnodes + filenodes
302 300 # cache nodes
303 301 for node in nodes:
304 302 self.nodes[node.path] = node
305 303 nodes.sort()
306 304
307 305 return nodes
308 306
309 307 def get_node(self, path):
310 308 """
311 309 Returns ``Node`` object from the given ``path``. If there is no node at
312 310 the given ``path``, ``ChangesetError`` would be raised.
313 311 """
314 312
315 313 path = self._fix_path(path)
316 314
317 315 if not path in self.nodes:
318 316 if path in self._file_paths:
319 317 node = FileNode(path, changeset=self)
320 318 elif path in self._dir_paths or path in self._dir_paths:
321 319 if path == '':
322 320 node = RootNode(changeset=self)
323 321 else:
324 322 node = DirNode(path, changeset=self)
325 323 else:
326 324 raise NodeDoesNotExistError("There is no file nor directory "
327 325 "at the given path: %r at revision %r"
328 326 % (path, self.short_id))
329 327 # cache node
330 328 self.nodes[path] = node
331 329 return self.nodes[path]
332 330
333 331 @LazyProperty
334 332 def affected_files(self):
335 333 """
336 334 Get's a fast accessible file changes for given changeset
337 335 """
338 336 return self._ctx.files()
339 337
340 338 @property
341 339 def added(self):
342 340 """
343 341 Returns list of added ``FileNode`` objects.
344 342 """
345 343 return AddedFileNodesGenerator([n for n in self.status[1]], self)
346 344
347 345 @property
348 346 def changed(self):
349 347 """
350 348 Returns list of modified ``FileNode`` objects.
351 349 """
352 350 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
353 351
354 352 @property
355 353 def removed(self):
356 354 """
357 355 Returns list of removed ``FileNode`` objects.
358 356 """
359 357 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
General Comments 0
You need to be logged in to leave comments. Login now