Source

hgban / exclude-changesets.py

Full commit
#!/usr/bin/env python

'''exclude-changesets is a Mercurial extension which sets up a pretxncommit hook.
Any changesets to be pushed / pulled / bundled into the repository will have their
changeset hashes compared against a list of excluded hashes in repo/.excludedchangesets.
If any new hash is already in this file of excluded hashes then the entire
changegroup will be rejected.

Enable the exclude-changesets just like any other Mercurial extension by adding the
following to your hgrc

[extensions]
exclude-changesets = /path/to/exclude-changesets
'''

import os.path, re
from mercurial import hg

#print "Checking For Excluded Changesets!"

def getSetOfExcludedChangesets(repo):
    try:
        excludedChangesetsPath = os.path.join(repo.root,'.excludedchangesets')
        f = open(excludedChangesetsPath, 'r')
        excluded = set()
        changesetPat = re.compile(r"(^[0-9a-fA-F]+).*")
        for line in f:
            m = re.match(changesetPat, line)
            if m:
                excluded.add(m.group(1))
        f.close()
        return excluded
    except:
        return {}

def changesetIsExcluded(node, excludedChangesets):
    for p in excludedChangesets:
        if re.search(p, node):
            return True
    return False

def colateChildren(ctx, colation = set()):
    children = ctx.children()
    colation.add(ctx.hex())
    for childCtx in children:
        if not childCtx.hex() in colation:
            colateChildren(childCtx, colation)
    return colation

def checkForExcludedChangesets(ui, repo, **kwargs):
    node = kwargs.get('node')
    rejectedChangesets = set()
    if node:
        excludedChangesets = getSetOfExcludedChangesets(repo)
        ctx = repo[node]
        allChildren = colateChildren(ctx)
        for childChangeset in allChildren:
            if changesetIsExcluded(childChangeset, excludedChangesets):
                rejectedChangesets.add(childChangeset)
        if len(rejectedChangesets) > 0:
            repoName = os.path.basename(repo.root)
            if len(rejectedChangesets) == 1:
                ui.warn('The following changeset was rejected by the repository \'%s\'\n' % repoName)
            else:
                ui.warn('The following %d changesets were rejected by the repository \'%s\'\n' % (len(rejectedChangesets), repoName))
            for changeset in rejectedChangesets:
                ui.warn('    %s\n' % changeset)
            if (len(rejectedChangesets) < len(allChildren)):
                if len(rejectedChangesets) == 1:
                    ui.warn('Rebase, transplant, or otherwise transfer any changesets you have which are derived from this rejected changeset.\n')
                else:
                    ui.warn('Rebase, transplant, or otherwise transfer any changesets you have which are derived from these rejected changesets.\n')
            return True # We found an excluded changeset, return True (exit code 1) which causes the changegroup addition to be aborted.
    return False # No excluded changesets were found. The changegroup addition can go ahead.

def reposetup(ui, repo):
    ui.setconfig("hooks", "pretxnchangegroup.exclude-changesets", checkForExcludedChangesets)