Changeset - b8c69e4deacd
[Not reviewed]
default
0 6 0
Thomas De Schampheleire - 11 years ago 2015-01-24 22:07:35
thomas.de_schampheleire@alcatel-lucent.com
remotes: add support to clone from Mercurial repositories over ssh

This commit adds support to clone a remote Mercurial repository over ssh.

Interactive password authentication is not implemented, nor is support for
pbulic key authentication with passphrases; the repository should be
accessible using bare ssh key authentication.
For this reason, the ssh options -oBatchMode=yes and -oIdentitiesOnly=yes
are added to the ui.ssh setting of Mercurial.
6 files changed with 17 insertions and 5 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/utils.py
Show inline comments
 
@@ -360,48 +360,53 @@ def make_ui(read_from='file', path=None,
 
                log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
 
                baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
 

	
 
    elif read_from == 'db':
 
        sa = meta.Session()
 
        ret = sa.query(Ui).all()
 

	
 
        hg_ui = ret
 
        for ui_ in hg_ui:
 
            if ui_.ui_active:
 
                ui_val = safe_str(ui_.ui_value)
 
                if ui_.ui_section == 'hooks' and BRAND != 'kallithea' and ui_val.startswith('python:' + BRAND + '.lib.hooks.'):
 
                    ui_val = ui_val.replace('python:' + BRAND + '.lib.hooks.', 'python:kallithea.lib.hooks.')
 
                log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
 
                          ui_.ui_key, ui_val)
 
                baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
 
                                 ui_val)
 
            if ui_.ui_key == 'push_ssl':
 
                # force set push_ssl requirement to False, kallithea
 
                # handles that
 
                baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
 
                                 False)
 
        if clear_session:
 
            meta.Session.remove()
 

	
 
        # prevent interactive questions for ssh password / passphrase
 
        ssh = baseui.config('ui', 'ssh', default='ssh')
 
        baseui.setconfig('ui', 'ssh', '%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
 

	
 
    return baseui
 

	
 

	
 
def set_app_settings(config):
 
    """
 
    Updates pylons config with new settings from database
 

	
 
    :param config:
 
    """
 
    hgsettings = Setting.get_app_settings()
 

	
 
    for k, v in hgsettings.items():
 
        config[k] = v
 

	
 

	
 
def set_vcs_config(config):
 
    """
 
    Patch VCS config with some Kallithea specific stuff
 

	
 
    :param config: kallithea.CONFIG
 
    """
 
    import kallithea
 
    from kallithea.lib.vcs import conf
 
    from kallithea.lib.utils2 import aslist
kallithea/lib/vcs/backends/hg/repository.py
Show inline comments
 
@@ -10,49 +10,49 @@
 
"""
 

	
 
import os
 
import time
 
import urllib
 
import urllib2
 
import logging
 
import datetime
 

	
 
from kallithea.lib.vcs.backends.base import BaseRepository, CollectionGenerator
 

	
 
from kallithea.lib.vcs.exceptions import (
 
    BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
 
    RepositoryError, VCSError, TagAlreadyExistError, TagDoesNotExistError
 
)
 
from kallithea.lib.vcs.utils import (
 
    author_email, author_name, date_fromtimestamp, makedate, safe_unicode, safe_str,
 
)
 
from kallithea.lib.vcs.utils.lazy import LazyProperty
 
from kallithea.lib.vcs.utils.ordered_dict import OrderedDict
 
from kallithea.lib.vcs.utils.paths import abspath
 
from kallithea.lib.vcs.utils.hgcompat import (
 
    ui, nullid, match, patch, diffopts, clone, get_contact, pull,
 
    localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
 
    httpbasicauthhandler, httpdigestauthhandler, peer, httppeer
 
    httpbasicauthhandler, httpdigestauthhandler, peer, httppeer, sshpeer
 
)
 

	
 
from .changeset import MercurialChangeset
 
from .inmemory import MercurialInMemoryChangeset
 
from .workdir import MercurialWorkdir
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class MercurialRepository(BaseRepository):
 
    """
 
    Mercurial repository backend
 
    """
 
    DEFAULT_BRANCH_NAME = 'default'
 
    scm = 'hg'
 

	
 
    def __init__(self, repo_path, create=False, baseui=None, src_url=None,
 
                 update_after_clone=False):
 
        """
 
        Raises RepositoryError if repository could not be find at the given
 
        ``repo_path``.
 

	
 
        :param repo_path: local path of the repository
 
        :param create=False: if set to True, would try to create repository if
 
@@ -261,48 +261,54 @@ class MercurialRepository(BaseRepository
 
            file_filter = match(self.path, '', [path])
 
        else:
 
            file_filter = None
 

	
 
        return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
 
                          opts=diffopts(git=True,
 
                                        ignorews=ignore_whitespace,
 
                                        context=context)))
 

	
 
    @classmethod
 
    def _check_url(cls, url, repoui=None):
 
        """
 
        Function will check given url and try to verify if it's a valid
 
        link. Sometimes it may happened that mercurial will issue basic
 
        auth request that can cause whole API to hang when used from python
 
        or other external calls.
 

	
 
        On failures it'll raise urllib2.HTTPError, exception is also thrown
 
        when the return code is non 200
 
        """
 
        # check first if it's not an local url
 
        if os.path.isdir(url) or url.startswith('file:'):
 
            return True
 

	
 
        if url.startswith('ssh:'):
 
            # in case of invalid uri or authentication issues, sshpeer will
 
            # throw an exception.
 
            sshpeer(repoui or ui.ui(), url).lookup('tip')
 
            return True
 

	
 
        url_prefix = None
 
        if '+' in url[:url.find('://')]:
 
            url_prefix, url = url.split('+', 1)
 

	
 
        handlers = []
 
        url_obj = hg_url(url)
 
        test_uri, authinfo = url_obj.authinfo()
 
        url_obj.passwd = '*****'
 
        cleaned_uri = str(url_obj)
 

	
 
        if authinfo:
 
            #create a password manager
 
            passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
 
            passmgr.add_password(*authinfo)
 

	
 
            handlers.extend((httpbasicauthhandler(passmgr),
 
                             httpdigestauthhandler(passmgr)))
 

	
 
        o = urllib2.build_opener(*handlers)
 
        o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
 
                        ('Accept', 'application/mercurial-0.1')]
 

	
 
        q = {"cmd": 'between'}
 
        q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
