view hgext/acl.py @ 5378:8a2915f57dfc

convert: add a mode where mercurial_sink skips empty revisions. The getchanges function of some converter_source classes can return some false positives. I.e. they sometimes claim that a file "foo" was changed in some revision, even though its contents are still the same. convert_svn is particularly bad, but I think this can also happen with convert_cvs and, at least in theory, with mercurial_source. For regular conversions this is not really a problem - as long as getfile returns the right contents, we'll get a converted revision with the right contents. But when we use --filemap, this could lead to superfluous revisions being converted. Instead of fixing every converter_source, I decided to change mercurial_sink to work around this problem. When --filemap is used, we're interested only in revisions that touch some specific files. If a revision doesn't change any of these files, then we're not interested in it (at least for revisions with a single parent; merges are special). For mercurial_sink, we abuse this property and rollback a commit if the manifest text hasn't changed. This avoids duplicating the logic from localrepo.filecommit to detect unchanged files.
author Alexis S. L. Carvalho <alexis@cecm.usp.br>
date Thu, 04 Oct 2007 23:21:37 -0300
parents 1b5b98837bb5
children
line wrap: on
line source

# acl.py - changeset access control for mercurial
#
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
#
# this hook allows to allow or deny access to parts of a repo when
# taking incoming changesets.
#
# authorization is against local user name on system where hook is
# run, not committer of original changeset (since that is easy to
# spoof).
#
# acl hook is best to use if you use hgsh to set up restricted shells
# for authenticated users to only push to / pull from.  not safe if
# user has interactive shell access, because they can disable hook.
# also not safe if remote users share one local account, because then
# no way to tell remote users apart.
#
# to use, configure acl extension in hgrc like this:
#
#   [extensions]
#   hgext.acl =
#
#   [hooks]
#   pretxnchangegroup.acl = python:hgext.acl.hook
#
#   [acl]
#   sources = serve        # check if source of incoming changes in this list
#                          # ("serve" == ssh or http, "push", "pull", "bundle")
#
# allow and deny lists have subtree pattern (default syntax is glob)
# on left, user names on right. deny list checked before allow list.
#
#   [acl.allow]
#   # if acl.allow not present, all users allowed by default
#   # empty acl.allow = no users allowed
#   docs/** = doc_writer
#   .hgtags = release_engineer
#
#   [acl.deny]
#   # if acl.deny not present, no users denied by default
#   # empty acl.deny = all users allowed
#   glob pattern = user4, user5
#   ** = user6

from mercurial.i18n import _
from mercurial.node import *
from mercurial import util
import getpass

class checker(object):
    '''acl checker.'''

    def buildmatch(self, key):
        '''return tuple of (match function, list enabled).'''
        if not self.ui.has_section(key):
            self.ui.debug(_('acl: %s not enabled\n') % key)
            return None, False

        thisuser = self.getuser()
        pats = [pat for pat, users in self.ui.configitems(key)
                if thisuser in users.replace(',', ' ').split()]
        self.ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
                      (key, len(pats), thisuser))
        if pats:
            match = util.matcher(self.repo.root, names=pats)[1]
        else:
            match = util.never
        return match, True

    def getuser(self):
        '''return name of authenticated user.'''
        return self.user

    def __init__(self, ui, repo):
        self.ui = ui
        self.repo = repo
        self.user = getpass.getuser()
        cfg = self.ui.config('acl', 'config')
        if cfg:
            self.ui.readsections(cfg, 'acl.allow', 'acl.deny')
        self.allow, self.allowable = self.buildmatch('acl.allow')
        self.deny, self.deniable = self.buildmatch('acl.deny')

    def skipsource(self, source):
        '''true if incoming changes from this source should be skipped.'''
        ok_sources = self.ui.config('acl', 'sources', 'serve').split()
        return source not in ok_sources

    def check(self, node):
        '''return if access allowed, raise exception if not.'''
        files = self.repo.changectx(node).files()
        if self.deniable:
            for f in files:
                if self.deny(f):
                    self.ui.debug(_('acl: user %s denied on %s\n') %
                                  (self.getuser(), f))
                    raise util.Abort(_('acl: access denied for changeset %s') %
                                     short(node))
        if self.allowable:
            for f in files:
                if not self.allow(f):
                    self.ui.debug(_('acl: user %s not allowed on %s\n') %
                                  (self.getuser(), f))
                    raise util.Abort(_('acl: access denied for changeset %s') %
                                     short(node))
        self.ui.debug(_('acl: allowing changeset %s\n') % short(node))

def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
    if hooktype != 'pretxnchangegroup':
        raise util.Abort(_('config error - hook type "%s" cannot stop '
                           'incoming changesets') % hooktype)

    c = checker(ui, repo)
    if c.skipsource(source):
        ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
        return

    start = repo.changelog.rev(bin(node))
    end = repo.changelog.count()
    for rev in xrange(start, end):
        c.check(repo.changelog.node(rev))