# The SVNBOOK's section called "The Final Word on Merge Tracking" # (http://svnbook.red-bean.com/en/1.7/svn.branchmerge.advanced.html#svn.branchmerge.advanced.finalword) # says that one of Subversion's best practices is to "avoid subtree # merges and subtree mergeinfo, perform merges only on the root of # your branches, not on subdirectories or files". # What follows is a pre-commit hook that checks when it's commiting # the result of a merge and that the merge root matches on of a list # of allowed regexes. my @allowed_merge_roots = ( qr@^(?:trunk|branches/[^/]+)/$@, # only on trunk and on branches roots ); # This hook loops over every path which had the svn:mergeinfo property # changed in this commit in string order. The first such path must be # the merge root and it must match at least one of the allowed merge # roots or die otherwise. PRE_COMMIT { my ($svnlook) = @_; my $headlook; # initialized inside the loop if needed foreach my $path (sort $svnlook->prop_modified()) { next unless exists $svnlook->proplist($_)->{'svn:mergeinfo'}; # Get a SVN::Look to the HEAD revision in order to see what # has changed in this commit transaction $headlook ||= SVN::Look->new($svnlook->repo()); # Try to get properties for the file in HEAD my $head_props = eval { $headlook->proplist($path) }; # If path didn't exist in HEAD it must be a copy and not a # merge root, so we skip it. next unless $head_props; # If it didn't have the svn:mergeinfo property or if the # property was different then, it must be the merge root. if (! exists $head_props->{'svn:mergeinfo'} || $head_props->{'svn:mergeinfo'} ne $svnlook->proplist($path)->{'svn:mergeinfo'} ) { # We've found a path that had the svn:mergeinfo property # modified in this commit. Since we're looking at them in # string order, the first one found must be the merge # root. Check if it matches any of the allowed roots or # die otherwise. foreach my $allowed_root (@allowed_merge_roots) { return if $path =~ $allowed_root; } die "Merge not allowed on '$path'\n"; } } }; 1;