##// END OF EJS Templates
fixes issue #860....
marcink -
r4070:008e460c default
parent child Browse files
Show More
@@ -1,732 +1,731 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29 import tempfile
30 import shutil
30 import shutil
31
31
32 from pylons import request, response, tmpl_context as c, url
32 from pylons import request, response, tmpl_context as c, url
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from rhodecode.lib.utils import jsonify, action_logger
35 from rhodecode.lib.utils import jsonify, action_logger
36
36
37 from rhodecode.lib import diffs
37 from rhodecode.lib import diffs
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39
39
40 from rhodecode.lib.compat import OrderedDict, json
40 from rhodecode.lib.compat import OrderedDict
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
42 str2bool
42 str2bool
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
50 NodeDoesNotExistError, ChangesetError, NodeError
50 NodeDoesNotExistError, ChangesetError, NodeError
51 from rhodecode.lib.vcs.nodes import FileNode
51 from rhodecode.lib.vcs.nodes import FileNode
52
52
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
58 _context_url, get_line_ctx, get_ignore_ws
58 _context_url, get_line_ctx, get_ignore_ws
59 from webob.exc import HTTPNotFound
59 from webob.exc import HTTPNotFound
60 from rhodecode.lib.exceptions import NonRelativePathError
60 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
61
61
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 class FilesController(BaseRepoController):
66 class FilesController(BaseRepoController):
67
67
68 def __before__(self):
68 def __before__(self):
69 super(FilesController, self).__before__()
69 super(FilesController, self).__before__()
70 c.cut_off_limit = self.cut_off_limit
70 c.cut_off_limit = self.cut_off_limit
71
71
72 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
73 """
73 """
74 Safe way to get changeset if error occur it redirects to tip with
74 Safe way to get changeset if error occur it redirects to tip with
75 proper message
75 proper message
76
76
77 :param rev: revision to fetch
77 :param rev: revision to fetch
78 :param repo_name: repo name to redirect after
78 :param repo_name: repo name to redirect after
79 """
79 """
80
80
81 try:
81 try:
82 return c.rhodecode_repo.get_changeset(rev)
82 return c.rhodecode_repo.get_changeset(rev)
83 except EmptyRepositoryError, e:
83 except EmptyRepositoryError, e:
84 if not redirect_after:
84 if not redirect_after:
85 return None
85 return None
86 url_ = url('files_add_home',
86 url_ = url('files_add_home',
87 repo_name=c.repo_name,
87 repo_name=c.repo_name,
88 revision=0, f_path='')
88 revision=0, f_path='')
89 add_new = h.link_to(_('Click here to add new file'), url_)
89 add_new = h.link_to(_('Click here to add new file'), url_)
90 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 h.flash(h.literal(_('There are no files yet %s') % add_new),
91 category='warning')
91 category='warning')
92 redirect(h.url('summary_home', repo_name=repo_name))
92 redirect(h.url('summary_home', repo_name=repo_name))
93
93
94 except RepositoryError, e: # including ChangesetDoesNotExistError
94 except RepositoryError, e: # including ChangesetDoesNotExistError
95 h.flash(str(e), category='error')
95 h.flash(str(e), category='error')
96 raise HTTPNotFound()
96 raise HTTPNotFound()
97
97
98 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 def __get_filenode_or_redirect(self, repo_name, cs, path):
99 """
99 """
100 Returns file_node, if error occurs or given path is directory,
100 Returns file_node, if error occurs or given path is directory,
101 it'll redirect to top level path
101 it'll redirect to top level path
102
102
103 :param repo_name: repo_name
103 :param repo_name: repo_name
104 :param cs: given changeset
104 :param cs: given changeset
105 :param path: path to lookup
105 :param path: path to lookup
106 """
106 """
107
107
108 try:
108 try:
109 file_node = cs.get_node(path)
109 file_node = cs.get_node(path)
110 if file_node.is_dir():
110 if file_node.is_dir():
111 raise RepositoryError('given path is a directory')
111 raise RepositoryError('given path is a directory')
112 except RepositoryError, e:
112 except RepositoryError, e:
113 h.flash(str(e), category='error')
113 h.flash(str(e), category='error')
114 raise HTTPNotFound()
114 raise HTTPNotFound()
115
115
116 return file_node
116 return file_node
117
117
118 @LoginRequired()
118 @LoginRequired()
119 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
120 'repository.admin')
120 'repository.admin')
121 def index(self, repo_name, revision, f_path, annotate=False):
121 def index(self, repo_name, revision, f_path, annotate=False):
122 # redirect to given revision from form if given
122 # redirect to given revision from form if given
123 post_revision = request.POST.get('at_rev', None)
123 post_revision = request.POST.get('at_rev', None)
124 if post_revision:
124 if post_revision:
125 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125 cs = self.__get_cs_or_redirect(post_revision, repo_name)
126
126
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
128 c.branch = request.GET.get('branch', None)
128 c.branch = request.GET.get('branch', None)
129 c.f_path = f_path
129 c.f_path = f_path
130 c.annotate = annotate
130 c.annotate = annotate
131 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
132 cur_rev = c.changeset.revision
132 cur_rev = c.changeset.revision
133
133
134 # prev link
134 # prev link
135 try:
135 try:
136 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
137 c.url_prev = url('files_home', repo_name=c.repo_name,
137 c.url_prev = url('files_home', repo_name=c.repo_name,
138 revision=prev_rev.raw_id, f_path=f_path)
138 revision=prev_rev.raw_id, f_path=f_path)
139 if c.branch:
139 if c.branch:
140 c.url_prev += '?branch=%s' % c.branch
140 c.url_prev += '?branch=%s' % c.branch
141 except (ChangesetDoesNotExistError, VCSError):
141 except (ChangesetDoesNotExistError, VCSError):
142 c.url_prev = '#'
142 c.url_prev = '#'
143
143
144 # next link
144 # next link
145 try:
145 try:
146 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
147 c.url_next = url('files_home', repo_name=c.repo_name,
147 c.url_next = url('files_home', repo_name=c.repo_name,
148 revision=next_rev.raw_id, f_path=f_path)
148 revision=next_rev.raw_id, f_path=f_path)
149 if c.branch:
149 if c.branch:
150 c.url_next += '?branch=%s' % c.branch
150 c.url_next += '?branch=%s' % c.branch
151 except (ChangesetDoesNotExistError, VCSError):
151 except (ChangesetDoesNotExistError, VCSError):
152 c.url_next = '#'
152 c.url_next = '#'
153
153
154 # files or dirs
154 # files or dirs
155 try:
155 try:
156 c.file = c.changeset.get_node(f_path)
156 c.file = c.changeset.get_node(f_path)
157
157
158 if c.file.is_file():
158 if c.file.is_file():
159 c.load_full_history = False
159 c.load_full_history = False
160 file_last_cs = c.file.last_changeset
160 file_last_cs = c.file.last_changeset
161 c.file_changeset = (c.changeset
161 c.file_changeset = (c.changeset
162 if c.changeset.revision < file_last_cs.revision
162 if c.changeset.revision < file_last_cs.revision
163 else file_last_cs)
163 else file_last_cs)
164 #determine if we're on branch head
164 #determine if we're on branch head
165 _branches = c.rhodecode_repo.branches
165 _branches = c.rhodecode_repo.branches
166 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 c.on_branch_head = revision in _branches.keys() + _branches.values()
167 _hist = []
167 _hist = []
168 c.file_history = []
168 c.file_history = []
169 if c.load_full_history:
169 if c.load_full_history:
170 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
171
171
172 c.authors = []
172 c.authors = []
173 for a in set([x.author for x in _hist]):
173 for a in set([x.author for x in _hist]):
174 c.authors.append((h.email(a), h.person(a)))
174 c.authors.append((h.email(a), h.person(a)))
175 else:
175 else:
176 c.authors = c.file_history = []
176 c.authors = c.file_history = []
177 except RepositoryError, e:
177 except RepositoryError, e:
178 h.flash(str(e), category='error')
178 h.flash(str(e), category='error')
179 raise HTTPNotFound()
179 raise HTTPNotFound()
180
180
181 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 if request.environ.get('HTTP_X_PARTIAL_XHR'):
182 return render('files/files_ypjax.html')
182 return render('files/files_ypjax.html')
183
183
184 return render('files/files.html')
184 return render('files/files.html')
185
185
186 @LoginRequired()
186 @LoginRequired()
187 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
188 'repository.admin')
188 'repository.admin')
189 def history(self, repo_name, revision, f_path, annotate=False):
189 def history(self, repo_name, revision, f_path, annotate=False):
190 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 if request.environ.get('HTTP_X_PARTIAL_XHR'):
191 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
191 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
192 c.f_path = f_path
192 c.f_path = f_path
193 c.annotate = annotate
193 c.annotate = annotate
194 c.file = c.changeset.get_node(f_path)
194 c.file = c.changeset.get_node(f_path)
195 if c.file.is_file():
195 if c.file.is_file():
196 file_last_cs = c.file.last_changeset
196 file_last_cs = c.file.last_changeset
197 c.file_changeset = (c.changeset
197 c.file_changeset = (c.changeset
198 if c.changeset.revision < file_last_cs.revision
198 if c.changeset.revision < file_last_cs.revision
199 else file_last_cs)
199 else file_last_cs)
200 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
200 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
201 c.authors = []
201 c.authors = []
202 for a in set([x.author for x in _hist]):
202 for a in set([x.author for x in _hist]):
203 c.authors.append((h.email(a), h.person(a)))
203 c.authors.append((h.email(a), h.person(a)))
204 return render('files/files_history_box.html')
204 return render('files/files_history_box.html')
205
205
206 @LoginRequired()
206 @LoginRequired()
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
208 'repository.admin')
208 'repository.admin')
209 def rawfile(self, repo_name, revision, f_path):
209 def rawfile(self, repo_name, revision, f_path):
210 cs = self.__get_cs_or_redirect(revision, repo_name)
210 cs = self.__get_cs_or_redirect(revision, repo_name)
211 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
211 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
212
212
213 response.content_disposition = 'attachment; filename=%s' % \
213 response.content_disposition = 'attachment; filename=%s' % \
214 safe_str(f_path.split(Repository.url_sep())[-1])
214 safe_str(f_path.split(Repository.url_sep())[-1])
215
215
216 response.content_type = file_node.mimetype
216 response.content_type = file_node.mimetype
217 return file_node.content
217 return file_node.content
218
218
219 @LoginRequired()
219 @LoginRequired()
220 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
221 'repository.admin')
221 'repository.admin')
222 def raw(self, repo_name, revision, f_path):
222 def raw(self, repo_name, revision, f_path):
223 cs = self.__get_cs_or_redirect(revision, repo_name)
223 cs = self.__get_cs_or_redirect(revision, repo_name)
224 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
224 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
225
225
226 raw_mimetype_mapping = {
226 raw_mimetype_mapping = {
227 # map original mimetype to a mimetype used for "show as raw"
227 # map original mimetype to a mimetype used for "show as raw"
228 # you can also provide a content-disposition to override the
228 # you can also provide a content-disposition to override the
229 # default "attachment" disposition.
229 # default "attachment" disposition.
230 # orig_type: (new_type, new_dispo)
230 # orig_type: (new_type, new_dispo)
231
231
232 # show images inline:
232 # show images inline:
233 'image/x-icon': ('image/x-icon', 'inline'),
233 'image/x-icon': ('image/x-icon', 'inline'),
234 'image/png': ('image/png', 'inline'),
234 'image/png': ('image/png', 'inline'),
235 'image/gif': ('image/gif', 'inline'),
235 'image/gif': ('image/gif', 'inline'),
236 'image/jpeg': ('image/jpeg', 'inline'),
236 'image/jpeg': ('image/jpeg', 'inline'),
237 'image/svg+xml': ('image/svg+xml', 'inline'),
237 'image/svg+xml': ('image/svg+xml', 'inline'),
238 }
238 }
239
239
240 mimetype = file_node.mimetype
240 mimetype = file_node.mimetype
241 try:
241 try:
242 mimetype, dispo = raw_mimetype_mapping[mimetype]
242 mimetype, dispo = raw_mimetype_mapping[mimetype]
243 except KeyError:
243 except KeyError:
244 # we don't know anything special about this, handle it safely
244 # we don't know anything special about this, handle it safely
245 if file_node.is_binary:
245 if file_node.is_binary:
246 # do same as download raw for binary files
246 # do same as download raw for binary files
247 mimetype, dispo = 'application/octet-stream', 'attachment'
247 mimetype, dispo = 'application/octet-stream', 'attachment'
248 else:
248 else:
249 # do not just use the original mimetype, but force text/plain,
249 # do not just use the original mimetype, but force text/plain,
250 # otherwise it would serve text/html and that might be unsafe.
250 # otherwise it would serve text/html and that might be unsafe.
251 # Note: underlying vcs library fakes text/plain mimetype if the
251 # Note: underlying vcs library fakes text/plain mimetype if the
252 # mimetype can not be determined and it thinks it is not
252 # mimetype can not be determined and it thinks it is not
253 # binary.This might lead to erroneous text display in some
253 # binary.This might lead to erroneous text display in some
254 # cases, but helps in other cases, like with text files
254 # cases, but helps in other cases, like with text files
255 # without extension.
255 # without extension.
256 mimetype, dispo = 'text/plain', 'inline'
256 mimetype, dispo = 'text/plain', 'inline'
257
257
258 if dispo == 'attachment':
258 if dispo == 'attachment':
259 dispo = 'attachment; filename=%s' % \
259 dispo = 'attachment; filename=%s' % \
260 safe_str(f_path.split(os.sep)[-1])
260 safe_str(f_path.split(os.sep)[-1])
261
261
262 response.content_disposition = dispo
262 response.content_disposition = dispo
263 response.content_type = mimetype
263 response.content_type = mimetype
264 return file_node.content
264 return file_node.content
265
265
266 @LoginRequired()
266 @LoginRequired()
267 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
267 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
268 def edit(self, repo_name, revision, f_path):
268 def edit(self, repo_name, revision, f_path):
269 repo = c.rhodecode_db_repo
269 repo = c.rhodecode_db_repo
270 if repo.enable_locking and repo.locked[0]:
270 if repo.enable_locking and repo.locked[0]:
271 h.flash(_('This repository is has been locked by %s on %s')
271 h.flash(_('This repository is has been locked by %s on %s')
272 % (h.person_by_id(repo.locked[0]),
272 % (h.person_by_id(repo.locked[0]),
273 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
273 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
274 'warning')
274 'warning')
275 return redirect(h.url('files_home',
275 return redirect(h.url('files_home',
276 repo_name=repo_name, revision='tip'))
276 repo_name=repo_name, revision='tip'))
277
277
278 # check if revision is a branch identifier- basically we cannot
278 # check if revision is a branch identifier- basically we cannot
279 # create multiple heads via file editing
279 # create multiple heads via file editing
280 _branches = repo.scm_instance.branches
280 _branches = repo.scm_instance.branches
281 # check if revision is a branch name or branch hash
281 # check if revision is a branch name or branch hash
282 if revision not in _branches.keys() + _branches.values():
282 if revision not in _branches.keys() + _branches.values():
283 h.flash(_('You can only edit files with revision '
283 h.flash(_('You can only edit files with revision '
284 'being a valid branch '), category='warning')
284 'being a valid branch '), category='warning')
285 return redirect(h.url('files_home',
285 return redirect(h.url('files_home',
286 repo_name=repo_name, revision='tip',
286 repo_name=repo_name, revision='tip',
287 f_path=f_path))
287 f_path=f_path))
288
288
289 r_post = request.POST
289 r_post = request.POST
290
290
291 c.cs = self.__get_cs_or_redirect(revision, repo_name)
291 c.cs = self.__get_cs_or_redirect(revision, repo_name)
292 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
292 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
293
293
294 if c.file.is_binary:
294 if c.file.is_binary:
295 return redirect(url('files_home', repo_name=c.repo_name,
295 return redirect(url('files_home', repo_name=c.repo_name,
296 revision=c.cs.raw_id, f_path=f_path))
296 revision=c.cs.raw_id, f_path=f_path))
297 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
297 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
298 c.f_path = f_path
298 c.f_path = f_path
299
299
300 if r_post:
300 if r_post:
301
301
302 old_content = c.file.content
302 old_content = c.file.content
303 sl = old_content.splitlines(1)
303 sl = old_content.splitlines(1)
304 first_line = sl[0] if sl else ''
304 first_line = sl[0] if sl else ''
305 # modes: 0 - Unix, 1 - Mac, 2 - DOS
305 # modes: 0 - Unix, 1 - Mac, 2 - DOS
306 mode = detect_mode(first_line, 0)
306 mode = detect_mode(first_line, 0)
307 content = convert_line_endings(r_post.get('content', ''), mode)
307 content = convert_line_endings(r_post.get('content', ''), mode)
308
308
309 message = r_post.get('message') or c.default_message
309 message = r_post.get('message') or c.default_message
310 author = self.rhodecode_user.full_contact
310 author = self.rhodecode_user.full_contact
311
311
312 if content == old_content:
312 if content == old_content:
313 h.flash(_('No changes'), category='warning')
313 h.flash(_('No changes'), category='warning')
314 return redirect(url('changeset_home', repo_name=c.repo_name,
314 return redirect(url('changeset_home', repo_name=c.repo_name,
315 revision='tip'))
315 revision='tip'))
316 try:
316 try:
317 self.scm_model.commit_change(repo=c.rhodecode_repo,
317 self.scm_model.commit_change(repo=c.rhodecode_repo,
318 repo_name=repo_name, cs=c.cs,
318 repo_name=repo_name, cs=c.cs,
319 user=self.rhodecode_user.user_id,
319 user=self.rhodecode_user.user_id,
320 author=author, message=message,
320 author=author, message=message,
321 content=content, f_path=f_path)
321 content=content, f_path=f_path)
322 h.flash(_('Successfully committed to %s') % f_path,
322 h.flash(_('Successfully committed to %s') % f_path,
323 category='success')
323 category='success')
324
325 except Exception:
324 except Exception:
326 log.error(traceback.format_exc())
325 log.error(traceback.format_exc())
327 h.flash(_('Error occurred during commit'), category='error')
326 h.flash(_('Error occurred during commit'), category='error')
328 return redirect(url('changeset_home',
327 return redirect(url('changeset_home',
329 repo_name=c.repo_name, revision='tip'))
328 repo_name=c.repo_name, revision='tip'))
330
329
331 return render('files/files_edit.html')
330 return render('files/files_edit.html')
332
331
333 @LoginRequired()
332 @LoginRequired()
334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
335 def add(self, repo_name, revision, f_path):
334 def add(self, repo_name, revision, f_path):
336
335
337 repo = Repository.get_by_repo_name(repo_name)
336 repo = Repository.get_by_repo_name(repo_name)
338 if repo.enable_locking and repo.locked[0]:
337 if repo.enable_locking and repo.locked[0]:
339 h.flash(_('This repository is has been locked by %s on %s')
338 h.flash(_('This repository is has been locked by %s on %s')
340 % (h.person_by_id(repo.locked[0]),
339 % (h.person_by_id(repo.locked[0]),
341 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
342 'warning')
341 'warning')
343 return redirect(h.url('files_home',
342 return redirect(h.url('files_home',
344 repo_name=repo_name, revision='tip'))
343 repo_name=repo_name, revision='tip'))
345
344
346 r_post = request.POST
345 r_post = request.POST
347 c.cs = self.__get_cs_or_redirect(revision, repo_name,
346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
348 redirect_after=False)
347 redirect_after=False)
349 if c.cs is None:
348 if c.cs is None:
350 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
351 c.default_message = (_('Added file via RhodeCode'))
350 c.default_message = (_('Added file via RhodeCode'))
352 c.f_path = f_path
351 c.f_path = f_path
353
352
354 if r_post:
353 if r_post:
355 unix_mode = 0
354 unix_mode = 0
356 content = convert_line_endings(r_post.get('content', ''), unix_mode)
355 content = convert_line_endings(r_post.get('content', ''), unix_mode)
357
356
358 message = r_post.get('message') or c.default_message
357 message = r_post.get('message') or c.default_message
359 filename = r_post.get('filename')
358 filename = r_post.get('filename')
360 location = r_post.get('location', '')
359 location = r_post.get('location', '')
361 file_obj = r_post.get('upload_file', None)
360 file_obj = r_post.get('upload_file', None)
362
361
363 if file_obj is not None and hasattr(file_obj, 'filename'):
362 if file_obj is not None and hasattr(file_obj, 'filename'):
364 filename = file_obj.filename
363 filename = file_obj.filename
365 content = file_obj.file
364 content = file_obj.file
366
365
367 if not content:
366 if not content:
368 h.flash(_('No content'), category='warning')
367 h.flash(_('No content'), category='warning')
369 return redirect(url('changeset_home', repo_name=c.repo_name,
368 return redirect(url('changeset_home', repo_name=c.repo_name,
370 revision='tip'))
369 revision='tip'))
371 if not filename:
370 if not filename:
372 h.flash(_('No filename'), category='warning')
371 h.flash(_('No filename'), category='warning')
373 return redirect(url('changeset_home', repo_name=c.repo_name,
372 return redirect(url('changeset_home', repo_name=c.repo_name,
374 revision='tip'))
373 revision='tip'))
375 #strip all crap out of file, just leave the basename
374 #strip all crap out of file, just leave the basename
376 filename = os.path.basename(filename)
375 filename = os.path.basename(filename)
377 node_path = os.path.join(location, filename)
376 node_path = os.path.join(location, filename)
378 author = self.rhodecode_user.full_contact
377 author = self.rhodecode_user.full_contact
379
378
380 try:
379 try:
381 nodes = {
380 nodes = {
382 node_path: {
381 node_path: {
383 'content': content
382 'content': content
384 }
383 }
385 }
384 }
386 self.scm_model.create_nodes(
385 self.scm_model.create_nodes(
387 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
386 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
388 message=message,
387 message=message,
389 nodes=nodes,
388 nodes=nodes,
390 parent_cs=c.cs,
389 parent_cs=c.cs,
391 author=author,
390 author=author,
392 )
391 )
393
392
394 h.flash(_('Successfully committed to %s') % node_path,
393 h.flash(_('Successfully committed to %s') % node_path,
395 category='success')
394 category='success')
396 except NonRelativePathError, e:
395 except NonRelativePathError, e:
397 h.flash(_('Location must be relative path and must not '
396 h.flash(_('Location must be relative path and must not '
398 'contain .. in path'), category='warning')
397 'contain .. in path'), category='warning')
399 return redirect(url('changeset_home', repo_name=c.repo_name,
398 return redirect(url('changeset_home', repo_name=c.repo_name,
400 revision='tip'))
399 revision='tip'))
401 except (NodeError, NodeAlreadyExistsError), e:
400 except (NodeError, NodeAlreadyExistsError), e:
402 h.flash(_(e), category='error')
401 h.flash(_(e), category='error')
403 except Exception:
402 except Exception:
404 log.error(traceback.format_exc())
403 log.error(traceback.format_exc())
405 h.flash(_('Error occurred during commit'), category='error')
404 h.flash(_('Error occurred during commit'), category='error')
406 return redirect(url('changeset_home',
405 return redirect(url('changeset_home',
407 repo_name=c.repo_name, revision='tip'))
406 repo_name=c.repo_name, revision='tip'))
408
407
409 return render('files/files_add.html')
408 return render('files/files_add.html')
410
409
411 @LoginRequired()
410 @LoginRequired()
412 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
411 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
413 'repository.admin')
412 'repository.admin')
414 def archivefile(self, repo_name, fname):
413 def archivefile(self, repo_name, fname):
415
414
416 fileformat = None
415 fileformat = None
417 revision = None
416 revision = None
418 ext = None
417 ext = None
419 subrepos = request.GET.get('subrepos') == 'true'
418 subrepos = request.GET.get('subrepos') == 'true'
420
419
421 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
420 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
422 archive_spec = fname.split(ext_data[1])
421 archive_spec = fname.split(ext_data[1])
423 if len(archive_spec) == 2 and archive_spec[1] == '':
422 if len(archive_spec) == 2 and archive_spec[1] == '':
424 fileformat = a_type or ext_data[1]
423 fileformat = a_type or ext_data[1]
425 revision = archive_spec[0]
424 revision = archive_spec[0]
426 ext = ext_data[1]
425 ext = ext_data[1]
427
426
428 try:
427 try:
429 dbrepo = RepoModel().get_by_repo_name(repo_name)
428 dbrepo = RepoModel().get_by_repo_name(repo_name)
430 if not dbrepo.enable_downloads:
429 if not dbrepo.enable_downloads:
431 return _('Downloads disabled')
430 return _('Downloads disabled')
432
431
433 if c.rhodecode_repo.alias == 'hg':
432 if c.rhodecode_repo.alias == 'hg':
434 # patch and reset hooks section of UI config to not run any
433 # patch and reset hooks section of UI config to not run any
435 # hooks on fetching archives with subrepos
434 # hooks on fetching archives with subrepos
436 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
435 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
437 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
436 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
438
437
439 cs = c.rhodecode_repo.get_changeset(revision)
438 cs = c.rhodecode_repo.get_changeset(revision)
440 content_type = settings.ARCHIVE_SPECS[fileformat][0]
439 content_type = settings.ARCHIVE_SPECS[fileformat][0]
441 except ChangesetDoesNotExistError:
440 except ChangesetDoesNotExistError:
442 return _('Unknown revision %s') % revision
441 return _('Unknown revision %s') % revision
443 except EmptyRepositoryError:
442 except EmptyRepositoryError:
444 return _('Empty repository')
443 return _('Empty repository')
445 except (ImproperArchiveTypeError, KeyError):
444 except (ImproperArchiveTypeError, KeyError):
446 return _('Unknown archive type')
445 return _('Unknown archive type')
447 # archive cache
446 # archive cache
448 from rhodecode import CONFIG
447 from rhodecode import CONFIG
449 rev_name = cs.raw_id[:12]
448 rev_name = cs.raw_id[:12]
450 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
449 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
451 safe_str(rev_name), ext)
450 safe_str(rev_name), ext)
452
451
453 use_cached_archive = False # defines if we use cached version of archive
452 use_cached_archive = False # defines if we use cached version of archive
454 archive_cache_enabled = CONFIG.get('archive_cache_dir')
453 archive_cache_enabled = CONFIG.get('archive_cache_dir')
455 if not subrepos and archive_cache_enabled:
454 if not subrepos and archive_cache_enabled:
456 #check if we it's ok to write
455 #check if we it's ok to write
457 if not os.path.isdir(CONFIG['archive_cache_dir']):
456 if not os.path.isdir(CONFIG['archive_cache_dir']):
458 os.makedirs(CONFIG['archive_cache_dir'])
457 os.makedirs(CONFIG['archive_cache_dir'])
459 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
458 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
460 if os.path.isfile(cached_archive_path):
459 if os.path.isfile(cached_archive_path):
461 log.debug('Found cached archive in %s' % cached_archive_path)
460 log.debug('Found cached archive in %s' % cached_archive_path)
462 fd, archive = None, cached_archive_path
461 fd, archive = None, cached_archive_path
463 use_cached_archive = True
462 use_cached_archive = True
464 else:
463 else:
465 log.debug('Archive %s is not yet cached' % (archive_name))
464 log.debug('Archive %s is not yet cached' % (archive_name))
466
465
467 if not use_cached_archive:
466 if not use_cached_archive:
468 #generate new archive
467 #generate new archive
469 try:
468 try:
470 fd, archive = tempfile.mkstemp()
469 fd, archive = tempfile.mkstemp()
471 t = open(archive, 'wb')
470 t = open(archive, 'wb')
472 log.debug('Creating new temp archive in %s' % archive)
471 log.debug('Creating new temp archive in %s' % archive)
473 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
472 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
474 if archive_cache_enabled:
473 if archive_cache_enabled:
475 #if we generated the archive and use cache rename that
474 #if we generated the archive and use cache rename that
476 log.debug('Storing new archive in %s' % cached_archive_path)
475 log.debug('Storing new archive in %s' % cached_archive_path)
477 shutil.move(archive, cached_archive_path)
476 shutil.move(archive, cached_archive_path)
478 archive = cached_archive_path
477 archive = cached_archive_path
479 finally:
478 finally:
480 t.close()
479 t.close()
481
480
482 def get_chunked_archive(archive):
481 def get_chunked_archive(archive):
483 stream = open(archive, 'rb')
482 stream = open(archive, 'rb')
484 while True:
483 while True:
485 data = stream.read(16 * 1024)
484 data = stream.read(16 * 1024)
486 if not data:
485 if not data:
487 stream.close()
486 stream.close()
488 if fd: # fd means we used temporary file
487 if fd: # fd means we used temporary file
489 os.close(fd)
488 os.close(fd)
490 if not archive_cache_enabled:
489 if not archive_cache_enabled:
491 log.debug('Destroing temp archive %s' % archive)
490 log.debug('Destroing temp archive %s' % archive)
492 os.remove(archive)
491 os.remove(archive)
493 break
492 break
494 yield data
493 yield data
495 # store download action
494 # store download action
496 action_logger(user=c.rhodecode_user,
495 action_logger(user=c.rhodecode_user,
497 action='user_downloaded_archive:%s' % (archive_name),
496 action='user_downloaded_archive:%s' % (archive_name),
498 repo=repo_name, ipaddr=self.ip_addr, commit=True)
497 repo=repo_name, ipaddr=self.ip_addr, commit=True)
499 response.content_disposition = str('attachment; filename=%s' % (archive_name))
498 response.content_disposition = str('attachment; filename=%s' % (archive_name))
500 response.content_type = str(content_type)
499 response.content_type = str(content_type)
501 return get_chunked_archive(archive)
500 return get_chunked_archive(archive)
502
501
503 @LoginRequired()
502 @LoginRequired()
504 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
503 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
505 'repository.admin')
504 'repository.admin')
506 def diff(self, repo_name, f_path):
505 def diff(self, repo_name, f_path):
507 ignore_whitespace = request.GET.get('ignorews') == '1'
506 ignore_whitespace = request.GET.get('ignorews') == '1'
508 line_context = request.GET.get('context', 3)
507 line_context = request.GET.get('context', 3)
509 diff1 = request.GET.get('diff1', '')
508 diff1 = request.GET.get('diff1', '')
510 diff2 = request.GET.get('diff2', '')
509 diff2 = request.GET.get('diff2', '')
511 c.action = request.GET.get('diff')
510 c.action = request.GET.get('diff')
512 c.no_changes = diff1 == diff2
511 c.no_changes = diff1 == diff2
513 c.f_path = f_path
512 c.f_path = f_path
514 c.big_diff = False
513 c.big_diff = False
515 c.anchor_url = anchor_url
514 c.anchor_url = anchor_url
516 c.ignorews_url = _ignorews_url
515 c.ignorews_url = _ignorews_url
517 c.context_url = _context_url
516 c.context_url = _context_url
518 c.changes = OrderedDict()
517 c.changes = OrderedDict()
519 c.changes[diff2] = []
518 c.changes[diff2] = []
520
519
521 #special case if we want a show rev only, it's impl here
520 #special case if we want a show rev only, it's impl here
522 #to reduce JS and callbacks
521 #to reduce JS and callbacks
523
522
524 if request.GET.get('show_rev'):
523 if request.GET.get('show_rev'):
525 if str2bool(request.GET.get('annotate', 'False')):
524 if str2bool(request.GET.get('annotate', 'False')):
526 _url = url('files_annotate_home', repo_name=c.repo_name,
525 _url = url('files_annotate_home', repo_name=c.repo_name,
527 revision=diff1, f_path=c.f_path)
526 revision=diff1, f_path=c.f_path)
528 else:
527 else:
529 _url = url('files_home', repo_name=c.repo_name,
528 _url = url('files_home', repo_name=c.repo_name,
530 revision=diff1, f_path=c.f_path)
529 revision=diff1, f_path=c.f_path)
531
530
532 return redirect(_url)
531 return redirect(_url)
533 try:
532 try:
534 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
533 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
535 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
534 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
536 try:
535 try:
537 node1 = c.changeset_1.get_node(f_path)
536 node1 = c.changeset_1.get_node(f_path)
538 if node1.is_dir():
537 if node1.is_dir():
539 raise NodeError('%s path is a %s not a file'
538 raise NodeError('%s path is a %s not a file'
540 % (node1, type(node1)))
539 % (node1, type(node1)))
541 except NodeDoesNotExistError:
540 except NodeDoesNotExistError:
542 c.changeset_1 = EmptyChangeset(cs=diff1,
541 c.changeset_1 = EmptyChangeset(cs=diff1,
543 revision=c.changeset_1.revision,
542 revision=c.changeset_1.revision,
544 repo=c.rhodecode_repo)
543 repo=c.rhodecode_repo)
545 node1 = FileNode(f_path, '', changeset=c.changeset_1)
544 node1 = FileNode(f_path, '', changeset=c.changeset_1)
546 else:
545 else:
547 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
546 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
548 node1 = FileNode(f_path, '', changeset=c.changeset_1)
547 node1 = FileNode(f_path, '', changeset=c.changeset_1)
549
548
550 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
549 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
551 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
550 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
552 try:
551 try:
553 node2 = c.changeset_2.get_node(f_path)
552 node2 = c.changeset_2.get_node(f_path)
554 if node2.is_dir():
553 if node2.is_dir():
555 raise NodeError('%s path is a %s not a file'
554 raise NodeError('%s path is a %s not a file'
556 % (node2, type(node2)))
555 % (node2, type(node2)))
557 except NodeDoesNotExistError:
556 except NodeDoesNotExistError:
558 c.changeset_2 = EmptyChangeset(cs=diff2,
557 c.changeset_2 = EmptyChangeset(cs=diff2,
559 revision=c.changeset_2.revision,
558 revision=c.changeset_2.revision,
560 repo=c.rhodecode_repo)
559 repo=c.rhodecode_repo)
561 node2 = FileNode(f_path, '', changeset=c.changeset_2)
560 node2 = FileNode(f_path, '', changeset=c.changeset_2)
562 else:
561 else:
563 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
562 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
564 node2 = FileNode(f_path, '', changeset=c.changeset_2)
563 node2 = FileNode(f_path, '', changeset=c.changeset_2)
565 except (RepositoryError, NodeError):
564 except (RepositoryError, NodeError):
566 log.error(traceback.format_exc())
565 log.error(traceback.format_exc())
567 return redirect(url('files_home', repo_name=c.repo_name,
566 return redirect(url('files_home', repo_name=c.repo_name,
568 f_path=f_path))
567 f_path=f_path))
569
568
570 if c.action == 'download':
569 if c.action == 'download':
571 _diff = diffs.get_gitdiff(node1, node2,
570 _diff = diffs.get_gitdiff(node1, node2,
572 ignore_whitespace=ignore_whitespace,
571 ignore_whitespace=ignore_whitespace,
573 context=line_context)
572 context=line_context)
574 diff = diffs.DiffProcessor(_diff, format='gitdiff')
573 diff = diffs.DiffProcessor(_diff, format='gitdiff')
575
574
576 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
575 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
577 response.content_type = 'text/plain'
576 response.content_type = 'text/plain'
578 response.content_disposition = (
577 response.content_disposition = (
579 'attachment; filename=%s' % diff_name
578 'attachment; filename=%s' % diff_name
580 )
579 )
581 return diff.as_raw()
580 return diff.as_raw()
582
581
583 elif c.action == 'raw':
582 elif c.action == 'raw':
584 _diff = diffs.get_gitdiff(node1, node2,
583 _diff = diffs.get_gitdiff(node1, node2,
585 ignore_whitespace=ignore_whitespace,
584 ignore_whitespace=ignore_whitespace,
586 context=line_context)
585 context=line_context)
587 diff = diffs.DiffProcessor(_diff, format='gitdiff')
586 diff = diffs.DiffProcessor(_diff, format='gitdiff')
588 response.content_type = 'text/plain'
587 response.content_type = 'text/plain'
589 return diff.as_raw()
588 return diff.as_raw()
590
589
591 else:
590 else:
592 fid = h.FID(diff2, node2.path)
591 fid = h.FID(diff2, node2.path)
593 line_context_lcl = get_line_ctx(fid, request.GET)
592 line_context_lcl = get_line_ctx(fid, request.GET)
594 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
593 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
595
594
596 lim = request.GET.get('fulldiff') or self.cut_off_limit
595 lim = request.GET.get('fulldiff') or self.cut_off_limit
597 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
596 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
598 filenode_new=node2,
597 filenode_new=node2,
599 cut_off_limit=lim,
598 cut_off_limit=lim,
600 ignore_whitespace=ign_whitespace_lcl,
599 ignore_whitespace=ign_whitespace_lcl,
601 line_context=line_context_lcl,
600 line_context=line_context_lcl,
602 enable_comments=False)
601 enable_comments=False)
603 op = ''
602 op = ''
604 filename = node1.path
603 filename = node1.path
605 cs_changes = {
604 cs_changes = {
606 'fid': [cs1, cs2, op, filename, diff, st]
605 'fid': [cs1, cs2, op, filename, diff, st]
607 }
606 }
608 c.changes = cs_changes
607 c.changes = cs_changes
609
608
610 return render('files/file_diff.html')
609 return render('files/file_diff.html')
611
610
612 @LoginRequired()
611 @LoginRequired()
613 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
612 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
614 'repository.admin')
613 'repository.admin')
615 def diff_2way(self, repo_name, f_path):
614 def diff_2way(self, repo_name, f_path):
616 diff1 = request.GET.get('diff1', '')
615 diff1 = request.GET.get('diff1', '')
617 diff2 = request.GET.get('diff2', '')
616 diff2 = request.GET.get('diff2', '')
618 try:
617 try:
619 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
618 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
620 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
619 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
621 try:
620 try:
622 node1 = c.changeset_1.get_node(f_path)
621 node1 = c.changeset_1.get_node(f_path)
623 if node1.is_dir():
622 if node1.is_dir():
624 raise NodeError('%s path is a %s not a file'
623 raise NodeError('%s path is a %s not a file'
625 % (node1, type(node1)))
624 % (node1, type(node1)))
626 except NodeDoesNotExistError:
625 except NodeDoesNotExistError:
627 c.changeset_1 = EmptyChangeset(cs=diff1,
626 c.changeset_1 = EmptyChangeset(cs=diff1,
628 revision=c.changeset_1.revision,
627 revision=c.changeset_1.revision,
629 repo=c.rhodecode_repo)
628 repo=c.rhodecode_repo)
630 node1 = FileNode(f_path, '', changeset=c.changeset_1)
629 node1 = FileNode(f_path, '', changeset=c.changeset_1)
631 else:
630 else:
632 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
631 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
633 node1 = FileNode(f_path, '', changeset=c.changeset_1)
632 node1 = FileNode(f_path, '', changeset=c.changeset_1)
634
633
635 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
634 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
636 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
635 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
637 try:
636 try:
638 node2 = c.changeset_2.get_node(f_path)
637 node2 = c.changeset_2.get_node(f_path)
639 if node2.is_dir():
638 if node2.is_dir():
640 raise NodeError('%s path is a %s not a file'
639 raise NodeError('%s path is a %s not a file'
641 % (node2, type(node2)))
640 % (node2, type(node2)))
642 except NodeDoesNotExistError:
641 except NodeDoesNotExistError:
643 c.changeset_2 = EmptyChangeset(cs=diff2,
642 c.changeset_2 = EmptyChangeset(cs=diff2,
644 revision=c.changeset_2.revision,
643 revision=c.changeset_2.revision,
645 repo=c.rhodecode_repo)
644 repo=c.rhodecode_repo)
646 node2 = FileNode(f_path, '', changeset=c.changeset_2)
645 node2 = FileNode(f_path, '', changeset=c.changeset_2)
647 else:
646 else:
648 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
647 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
649 node2 = FileNode(f_path, '', changeset=c.changeset_2)
648 node2 = FileNode(f_path, '', changeset=c.changeset_2)
650 except (RepositoryError, NodeError):
649 except (RepositoryError, NodeError):
651 log.error(traceback.format_exc())
650 log.error(traceback.format_exc())
652 return redirect(url('files_home', repo_name=c.repo_name,
651 return redirect(url('files_home', repo_name=c.repo_name,
653 f_path=f_path))
652 f_path=f_path))
654 if node2.is_binary:
653 if node2.is_binary:
655 node2_content = 'binary file'
654 node2_content = 'binary file'
656 else:
655 else:
657 node2_content = node2.content
656 node2_content = node2.content
658
657
659 if node1.is_binary:
658 if node1.is_binary:
660 node1_content = 'binary file'
659 node1_content = 'binary file'
661 else:
660 else:
662 node1_content = node1.content
661 node1_content = node1.content
663
662
664 html_escape_table = {
663 html_escape_table = {
665 "&": "\u0026",
664 "&": "\u0026",
666 '"': "\u0022",
665 '"': "\u0022",
667 "'": "\u0027",
666 "'": "\u0027",
668 ">": "\u003e",
667 ">": "\u003e",
669 "<": "\u003c",
668 "<": "\u003c",
670 '\\': "\u005c",
669 '\\': "\u005c",
671 '\n': '\\n'
670 '\n': '\\n'
672 }
671 }
673
672
674 c.orig1 = h.html_escape((node1_content), html_escape_table)
673 c.orig1 = h.html_escape((node1_content), html_escape_table)
675 c.orig2 = h.html_escape((node2_content), html_escape_table)
674 c.orig2 = h.html_escape((node2_content), html_escape_table)
676 c.node1 = node1
675 c.node1 = node1
677 c.node2 = node2
676 c.node2 = node2
678 c.cs1 = c.changeset_1
677 c.cs1 = c.changeset_1
679 c.cs2 = c.changeset_2
678 c.cs2 = c.changeset_2
680
679
681 return render('files/diff_2way.html')
680 return render('files/diff_2way.html')
682
681
683 def _get_node_history(self, cs, f_path, changesets=None):
682 def _get_node_history(self, cs, f_path, changesets=None):
684 """
683 """
685 get changesets history for given node
684 get changesets history for given node
686
685
687 :param cs: changeset to calculate history
686 :param cs: changeset to calculate history
688 :param f_path: path for node to calculate history for
687 :param f_path: path for node to calculate history for
689 :param changesets: if passed don't calculate history and take
688 :param changesets: if passed don't calculate history and take
690 changesets defined in this list
689 changesets defined in this list
691 """
690 """
692 # calculate history based on tip
691 # calculate history based on tip
693 tip_cs = c.rhodecode_repo.get_changeset()
692 tip_cs = c.rhodecode_repo.get_changeset()
694 if changesets is None:
693 if changesets is None:
695 try:
694 try:
696 changesets = tip_cs.get_file_history(f_path)
695 changesets = tip_cs.get_file_history(f_path)
697 except (NodeDoesNotExistError, ChangesetError):
696 except (NodeDoesNotExistError, ChangesetError):
698 #this node is not present at tip !
697 #this node is not present at tip !
699 changesets = cs.get_file_history(f_path)
698 changesets = cs.get_file_history(f_path)
700 hist_l = []
699 hist_l = []
701
700
702 changesets_group = ([], _("Changesets"))
701 changesets_group = ([], _("Changesets"))
703 branches_group = ([], _("Branches"))
702 branches_group = ([], _("Branches"))
704 tags_group = ([], _("Tags"))
703 tags_group = ([], _("Tags"))
705 _hg = cs.repository.alias == 'hg'
704 _hg = cs.repository.alias == 'hg'
706 for chs in changesets:
705 for chs in changesets:
707 #_branch = '(%s)' % chs.branch if _hg else ''
706 #_branch = '(%s)' % chs.branch if _hg else ''
708 _branch = chs.branch
707 _branch = chs.branch
709 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
708 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
710 changesets_group[0].append((chs.raw_id, n_desc,))
709 changesets_group[0].append((chs.raw_id, n_desc,))
711 hist_l.append(changesets_group)
710 hist_l.append(changesets_group)
712
711
713 for name, chs in c.rhodecode_repo.branches.items():
712 for name, chs in c.rhodecode_repo.branches.items():
714 branches_group[0].append((chs, name),)
713 branches_group[0].append((chs, name),)
715 hist_l.append(branches_group)
714 hist_l.append(branches_group)
716
715
717 for name, chs in c.rhodecode_repo.tags.items():
716 for name, chs in c.rhodecode_repo.tags.items():
718 tags_group[0].append((chs, name),)
717 tags_group[0].append((chs, name),)
719 hist_l.append(tags_group)
718 hist_l.append(tags_group)
720
719
721 return hist_l, changesets
720 return hist_l, changesets
722
721
723 @LoginRequired()
722 @LoginRequired()
724 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
723 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
725 'repository.admin')
724 'repository.admin')
726 @jsonify
725 @jsonify
727 def nodelist(self, repo_name, revision, f_path):
726 def nodelist(self, repo_name, revision, f_path):
728 if request.environ.get('HTTP_X_PARTIAL_XHR'):
727 if request.environ.get('HTTP_X_PARTIAL_XHR'):
729 cs = self.__get_cs_or_redirect(revision, repo_name)
728 cs = self.__get_cs_or_redirect(revision, repo_name)
730 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
729 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
731 flat=False)
730 flat=False)
732 return {'nodes': _d + _f}
731 return {'nodes': _d + _f}
@@ -1,88 +1,92 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.exceptions
3 rhodecode.lib.exceptions
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Set of custom exceptions used in RhodeCode
6 Set of custom exceptions used in RhodeCode
7
7
8 :created_on: Nov 17, 2010
8 :created_on: Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 from webob.exc import HTTPClientError
26 from webob.exc import HTTPClientError
27
27
28
28
29 class LdapUsernameError(Exception):
29 class LdapUsernameError(Exception):
30 pass
30 pass
31
31
32
32
33 class LdapPasswordError(Exception):
33 class LdapPasswordError(Exception):
34 pass
34 pass
35
35
36
36
37 class LdapConnectionError(Exception):
37 class LdapConnectionError(Exception):
38 pass
38 pass
39
39
40
40
41 class LdapImportError(Exception):
41 class LdapImportError(Exception):
42 pass
42 pass
43
43
44
44
45 class DefaultUserException(Exception):
45 class DefaultUserException(Exception):
46 pass
46 pass
47
47
48
48
49 class UserOwnsReposException(Exception):
49 class UserOwnsReposException(Exception):
50 pass
50 pass
51
51
52
52
53 class UserGroupsAssignedException(Exception):
53 class UserGroupsAssignedException(Exception):
54 pass
54 pass
55
55
56
56
57 class StatusChangeOnClosedPullRequestError(Exception):
57 class StatusChangeOnClosedPullRequestError(Exception):
58 pass
58 pass
59
59
60
60
61 class AttachedForksError(Exception):
61 class AttachedForksError(Exception):
62 pass
62 pass
63
63
64
64
65 class RepoGroupAssignmentError(Exception):
65 class RepoGroupAssignmentError(Exception):
66 pass
66 pass
67
67
68
68
69 class NonRelativePathError(Exception):
69 class NonRelativePathError(Exception):
70 pass
70 pass
71
71
72
72
73 class HTTPLockedRC(HTTPClientError):
73 class HTTPLockedRC(HTTPClientError):
74 """
74 """
75 Special Exception For locked Repos in RhodeCode, the return code can
75 Special Exception For locked Repos in RhodeCode, the return code can
76 be overwritten by _code keyword argument passed into constructors
76 be overwritten by _code keyword argument passed into constructors
77 """
77 """
78 code = 423
78 code = 423
79 title = explanation = 'Repository Locked'
79 title = explanation = 'Repository Locked'
80
80
81 def __init__(self, reponame, username, *args, **kwargs):
81 def __init__(self, reponame, username, *args, **kwargs):
82 from rhodecode import CONFIG
82 from rhodecode import CONFIG
83 from rhodecode.lib.utils2 import safe_int
83 from rhodecode.lib.utils2 import safe_int
84 _code = CONFIG.get('lock_ret_code')
84 _code = CONFIG.get('lock_ret_code')
85 self.code = safe_int(_code, self.code)
85 self.code = safe_int(_code, self.code)
86 self.title = self.explanation = ('Repository `%s` locked by '
86 self.title = self.explanation = ('Repository `%s` locked by '
87 'user `%s`' % (reponame, username))
87 'user `%s`' % (reponame, username))
88 super(HTTPLockedRC, self).__init__(*args, **kwargs)
88 super(HTTPLockedRC, self).__init__(*args, **kwargs)
89
90
91 class IMCCommitError(Exception):
92 pass
@@ -1,755 +1,759 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import re
27 import re
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31 import cStringIO
31 import cStringIO
32 import pkg_resources
32 import pkg_resources
33 from os.path import join as jn
33 from os.path import join as jn
34
34
35 from sqlalchemy import func
35 from sqlalchemy import func
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs.exceptions import RepositoryError
40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.nodes import FileNode
42 from rhodecode.lib.vcs.nodes import FileNode
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44
44
45 from rhodecode import BACKENDS
45 from rhodecode import BACKENDS
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
48 _set_extras
48 _set_extras
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
50 HasUserGroupPermissionAny
50 HasUserGroupPermissionAny
51 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
51 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
52 action_logger
52 action_logger
53 from rhodecode.model import BaseModel
53 from rhodecode.model import BaseModel
54 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
54 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
55 UserFollowing, UserLog, User, RepoGroup, PullRequest
55 UserFollowing, UserLog, User, RepoGroup, PullRequest
56 from rhodecode.lib.hooks import log_push_action
56 from rhodecode.lib.hooks import log_push_action
57 from rhodecode.lib.exceptions import NonRelativePathError
57 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class UserTemp(object):
62 class UserTemp(object):
63 def __init__(self, user_id):
63 def __init__(self, user_id):
64 self.user_id = user_id
64 self.user_id = user_id
65
65
66 def __repr__(self):
66 def __repr__(self):
67 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
67 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
68
68
69
69
70 class RepoTemp(object):
70 class RepoTemp(object):
71 def __init__(self, repo_id):
71 def __init__(self, repo_id):
72 self.repo_id = repo_id
72 self.repo_id = repo_id
73
73
74 def __repr__(self):
74 def __repr__(self):
75 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
75 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
76
76
77
77
78 class CachedRepoList(object):
78 class CachedRepoList(object):
79 """
79 """
80 Cached repo list, uses in-memory cache after initialization, that is
80 Cached repo list, uses in-memory cache after initialization, that is
81 super fast
81 super fast
82 """
82 """
83
83
84 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
84 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
85 self.db_repo_list = db_repo_list
85 self.db_repo_list = db_repo_list
86 self.repos_path = repos_path
86 self.repos_path = repos_path
87 self.order_by = order_by
87 self.order_by = order_by
88 self.reversed = (order_by or '').startswith('-')
88 self.reversed = (order_by or '').startswith('-')
89 if not perm_set:
89 if not perm_set:
90 perm_set = ['repository.read', 'repository.write',
90 perm_set = ['repository.read', 'repository.write',
91 'repository.admin']
91 'repository.admin']
92 self.perm_set = perm_set
92 self.perm_set = perm_set
93
93
94 def __len__(self):
94 def __len__(self):
95 return len(self.db_repo_list)
95 return len(self.db_repo_list)
96
96
97 def __repr__(self):
97 def __repr__(self):
98 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
98 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
99
99
100 def __iter__(self):
100 def __iter__(self):
101 # pre-propagated valid_cache_keys to save executing select statements
101 # pre-propagated valid_cache_keys to save executing select statements
102 # for each repo
102 # for each repo
103 valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
103 valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
104
104
105 for dbr in self.db_repo_list:
105 for dbr in self.db_repo_list:
106 scmr = dbr.scm_instance_cached(valid_cache_keys)
106 scmr = dbr.scm_instance_cached(valid_cache_keys)
107 # check permission at this level
107 # check permission at this level
108 if not HasRepoPermissionAny(
108 if not HasRepoPermissionAny(
109 *self.perm_set)(dbr.repo_name, 'get repo check'):
109 *self.perm_set)(dbr.repo_name, 'get repo check'):
110 continue
110 continue
111
111
112 try:
112 try:
113 last_change = scmr.last_change
113 last_change = scmr.last_change
114 tip = h.get_changeset_safe(scmr, 'tip')
114 tip = h.get_changeset_safe(scmr, 'tip')
115 except Exception:
115 except Exception:
116 log.error(
116 log.error(
117 '%s this repository is present in database but it '
117 '%s this repository is present in database but it '
118 'cannot be created as an scm instance, org_exc:%s'
118 'cannot be created as an scm instance, org_exc:%s'
119 % (dbr.repo_name, traceback.format_exc())
119 % (dbr.repo_name, traceback.format_exc())
120 )
120 )
121 continue
121 continue
122
122
123 tmp_d = {}
123 tmp_d = {}
124 tmp_d['name'] = dbr.repo_name
124 tmp_d['name'] = dbr.repo_name
125 tmp_d['name_sort'] = tmp_d['name'].lower()
125 tmp_d['name_sort'] = tmp_d['name'].lower()
126 tmp_d['raw_name'] = tmp_d['name'].lower()
126 tmp_d['raw_name'] = tmp_d['name'].lower()
127 tmp_d['description'] = dbr.description
127 tmp_d['description'] = dbr.description
128 tmp_d['description_sort'] = tmp_d['description'].lower()
128 tmp_d['description_sort'] = tmp_d['description'].lower()
129 tmp_d['last_change'] = last_change
129 tmp_d['last_change'] = last_change
130 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
130 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
131 tmp_d['tip'] = tip.raw_id
131 tmp_d['tip'] = tip.raw_id
132 tmp_d['tip_sort'] = tip.revision
132 tmp_d['tip_sort'] = tip.revision
133 tmp_d['rev'] = tip.revision
133 tmp_d['rev'] = tip.revision
134 tmp_d['contact'] = dbr.user.full_contact
134 tmp_d['contact'] = dbr.user.full_contact
135 tmp_d['contact_sort'] = tmp_d['contact']
135 tmp_d['contact_sort'] = tmp_d['contact']
136 tmp_d['owner_sort'] = tmp_d['contact']
136 tmp_d['owner_sort'] = tmp_d['contact']
137 tmp_d['repo_archives'] = list(scmr._get_archives())
137 tmp_d['repo_archives'] = list(scmr._get_archives())
138 tmp_d['last_msg'] = tip.message
138 tmp_d['last_msg'] = tip.message
139 tmp_d['author'] = tip.author
139 tmp_d['author'] = tip.author
140 tmp_d['dbrepo'] = dbr.get_dict()
140 tmp_d['dbrepo'] = dbr.get_dict()
141 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
141 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
142 yield tmp_d
142 yield tmp_d
143
143
144
144
145 class SimpleCachedRepoList(CachedRepoList):
145 class SimpleCachedRepoList(CachedRepoList):
146 """
146 """
147 Lighter version of CachedRepoList without the scm initialisation
147 Lighter version of CachedRepoList without the scm initialisation
148 """
148 """
149
149
150 def __iter__(self):
150 def __iter__(self):
151 for dbr in self.db_repo_list:
151 for dbr in self.db_repo_list:
152 # check permission at this level
152 # check permission at this level
153 if not HasRepoPermissionAny(
153 if not HasRepoPermissionAny(
154 *self.perm_set)(dbr.repo_name, 'get repo check'):
154 *self.perm_set)(dbr.repo_name, 'get repo check'):
155 continue
155 continue
156
156
157 tmp_d = {}
157 tmp_d = {}
158 tmp_d['name'] = dbr.repo_name
158 tmp_d['name'] = dbr.repo_name
159 tmp_d['name_sort'] = tmp_d['name'].lower()
159 tmp_d['name_sort'] = tmp_d['name'].lower()
160 tmp_d['raw_name'] = tmp_d['name'].lower()
160 tmp_d['raw_name'] = tmp_d['name'].lower()
161 tmp_d['description'] = dbr.description
161 tmp_d['description'] = dbr.description
162 tmp_d['description_sort'] = tmp_d['description'].lower()
162 tmp_d['description_sort'] = tmp_d['description'].lower()
163 tmp_d['dbrepo'] = dbr.get_dict()
163 tmp_d['dbrepo'] = dbr.get_dict()
164 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
164 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
165 yield tmp_d
165 yield tmp_d
166
166
167
167
168 class _PermCheckIterator(object):
168 class _PermCheckIterator(object):
169 def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
169 def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
170 """
170 """
171 Creates iterator from given list of objects, additionally
171 Creates iterator from given list of objects, additionally
172 checking permission for them from perm_set var
172 checking permission for them from perm_set var
173
173
174 :param obj_list: list of db objects
174 :param obj_list: list of db objects
175 :param obj_attr: attribute of object to pass into perm_checker
175 :param obj_attr: attribute of object to pass into perm_checker
176 :param perm_set: list of permissions to check
176 :param perm_set: list of permissions to check
177 :param perm_checker: callable to check permissions against
177 :param perm_checker: callable to check permissions against
178 """
178 """
179 self.obj_list = obj_list
179 self.obj_list = obj_list
180 self.obj_attr = obj_attr
180 self.obj_attr = obj_attr
181 self.perm_set = perm_set
181 self.perm_set = perm_set
182 self.perm_checker = perm_checker
182 self.perm_checker = perm_checker
183
183
184 def __len__(self):
184 def __len__(self):
185 return len(self.obj_list)
185 return len(self.obj_list)
186
186
187 def __repr__(self):
187 def __repr__(self):
188 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
188 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
189
189
190 def __iter__(self):
190 def __iter__(self):
191 for db_obj in self.obj_list:
191 for db_obj in self.obj_list:
192 # check permission at this level
192 # check permission at this level
193 name = getattr(db_obj, self.obj_attr, None)
193 name = getattr(db_obj, self.obj_attr, None)
194 if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
194 if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
195 continue
195 continue
196
196
197 yield db_obj
197 yield db_obj
198
198
199
199
200 class RepoList(_PermCheckIterator):
200 class RepoList(_PermCheckIterator):
201
201
202 def __init__(self, db_repo_list, perm_set=None):
202 def __init__(self, db_repo_list, perm_set=None):
203 if not perm_set:
203 if not perm_set:
204 perm_set = ['repository.read', 'repository.write', 'repository.admin']
204 perm_set = ['repository.read', 'repository.write', 'repository.admin']
205
205
206 super(RepoList, self).__init__(obj_list=db_repo_list,
206 super(RepoList, self).__init__(obj_list=db_repo_list,
207 obj_attr='repo_name', perm_set=perm_set,
207 obj_attr='repo_name', perm_set=perm_set,
208 perm_checker=HasRepoPermissionAny)
208 perm_checker=HasRepoPermissionAny)
209
209
210
210
211 class RepoGroupList(_PermCheckIterator):
211 class RepoGroupList(_PermCheckIterator):
212
212
213 def __init__(self, db_repo_group_list, perm_set=None):
213 def __init__(self, db_repo_group_list, perm_set=None):
214 if not perm_set:
214 if not perm_set:
215 perm_set = ['group.read', 'group.write', 'group.admin']
215 perm_set = ['group.read', 'group.write', 'group.admin']
216
216
217 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
217 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
218 obj_attr='group_name', perm_set=perm_set,
218 obj_attr='group_name', perm_set=perm_set,
219 perm_checker=HasReposGroupPermissionAny)
219 perm_checker=HasReposGroupPermissionAny)
220
220
221
221
222 class UserGroupList(_PermCheckIterator):
222 class UserGroupList(_PermCheckIterator):
223
223
224 def __init__(self, db_user_group_list, perm_set=None):
224 def __init__(self, db_user_group_list, perm_set=None):
225 if not perm_set:
225 if not perm_set:
226 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
226 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
227
227
228 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
228 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
229 obj_attr='users_group_name', perm_set=perm_set,
229 obj_attr='users_group_name', perm_set=perm_set,
230 perm_checker=HasUserGroupPermissionAny)
230 perm_checker=HasUserGroupPermissionAny)
231
231
232
232
233 class ScmModel(BaseModel):
233 class ScmModel(BaseModel):
234 """
234 """
235 Generic Scm Model
235 Generic Scm Model
236 """
236 """
237
237
238 def __get_repo(self, instance):
238 def __get_repo(self, instance):
239 cls = Repository
239 cls = Repository
240 if isinstance(instance, cls):
240 if isinstance(instance, cls):
241 return instance
241 return instance
242 elif isinstance(instance, int) or safe_str(instance).isdigit():
242 elif isinstance(instance, int) or safe_str(instance).isdigit():
243 return cls.get(instance)
243 return cls.get(instance)
244 elif isinstance(instance, basestring):
244 elif isinstance(instance, basestring):
245 return cls.get_by_repo_name(instance)
245 return cls.get_by_repo_name(instance)
246 elif instance:
246 elif instance:
247 raise Exception('given object must be int, basestr or Instance'
247 raise Exception('given object must be int, basestr or Instance'
248 ' of %s got %s' % (type(cls), type(instance)))
248 ' of %s got %s' % (type(cls), type(instance)))
249
249
250 @LazyProperty
250 @LazyProperty
251 def repos_path(self):
251 def repos_path(self):
252 """
252 """
253 Get's the repositories root path from database
253 Get's the repositories root path from database
254 """
254 """
255
255
256 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
256 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
257
257
258 return q.ui_value
258 return q.ui_value
259
259
260 def repo_scan(self, repos_path=None):
260 def repo_scan(self, repos_path=None):
261 """
261 """
262 Listing of repositories in given path. This path should not be a
262 Listing of repositories in given path. This path should not be a
263 repository itself. Return a dictionary of repository objects
263 repository itself. Return a dictionary of repository objects
264
264
265 :param repos_path: path to directory containing repositories
265 :param repos_path: path to directory containing repositories
266 """
266 """
267
267
268 if repos_path is None:
268 if repos_path is None:
269 repos_path = self.repos_path
269 repos_path = self.repos_path
270
270
271 log.info('scanning for repositories in %s' % repos_path)
271 log.info('scanning for repositories in %s' % repos_path)
272
272
273 baseui = make_ui('db')
273 baseui = make_ui('db')
274 repos = {}
274 repos = {}
275
275
276 for name, path in get_filesystem_repos(repos_path, recursive=True):
276 for name, path in get_filesystem_repos(repos_path, recursive=True):
277 # name need to be decomposed and put back together using the /
277 # name need to be decomposed and put back together using the /
278 # since this is internal storage separator for rhodecode
278 # since this is internal storage separator for rhodecode
279 name = Repository.normalize_repo_name(name)
279 name = Repository.normalize_repo_name(name)
280
280
281 try:
281 try:
282 if name in repos:
282 if name in repos:
283 raise RepositoryError('Duplicate repository name %s '
283 raise RepositoryError('Duplicate repository name %s '
284 'found in %s' % (name, path))
284 'found in %s' % (name, path))
285 else:
285 else:
286
286
287 klass = get_backend(path[0])
287 klass = get_backend(path[0])
288
288
289 if path[0] == 'hg' and path[0] in BACKENDS.keys():
289 if path[0] == 'hg' and path[0] in BACKENDS.keys():
290 repos[name] = klass(safe_str(path[1]), baseui=baseui)
290 repos[name] = klass(safe_str(path[1]), baseui=baseui)
291
291
292 if path[0] == 'git' and path[0] in BACKENDS.keys():
292 if path[0] == 'git' and path[0] in BACKENDS.keys():
293 repos[name] = klass(path[1])
293 repos[name] = klass(path[1])
294 except OSError:
294 except OSError:
295 continue
295 continue
296 log.debug('found %s paths with repositories' % (len(repos)))
296 log.debug('found %s paths with repositories' % (len(repos)))
297 return repos
297 return repos
298
298
299 def get_repos(self, all_repos=None, sort_key=None, simple=False):
299 def get_repos(self, all_repos=None, sort_key=None, simple=False):
300 """
300 """
301 Get all repos from db and for each repo create it's
301 Get all repos from db and for each repo create it's
302 backend instance and fill that backed with information from database
302 backend instance and fill that backed with information from database
303
303
304 :param all_repos: list of repository names as strings
304 :param all_repos: list of repository names as strings
305 give specific repositories list, good for filtering
305 give specific repositories list, good for filtering
306
306
307 :param sort_key: initial sorting of repos
307 :param sort_key: initial sorting of repos
308 :param simple: use SimpleCachedList - one without the SCM info
308 :param simple: use SimpleCachedList - one without the SCM info
309 """
309 """
310 if all_repos is None:
310 if all_repos is None:
311 all_repos = self.sa.query(Repository)\
311 all_repos = self.sa.query(Repository)\
312 .filter(Repository.group_id == None)\
312 .filter(Repository.group_id == None)\
313 .order_by(func.lower(Repository.repo_name)).all()
313 .order_by(func.lower(Repository.repo_name)).all()
314 if simple:
314 if simple:
315 repo_iter = SimpleCachedRepoList(all_repos,
315 repo_iter = SimpleCachedRepoList(all_repos,
316 repos_path=self.repos_path,
316 repos_path=self.repos_path,
317 order_by=sort_key)
317 order_by=sort_key)
318 else:
318 else:
319 repo_iter = CachedRepoList(all_repos,
319 repo_iter = CachedRepoList(all_repos,
320 repos_path=self.repos_path,
320 repos_path=self.repos_path,
321 order_by=sort_key)
321 order_by=sort_key)
322
322
323 return repo_iter
323 return repo_iter
324
324
325 def get_repos_groups(self, all_groups=None):
325 def get_repos_groups(self, all_groups=None):
326 if all_groups is None:
326 if all_groups is None:
327 all_groups = RepoGroup.query()\
327 all_groups = RepoGroup.query()\
328 .filter(RepoGroup.group_parent_id == None).all()
328 .filter(RepoGroup.group_parent_id == None).all()
329 return [x for x in RepoGroupList(all_groups)]
329 return [x for x in RepoGroupList(all_groups)]
330
330
331 def mark_for_invalidation(self, repo_name):
331 def mark_for_invalidation(self, repo_name):
332 """
332 """
333 Mark caches of this repo invalid in the database.
333 Mark caches of this repo invalid in the database.
334
334
335 :param repo_name: the repo for which caches should be marked invalid
335 :param repo_name: the repo for which caches should be marked invalid
336 """
336 """
337 CacheInvalidation.set_invalidate(repo_name)
337 CacheInvalidation.set_invalidate(repo_name)
338 repo = Repository.get_by_repo_name(repo_name)
338 repo = Repository.get_by_repo_name(repo_name)
339 if repo:
339 if repo:
340 repo.update_changeset_cache()
340 repo.update_changeset_cache()
341
341
342 def toggle_following_repo(self, follow_repo_id, user_id):
342 def toggle_following_repo(self, follow_repo_id, user_id):
343
343
344 f = self.sa.query(UserFollowing)\
344 f = self.sa.query(UserFollowing)\
345 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
345 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
346 .filter(UserFollowing.user_id == user_id).scalar()
346 .filter(UserFollowing.user_id == user_id).scalar()
347
347
348 if f is not None:
348 if f is not None:
349 try:
349 try:
350 self.sa.delete(f)
350 self.sa.delete(f)
351 action_logger(UserTemp(user_id),
351 action_logger(UserTemp(user_id),
352 'stopped_following_repo',
352 'stopped_following_repo',
353 RepoTemp(follow_repo_id))
353 RepoTemp(follow_repo_id))
354 return
354 return
355 except Exception:
355 except Exception:
356 log.error(traceback.format_exc())
356 log.error(traceback.format_exc())
357 raise
357 raise
358
358
359 try:
359 try:
360 f = UserFollowing()
360 f = UserFollowing()
361 f.user_id = user_id
361 f.user_id = user_id
362 f.follows_repo_id = follow_repo_id
362 f.follows_repo_id = follow_repo_id
363 self.sa.add(f)
363 self.sa.add(f)
364
364
365 action_logger(UserTemp(user_id),
365 action_logger(UserTemp(user_id),
366 'started_following_repo',
366 'started_following_repo',
367 RepoTemp(follow_repo_id))
367 RepoTemp(follow_repo_id))
368 except Exception:
368 except Exception:
369 log.error(traceback.format_exc())
369 log.error(traceback.format_exc())
370 raise
370 raise
371
371
372 def toggle_following_user(self, follow_user_id, user_id):
372 def toggle_following_user(self, follow_user_id, user_id):
373 f = self.sa.query(UserFollowing)\
373 f = self.sa.query(UserFollowing)\
374 .filter(UserFollowing.follows_user_id == follow_user_id)\
374 .filter(UserFollowing.follows_user_id == follow_user_id)\
375 .filter(UserFollowing.user_id == user_id).scalar()
375 .filter(UserFollowing.user_id == user_id).scalar()
376
376
377 if f is not None:
377 if f is not None:
378 try:
378 try:
379 self.sa.delete(f)
379 self.sa.delete(f)
380 return
380 return
381 except Exception:
381 except Exception:
382 log.error(traceback.format_exc())
382 log.error(traceback.format_exc())
383 raise
383 raise
384
384
385 try:
385 try:
386 f = UserFollowing()
386 f = UserFollowing()
387 f.user_id = user_id
387 f.user_id = user_id
388 f.follows_user_id = follow_user_id
388 f.follows_user_id = follow_user_id
389 self.sa.add(f)
389 self.sa.add(f)
390 except Exception:
390 except Exception:
391 log.error(traceback.format_exc())
391 log.error(traceback.format_exc())
392 raise
392 raise
393
393
394 def is_following_repo(self, repo_name, user_id, cache=False):
394 def is_following_repo(self, repo_name, user_id, cache=False):
395 r = self.sa.query(Repository)\
395 r = self.sa.query(Repository)\
396 .filter(Repository.repo_name == repo_name).scalar()
396 .filter(Repository.repo_name == repo_name).scalar()
397
397
398 f = self.sa.query(UserFollowing)\
398 f = self.sa.query(UserFollowing)\
399 .filter(UserFollowing.follows_repository == r)\
399 .filter(UserFollowing.follows_repository == r)\
400 .filter(UserFollowing.user_id == user_id).scalar()
400 .filter(UserFollowing.user_id == user_id).scalar()
401
401
402 return f is not None
402 return f is not None
403
403
404 def is_following_user(self, username, user_id, cache=False):
404 def is_following_user(self, username, user_id, cache=False):
405 u = User.get_by_username(username)
405 u = User.get_by_username(username)
406
406
407 f = self.sa.query(UserFollowing)\
407 f = self.sa.query(UserFollowing)\
408 .filter(UserFollowing.follows_user == u)\
408 .filter(UserFollowing.follows_user == u)\
409 .filter(UserFollowing.user_id == user_id).scalar()
409 .filter(UserFollowing.user_id == user_id).scalar()
410
410
411 return f is not None
411 return f is not None
412
412
413 def get_followers(self, repo):
413 def get_followers(self, repo):
414 repo = self._get_repo(repo)
414 repo = self._get_repo(repo)
415
415
416 return self.sa.query(UserFollowing)\
416 return self.sa.query(UserFollowing)\
417 .filter(UserFollowing.follows_repository == repo).count()
417 .filter(UserFollowing.follows_repository == repo).count()
418
418
419 def get_forks(self, repo):
419 def get_forks(self, repo):
420 repo = self._get_repo(repo)
420 repo = self._get_repo(repo)
421 return self.sa.query(Repository)\
421 return self.sa.query(Repository)\
422 .filter(Repository.fork == repo).count()
422 .filter(Repository.fork == repo).count()
423
423
424 def get_pull_requests(self, repo):
424 def get_pull_requests(self, repo):
425 repo = self._get_repo(repo)
425 repo = self._get_repo(repo)
426 return self.sa.query(PullRequest)\
426 return self.sa.query(PullRequest)\
427 .filter(PullRequest.other_repo == repo)\
427 .filter(PullRequest.other_repo == repo)\
428 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
428 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
429
429
430 def mark_as_fork(self, repo, fork, user):
430 def mark_as_fork(self, repo, fork, user):
431 repo = self.__get_repo(repo)
431 repo = self.__get_repo(repo)
432 fork = self.__get_repo(fork)
432 fork = self.__get_repo(fork)
433 if fork and repo.repo_id == fork.repo_id:
433 if fork and repo.repo_id == fork.repo_id:
434 raise Exception("Cannot set repository as fork of itself")
434 raise Exception("Cannot set repository as fork of itself")
435 repo.fork = fork
435 repo.fork = fork
436 self.sa.add(repo)
436 self.sa.add(repo)
437 return repo
437 return repo
438
438
439 def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
439 def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
440 action=None):
440 action=None):
441 from rhodecode import CONFIG
441 from rhodecode import CONFIG
442 from rhodecode.lib.base import _get_ip_addr
442 from rhodecode.lib.base import _get_ip_addr
443 try:
443 try:
444 from pylons import request
444 from pylons import request
445 environ = request.environ
445 environ = request.environ
446 except TypeError:
446 except TypeError:
447 # we might use this outside of request context, let's fake the
447 # we might use this outside of request context, let's fake the
448 # environ data
448 # environ data
449 from webob import Request
449 from webob import Request
450 environ = Request.blank('').environ
450 environ = Request.blank('').environ
451 extras = {
451 extras = {
452 'ip': _get_ip_addr(environ),
452 'ip': _get_ip_addr(environ),
453 'username': username,
453 'username': username,
454 'action': action or 'push_local',
454 'action': action or 'push_local',
455 'repository': repo_name,
455 'repository': repo_name,
456 'scm': repo_alias,
456 'scm': repo_alias,
457 'config': CONFIG['__file__'],
457 'config': CONFIG['__file__'],
458 'server_url': get_server_url(environ),
458 'server_url': get_server_url(environ),
459 'make_lock': None,
459 'make_lock': None,
460 'locked_by': [None, None]
460 'locked_by': [None, None]
461 }
461 }
462 _set_extras(extras)
462 _set_extras(extras)
463
463
464 def _handle_push(self, repo, username, action, repo_name, revisions):
464 def _handle_push(self, repo, username, action, repo_name, revisions):
465 """
465 """
466 Triggers push action hooks
466 Triggers push action hooks
467
467
468 :param repo: SCM repo
468 :param repo: SCM repo
469 :param username: username who pushes
469 :param username: username who pushes
470 :param action: push/push_loca/push_remote
470 :param action: push/push_loca/push_remote
471 :param repo_name: name of repo
471 :param repo_name: name of repo
472 :param revisions: list of revisions that we pushed
472 :param revisions: list of revisions that we pushed
473 """
473 """
474 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
474 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
475 _scm_repo = repo._repo
475 _scm_repo = repo._repo
476 # trigger push hook
476 # trigger push hook
477 if repo.alias == 'hg':
477 if repo.alias == 'hg':
478 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
478 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
479 elif repo.alias == 'git':
479 elif repo.alias == 'git':
480 log_push_action(None, _scm_repo, _git_revs=revisions)
480 log_push_action(None, _scm_repo, _git_revs=revisions)
481
481
482 def _get_IMC_module(self, scm_type):
482 def _get_IMC_module(self, scm_type):
483 """
483 """
484 Returns InMemoryCommit class based on scm_type
484 Returns InMemoryCommit class based on scm_type
485
485
486 :param scm_type:
486 :param scm_type:
487 """
487 """
488 if scm_type == 'hg':
488 if scm_type == 'hg':
489 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset
489 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset
490 return MercurialInMemoryChangeset
490 return MercurialInMemoryChangeset
491
491
492 if scm_type == 'git':
492 if scm_type == 'git':
493 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset
493 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset
494 return GitInMemoryChangeset
494 return GitInMemoryChangeset
495
495
496 raise Exception('Invalid scm_type, must be one of hg,git got %s'
496 raise Exception('Invalid scm_type, must be one of hg,git got %s'
497 % (scm_type,))
497 % (scm_type,))
498
498
499 def pull_changes(self, repo, username):
499 def pull_changes(self, repo, username):
500 dbrepo = self.__get_repo(repo)
500 dbrepo = self.__get_repo(repo)
501 clone_uri = dbrepo.clone_uri
501 clone_uri = dbrepo.clone_uri
502 if not clone_uri:
502 if not clone_uri:
503 raise Exception("This repository doesn't have a clone uri")
503 raise Exception("This repository doesn't have a clone uri")
504
504
505 repo = dbrepo.scm_instance
505 repo = dbrepo.scm_instance
506 repo_name = dbrepo.repo_name
506 repo_name = dbrepo.repo_name
507 try:
507 try:
508 if repo.alias == 'git':
508 if repo.alias == 'git':
509 repo.fetch(clone_uri)
509 repo.fetch(clone_uri)
510 # git doesn't really have something like post-fetch action
510 # git doesn't really have something like post-fetch action
511 # we fake that now. #TODO: extract fetched revisions somehow
511 # we fake that now. #TODO: extract fetched revisions somehow
512 # here
512 # here
513 self._handle_push(repo,
513 self._handle_push(repo,
514 username=username,
514 username=username,
515 action='push_remote',
515 action='push_remote',
516 repo_name=repo_name,
516 repo_name=repo_name,
517 revisions=[])
517 revisions=[])
518 else:
518 else:
519 self._handle_rc_scm_extras(username, dbrepo.repo_name,
519 self._handle_rc_scm_extras(username, dbrepo.repo_name,
520 repo.alias, action='push_remote')
520 repo.alias, action='push_remote')
521 repo.pull(clone_uri)
521 repo.pull(clone_uri)
522
522
523 self.mark_for_invalidation(repo_name)
523 self.mark_for_invalidation(repo_name)
524 except Exception:
524 except Exception:
525 log.error(traceback.format_exc())
525 log.error(traceback.format_exc())
526 raise
526 raise
527
527
528 def commit_change(self, repo, repo_name, cs, user, author, message,
528 def commit_change(self, repo, repo_name, cs, user, author, message,
529 content, f_path):
529 content, f_path):
530 """
530 """
531 Commits changes
531 Commits changes
532
532
533 :param repo: SCM instance
533 :param repo: SCM instance
534
534
535 """
535 """
536 user = self._get_user(user)
536 user = self._get_user(user)
537 IMC = self._get_IMC_module(repo.alias)
537 IMC = self._get_IMC_module(repo.alias)
538
538
539 # decoding here will force that we have proper encoded values
539 # decoding here will force that we have proper encoded values
540 # in any other case this will throw exceptions and deny commit
540 # in any other case this will throw exceptions and deny commit
541 content = safe_str(content)
541 content = safe_str(content)
542 path = safe_str(f_path)
542 path = safe_str(f_path)
543 # message and author needs to be unicode
543 # message and author needs to be unicode
544 # proper backend should then translate that into required type
544 # proper backend should then translate that into required type
545 message = safe_unicode(message)
545 message = safe_unicode(message)
546 author = safe_unicode(author)
546 author = safe_unicode(author)
547 imc = IMC(repo)
547 imc = IMC(repo)
548 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
548 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
549 tip = imc.commit(message=message,
549 try:
550 author=author,
550 tip = imc.commit(message=message, author=author,
551 parents=[cs], branch=cs.branch)
551 parents=[cs], branch=cs.branch)
552
552 except Exception, e:
553 self.mark_for_invalidation(repo_name)
553 log.error(traceback.format_exc())
554 raise IMCCommitError(str(e))
555 finally:
556 # always clear caches, if commit fails we want fresh object also
557 self.mark_for_invalidation(repo_name)
554 self._handle_push(repo,
558 self._handle_push(repo,
555 username=user.username,
559 username=user.username,
556 action='push_local',
560 action='push_local',
557 repo_name=repo_name,
561 repo_name=repo_name,
558 revisions=[tip.raw_id])
562 revisions=[tip.raw_id])
559 return tip
563 return tip
560
564
561 def create_nodes(self, user, repo, message, nodes, parent_cs=None,
565 def create_nodes(self, user, repo, message, nodes, parent_cs=None,
562 author=None, trigger_push_hook=True):
566 author=None, trigger_push_hook=True):
563 """
567 """
564 Commits given multiple nodes into repo
568 Commits given multiple nodes into repo
565
569
566 :param user: RhodeCode User object or user_id, the commiter
570 :param user: RhodeCode User object or user_id, the commiter
567 :param repo: RhodeCode Repository object
571 :param repo: RhodeCode Repository object
568 :param message: commit message
572 :param message: commit message
569 :param nodes: mapping {filename:{'content':content},...}
573 :param nodes: mapping {filename:{'content':content},...}
570 :param parent_cs: parent changeset, can be empty than it's initial commit
574 :param parent_cs: parent changeset, can be empty than it's initial commit
571 :param author: author of commit, cna be different that commiter only for git
575 :param author: author of commit, cna be different that commiter only for git
572 :param trigger_push_hook: trigger push hooks
576 :param trigger_push_hook: trigger push hooks
573
577
574 :returns: new commited changeset
578 :returns: new commited changeset
575 """
579 """
576
580
577 user = self._get_user(user)
581 user = self._get_user(user)
578 scm_instance = repo.scm_instance_no_cache()
582 scm_instance = repo.scm_instance_no_cache()
579
583
580 processed_nodes = []
584 processed_nodes = []
581 for f_path in nodes:
585 for f_path in nodes:
582 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
586 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
583 raise NonRelativePathError('%s is not an relative path' % f_path)
587 raise NonRelativePathError('%s is not an relative path' % f_path)
584 if f_path:
588 if f_path:
585 f_path = os.path.normpath(f_path)
589 f_path = os.path.normpath(f_path)
586 content = nodes[f_path]['content']
590 content = nodes[f_path]['content']
587 f_path = safe_str(f_path)
591 f_path = safe_str(f_path)
588 # decoding here will force that we have proper encoded values
592 # decoding here will force that we have proper encoded values
589 # in any other case this will throw exceptions and deny commit
593 # in any other case this will throw exceptions and deny commit
590 if isinstance(content, (basestring,)):
594 if isinstance(content, (basestring,)):
591 content = safe_str(content)
595 content = safe_str(content)
592 elif isinstance(content, (file, cStringIO.OutputType,)):
596 elif isinstance(content, (file, cStringIO.OutputType,)):
593 content = content.read()
597 content = content.read()
594 else:
598 else:
595 raise Exception('Content is of unrecognized type %s' % (
599 raise Exception('Content is of unrecognized type %s' % (
596 type(content)
600 type(content)
597 ))
601 ))
598 processed_nodes.append((f_path, content))
602 processed_nodes.append((f_path, content))
599
603
600 message = safe_unicode(message)
604 message = safe_unicode(message)
601 commiter = user.full_contact
605 commiter = user.full_contact
602 author = safe_unicode(author) if author else commiter
606 author = safe_unicode(author) if author else commiter
603
607
604 IMC = self._get_IMC_module(scm_instance.alias)
608 IMC = self._get_IMC_module(scm_instance.alias)
605 imc = IMC(scm_instance)
609 imc = IMC(scm_instance)
606
610
607 if not parent_cs:
611 if not parent_cs:
608 parent_cs = EmptyChangeset(alias=scm_instance.alias)
612 parent_cs = EmptyChangeset(alias=scm_instance.alias)
609
613
610 if isinstance(parent_cs, EmptyChangeset):
614 if isinstance(parent_cs, EmptyChangeset):
611 # EmptyChangeset means we we're editing empty repository
615 # EmptyChangeset means we we're editing empty repository
612 parents = None
616 parents = None
613 else:
617 else:
614 parents = [parent_cs]
618 parents = [parent_cs]
615 # add multiple nodes
619 # add multiple nodes
616 for path, content in processed_nodes:
620 for path, content in processed_nodes:
617 imc.add(FileNode(path, content=content))
621 imc.add(FileNode(path, content=content))
618
622
619 tip = imc.commit(message=message,
623 tip = imc.commit(message=message,
620 author=author,
624 author=author,
621 parents=parents,
625 parents=parents,
622 branch=parent_cs.branch)
626 branch=parent_cs.branch)
623
627
624 self.mark_for_invalidation(repo.repo_name)
628 self.mark_for_invalidation(repo.repo_name)
625 if trigger_push_hook:
629 if trigger_push_hook:
626 self._handle_push(scm_instance,
630 self._handle_push(scm_instance,
627 username=user.username,
631 username=user.username,
628 action='push_local',
632 action='push_local',
629 repo_name=repo.repo_name,
633 repo_name=repo.repo_name,
630 revisions=[tip.raw_id])
634 revisions=[tip.raw_id])
631 return tip
635 return tip
632
636
633 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
637 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
634 """
638 """
635 recursive walk in root dir and return a set of all path in that dir
639 recursive walk in root dir and return a set of all path in that dir
636 based on repository walk function
640 based on repository walk function
637
641
638 :param repo_name: name of repository
642 :param repo_name: name of repository
639 :param revision: revision for which to list nodes
643 :param revision: revision for which to list nodes
640 :param root_path: root path to list
644 :param root_path: root path to list
641 :param flat: return as a list, if False returns a dict with decription
645 :param flat: return as a list, if False returns a dict with decription
642
646
643 """
647 """
644 _files = list()
648 _files = list()
645 _dirs = list()
649 _dirs = list()
646 try:
650 try:
647 _repo = self.__get_repo(repo_name)
651 _repo = self.__get_repo(repo_name)
648 changeset = _repo.scm_instance.get_changeset(revision)
652 changeset = _repo.scm_instance.get_changeset(revision)
649 root_path = root_path.lstrip('/')
653 root_path = root_path.lstrip('/')
650 for topnode, dirs, files in changeset.walk(root_path):
654 for topnode, dirs, files in changeset.walk(root_path):
651 for f in files:
655 for f in files:
652 _files.append(f.path if flat else {"name": f.path,
656 _files.append(f.path if flat else {"name": f.path,
653 "type": "file"})
657 "type": "file"})
654 for d in dirs:
658 for d in dirs:
655 _dirs.append(d.path if flat else {"name": d.path,
659 _dirs.append(d.path if flat else {"name": d.path,
656 "type": "dir"})
660 "type": "dir"})
657 except RepositoryError:
661 except RepositoryError:
658 log.debug(traceback.format_exc())
662 log.debug(traceback.format_exc())
659 raise
663 raise
660
664
661 return _dirs, _files
665 return _dirs, _files
662
666
663 def get_unread_journal(self):
667 def get_unread_journal(self):
664 return self.sa.query(UserLog).count()
668 return self.sa.query(UserLog).count()
665
669
666 def get_repo_landing_revs(self, repo=None):
670 def get_repo_landing_revs(self, repo=None):
667 """
671 """
668 Generates select option with tags branches and bookmarks (for hg only)
672 Generates select option with tags branches and bookmarks (for hg only)
669 grouped by type
673 grouped by type
670
674
671 :param repo:
675 :param repo:
672 """
676 """
673
677
674 hist_l = []
678 hist_l = []
675 choices = []
679 choices = []
676 repo = self.__get_repo(repo)
680 repo = self.__get_repo(repo)
677 hist_l.append(['tip', _('latest tip')])
681 hist_l.append(['tip', _('latest tip')])
678 choices.append('tip')
682 choices.append('tip')
679 if not repo:
683 if not repo:
680 return choices, hist_l
684 return choices, hist_l
681
685
682 repo = repo.scm_instance
686 repo = repo.scm_instance
683
687
684 branches_group = ([(k, k) for k, v in
688 branches_group = ([(k, k) for k, v in
685 repo.branches.iteritems()], _("Branches"))
689 repo.branches.iteritems()], _("Branches"))
686 hist_l.append(branches_group)
690 hist_l.append(branches_group)
687 choices.extend([x[0] for x in branches_group[0]])
691 choices.extend([x[0] for x in branches_group[0]])
688
692
689 if repo.alias == 'hg':
693 if repo.alias == 'hg':
690 bookmarks_group = ([(k, k) for k, v in
694 bookmarks_group = ([(k, k) for k, v in
691 repo.bookmarks.iteritems()], _("Bookmarks"))
695 repo.bookmarks.iteritems()], _("Bookmarks"))
692 hist_l.append(bookmarks_group)
696 hist_l.append(bookmarks_group)
693 choices.extend([x[0] for x in bookmarks_group[0]])
697 choices.extend([x[0] for x in bookmarks_group[0]])
694
698
695 tags_group = ([(k, k) for k, v in
699 tags_group = ([(k, k) for k, v in
696 repo.tags.iteritems()], _("Tags"))
700 repo.tags.iteritems()], _("Tags"))
697 hist_l.append(tags_group)
701 hist_l.append(tags_group)
698 choices.extend([x[0] for x in tags_group[0]])
702 choices.extend([x[0] for x in tags_group[0]])
699
703
700 return choices, hist_l
704 return choices, hist_l
701
705
702 def install_git_hook(self, repo, force_create=False):
706 def install_git_hook(self, repo, force_create=False):
703 """
707 """
704 Creates a rhodecode hook inside a git repository
708 Creates a rhodecode hook inside a git repository
705
709
706 :param repo: Instance of VCS repo
710 :param repo: Instance of VCS repo
707 :param force_create: Create even if same name hook exists
711 :param force_create: Create even if same name hook exists
708 """
712 """
709
713
710 loc = jn(repo.path, 'hooks')
714 loc = jn(repo.path, 'hooks')
711 if not repo.bare:
715 if not repo.bare:
712 loc = jn(repo.path, '.git', 'hooks')
716 loc = jn(repo.path, '.git', 'hooks')
713 if not os.path.isdir(loc):
717 if not os.path.isdir(loc):
714 os.makedirs(loc)
718 os.makedirs(loc)
715
719
716 tmpl_post = pkg_resources.resource_string(
720 tmpl_post = pkg_resources.resource_string(
717 'rhodecode', jn('config', 'post_receive_tmpl.py')
721 'rhodecode', jn('config', 'post_receive_tmpl.py')
718 )
722 )
719 tmpl_pre = pkg_resources.resource_string(
723 tmpl_pre = pkg_resources.resource_string(
720 'rhodecode', jn('config', 'pre_receive_tmpl.py')
724 'rhodecode', jn('config', 'pre_receive_tmpl.py')
721 )
725 )
722
726
723 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
727 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
724 _hook_file = jn(loc, '%s-receive' % h_type)
728 _hook_file = jn(loc, '%s-receive' % h_type)
725 _rhodecode_hook = False
729 _rhodecode_hook = False
726 log.debug('Installing git hook in repo %s' % repo)
730 log.debug('Installing git hook in repo %s' % repo)
727 if os.path.exists(_hook_file):
731 if os.path.exists(_hook_file):
728 # let's take a look at this hook, maybe it's rhodecode ?
732 # let's take a look at this hook, maybe it's rhodecode ?
729 log.debug('hook exists, checking if it is from rhodecode')
733 log.debug('hook exists, checking if it is from rhodecode')
730 with open(_hook_file, 'rb') as f:
734 with open(_hook_file, 'rb') as f:
731 data = f.read()
735 data = f.read()
732 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
736 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
733 % 'RC_HOOK_VER').search(data)
737 % 'RC_HOOK_VER').search(data)
734 if matches:
738 if matches:
735 try:
739 try:
736 ver = matches.groups()[0]
740 ver = matches.groups()[0]
737 log.debug('got %s it is rhodecode' % (ver))
741 log.debug('got %s it is rhodecode' % (ver))
738 _rhodecode_hook = True
742 _rhodecode_hook = True
739 except Exception:
743 except Exception:
740 log.error(traceback.format_exc())
744 log.error(traceback.format_exc())
741 else:
745 else:
742 # there is no hook in this dir, so we want to create one
746 # there is no hook in this dir, so we want to create one
743 _rhodecode_hook = True
747 _rhodecode_hook = True
744
748
745 if _rhodecode_hook or force_create:
749 if _rhodecode_hook or force_create:
746 log.debug('writing %s hook file !' % (h_type,))
750 log.debug('writing %s hook file !' % (h_type,))
747 try:
751 try:
748 with open(_hook_file, 'wb') as f:
752 with open(_hook_file, 'wb') as f:
749 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
753 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
750 f.write(tmpl)
754 f.write(tmpl)
751 os.chmod(_hook_file, 0755)
755 os.chmod(_hook_file, 0755)
752 except IOError, e:
756 except IOError, e:
753 log.error('error writing %s: %s' % (_hook_file, e))
757 log.error('error writing %s: %s' % (_hook_file, e))
754 else:
758 else:
755 log.debug('skipping writing hook file')
759 log.debug('skipping writing hook file')
General Comments 0
You need to be logged in to leave comments. Login now