# -*- coding: utf-8 -*-
"""
rhodecode.controllers.files
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Files controller for RhodeCode
:created_on: Apr 21, 2010
:author: marcink
:copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
:license: GPLv3, see COPYING for more details.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
import os
import logging
import traceback
import tempfile
import shutil
from pylons import request, response, tmpl_context as c, url
from pylons.i18n.translation import _
from pylons.controllers.util import redirect
from rhodecode.lib.utils import jsonify, action_logger
from rhodecode.lib import diffs
from rhodecode.lib import helpers as h
from rhodecode.lib.compat import OrderedDict, json
from rhodecode.lib.compat import OrderedDict
from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
str2bool
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.vcs.backends.base import EmptyChangeset
from rhodecode.lib.vcs.conf import settings
from rhodecode.lib.vcs.exceptions import RepositoryError, \
ChangesetDoesNotExistError, EmptyRepositoryError, \
ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
NodeDoesNotExistError, ChangesetError, NodeError
from rhodecode.lib.vcs.nodes import FileNode
from rhodecode.model.repo import RepoModel
from rhodecode.model.scm import ScmModel
from rhodecode.model.db import Repository
from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
_context_url, get_line_ctx, get_ignore_ws
from webob.exc import HTTPNotFound
from rhodecode.lib.exceptions import NonRelativePathError
from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
log = logging.getLogger(__name__)
class FilesController(BaseRepoController):
def __before__(self):
super(FilesController, self).__before__()
c.cut_off_limit = self.cut_off_limit
def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
Safe way to get changeset if error occur it redirects to tip with
proper message
:param rev: revision to fetch
:param repo_name: repo name to redirect after
try:
return c.rhodecode_repo.get_changeset(rev)
except EmptyRepositoryError, e:
if not redirect_after:
return None
url_ = url('files_add_home',
repo_name=c.repo_name,
revision=0, f_path='')
add_new = h.link_to(_('Click here to add new file'), url_)
h.flash(h.literal(_('There are no files yet %s') % add_new),
category='warning')
redirect(h.url('summary_home', repo_name=repo_name))
except RepositoryError, e: # including ChangesetDoesNotExistError
h.flash(str(e), category='error')
raise HTTPNotFound()
def __get_filenode_or_redirect(self, repo_name, cs, path):
Returns file_node, if error occurs or given path is directory,
it'll redirect to top level path
:param repo_name: repo_name
:param cs: given changeset
:param path: path to lookup
file_node = cs.get_node(path)
if file_node.is_dir():
raise RepositoryError('given path is a directory')
except RepositoryError, e:
return file_node
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def index(self, repo_name, revision, f_path, annotate=False):
# redirect to given revision from form if given
post_revision = request.POST.get('at_rev', None)
if post_revision:
cs = self.__get_cs_or_redirect(post_revision, repo_name)
c.changeset = self.__get_cs_or_redirect(revision, repo_name)
c.branch = request.GET.get('branch', None)
c.f_path = f_path
c.annotate = annotate
cur_rev = c.changeset.revision
# prev link
prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
c.url_prev = url('files_home', repo_name=c.repo_name,
revision=prev_rev.raw_id, f_path=f_path)
if c.branch:
c.url_prev += '?branch=%s' % c.branch
except (ChangesetDoesNotExistError, VCSError):
c.url_prev = '#'
# next link
next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
c.url_next = url('files_home', repo_name=c.repo_name,
revision=next_rev.raw_id, f_path=f_path)
c.url_next += '?branch=%s' % c.branch
c.url_next = '#'
# files or dirs
c.file = c.changeset.get_node(f_path)
if c.file.is_file():
c.load_full_history = False
file_last_cs = c.file.last_changeset
c.file_changeset = (c.changeset
if c.changeset.revision < file_last_cs.revision
else file_last_cs)
#determine if we're on branch head
_branches = c.rhodecode_repo.branches
c.on_branch_head = revision in _branches.keys() + _branches.values()
_hist = []
c.file_history = []
if c.load_full_history:
c.file_history, _hist = self._get_node_history(c.changeset, f_path)
c.authors = []
for a in set([x.author for x in _hist]):
c.authors.append((h.email(a), h.person(a)))
else:
c.authors = c.file_history = []
if request.environ.get('HTTP_X_PARTIAL_XHR'):
return render('files/files_ypjax.html')
return render('files/files.html')
def history(self, repo_name, revision, f_path, annotate=False):
return render('files/files_history_box.html')
def rawfile(self, repo_name, revision, f_path):
cs = self.__get_cs_or_redirect(revision, repo_name)
file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
response.content_disposition = 'attachment; filename=%s' % \
safe_str(f_path.split(Repository.url_sep())[-1])
response.content_type = file_node.mimetype
return file_node.content
def raw(self, repo_name, revision, f_path):
raw_mimetype_mapping = {
# map original mimetype to a mimetype used for "show as raw"
# you can also provide a content-disposition to override the
# default "attachment" disposition.
# orig_type: (new_type, new_dispo)
# show images inline:
'image/x-icon': ('image/x-icon', 'inline'),
'image/png': ('image/png', 'inline'),
'image/gif': ('image/gif', 'inline'),
'image/jpeg': ('image/jpeg', 'inline'),
'image/svg+xml': ('image/svg+xml', 'inline'),
}
mimetype = file_node.mimetype
mimetype, dispo = raw_mimetype_mapping[mimetype]
except KeyError:
# we don't know anything special about this, handle it safely
if file_node.is_binary:
# do same as download raw for binary files
mimetype, dispo = 'application/octet-stream', 'attachment'
# do not just use the original mimetype, but force text/plain,
# otherwise it would serve text/html and that might be unsafe.
# Note: underlying vcs library fakes text/plain mimetype if the
# mimetype can not be determined and it thinks it is not
# binary.This might lead to erroneous text display in some
# cases, but helps in other cases, like with text files
# without extension.
mimetype, dispo = 'text/plain', 'inline'
if dispo == 'attachment':
dispo = 'attachment; filename=%s' % \
safe_str(f_path.split(os.sep)[-1])
response.content_disposition = dispo
response.content_type = mimetype
@HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
def edit(self, repo_name, revision, f_path):
repo = c.rhodecode_db_repo
if repo.enable_locking and repo.locked[0]:
h.flash(_('This repository is has been locked by %s on %s')
% (h.person_by_id(repo.locked[0]),
h.fmt_date(h.time_to_datetime(repo.locked[1]))),
'warning')
return redirect(h.url('files_home',
repo_name=repo_name, revision='tip'))
# check if revision is a branch identifier- basically we cannot
# create multiple heads via file editing
_branches = repo.scm_instance.branches
# check if revision is a branch name or branch hash
if revision not in _branches.keys() + _branches.values():
h.flash(_('You can only edit files with revision '
'being a valid branch '), category='warning')
repo_name=repo_name, revision='tip',
f_path=f_path))
r_post = request.POST
c.cs = self.__get_cs_or_redirect(revision, repo_name)
c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
if c.file.is_binary:
return redirect(url('files_home', repo_name=c.repo_name,
revision=c.cs.raw_id, f_path=f_path))
c.default_message = _('Edited file %s via RhodeCode') % (f_path)
if r_post:
old_content = c.file.content
sl = old_content.splitlines(1)
first_line = sl[0] if sl else ''
# modes: 0 - Unix, 1 - Mac, 2 - DOS
mode = detect_mode(first_line, 0)
content = convert_line_endings(r_post.get('content', ''), mode)
message = r_post.get('message') or c.default_message
author = self.rhodecode_user.full_contact
if content == old_content:
h.flash(_('No changes'), category='warning')
return redirect(url('changeset_home', repo_name=c.repo_name,
revision='tip'))
self.scm_model.commit_change(repo=c.rhodecode_repo,
repo_name=repo_name, cs=c.cs,
user=self.rhodecode_user.user_id,
author=author, message=message,
content=content, f_path=f_path)
h.flash(_('Successfully committed to %s') % f_path,
category='success')
except Exception:
log.error(traceback.format_exc())
h.flash(_('Error occurred during commit'), category='error')
return redirect(url('changeset_home',
repo_name=c.repo_name, revision='tip'))
return render('files/files_edit.html')
def add(self, repo_name, revision, f_path):
repo = Repository.get_by_repo_name(repo_name)
c.cs = self.__get_cs_or_redirect(revision, repo_name,
redirect_after=False)
if c.cs is None:
c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
c.default_message = (_('Added file via RhodeCode'))
unix_mode = 0
content = convert_line_endings(r_post.get('content', ''), unix_mode)
filename = r_post.get('filename')
location = r_post.get('location', '')
file_obj = r_post.get('upload_file', None)
if file_obj is not None and hasattr(file_obj, 'filename'):
filename = file_obj.filename
content = file_obj.file
if not content:
h.flash(_('No content'), category='warning')
if not filename:
h.flash(_('No filename'), category='warning')
#strip all crap out of file, just leave the basename
filename = os.path.basename(filename)
node_path = os.path.join(location, filename)
nodes = {
node_path: {
'content': content
self.scm_model.create_nodes(
user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
message=message,
nodes=nodes,
parent_cs=c.cs,
author=author,
)
h.flash(_('Successfully committed to %s') % node_path,
except NonRelativePathError, e:
h.flash(_('Location must be relative path and must not '
'contain .. in path'), category='warning')
except (NodeError, NodeAlreadyExistsError), e:
h.flash(_(e), category='error')
return render('files/files_add.html')
def archivefile(self, repo_name, fname):
fileformat = None
revision = None
ext = None
subrepos = request.GET.get('subrepos') == 'true'
for a_type, ext_data in settings.ARCHIVE_SPECS.items():
archive_spec = fname.split(ext_data[1])
if len(archive_spec) == 2 and archive_spec[1] == '':
fileformat = a_type or ext_data[1]
revision = archive_spec[0]
ext = ext_data[1]
dbrepo = RepoModel().get_by_repo_name(repo_name)
if not dbrepo.enable_downloads:
return _('Downloads disabled')
if c.rhodecode_repo.alias == 'hg':
# patch and reset hooks section of UI config to not run any
# hooks on fetching archives with subrepos
for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
cs = c.rhodecode_repo.get_changeset(revision)
content_type = settings.ARCHIVE_SPECS[fileformat][0]
except ChangesetDoesNotExistError:
return _('Unknown revision %s') % revision
except EmptyRepositoryError:
return _('Empty repository')
except (ImproperArchiveTypeError, KeyError):
return _('Unknown archive type')
# archive cache
from rhodecode import CONFIG
rev_name = cs.raw_id[:12]
archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
safe_str(rev_name), ext)
use_cached_archive = False # defines if we use cached version of archive
archive_cache_enabled = CONFIG.get('archive_cache_dir')
if not subrepos and archive_cache_enabled:
#check if we it's ok to write
if not os.path.isdir(CONFIG['archive_cache_dir']):
os.makedirs(CONFIG['archive_cache_dir'])
cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
if os.path.isfile(cached_archive_path):
log.debug('Found cached archive in %s' % cached_archive_path)
fd, archive = None, cached_archive_path
use_cached_archive = True
log.debug('Archive %s is not yet cached' % (archive_name))
if not use_cached_archive:
#generate new archive
fd, archive = tempfile.mkstemp()
t = open(archive, 'wb')
log.debug('Creating new temp archive in %s' % archive)
cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
if archive_cache_enabled:
#if we generated the archive and use cache rename that
log.debug('Storing new archive in %s' % cached_archive_path)
shutil.move(archive, cached_archive_path)
archive = cached_archive_path
finally:
t.close()
def get_chunked_archive(archive):
stream = open(archive, 'rb')
while True:
data = stream.read(16 * 1024)
if not data:
stream.close()
if fd: # fd means we used temporary file
os.close(fd)
if not archive_cache_enabled:
log.debug('Destroing temp archive %s' % archive)
os.remove(archive)
break
yield data
# store download action
action_logger(user=c.rhodecode_user,
action='user_downloaded_archive:%s' % (archive_name),
repo=repo_name, ipaddr=self.ip_addr, commit=True)
response.content_disposition = str('attachment; filename=%s' % (archive_name))
response.content_type = str(content_type)
return get_chunked_archive(archive)
def diff(self, repo_name, f_path):
ignore_whitespace = request.GET.get('ignorews') == '1'
line_context = request.GET.get('context', 3)
diff1 = request.GET.get('diff1', '')
diff2 = request.GET.get('diff2', '')
c.action = request.GET.get('diff')
c.no_changes = diff1 == diff2
c.big_diff = False
c.anchor_url = anchor_url
c.ignorews_url = _ignorews_url
rhodecode.lib.exceptions
~~~~~~~~~~~~~~~~~~~~~~~~
Set of custom exceptions used in RhodeCode
:created_on: Nov 17, 2010
from webob.exc import HTTPClientError
class LdapUsernameError(Exception):
pass
class LdapPasswordError(Exception):
class LdapConnectionError(Exception):
class LdapImportError(Exception):
class DefaultUserException(Exception):
class UserOwnsReposException(Exception):
class UserGroupsAssignedException(Exception):
class StatusChangeOnClosedPullRequestError(Exception):
class AttachedForksError(Exception):
class RepoGroupAssignmentError(Exception):
class NonRelativePathError(Exception):
class HTTPLockedRC(HTTPClientError):
Special Exception For locked Repos in RhodeCode, the return code can
be overwritten by _code keyword argument passed into constructors
code = 423
title = explanation = 'Repository Locked'
def __init__(self, reponame, username, *args, **kwargs):
from rhodecode.lib.utils2 import safe_int
_code = CONFIG.get('lock_ret_code')
self.code = safe_int(_code, self.code)
self.title = self.explanation = ('Repository `%s` locked by '
'user `%s`' % (reponame, username))
super(HTTPLockedRC, self).__init__(*args, **kwargs)
class IMCCommitError(Exception):
rhodecode.model.scm
~~~~~~~~~~~~~~~~~~~
Scm model for RhodeCode
:created_on: Apr 9, 2010
import re
import time
import cStringIO
import pkg_resources
from os.path import join as jn
from sqlalchemy import func
import rhodecode
from rhodecode.lib.vcs import get_backend
from rhodecode.lib.vcs.exceptions import RepositoryError
from rhodecode.lib.vcs.utils.lazy import LazyProperty
from rhodecode import BACKENDS
from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
_set_extras
from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
HasUserGroupPermissionAny
from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
action_logger
from rhodecode.model import BaseModel
from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
UserFollowing, UserLog, User, RepoGroup, PullRequest
from rhodecode.lib.hooks import log_push_action
class UserTemp(object):
def __init__(self, user_id):
self.user_id = user_id
def __repr__(self):
return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
class RepoTemp(object):
def __init__(self, repo_id):
self.repo_id = repo_id
return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
class CachedRepoList(object):
Cached repo list, uses in-memory cache after initialization, that is
super fast
def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
self.db_repo_list = db_repo_list
self.repos_path = repos_path
self.order_by = order_by
self.reversed = (order_by or '').startswith('-')
if not perm_set:
perm_set = ['repository.read', 'repository.write',
'repository.admin']
self.perm_set = perm_set
def __len__(self):
return len(self.db_repo_list)
return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
def __iter__(self):
# pre-propagated valid_cache_keys to save executing select statements
# for each repo
valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
for dbr in self.db_repo_list:
scmr = dbr.scm_instance_cached(valid_cache_keys)
# check permission at this level
if not HasRepoPermissionAny(
*self.perm_set)(dbr.repo_name, 'get repo check'):
continue
last_change = scmr.last_change
tip = h.get_changeset_safe(scmr, 'tip')
log.error(
'%s this repository is present in database but it '
'cannot be created as an scm instance, org_exc:%s'
% (dbr.repo_name, traceback.format_exc())
tmp_d = {}
tmp_d['name'] = dbr.repo_name
tmp_d['name_sort'] = tmp_d['name'].lower()
tmp_d['raw_name'] = tmp_d['name'].lower()
tmp_d['description'] = dbr.description
tmp_d['description_sort'] = tmp_d['description'].lower()
tmp_d['last_change'] = last_change
tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
tmp_d['tip'] = tip.raw_id
tmp_d['tip_sort'] = tip.revision
tmp_d['rev'] = tip.revision
tmp_d['contact'] = dbr.user.full_contact
tmp_d['contact_sort'] = tmp_d['contact']
tmp_d['owner_sort'] = tmp_d['contact']
tmp_d['repo_archives'] = list(scmr._get_archives())
tmp_d['last_msg'] = tip.message
tmp_d['author'] = tip.author
tmp_d['dbrepo'] = dbr.get_dict()
tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
yield tmp_d
class SimpleCachedRepoList(CachedRepoList):
Lighter version of CachedRepoList without the scm initialisation
class _PermCheckIterator(object):
def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
Creates iterator from given list of objects, additionally
checking permission for them from perm_set var
:param obj_list: list of db objects
:param obj_attr: attribute of object to pass into perm_checker
:param perm_set: list of permissions to check
:param perm_checker: callable to check permissions against
self.obj_list = obj_list
self.obj_attr = obj_attr
self.perm_checker = perm_checker
return len(self.obj_list)
for db_obj in self.obj_list:
name = getattr(db_obj, self.obj_attr, None)
if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
yield db_obj
class RepoList(_PermCheckIterator):
def __init__(self, db_repo_list, perm_set=None):
perm_set = ['repository.read', 'repository.write', 'repository.admin']
super(RepoList, self).__init__(obj_list=db_repo_list,
obj_attr='repo_name', perm_set=perm_set,
perm_checker=HasRepoPermissionAny)
class RepoGroupList(_PermCheckIterator):
def __init__(self, db_repo_group_list, perm_set=None):
perm_set = ['group.read', 'group.write', 'group.admin']
super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
obj_attr='group_name', perm_set=perm_set,
perm_checker=HasReposGroupPermissionAny)
class UserGroupList(_PermCheckIterator):
def __init__(self, db_user_group_list, perm_set=None):
perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
super(UserGroupList, self).__init__(obj_list=db_user_group_list,
obj_attr='users_group_name', perm_set=perm_set,
perm_checker=HasUserGroupPermissionAny)
class ScmModel(BaseModel):
Generic Scm Model
def __get_repo(self, instance):
cls = Repository
if isinstance(instance, cls):
return instance
elif isinstance(instance, int) or safe_str(instance).isdigit():
return cls.get(instance)
elif isinstance(instance, basestring):
return cls.get_by_repo_name(instance)
elif instance:
raise Exception('given object must be int, basestr or Instance'
' of %s got %s' % (type(cls), type(instance)))
@@ -357,389 +357,393 @@ class ScmModel(BaseModel):
raise
f = UserFollowing()
f.user_id = user_id
f.follows_repo_id = follow_repo_id
self.sa.add(f)
action_logger(UserTemp(user_id),
'started_following_repo',
RepoTemp(follow_repo_id))
def toggle_following_user(self, follow_user_id, user_id):
f = self.sa.query(UserFollowing)\
.filter(UserFollowing.follows_user_id == follow_user_id)\
.filter(UserFollowing.user_id == user_id).scalar()
if f is not None:
self.sa.delete(f)
return
f.follows_user_id = follow_user_id
def is_following_repo(self, repo_name, user_id, cache=False):
r = self.sa.query(Repository)\
.filter(Repository.repo_name == repo_name).scalar()
.filter(UserFollowing.follows_repository == r)\
return f is not None
def is_following_user(self, username, user_id, cache=False):
u = User.get_by_username(username)
.filter(UserFollowing.follows_user == u)\
def get_followers(self, repo):
repo = self._get_repo(repo)
return self.sa.query(UserFollowing)\
.filter(UserFollowing.follows_repository == repo).count()
def get_forks(self, repo):
return self.sa.query(Repository)\
.filter(Repository.fork == repo).count()
def get_pull_requests(self, repo):
return self.sa.query(PullRequest)\
.filter(PullRequest.other_repo == repo)\
.filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
def mark_as_fork(self, repo, fork, user):
repo = self.__get_repo(repo)
fork = self.__get_repo(fork)
if fork and repo.repo_id == fork.repo_id:
raise Exception("Cannot set repository as fork of itself")
repo.fork = fork
self.sa.add(repo)
return repo
def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
action=None):
from rhodecode.lib.base import _get_ip_addr
from pylons import request
environ = request.environ
except TypeError:
# we might use this outside of request context, let's fake the
# environ data
from webob import Request
environ = Request.blank('').environ
extras = {
'ip': _get_ip_addr(environ),
'username': username,
'action': action or 'push_local',
'repository': repo_name,
'scm': repo_alias,
'config': CONFIG['__file__'],
'server_url': get_server_url(environ),
'make_lock': None,
'locked_by': [None, None]
_set_extras(extras)
def _handle_push(self, repo, username, action, repo_name, revisions):
Triggers push action hooks
:param repo: SCM repo
:param username: username who pushes
:param action: push/push_loca/push_remote
:param repo_name: name of repo
:param revisions: list of revisions that we pushed
self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
_scm_repo = repo._repo
# trigger push hook
if repo.alias == 'hg':
log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
elif repo.alias == 'git':
log_push_action(None, _scm_repo, _git_revs=revisions)
def _get_IMC_module(self, scm_type):
Returns InMemoryCommit class based on scm_type
:param scm_type:
if scm_type == 'hg':
from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset
return MercurialInMemoryChangeset
if scm_type == 'git':
from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset
return GitInMemoryChangeset
raise Exception('Invalid scm_type, must be one of hg,git got %s'
% (scm_type,))
def pull_changes(self, repo, username):
dbrepo = self.__get_repo(repo)
clone_uri = dbrepo.clone_uri
if not clone_uri:
raise Exception("This repository doesn't have a clone uri")
repo = dbrepo.scm_instance
repo_name = dbrepo.repo_name
if repo.alias == 'git':
repo.fetch(clone_uri)
# git doesn't really have something like post-fetch action
# we fake that now. #TODO: extract fetched revisions somehow
# here
self._handle_push(repo,
username=username,
action='push_remote',
repo_name=repo_name,
revisions=[])
self._handle_rc_scm_extras(username, dbrepo.repo_name,
repo.alias, action='push_remote')
repo.pull(clone_uri)
self.mark_for_invalidation(repo_name)
def commit_change(self, repo, repo_name, cs, user, author, message,
content, f_path):
Commits changes
:param repo: SCM instance
user = self._get_user(user)
IMC = self._get_IMC_module(repo.alias)
# decoding here will force that we have proper encoded values
# in any other case this will throw exceptions and deny commit
content = safe_str(content)
path = safe_str(f_path)
# message and author needs to be unicode
# proper backend should then translate that into required type
message = safe_unicode(message)
author = safe_unicode(author)
imc = IMC(repo)
imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
tip = imc.commit(message=message,
parents=[cs], branch=cs.branch)
tip = imc.commit(message=message, author=author,
except Exception, e:
raise IMCCommitError(str(e))
# always clear caches, if commit fails we want fresh object also
username=user.username,
action='push_local',
revisions=[tip.raw_id])
return tip
def create_nodes(self, user, repo, message, nodes, parent_cs=None,
author=None, trigger_push_hook=True):
Commits given multiple nodes into repo
:param user: RhodeCode User object or user_id, the commiter
:param repo: RhodeCode Repository object
:param message: commit message
:param nodes: mapping {filename:{'content':content},...}
:param parent_cs: parent changeset, can be empty than it's initial commit
:param author: author of commit, cna be different that commiter only for git
:param trigger_push_hook: trigger push hooks
:returns: new commited changeset
scm_instance = repo.scm_instance_no_cache()
processed_nodes = []
for f_path in nodes:
if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
raise NonRelativePathError('%s is not an relative path' % f_path)
if f_path:
f_path = os.path.normpath(f_path)
content = nodes[f_path]['content']
f_path = safe_str(f_path)
if isinstance(content, (basestring,)):
elif isinstance(content, (file, cStringIO.OutputType,)):
content = content.read()
raise Exception('Content is of unrecognized type %s' % (
type(content)
))
processed_nodes.append((f_path, content))
commiter = user.full_contact
author = safe_unicode(author) if author else commiter
IMC = self._get_IMC_module(scm_instance.alias)
imc = IMC(scm_instance)
if not parent_cs:
parent_cs = EmptyChangeset(alias=scm_instance.alias)
if isinstance(parent_cs, EmptyChangeset):
# EmptyChangeset means we we're editing empty repository
parents = None
parents = [parent_cs]
# add multiple nodes
for path, content in processed_nodes:
imc.add(FileNode(path, content=content))
parents=parents,
branch=parent_cs.branch)
self.mark_for_invalidation(repo.repo_name)
if trigger_push_hook:
self._handle_push(scm_instance,
repo_name=repo.repo_name,
def get_nodes(self, repo_name, revision, root_path='/', flat=True):
recursive walk in root dir and return a set of all path in that dir
based on repository walk function
:param repo_name: name of repository
:param revision: revision for which to list nodes
:param root_path: root path to list
:param flat: return as a list, if False returns a dict with decription
_files = list()
_dirs = list()
_repo = self.__get_repo(repo_name)
changeset = _repo.scm_instance.get_changeset(revision)
root_path = root_path.lstrip('/')
for topnode, dirs, files in changeset.walk(root_path):
for f in files:
_files.append(f.path if flat else {"name": f.path,
"type": "file"})
for d in dirs:
_dirs.append(d.path if flat else {"name": d.path,
"type": "dir"})
except RepositoryError:
log.debug(traceback.format_exc())
return _dirs, _files
def get_unread_journal(self):
return self.sa.query(UserLog).count()
def get_repo_landing_revs(self, repo=None):
Generates select option with tags branches and bookmarks (for hg only)
grouped by type
:param repo:
hist_l = []
choices = []
hist_l.append(['tip', _('latest tip')])
choices.append('tip')
if not repo:
return choices, hist_l
repo = repo.scm_instance
branches_group = ([(k, k) for k, v in
repo.branches.iteritems()], _("Branches"))
hist_l.append(branches_group)
choices.extend([x[0] for x in branches_group[0]])
bookmarks_group = ([(k, k) for k, v in
repo.bookmarks.iteritems()], _("Bookmarks"))
hist_l.append(bookmarks_group)
choices.extend([x[0] for x in bookmarks_group[0]])
tags_group = ([(k, k) for k, v in
repo.tags.iteritems()], _("Tags"))
hist_l.append(tags_group)
choices.extend([x[0] for x in tags_group[0]])
def install_git_hook(self, repo, force_create=False):
Creates a rhodecode hook inside a git repository
:param repo: Instance of VCS repo
:param force_create: Create even if same name hook exists
loc = jn(repo.path, 'hooks')
if not repo.bare:
loc = jn(repo.path, '.git', 'hooks')
if not os.path.isdir(loc):
os.makedirs(loc)
tmpl_post = pkg_resources.resource_string(
'rhodecode', jn('config', 'post_receive_tmpl.py')
tmpl_pre = pkg_resources.resource_string(
'rhodecode', jn('config', 'pre_receive_tmpl.py')
for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
_hook_file = jn(loc, '%s-receive' % h_type)
_rhodecode_hook = False
log.debug('Installing git hook in repo %s' % repo)
if os.path.exists(_hook_file):
# let's take a look at this hook, maybe it's rhodecode ?
log.debug('hook exists, checking if it is from rhodecode')
with open(_hook_file, 'rb') as f:
data = f.read()
matches = re.compile(r'(?:%s)\s*=\s*(.*)'
% 'RC_HOOK_VER').search(data)
if matches:
ver = matches.groups()[0]
log.debug('got %s it is rhodecode' % (ver))
_rhodecode_hook = True
# there is no hook in this dir, so we want to create one
if _rhodecode_hook or force_create:
Status change: