##// END OF EJS Templates
vcs: Only allow 'pull' actions on shadow repositories....
vcs: Only allow 'pull' actions on shadow repositories. We are exposing the shadow repositories of pull requests to allow easy CI integration or users to access the pull request shadow repo for investigating on it. But we don't want someone/something to push changes to a shadow repository.

File last commit:

r64:0b85876a default
r891:910a0be0 default
Show More
compare.py
267 lines | 10.6 KiB | text/x-python | PythonLexer
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2016 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# 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 Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
"""
Compare controller for showing differences between two commits/refs/tags etc.
"""
import logging
from webob.exc import HTTPBadRequest
from pylons import request, tmpl_context as c, url
from pylons.controllers.util import redirect
from pylons.i18n.translation import _
from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
from rhodecode.lib import helpers as h
from rhodecode.lib import diffs
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.utils import safe_str
from rhodecode.lib.utils2 import safe_unicode, str2bool
from rhodecode.lib.vcs.exceptions import (
EmptyRepositoryError, RepositoryError, RepositoryRequirementError)
from rhodecode.model.db import Repository, ChangesetStatus
log = logging.getLogger(__name__)
class CompareController(BaseRepoController):
def __before__(self):
super(CompareController, self).__before__()
def _get_commit_or_redirect(
self, ref, ref_type, repo, redirect_after=True, partial=False):
"""
This is a safe way to get a commit. If an error occurs it
redirects to a commit with a proper message. If partial is set
then it does not do redirect raise and throws an exception instead.
"""
try:
return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
except EmptyRepositoryError:
if not redirect_after:
return repo.scm_instance().EMPTY_COMMIT
h.flash(h.literal(_('There are no commits yet')),
category='warning')
redirect(url('summary_home', repo_name=repo.repo_name))
except RepositoryError as e:
msg = safe_str(e)
log.exception(msg)
h.flash(msg, category='warning')
if not partial:
redirect(h.url('summary_home', repo_name=repo.repo_name))
raise HTTPBadRequest()
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def index(self, repo_name):
c.compare_home = True
c.commit_ranges = []
c.files = []
c.limited_diff = False
source_repo = c.rhodecode_db_repo.repo_name
target_repo = request.GET.get('target_repo', source_repo)
c.source_repo = Repository.get_by_repo_name(source_repo)
c.target_repo = Repository.get_by_repo_name(target_repo)
c.source_ref = c.target_ref = _('Select commit')
c.source_ref_type = ""
c.target_ref_type = ""
c.commit_statuses = ChangesetStatus.STATUSES
c.preview_mode = False
return render('compare/compare_diff.html')
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def compare(self, repo_name, source_ref_type, source_ref,
target_ref_type, target_ref):
# source_ref will be evaluated in source_repo
source_repo_name = c.rhodecode_db_repo.repo_name
source_path, source_id = parse_path_ref(source_ref)
# target_ref will be evaluated in target_repo
target_repo_name = request.GET.get('target_repo', source_repo_name)
target_path, target_id = parse_path_ref(target_ref)
c.commit_statuses = ChangesetStatus.STATUSES
# if merge is True
# Show what changes since the shared ancestor commit of target/source
# the source would get if it was merged with target. Only commits
# which are in target but not in source will be shown.
merge = str2bool(request.GET.get('merge'))
# if merge is False
# Show a raw diff of source/target refs even if no ancestor exists
# c.fulldiff disables cut_off_limit
c.fulldiff = str2bool(request.GET.get('fulldiff'))
# if partial, returns just compare_commits.html (commits log)
partial = request.is_xhr
# swap url for compare_diff page
c.swap_url = h.url(
'compare_url',
repo_name=target_repo_name,
source_ref_type=target_ref_type,
source_ref=target_ref,
target_repo=source_repo_name,
target_ref_type=source_ref_type,
target_ref=source_ref,
merge=merge and '1' or '')
source_repo = Repository.get_by_repo_name(source_repo_name)
target_repo = Repository.get_by_repo_name(target_repo_name)
if source_repo is None:
msg = _('Could not find the original repo: %(repo)s') % {
'repo': source_repo}
log.error(msg)
h.flash(msg, category='error')
return redirect(url('compare_home', repo_name=c.repo_name))
if target_repo is None:
msg = _('Could not find the other repo: %(repo)s') % {
'repo': target_repo_name}
log.error(msg)
h.flash(msg, category='error')
return redirect(url('compare_home', repo_name=c.repo_name))
source_alias = source_repo.scm_instance().alias
target_alias = target_repo.scm_instance().alias
if source_alias != target_alias:
msg = _('The comparison of two different kinds of remote repos '
'is not available')
log.error(msg)
h.flash(msg, category='error')
return redirect(url('compare_home', repo_name=c.repo_name))
source_commit = self._get_commit_or_redirect(
ref=source_id, ref_type=source_ref_type, repo=source_repo,
partial=partial)
target_commit = self._get_commit_or_redirect(
ref=target_id, ref_type=target_ref_type, repo=target_repo,
partial=partial)
c.compare_home = False
c.source_repo = source_repo
c.target_repo = target_repo
c.source_ref = source_ref
c.target_ref = target_ref
c.source_ref_type = source_ref_type
c.target_ref_type = target_ref_type
source_scm = source_repo.scm_instance()
target_scm = target_repo.scm_instance()
pre_load = ["author", "branch", "date", "message"]
c.ancestor = None
try:
c.commit_ranges = source_scm.compare(
source_commit.raw_id, target_commit.raw_id,
target_scm, merge, pre_load=pre_load)
if merge:
c.ancestor = source_scm.get_common_ancestor(
source_commit.raw_id, target_commit.raw_id, target_scm)
except RepositoryRequirementError:
msg = _('Could not compare repos with different '
'large file settings')
log.error(msg)
if partial:
return msg
h.flash(msg, category='error')
return redirect(url('compare_home', repo_name=c.repo_name))
c.statuses = c.rhodecode_db_repo.statuses(
[x.raw_id for x in c.commit_ranges])
if partial: # for PR ajax commits loader
if not c.ancestor:
return '' # cannot merge if there is no ancestor
return render('compare/compare_commits.html')
if c.ancestor:
# case we want a simple diff without incoming commits,
# previewing what will be merged.
# Make the diff on target repo (which is known to have target_ref)
log.debug('Using ancestor %s as source_ref instead of %s'
% (c.ancestor, source_ref))
source_repo = target_repo
source_commit = target_repo.get_commit(commit_id=c.ancestor)
# diff_limit will cut off the whole diff if the limit is applied
# otherwise it will just hide the big files from the front-end
diff_limit = self.cut_off_limit_diff
file_limit = self.cut_off_limit_file
log.debug('calculating diff between '
'source_ref:%s and target_ref:%s for repo `%s`',
source_commit, target_commit,
safe_unicode(source_repo.scm_instance().path))
if source_commit.repository != target_commit.repository:
msg = _(
"Repositories unrelated. "
"Cannot compare commit %(commit1)s from repository %(repo1)s "
"with commit %(commit2)s from repository %(repo2)s.") % {
'commit1': h.show_id(source_commit),
'repo1': source_repo.repo_name,
'commit2': h.show_id(target_commit),
'repo2': target_repo.repo_name,
}
h.flash(msg, category='error')
raise HTTPBadRequest()
txtdiff = source_repo.scm_instance().get_diff(
commit1=source_commit, commit2=target_commit,
path1=source_path, path=target_path)
diff_processor = diffs.DiffProcessor(
txtdiff, format='gitdiff', diff_limit=diff_limit,
file_limit=file_limit, show_full_diff=c.fulldiff)
_parsed = diff_processor.prepare()
c.limited_diff = False
if isinstance(_parsed, diffs.LimitedDiffContainer):
c.limited_diff = True
c.files = []
c.changes = {}
c.lines_added = 0
c.lines_deleted = 0
for f in _parsed:
st = f['stats']
if not st['binary']:
c.lines_added += st['added']
c.lines_deleted += st['deleted']
fid = h.FID('', f['filename'])
c.files.append([fid, f['operation'], f['filename'], f['stats'], f])
htmldiff = diff_processor.as_html(
enable_comments=False, parsed_lines=[f])
c.changes[fid] = [f['operation'], f['filename'], htmldiff, f]
c.preview_mode = merge
return render('compare/compare_diff.html')