# HG changeset patch
# User Ronny Voelker <ronny.voel...@googlemail.com>
# Date 1325429955 -3600
# Node ID 3587420f3d1633c269585889b7647ecf8d9faa79
# Parent bd12a4da0f35b5d04ed7cffe262d6298a5169c5d
Honor SVN auto-props (solves issue #186)
The implementation completely ignores the subversion bindings,
because the current bindings provide little support for this functionality.
diff -r bd12a4da0f35 -r 3587420f3d16 hgsubversion/pushmod.py
--- a/hgsubversion/pushmod.py Sun May 13 15:28:50 2012 +0200
+++ b/hgsubversion/pushmod.py Sun Jan 01 15:59:15 2012 +0100
@@ -133,6 +133,9 @@
# this kind of renames: a -> b, b -> c
copies[file] = renamed[0]
base_data = parent[renamed[0]].data()
+ else:
+ props.setdefault(file, {}).update(
+ svn.autoprops_config.properties(file))
action = 'add'
dirname = '/'.join(file.split('/')[:-1] + [''])
diff -r bd12a4da0f35 -r 3587420f3d16 hgsubversion/svnwrap/common.py
--- a/hgsubversion/svnwrap/common.py Sun May 13 15:28:50 2012 +0200
+++ b/hgsubversion/svnwrap/common.py Sun Jan 01 15:59:15 2012 +0100
@@ -8,6 +8,10 @@
import urlparse
import urllib
import collections
+import fnmatch
+import ConfigParser
+import sys
+import os
class SubversionRepoCanNotReplay(Exception):
"""Exception raised when the svn server is too old to have replay.
@@ -78,3 +82,72 @@
def __str__(self):
return 'r%d by %s' % (self.revnum, self.author)
+
+
+svn_config_dir = None
+
+
+def set_svn_config_dir(path):
+ global svn_config_dir
+ svn_config_dir = path
+
+
+def get_svn_config_dir():
+ return svn_config_dir
+
+
+class AutoPropsConfig(object):
+ """Provides the subversion auto-props functionality
+ when pushing new files.
+ """
+ def __init__(self, config_dir=None):
+ config_file = config_file_path(config_dir)
+ self.config = ConfigParser.RawConfigParser()
+ self.config.read([config_file])
+
+ def properties(self, file):
+ """Returns a dictionary of the auto-props applicable for file.
+ Takes enable-auto-props into account.
+ """
+ properties = {}
+ if self.autoprops_enabled():
+ for pattern,prop_list in self.config.items('auto-props'):
+ if fnmatch.fnmatchcase(os.path.basename(file), pattern):
+ properties.update(parse_autoprops(prop_list))
+ return properties
+
+ def autoprops_enabled(self):
+ return (self.config.has_option('miscellany', 'enable-auto-props')
+ and self.config.getboolean( 'miscellany', 'enable-auto-props')
+ and self.config.has_section('auto-props'))
+
+
+def config_file_path(config_dir):
+ if config_dir == None:
+ if sys.platform == 'win32':
+ config_dir = os.environ['APPDATA'] + '/Subversion'
+ else:
+ config_dir = os.environ['HOME'] + '/.subversion'
+ return config_dir + '/config'
+
+
+def parse_autoprops(prop_list):
+ """Parses a string of autoprops and returns a dictionary of
+ the results.
+ Emulates the parsing of core.auto_props_enumerator.
+ """
+ def unquote(s):
+ if len(s)>1 and s[0] in ['"', "'"] and s[0]==s[-1]:
+ return s[1:-1]
+ return s
+
+ properties = {}
+ for prop in prop_list.split(';'):
+ if '=' in prop:
+ prop, value = prop.split('=',1)
+ value = unquote(value.strip())
+ else:
+ value = ''
+ properties[prop.strip()] = value
+ return properties
+
diff -r bd12a4da0f35 -r 3587420f3d16 hgsubversion/svnwrap/subvertpy_wrapper.py
--- a/hgsubversion/svnwrap/subvertpy_wrapper.py Sun May 13 15:28:50 2012 +0200
+++ b/hgsubversion/svnwrap/subvertpy_wrapper.py Sun Jan 01 15:59:15 2012 +0100
@@ -191,6 +191,7 @@
# expects unquoted paths
self.subdir = urllib.unquote(self.subdir)
self.hasdiff3 = True
+ self.autoprops_config = common.AutoPropsConfig(common.svn_config_dir)
def init_ra_and_client(self):
"""
diff -r bd12a4da0f35 -r 3587420f3d16 hgsubversion/svnwrap/svn_swig_wrapper.py
--- a/hgsubversion/svnwrap/svn_swig_wrapper.py Sun May 13 15:28:50 2012 +0200
+++ b/hgsubversion/svnwrap/svn_swig_wrapper.py Sun Jan 01 15:59:15 2012 +0100
@@ -184,6 +184,7 @@
# expects unquoted paths
self.subdir = urllib.unquote(self.subdir)
self.hasdiff3 = True
+ self.autoprops_config = common.AutoPropsConfig(common.svn_config_dir)
def init_ra_and_client(self):
"""Initializes the RA and client layers, because sometimes getting
diff -r bd12a4da0f35 -r 3587420f3d16 tests/run.py
--- a/tests/run.py Sun May 13 15:28:50 2012 +0200
+++ b/tests/run.py Sun Jan 01 15:59:15 2012 +0100
@@ -23,6 +23,7 @@
import test_push_renames
import test_push_dirs
import test_push_eol
+ import test_push_autoprops
import test_rebuildmeta
import test_single_dir_clone
import test_svnwrap
diff -r bd12a4da0f35 -r 3587420f3d16 tests/test_push_autoprops.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_push_autoprops.py Sun Jan 01 15:59:15 2012 +0100
@@ -0,0 +1,124 @@
+import subprocess
+import sys
+import unittest
+import os
+
+import test_util
+
+from hgsubversion import svnwrap
+
+class PushAutoPropsTests(test_util.TestBase):
+ def setUp(self):
+ test_util.TestBase.setUp(self)
+ self.config_dir = self.tmpdir
+ svnwrap.set_svn_config_dir(self.config_dir)
+ self.setup_svn_config('')
+ repo, self.repo_path = self.load_and_fetch('emptyrepo.svndump')
+
+ def test_push_honors_svn_autoprops(self):
+ self.setup_svn_config(
+ "[miscellany]\n"
+ "enable-auto-props = yes\n"
+ "[auto-props]\n"
+ "*.py = test:prop=success\n")
+ changes = [('test.py', 'test.py', 'echo hallo')]
+ self.commitchanges(changes)
+ self.pushrevisions(True)
+ prop_val = test_util.svnpropget(
+ self.repo_path, "trunk/test.py", 'test:prop')
+ self.assertEqual('success', prop_val)
+
+ def setup_svn_config(self, config):
+ with open(self.config_dir + '/config', 'w') as c:
+ c.write(config)
+
+ def tearDown(self):
+ test_util.TestBase.tearDown(self)
+ svnwrap.set_svn_config_dir(None)
+
+
+class AutoPropsConfigTest(test_util.TestBase):
+ def setUp(self):
+ test_util.TestBase.setUp(self)
+ self.config_dir = self.tmpdir
+
+ def test_use_autoprops_for_matching_file_when_enabled(self):
+ self.setup_svn_config(
+ "[miscellany]\n"
+ "enable-auto-props = yes\n"
+ "[auto-props]\n"
+ "*.py = test:prop=success\n")
+ props = self.new_autoprops_config().properties('xxx/test.py')
+ self.assertEqual({ 'test:prop': 'success'}, props)
+
+ def setup_svn_config(self, config):
+ with open(self.config_dir + '/config', 'w') as c:
+ c.write(config)
+
+ def new_autoprops_config(self):
+ return svnwrap.AutoPropsConfig(self.config_dir)
+
+ def test_ignore_nonexisting_config(self):
+ self.assertTrue(not os.path.exists(self.config_dir + '/config'))
+ props = self.new_autoprops_config().properties('xxx/test.py')
+ self.assertEqual({}, props)
+
+ def test_ignore_autoprops_when_file_doesnt_match(self):
+ self.setup_svn_config(
+ "[miscellany]\n"
+ "enable-auto-props = yes\n"
+ "[auto-props]\n"
+ "*.py = test:prop=success\n")
+ props = self.new_autoprops_config().properties('xxx/test.sh')
+ self.assertEqual({}, props)
+
+ def test_ignore_autoprops_when_disabled(self):
+ self.setup_svn_config(
+ "[miscellany]\n"
+ "#enable-auto-props = yes\n"
+ "[auto-props]\n"
+ "*.py = test:prop=success\n")
+ props = self.new_autoprops_config().properties('xxx/test.py')
+ self.assertEqual({}, props)
+
+ def test_combine_properties_of_multiple_matches(self):
+ self.setup_svn_config(
+ "[miscellany]\n"
+ "enable-auto-props = yes\n"
+ "[auto-props]\n"
+ "*.py = test:prop=success\n"
+ "test.* = test:prop2=success\n")
+ props = self.new_autoprops_config().properties('xxx/test.py')
+ self.assertEqual({
+ 'test:prop': 'success', 'test:prop2': 'success'}, props)
+
+
+class ParseAutoPropsTests(test_util.TestBase):
+ def test_property_value_is_optional(self):
+ props = svnwrap.parse_autoprops("svn:executable")
+ self.assertEqual({'svn:executable': ''}, props)
+ props = svnwrap.parse_autoprops("svn:executable=")
+ self.assertEqual({'svn:executable': ''}, props)
+
+ def test_property_value_may_be_quoted(self):
+ props = svnwrap.parse_autoprops("svn:eol-style=\" native \"")
+ self.assertEqual({'svn:eol-style': ' native '}, props)
+ props = svnwrap.parse_autoprops("svn:eol-style=' native '")
+ self.assertEqual({'svn:eol-style': ' native '}, props)
+
+ def test_surrounding_whitespaces_are_ignored(self):
+ props = svnwrap.parse_autoprops(" svn:eol-style = native ")
+ self.assertEqual({'svn:eol-style': 'native'}, props)
+
+ def test_multiple_properties_are_separated_by_semicolon(self):
+ props = svnwrap.parse_autoprops(
+ "svn:eol-style=native;svn:executable=true\n")
+ self.assertEqual({
+ 'svn:eol-style': 'native',
+ 'svn:executable': 'true'},
+ props)
+
+
+def suite():
+ return unittest.findTestCases(sys.modules[__name__])
+