diff --git a/dev/test2.py b/dev/test2.py new file mode 100644 index 0000000..df09d61 --- /dev/null +++ b/dev/test2.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python2.7 + +from __future__ import print_function # PY3 + +import sys +import os +import os.path +from pprint import pprint + +import svn +from svn.exception import SvnException +from svn.test_support import temp_repo, temp_checkout, populate_prop_files + + +def cont(conttext='continue'): + try: + ctext=raw_input(' ENTER to %s:' % conttext) + except: # PY3 + ctext=input(' ENTER to %s:' % conttext) + return ctext + + +tempdir = os.environ.get('TEMPDIR',os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../temp'))) + +dev_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +sys.path.insert(0, dev_path) + +import logging +_logger = logging.getLogger() +_logger.setLevel(logging.DEBUG) +logging.getLogger('boto').setLevel(logging.INFO) + +ch = logging.StreamHandler() + +FMT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +formatter = logging.Formatter(FMT) +ch.setFormatter(formatter) +_logger.addHandler(ch) + + +with temp_repo() as (repo_path, _): + print('Temp repo created at %s' % repo_path) + + with temp_checkout() as (wcpath, lc): + print('Working Copy created at %s' % wcpath) + + populate_prop_files() + lc.commit("Second revision.") + + lc.propset('svn:mime-type','image/jpeg', rel_path='foo.jpg') + lc.propset('owner','sally', rel_path='foo.bar') + lc.propset('svn:ignore','foo.bak') # set on directory level + + print('\n--- info for foo.bar ---') + pprint(lc.info('foo.bar')) + + pprint(lc.properties('foo.bar')) + + print('\n--- info for foo.jpg ---') + pprint(lc.info('foo.jpg')) + pprint(lc.properties('foo.jpg')) + + cont('before committing properties') + lc.commit('Committing properties') + lc.update() + + print('\npropget for foo.jpg for svn:mime-type:', lc.propget('svn:mime-type', rel_path='foo.jpg')) + + print('\n--- info for foo.bar after setting properties owner svn:ignore & commit---') + pprint(lc.info('foo.bar')) + cont() + + lc.propset('svn:keywords','Author Date Rev', rel_path='foo.bar') + + print('\n--- info and properties for foo.bar ---') + pprint(lc.info('foo.bar')) + pprint(lc.properties(rel_path='foo.bar')) + + lc.propdel('owner', rel_path='foo.bar') + + lc.commit('Committing after deleting property') + lc.update() + cont() + + print('\nget property svn:ignore on . =', lc.propget('svn:ignore','.') ) + + print('--- properties for foo.bar HEAD ---') + pprint(lc.properties(rel_path='foo.bar')) + + print('info on foo.bar') + pprint(lc.info(rel_path='foo.bar')) + + print('\n--- properties for foo.bar rev 1 ---') + pprint(lc.properties(rel_path='foo.bar',revision=1)) + print('properties for foo.bar rev 2:') + pprint(lc.properties(rel_path='foo.bar',revision=2)) + print('\n--- properties for foo.bar rev 3 ---') + pprint(lc.properties(rel_path='foo.bar',revision=3)) + +#with self.assertRaises(Exception): # svn.exception.SvnException): +# lc.propget('owner', rel_path='foo.bar') +# self.assertRaises(Exception, lc.propget,'owner', rel_path='foo.bar') + + try: + lc.propget('owner', rel_path='foo.bar') + except SvnException as sx: + pass + + print(lc.properties(rel_path='foo.bar')) + + cont('end of script') + diff --git a/svn/common.py b/svn/common.py index a53e2f2..6a3bc5b 100644 --- a/svn/common.py +++ b/svn/common.py @@ -18,6 +18,29 @@ _HUNK_HEADER_RIGHT_PREFIX = '+++ ' _HUNK_HEADER_LINE_NUMBERS_PREFIX = '@@ ' +def normpath2(url_or_path): + """ Normalize url or path on Windows + convert to lower case and replace backslash by slash + This avoids path strings to change during processing makeing them difficult to compare: + - backslash appears as 2 backslashes or as %5C + - drive letter upper or lower case + """ + import sys + if not sys.platform.startswith('win'): + return url_or_path + else: + up = url_or_path.replace('\\','/') + up = up.replace('%5C','/') + if len(up) < 3: + return up + elif up[1]==':': + return up[0:1].upper()+up[1:] + elif len(up) < 10: + return up + elif up.startswith('file:///') and up[9]==':': + return up[:8]+up[8:9].upper()+up[9:] + else: + return up class CommonClient(svn.common_base.CommonBase): def __init__(self, url_or_path, type_, username=None, password=None, @@ -133,38 +156,53 @@ def info(self, rel_path=None, revision=None): return info - def properties(self, rel_path=None): + def properties(self, rel_path=None, revision=None): """ Return a dictionary with all svn-properties associated with a relative path. :param rel_path: relative path in the svn repo to query the properties from + :param revision: revision number (default working copy) :returns: a dictionary with the property name as key and the content as value """ + cmd = [] + if revision is not None: + cmd += ['-r', str(revision)] full_url_or_path = self.__url_or_path if rel_path is not None: full_url_or_path += '/' + rel_path + cmd += ['--xml', full_url_or_path] result = self.run_command( 'proplist', - ['--xml', full_url_or_path], + cmd, do_combine=True) # query the proper list of this path root = xml.etree.ElementTree.fromstring(result) target_elem = root.find('target') - property_names = [p.attrib["name"] - for p in target_elem.findall('property')] + + if target_elem is not None: + property_names = [p.attrib["name"] + for p in target_elem.findall('property')] + else: # no properties found + property_names = [] # now query the content of each propery property_dict = {} for property_name in property_names: + cmd = [] + if revision is not None: + cmd += ['-r', str(revision)] + cmd += ['--xml', property_name, full_url_or_path, ] + result = self.run_command( 'propget', - ['--xml', property_name, full_url_or_path, ], + cmd, do_combine=True) + root = xml.etree.ElementTree.fromstring(result) target_elem = root.find('target') property_elem = target_elem.find('property') @@ -172,6 +210,80 @@ def properties(self, rel_path=None): return property_dict + def propdel(self, property_name, rel_path=None, revision=None): + """ Delete a property with property_anme for the + url_or_path + + :param rel_path: relative path in the svn repo to query the + properties from + :param revision: revision number (default working copy) + + """ + + cmd = [property_name,] + + full_url_or_path = self.__url_or_path + + if rel_path is not None: + full_url_or_path += '/' + rel_path + + if revision is not None: + cmd += ['-r', str(revision)] + + cmd.append(full_url_or_path) + + self.run_command('propdel', cmd) + + def propget(self, property_name, rel_path=None, revision=None): + """ Return a dictionary with the url_or_path as key and the + text for the property_name as value + + :param rel_path: relative path in the svn repo to query the + properties from + :param revision: revision number (default working copy) + :returns: a dictionary with the url_or_path as key and the content + of the property as value + """ + cmd = [] + if revision is not None: + cmd += ['-r', str(revision)] + + full_url_or_path = self.__url_or_path + if rel_path is not None: + full_url_or_path += '/' + rel_path + cmd += ['--xml', property_name, full_url_or_path, ] + + result = self.run_command( + 'propget', + cmd, + do_combine=True) + root = xml.etree.ElementTree.fromstring(result) + target_elem = root.find('target') + property_elem = target_elem.find('property') + + return { normpath2(full_url_or_path): property_elem.text.strip('\n') } + + def propset(self, property_name, property_value, rel_path=None, revision=None): + """ Set the property_name to the property_value + for the url_or_path as key + :param property_name: name of the property + :param property_value: value of the property to be set + :param rel_path: relative path in the svn repo to query the + properties from + :param revision: revision number (default working copy) + """ + cmd = [property_name, property_value] + + if revision is not None: + cmd += ['-r', str(revision)] + + full_url_or_path = self.__url_or_path + if rel_path is not None: + full_url_or_path += '/' + rel_path + cmd.append(full_url_or_path) + + self.run_command('propset', cmd) + def cat(self, rel_filepath, revision=None): cmd = [] if revision is not None: @@ -303,7 +415,7 @@ def list(self, extended=False, rel_path=None): 'ls', ['--xml', full_url_or_path], do_combine=True) - + root = xml.etree.ElementTree.fromstring(raw) list_ = root.findall('list/entry') @@ -312,7 +424,6 @@ def list(self, extended=False, rel_path=None): kind = entry_attr['kind'] name = entry.find('name').text - size = entry.find('size') # This will be None for directories. @@ -404,14 +515,14 @@ def diff_summary(self, old, new, rel_path=None): diff = [] for element in root.findall('paths/path'): diff.append({ - 'path': element.text, + 'path': normpath2(element.text), 'item': element.attrib['item'], 'kind': element.attrib['kind'], }) return diff - def diff(self, old, new, rel_path=None): + def diff(self, old, new, rel_path=None, raw=False): """Provides output of a diff between two revisions (file, change type, file type) """ @@ -431,6 +542,9 @@ def diff(self, old, new, rel_path=None): do_combine=True) diff_result = diff_result.strip() + + if raw: + return diff_result # Split the hunks. @@ -484,7 +598,7 @@ def _split_file_hunk(self, file_hunk): # Index: /tmp/testsvnwc/bb # =================================================================== - filepath = lines[0][len(_FILE_HUNK_PREFIX):] + filepath = normpath2(lines[0][len(_FILE_HUNK_PREFIX):]) # File was added. We have the file-hunk header but no actual hunks. if len(lines) == 3: @@ -559,8 +673,8 @@ def _split_file_hunk(self, file_hunk): }) hunks_info = { - 'left_phrase': file_hunk_left_phrase, - 'right_phrase': file_hunk_right_phrase, + 'left_phrase': [normpath2(file_hunk_left_phrase[0]), file_hunk_left_phrase[1]], + 'right_phrase':[normpath2(file_hunk_right_phrase[0]), file_hunk_right_phrase[1]], 'hunks': hunks, } diff --git a/svn/local.py b/svn/local.py index 686063b..9f5e913 100644 --- a/svn/local.py +++ b/svn/local.py @@ -94,7 +94,7 @@ def status(self, rel_path=None): revision = int(revision) yield _STATUS_ENTRY( - name=name, + name=svn.common.normpath2(name), type_raw_name=change_type_raw, type=change_type, revision=revision diff --git a/svn/resources/README.md b/svn/resources/README.md index d8b6aad..57e7e0a 100644 --- a/svn/resources/README.md +++ b/svn/resources/README.md @@ -12,19 +12,22 @@ installed on the local system. Functions currently implemented: -- list -- info -- log +- add +- cat - checkout +- cleanup +- commit - export -- cat - diff (with raw and summary support) +- info +- list +- log +- propdel +- propget +- propset +- remove (i.e. rm, del, delete) - status -- add -- commit - update -- cleanup -- remove (i.e. rm, del, delete) In addition, there is also an "admin" class (`svn.admin.Admin`) that provides a `create` method with which to create repositories. @@ -53,19 +56,43 @@ constructor and utility function. *LocalClient* allows access to a local working copy. +The following methods are available for a local client. -## RemoteClient +### add(rel_path, do_include_parents=False) -*RemoteClient* allows access to a remote repository. +Add files or directories to the repository. + +### cleanup() +Recursively clean up the working copy. -## SvnException -*SvnException* is raised whenever there is an issue with the svn repository. We -are no longer supporting catching *ValueError*. +### commit(message, rel_filepaths=[]) + +Send changes from your working copy to the repository. + + +### remove(rel_path, do_keep_local=False, do_force=False) +Delete/remove an item from a working copy -## checkout(path) + +### status(rel_path=None) + +Return the status of working copy files and directories. + + +### update(rel_filepaths=[], revision=None) + +Update the working copy. + +## RemoteClient + + +*RemoteClient* allows access to a remote repository. + + +### checkout(path) Checkout a remote repository: @@ -76,65 +103,65 @@ r = svn.remote.RemoteClient('https://repo.local/svn') r.checkout('/tmp/working') ``` +### remove(rel_path, message, do_force=False): + +Delete/remove an item from the repository + + +### SvnException + +*SvnException* is raised whenever there is an issue with the svn repository. We +are no longer supporting catching *ValueError*. + ## Common Functionality These methods are available on both clients. -### info(rel_path=None) +### cat(rel_filepath) -Get information about the directory. +Get file-data as string. ``` -import pprint - import svn.local -r = svn.local.LocalClient('/tmp/test_repo.co') -info = r.info() -pprint.pprint(info) - -#{'commit#revision': 0, -# 'commit/author': None, -# 'commit/date': datetime.datetime(2015, 4, 24, 2, 53, 21, 874970, tzinfo=tzutc()), -# 'commit_author': None, -# 'commit_date': datetime.datetime(2015, 4, 24, 2, 53, 21, 874970, tzinfo=tzutc()), -# 'commit_revision': 0, -# 'entry#kind': 'dir', -# 'entry#path': '/tmp/test_repo.co', -# 'entry#revision': 0, -# 'entry_kind': 'dir', -# 'entry_path': '/tmp/test_repo.co', -# 'entry_revision': 0, -# 'relative_url': None, -# 'repository/root': 'file:///tmp/test_repo', -# 'repository/uuid': '7446d4e9-8846-46c0-858a-34a2a1739d1c', -# 'repository_root': 'file:///tmp/test_repo', -# 'repository_uuid': '7446d4e9-8846-46c0-858a-34a2a1739d1c', -# 'url': 'file:///tmp/test_repo', -# 'wc-info/depth': None, -# 'wc-info/schedule': None, -# 'wc-info/wcroot-abspath': None, -# 'wcinfo_depth': None, -# 'wcinfo_schedule': None, -# 'wcinfo_wcroot_abspath': None} +l = svn.local.LocalClient('/tmp/test_repo') +content = l.cat('test_file') ``` -NOTE: The keys named with dashes, slashes, and hashes are considered - obsolete, and only available for backwards compatibility. We - have since moved to using only underscores to separate words. +### diff(start_revision, end_revision) +Diffs between start and end revisions -### cat(rel_filepath) +#### Notice of Diff Reimplementation in 1.0.0 -Get file-data as string. +There was a previous contribution to the diff implementation that has been +reported and confirmed to often throw an exception due to shoddy handling of +the file-paths in the output. It also made secondary shell calls and mixed both +text and XML output in the response. As a result of this, the decision has been +made to just reimplement it and reshape the output in a backwards-incompatible +way at the same time. If you need to stick to the older implementation, tie your +dependencies to the 0.3.46 release. + + +### diff_summary(start_revision, end_revision) + +A lower-level diff summary that doesn't actually provide the content +differences. ``` -import svn.local +import svn.remote -l = svn.local.LocalClient('/tmp/test_repo') -content = l.cat('test_file') +l = svn.remote.RemoteClient('http://svn.apache.org/repos/asf') +print l.diff_summary(1760022, 1760023) + +# [{'item': 'modified', +# 'kind': 'file', +# 'path': 'http://svn.apache.org/repos/asf/sling/trunk/pom.xml'}, +# {'item': 'added', +# 'kind': 'file', +# 'path': 'http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/pom.xml'}] ``` @@ -156,6 +183,7 @@ for e in l.log_default(): ``` + ### export(to_path, revision=None, force=False) Checkout the tree without embedding an meta-information. @@ -170,6 +198,50 @@ r.export('/tmp/test_export') We can also use `force` option to force the svn export. +### info(rel_path=None) + +Get information about the directory. + +``` +import pprint + +import svn.local + +r = svn.local.LocalClient('/tmp/test_repo.co') +info = r.info() +pprint.pprint(info) + +#{'commit#revision': 0, +# 'commit/author': None, +# 'commit/date': datetime.datetime(2015, 4, 24, 2, 53, 21, 874970, tzinfo=tzutc()), +# 'commit_author': None, +# 'commit_date': datetime.datetime(2015, 4, 24, 2, 53, 21, 874970, tzinfo=tzutc()), +# 'commit_revision': 0, +# 'entry#kind': 'dir', +# 'entry#path': '/tmp/test_repo.co', +# 'entry#revision': 0, +# 'entry_kind': 'dir', +# 'entry_path': '/tmp/test_repo.co', +# 'entry_revision': 0, +# 'relative_url': None, +# 'repository/root': 'file:///tmp/test_repo', +# 'repository/uuid': '7446d4e9-8846-46c0-858a-34a2a1739d1c', +# 'repository_root': 'file:///tmp/test_repo', +# 'repository_uuid': '7446d4e9-8846-46c0-858a-34a2a1739d1c', +# 'url': 'file:///tmp/test_repo', +# 'wc-info/depth': None, +# 'wc-info/schedule': None, +# 'wc-info/wcroot-abspath': None, +# 'wcinfo_depth': None, +# 'wcinfo_schedule': None, +# 'wcinfo_wcroot_abspath': None} +``` + +NOTE: The keys named with dashes, slashes, and hashes are considered + obsolete, and only available for backwards compatibility. We + have since moved to using only underscores to separate words. + + ### list(extended=False, rel_path=None) Return either a flat-list of filenames or a list of objects describing even @@ -269,37 +341,39 @@ for rel_path, e in l.list_recursive(): ``` -### diff_summary(start_revision, end_revision) +### properties(rel_path=None, revision=None): -A lower-level diff summary that doesn't actually provide the content -differences. +Return a dictionary with all svn-properties associated with a relative path. + + +### propdel(property_name, rel_path=None, revision=None) + +Delete a property with property_anme for the url_or_path. ``` -import svn.remote +lc.propdel('svn:mime-type', rel_path='foo.jpg') +``` -l = svn.remote.RemoteClient('http://svn.apache.org/repos/asf') -print l.diff_summary(1760022, 1760023) + +### propget(property_name, rel_path=None, revision=None): -# [{'item': 'modified', -# 'kind': 'file', -# 'path': 'http://svn.apache.org/repos/asf/sling/trunk/pom.xml'}, -# {'item': 'added', -# 'kind': 'file', -# 'path': 'http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/pom.xml'}] +Return a dictionary with the url_or_path as key and the text for the property_name +as value + +``` +propdict = lc.propget('svn:mime-type', rel_path='foo.jpg') ``` -### diff(start_revision, end_revision) +### propset(property_name, property_value, rel_path=None, revision=None) + +Set the property_name to the property_value for the url_or_path as key + +``` +c.propset('svn:mime-type','image/jpeg', rel_path='foo.jpg') + +``` -Diffs between start and end revisions -# Notice of Diff Reimplementation in 1.0.0 -There was a previous contribution to the diff implementation that has been -reported and confirmed to often throw an exception due to shoddy handling of -the file-paths in the output. It also made secondary shell calls and mixed both -text and XML output in the response. As a result of this, the decision has been -made to just reimplement it and reshape the output in a backwards-incompatible -way at the same time. If you need to stick to the older implementation, tie your -dependencies to the 0.3.46 release. diff --git a/svn/test_support.py b/svn/test_support.py index 9d1910a..e475a22 100644 --- a/svn/test_support.py +++ b/svn/test_support.py @@ -12,7 +12,7 @@ @contextlib.contextmanager def temp_path(): - original_wd = os.getcwd() + original_wd = os.path.normpath(os.getcwd()) temp_path = None try: @@ -37,7 +37,7 @@ def temp_repo(): a = svn.admin.Admin() a.create('.') - rc = svn.remote.RemoteClient('file://{}'.format(repo_path)) + rc = svn.remote.RemoteClient('file:///{}'.format(repo_path)) yield repo_path, rc @@ -47,10 +47,10 @@ def temp_checkout(): current path. """ - repo_path = os.getcwd() + repo_path = os.path.normpath(os.getcwd()) with temp_path() as working_path: - rc = svn.remote.RemoteClient('file://{}'.format(repo_path)) + rc = svn.remote.RemoteClient('file:///{}'.format(repo_path)) rc.checkout('.') lc = svn.local.LocalClient(working_path) @@ -80,7 +80,7 @@ def populate_bigger_file_changes1(): "test_local_populate1() must be called with the working-directory " \ "as the CWD." - working_path = os.getcwd() + working_path = os.path.normpath(os.getcwd()) lc = svn.local.LocalClient(working_path) # Create a file that will not be committed. @@ -150,7 +150,7 @@ def populate_bigger_file_change1(): "test_local_populate1() must be called with the working-directory " \ "as the CWD." - working_path = os.getcwd() + working_path = os.path.normpath(os.getcwd()) lc = svn.local.LocalClient(working_path) # Create a file that will be committed and then changed a lot. @@ -249,3 +249,36 @@ def populate_bigger_file_change1(): lc.update() return rel_filepath + +def populate_prop_files(): + """Establish files for testing property changes""" + + assert \ + os.path.exists('.svn') is True, \ + "populate_properties() must be called with the working-directory " \ + "as the CWD." + + working_path = os.path.normpath(os.getcwd()) + lc = svn.local.LocalClient(working_path) + + rel_filepath = 'foo.jpg' + with open(rel_filepath, 'w') as f: + pass + lc.add(rel_filepath) + + rel_filepath = 'foo.bar' + with open(rel_filepath, 'w') as f: + pass + lc.add(rel_filepath) + + rel_filepath = 'foo.bak' + with open(rel_filepath, 'w') as f: + pass + + + # Commit the new files. + lc.commit("Initial commit.") + + # Do an update to pick-up the changes from the commit. + lc.update() + diff --git a/svn/utility.py b/svn/utility.py index c8c0951..3b13368 100644 --- a/svn/utility.py +++ b/svn/utility.py @@ -13,7 +13,7 @@ def get_client(url_or_path, *args, **kwargs): def get_common_for_cwd(): path = os.getcwd() - uri = 'file://{}'.format(path) + uri = 'file:///{}'.format(path) cc = svn.common.CommonClient(uri, svn.constants.LT_URL) return cc diff --git a/tests/test_admin.py b/tests/test_admin.py index 9e9a7b6..00cd84f 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -35,7 +35,7 @@ def test_create_repository(self): a.create(temp_path) # Do a read. - rc = svn.remote.RemoteClient('file://' + temp_path) + rc = svn.remote.RemoteClient('file:///' + temp_path) info = rc.info() _LOGGER.debug("Info from new repository: [%s]", str(info)) finally: diff --git a/tests/test_common.py b/tests/test_common.py index ba74bec..21aaf6c 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,3 +1,5 @@ +from __future__ import print_function # PY3 + import os import unittest import shutil @@ -7,7 +9,8 @@ import svn.local import svn.utility import svn.test_support - +from svn.common import normpath2 # unify file paths for comparison (win) +from svn.exception import SvnException class TestCommonClient(unittest.TestCase): def __init__(self, *args, **kwargs): @@ -44,7 +47,7 @@ def test_diff_summary(self): self.assertEquals(len(index), 1) - file_uri2 = 'file://{}/{}'.format(repo_path, 'committed_changed') + file_uri2 = 'file:///{}'.format(normpath2( os.path.join(repo_path, 'committed_changed'))) self.assertEquals(index[file_uri2]['item'], 'modified') def test_diff__with_diff(self): @@ -57,7 +60,7 @@ def test_diff__with_diff(self): 1, 2) - filepath = os.path.join(working_path, 'committed_changed') + filepath = normpath2(os.path.join(working_path, 'committed_changed')) expected = { filepath: { @@ -87,14 +90,13 @@ def test_diff__with_add(self): with svn.test_support.temp_common() as (_, working_path, cc): svn.test_support.populate_bigger_file_changes1() rel_filepath2 = svn.test_support.populate_bigger_file_change1() - actual = \ cc.diff( 2, 3) - - filepath1 = os.path.join(working_path, 'added') - filepath2 = os.path.join(working_path, rel_filepath2) + + filepath1 = normpath2(os.path.join(working_path, 'added')) + filepath2 = normpath2(os.path.join(working_path, rel_filepath2)) expected = { filepath1: None, @@ -136,11 +138,11 @@ def test_info(self): info['entry_path'], '.') - uri = 'file://{}'.format(repo_path) + uri = 'file:///{}'.format(repo_path) self.assertEqual( - info['repository_root'], - uri) + normpath2(info['repository_root']), + normpath2(uri)) self.assertEqual( info['entry#kind'], @@ -219,3 +221,63 @@ def test_force__export(self): content = f.read() self.assertEquals(content, "new data") + +class TestCommonProperties(unittest.TestCase): + def test_prop_commands(self): + with svn.test_support.temp_repo(): + with svn.test_support.temp_checkout() as (wc, lc): + svn.test_support.populate_prop_files() + + lc.propset('svn:mime-type','image/jpeg', rel_path='foo.jpg') + lc.propset('owner','sally', rel_path='foo.bar') + lc.propset('svn:ignore','foo.bak') # set on directory level + + lc.commit('Committing properties') + lc.update() + + lc.propset('svn:keywords','Author Date Rev', rel_path='foo.bar') + + propdict = lc.propget('svn:mime-type', rel_path='foo.jpg') + path1 = normpath2(os.path.join(wc, 'foo.jpg')) + self.assertEquals(len(propdict), 1) + self.assertEquals(propdict[path1], 'image/jpeg') + + propdict = lc.propget('svn:ignore', '.') # at directory level + path1 = normpath2(os.path.join(wc, '.')) + self.assertEquals(len(propdict), 1) + self.assertEquals(propdict[path1], 'foo.bak') + + propdict = lc.properties(rel_path='foo.bar') + self.assertEquals(len(propdict), 2) + self.assertEquals(propdict['owner'], 'sally') + self.assertEquals(propdict['svn:keywords'], 'Author Date Rev') + + lc.propdel('owner', rel_path='foo.bar') + + lc.commit('Committing after deleting property') + lc.update() + + propdict = lc.properties(rel_path='foo.bar') + self.assertEquals(len(propdict), 1) + self.assertEquals(propdict['svn:keywords'], 'Author Date Rev') + + propdict = lc.properties(rel_path='foo.bar',revision=1) + self.assertEquals(len(propdict), 0) + + propdict = lc.properties(rel_path='foo.bar',revision=2) + self.assertEquals(len(propdict), 1) + self.assertEquals(propdict['owner'], 'sally') + + propdict = lc.propget('owner', rel_path='foo.bar',revision=2) + path1 = normpath2(os.path.join(wc, 'foo.bar')) + self.assertEquals(len(propdict), 1) + self.assertEquals(propdict[path1], 'sally') + + # property was deleted before + with self.assertRaises(SvnException) as cx: + lc.propget('owner', rel_path='foo.bar') + """ + # to get the multi-line error message + #exc = cx.exception + #print('\nMessage =\n',exc.message) + """ diff --git a/tests/test_local.py b/tests/test_local.py index 445f4b8..b62cfa1 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -1,6 +1,7 @@ import os import unittest +import svn.common import svn.constants import svn.test_support @@ -52,12 +53,13 @@ def test_remove(self): status_all = list(status_all) status_index = { - s.name: s + svn.common.normpath2(s.name): s for s in status_all } - filepath = os.path.join(wc_path, 'new_file') + filepath = svn.common.normpath2(os.path.join(wc_path, 'new_file')) + status = status_index[filepath] self.assertEquals(status.type, svn.constants.ST_DELETED)