On Fri, Feb 20, 2009 at 04:22:18PM -0500, Bob Archer wrote:
>
> > Ah hah! That is the best thing I've read all month. I can simply
> > do that in a post-commit hook after detecting that a branch has
> > just been re-integrated. Sweeeet.
>
> I'm not sure how you are going to detect that. As far as the server
> knows this is a commit no different than any other.
% find ~e/data/python/lib/shell/svn -type f -iname "*.py" | xargs cat | wc -l
6632
With a 6000+ line Python framework, that's how ;-)
> If it were this easy, wouldn't the svn devs be able to do this. But,
> then again, they could create a merge property on the target of the
> --reintegrate to identify it as such and then when that property is
> committed it could update the merge properties on the branch the
> property references.
>
> Hmm... seems too easy... what am I missing?
It was far from easy. It's sucked the life out of me for the past
three weeks or so. I had to build another semantic layer on top of
Subversion, such that, for a given repository, you would define the
paths that correspond to trunk, tags, and branches, such that you
can analyse a commit and figure out exactly what a user is doing.
Are they trying to copy a tag? Are they trying to create a branch
from a tag? Have they correctly rooted their commit (i.e. they're
not committing files in branch/foo as well as trunk)? Are they
trying to re-integrate more than one branch at a time? Are they
trying to revision merge a branch back to trunk when they should
be re-integrating? Are they reverse merging, and if so, are they
reverse merging within the agreed parameters (i.e. only allow a
commit to be a single reverse merge of a previously re-integrated
branch; no multiple reverse merges or combined forward+reverse
merges are allowed). Are they trying to modify a tag in any way,
shape or form?
Subversion lets you do all of this. In an enterprise context, where
you can't control the calibre of developers, you really need to lock
this down though -- mainly to protect developers that don't know the
ins and outs of Subversion more than anything else.
Now that I've got this semantic layer sitting on top of each repo, I
can write hooks like this:
class MergeControl(RepositoryHook):
id = 'validate-merge'
def pre_commit(self, txn):
pathmatcher = self.repo.get_path_matcher()
changeset = self.repo.changeset(txn)
analysis = changeset.analysis
root_path = changeset.root_dir
root_dir, root_type, root_name = pathmatcher.get_root_details(root_path)
if analysis.is_merge:
if not analysis.is_single_merge_source:
self.terminate('detected multiple merge sources')
if analysis.is_foreign_merge_source:
self.terminate('detected attempt to merge foreign branch')
if root_type == 'trunk':
if not analysis.is_reintegration:
self.terminate('feature branches must be merged back to '
'trunk via re-integration; cherry-picking '
'revisions is not permitted')
elif root_type == 'branch':
if analysis.merge_root_type != 'trunk':
self.terminate('detected attempt to synchronise feature '
'branch with something other than trunk')
def post_commit(self, rev):
pathmatcher = self.repo.get_path_matcher()
changset = self.repo.changeset(rev)
analysis = changeset.analysis
root_path = changeset.root_dir
root_dir, root_type, root_name = pathmatcher.get_root_details(root_path)
if root_type == 'trunk':
if analysis.is_reintegration:
# Record the merge back on the branch such that future
# re-integrations work.
self.repo.merge(analysis.merge_root_dir, root_dir, rev,
record_only=True)
class TagControl(RepositoryHook):
id = 'tag-control'
def pre_commit(self, txn):
pathmatcher = self.repo.get_path_matcher()
changeset = self.repo.changeset(txn)
analysis = changeset.analysis
root_path = changeset.root_dir
root_dir, root_type, root_name = pathmatcher.get_root_details(root_path)
if root_type == 'tag':
if not analysis.is_copy:
self.terminate('tags cannot be modified after creation')
if not analysis.is_clean:
self.terminate('tag is not a clean copy')
assert not analysis.is_subtree_change
base_path = changeset.root_change.base_path
base_root_details = pathmatcher.get_root_details(base_path)
# Verify that a tag is being made of something legitimate.
base_root_dir, base_root_type, base_root_name = base_root_details
if base_root_type == 'tag':
self.terminate('tags cannot be copied')
elif base_root_type not in ('branch', 'trunk') or \
base_root_name.startswith('GLB'):
self.terminate('only release branches and trunk can be tagged')
Received on 2009-02-20 22:46:44 CET