kallithea/lib/vcs/utils/hgcompat.py
Show inline comments
 
@@ -4,39 +4,40 @@ Mercurial libs compatibility
 

	
 
import mercurial
 
import mercurial.demandimport
 
## patch demandimport, due to bug in mercurial when it always triggers demandimport.enable()
 
mercurial.demandimport.enable = lambda *args, **kwargs: 1
 
from mercurial import archival, merge as hg_merge, patch, ui
 
from mercurial import discovery
 
from mercurial import localrepo
 
from mercurial import unionrepo
 
from mercurial import scmutil
 
from mercurial import config
 
from mercurial.commands import clone, nullid, pull
 
from mercurial.context import memctx, memfilectx
 
from mercurial.error import RepoError, RepoLookupError, Abort
 
from mercurial.hgweb import hgweb_mod
 
from mercurial.hgweb.common import get_contact
 
from mercurial.localrepo import localrepository
 
from mercurial.match import match
 
from mercurial.mdiff import diffopts
 
from mercurial.node import hex
 
from mercurial.encoding import tolocal
 
from mercurial.discovery import findcommonoutgoing
 
from mercurial.hg import peer
 
from mercurial.httppeer import httppeer
 
from mercurial.sshpeer import sshpeer
 
from mercurial.util import url as hg_url
 
from mercurial.scmutil import revrange
 
from mercurial.node import nullrev
 

	
 
# those authnadlers are patched for python 2.6.5 bug an
 
# infinit looping when given invalid resources
 
from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
 

	
 
import inspect
 
# Mercurial 3.1 503bb3af70fe
 
if inspect.getargspec(memfilectx.__init__).args[1] != 'repo':
 
    _org__init__=memfilectx.__init__
 
    def _memfilectx__init__(self, repo, *a, **b):
 
        return _org__init__(self, *a, **b)
 
    memfilectx.__init__ = _memfilectx__init__
kallithea/model/validators.py
Show inline comments
 
@@ -423,78 +423,78 @@ def ValidRepoName(edit=False, old_data={
 

	
 

	
 
def ValidForkName(*args, **kwargs):
 
    return ValidRepoName(*args, **kwargs)
 

	
 

	
 
def SlugifyName():
 
    class _validator(formencode.validators.FancyValidator):
 

	
 
        def _to_python(self, value, state):
 
            return repo_name_slug(value)
 

	
 
        def validate_python(self, value, state):
 
            pass
 

	
 
    return _validator
 

	
 

	
 
def ValidCloneUri():
 
    from kallithea.lib.utils import make_ui
 

	
 
    def url_handler(repo_type, url, ui):
 
        if repo_type == 'hg':
 
            from kallithea.lib.vcs.backends.hg.repository import MercurialRepository
 
            if url.startswith('http'):
 
            if url.startswith('http') or url.startswith('ssh'):
 
                # initially check if it's at least the proper URL
 
                # or does it pass basic auth
 
                MercurialRepository._check_url(url, ui)
 
            elif url.startswith('svn+http'):
 
                from hgsubversion.svnrepo import svnremoterepo
 
                svnremoterepo(ui, url).svn.uuid
 
            elif url.startswith('git+http'):
 
                raise NotImplementedError()
 
            else:
 
                raise Exception('clone from URI %s not allowed' % (url,))
 

	
 
        elif repo_type == 'git':
 
            from kallithea.lib.vcs.backends.git.repository import GitRepository
 
            if url.startswith('http'):
 
                # initially check if it's at least the proper URL
 
                # or does it pass basic auth
 
                GitRepository._check_url(url)
 
            elif url.startswith('svn+http'):
 
                raise NotImplementedError()
 
            elif url.startswith('hg+http'):
 
                raise NotImplementedError()
 
            else:
 
                raise Exception('clone from URI %s not allowed' % (url))
 

	
 
    class _validator(formencode.validators.FancyValidator):
 
        messages = {
 
            'clone_uri': _(u'invalid clone url'),
 
            'invalid_clone_uri': _(u'Invalid clone url, provide a '
 
                                    'valid clone http(s)/svn+http(s) url')
 
                                    'valid clone http(s)/svn+http(s)/ssh url')
 
        }
 

	
 
        def validate_python(self, value, state):
 
            repo_type = value.get('repo_type')
 
            url = value.get('clone_uri')
 

	
 
            if not url:
 
                pass
 
            else:
 
                try:
 
                    url_handler(repo_type, url, make_ui('db', clear_session=False))
 
                except Exception:
 
                    log.exception('Url validation failed')
 
                    msg = M(self, 'clone_uri')
 
                    raise formencode.Invalid(msg, value, state,
 
                        error_dict=dict(clone_uri=msg)
 
                    )
 
    return _validator
 

	
 

	
 
def ValidForkType(old_data={}):
 
    class _validator(formencode.validators.FancyValidator):
 
        messages = {
 
            'invalid_fork_type': _(u'Fork has to be the same type as parent')
kallithea/templates/admin/repos/repo_add_base.html
Show inline comments
 
@@ -3,49 +3,49 @@
 
${h.form(url('repos'))}
 
<div class="form">
 
    <!-- fields -->
 
    <div class="fields">
 
        <div class="field">
 
            <div class="label">
 
                <label for="repo_name">${_('Name')}:</label>
 
            </div>
 
            <div class="input">
 
                ${h.text('repo_name',class_="small")}
 
                <div style="margin: 6px 0px 0px 0px">
 
                    <a id="remote_clone_toggle" href="#"><i class="icon-download-cloud"></i> ${_('Import existing repository ?')}</a>
 
                </div>
 
                %if not c.authuser.is_admin:
 
                    ${h.hidden('user_created',True)}
 
                %endif
 
            </div>
 
         </div>
 
        <div id="remote_clone" class="field" style="display: none">
 
            <div class="label">
 
                <label for="clone_uri">${_('Clone from')}:</label>
 
            </div>
 
            <div class="input">
 
                ${h.text('clone_uri',class_="small")}
 
                <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
 
                <span class="help-block">${_('Optional url from which repository should be cloned.')}</span>
 
            </div>
 
        </div>
 
        <div class="field">
 
            <div class="label label-textarea">
 
                <label for="repo_description">${_('Description')}:</label>
 
            </div>
 
            <div class="textarea-repo editor">
 
                ${h.textarea('repo_description')}
 
                <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
 
            </div>
 
        </div>
 
        <div class="field">
 
             <div class="label">
 
                 <label for="repo_group">${_('Repository group')}:</label>
 
             </div>
 
             <div class="input">
 
                 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
 
                 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
 
             </div>
 
        </div>
 
        <div id="copy_perms" class="field">
 
            <div class="label label-checkbox">
 
                <label for="repo_copy_permissions">${_('Copy parent group permissions')}:</label>
 
            </div>
kallithea/templates/admin/repos/repo_edit_settings.html
Show inline comments
 
@@ -13,49 +13,49 @@ ${h.form(url('repo', repo_name=c.repo_in
 
                        ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}` </br>
 
                        ${_('''In case this repository is renamed or moved into another group the repository url changes.
 
                               Using the above url guarantees that this repository will always be accessible under such url.
 
                               Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</span>
 
                </div>
 
           </div>
 
           <div class="field">
 
               <div class="label">
 
                   <label for="clone_uri">${_('Clone uri')}:</label>
 
               </div>
 
               <div class="input">
 
                   %if c.repo_info.clone_uri:
 
                    <div id="clone_uri_hidden" style="font-size: 14px">
 
                        <span id="clone_uri_hidden_value">${c.repo_info.clone_uri_hidden}</span>
 
                        <span style="cursor: pointer; padding: 0px 0px 5px 0px" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
 
                    </div>
 
                    <div id="alter_clone_uri" style="display: none">
 
                        ${h.text('clone_uri',class_="medium",  placeholder=_('new value'))}
 
                    </div>
 
                   %else:
 
                    ## not set yet, display form to set it
 
                    ${h.text('clone_uri',class_="medium")}
 
                    ${h.hidden('clone_uri_change', 'NEW')}
 
                   %endif
 
                 <span id="alter_clone_uri_help_block" class="help-block">${_('http[s] url used for doing remote pulls.')}</span>
 
                 <span id="alter_clone_uri_help_block" class="help-block">${_('Url used for doing remote pulls.')}</span>
 
               </div>
 
            </div>
 
            <div class="field">
 
                <div class="label">
 
                    <label for="repo_group">${_('Repository group')}:</label>
 
                </div>
 
                <div class="input">
 
                    ${h.select('repo_group','',c.repo_groups,class_="medium")}
 
                    <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
 
                </div>
 
            </div>
 
            <div class="field">
 
                <div class="label">
 
                    <label for="repo_landing_rev">${_('Landing revision')}:</label>
 
                </div>
 
                <div class="input">
 
                    ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
 
                    <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
 
                </div>
 
            </div>
 
            <div class="field">
 
                <div class="label">
 
                    <label for="user">${_('Owner')}:</label>
 
                </div>
0 comments (0 inline, 0 general)