From ec0121e45d287e44637e0db6d6e61c4684093bb4 Mon Sep 17 00:00:00 2001 From: Jean-Francois Thuong Date: Tue, 22 Dec 2020 19:43:29 +0800 Subject: [PATCH 1/8] #96: Added options & tests for checkout, update, and info --- svn/common.py | 18 +++++++++++++++++- svn/local.py | 21 +++++++++++++++++++-- svn/remote.py | 8 +++++++- tests/test_common.py | 18 +++++++++++++++++- tests/test_remote.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 5 deletions(-) diff --git a/svn/common.py b/svn/common.py index a53e2f2..353b971 100644 --- a/svn/common.py +++ b/svn/common.py @@ -19,6 +19,20 @@ _HUNK_HEADER_LINE_NUMBERS_PREFIX = '@@ ' +def get_depth_options(depth, set_depth=False): + """Get options for depth and check (set_depth=True for --set-depth)""" + depth_values = {"empty", "files", "immediates", "infinity"} + if set_depth: + depth_values = depth_values.union({"exclude"}) + + if depth not in depth_values: + raise svn.exception.SvnException( + "Invalid depth '{d}' (values allowed: {v})!".format(d=depth, v=depth_values) + ) + + return ["--set-depth" if set_depth else "--depth", depth] + + class CommonClient(svn.common_base.CommonBase): def __init__(self, url_or_path, type_, username=None, password=None, svn_filepath='svn', trust_cert=None, env={}, *args, **kwargs): @@ -61,7 +75,7 @@ def __element_text(self, element): return None - def info(self, rel_path=None, revision=None): + def info(self, rel_path=None, revision=None, include_ext=False): cmd = [] if revision is not None: cmd += ['-r', str(revision)] @@ -70,6 +84,8 @@ def info(self, rel_path=None, revision=None): if rel_path is not None: full_url_or_path += '/' + rel_path cmd += ['--xml', full_url_or_path] + if include_ext: + cmd += ["--include-externals"] result = self.run_command( 'info', diff --git a/svn/local.py b/svn/local.py index 686063b..f160a15 100644 --- a/svn/local.py +++ b/svn/local.py @@ -48,10 +48,27 @@ def commit(self, message, rel_filepaths=[]): args, wd=self.path) - def update(self, rel_filepaths=[], revision=None): + def update( + self, + rel_filepaths=[], + revision=None, + force=False, + depth=None, + set_depth=None, + ignore_ext=False, + ): cmd = [] if revision is not None: - cmd += ['-r', str(revision)] + cmd += ["-r", str(revision)] + if force: + cmd += ["--force"] + if depth: + cmd += svn.common.get_depth_options(depth, set_depth=False) + if set_depth: + cmd += svn.common.get_depth_options(set_depth, set_depth=True) + if ignore_ext: + cmd += ["--ignore-externals"] + cmd += rel_filepaths self.run_command( 'update', diff --git a/svn/remote.py b/svn/remote.py index c55bf96..3dadd20 100644 --- a/svn/remote.py +++ b/svn/remote.py @@ -10,10 +10,16 @@ def __init__(self, url, *args, **kwargs): svn.constants.LT_URL, *args, **kwargs) - def checkout(self, path, revision=None): + def checkout(self, path, revision=None, force=False, depth=None, ignore_ext=False): cmd = [] if revision is not None: cmd += ['-r', str(revision)] + if force: + cmd += ["--force"] + if depth: + cmd += svn.common.get_depth_options(depth, set_depth=False) + if ignore_ext: + cmd += ["--ignore-externals"] cmd += [self.url, path] diff --git a/tests/test_common.py b/tests/test_common.py index ba74bec..b558257 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -23,7 +23,14 @@ def test_update(self): lc.update() self.assertEqual(3, lc.info()['commit_revision']) - lc.update(revision=1) + lc.update(set_depth="files") + self.assertEqual("files", lc.info()['wcinfo_depth']) + + lc.update(depth="empty") # depth is not changed + self.assertEqual("files", lc.info()['wcinfo_depth']) + + lc.update(revision=1, ignore_ext=True) + # TODO: ignore_ext not really tested self.assertEqual(1, lc.info()['commit_revision']) def test_diff_summary(self): @@ -146,6 +153,15 @@ def test_info(self): info['entry#kind'], 'dir') + info = cc.info(revision=1) + self.assertEqual( + info['commit_revision'], + 1) + + info = cc.info(include_ext==True) + self.assertIsNotNone(info) + # TODO: --include-externals not really tested + def test_info_revision(self): with svn.test_support.temp_common() as (_, working_path, cc): svn.test_support.populate_bigger_file_changes1() diff --git a/tests/test_remote.py b/tests/test_remote.py index 5b83c85..9d3eec9 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,3 +1,4 @@ +import glob import os import shutil import unittest @@ -27,6 +28,10 @@ def tearDown(self): shutil.rmtree('trial') def test_error_client_formation(self): + """ + Testing checkout of incorrect URL + :return: + """ try: svn.remote.RemoteClient(self.test_fake_url).checkout('.') except svn.exception.SvnException: @@ -42,7 +47,47 @@ def test_checkout(self): svn.remote.RemoteClient(self.test_svn_url).checkout('trial') self.assertTrue(os.path.exists('trial')) + def test_error_checkout_depth(self): + """ + Testing checkout with incorrect argument for option depth + :return: + """ + repo = "trial_checkout_opt_err" + try: + svn.remote.RemoteClient(self.test_svn_url).checkout(repo, depth="toto") + except svn.exception.SvnException: + self.assertFalse(os.path.exists(repo)) + else: + if os.path.exists(repo): + shutil.rmtree(repo) + raise Exception("Expected exception for bad URL.") + + def test_checkout_options(self): + """ + Testing options of checkout + :return: + """ + repo = "trial_checkout_opt" + rc = svn.remote.RemoteClient(self.test_svn_url) + + rc.checkout(repo, force=True) + self.assertTrue(os.path.exists(repo)) + shutil.rmtree(repo) + + rc.checkout(repo, depth="empty") + self.assertTrue(os.path.exists(repo)) + self.assertListEqual(glob.glob(repo + "/*.*"), []) + shutil.rmtree(repo) + + rc.checkout(repo, ignore_ext=True) + self.assertTrue(os.path.exists(repo)) + shutil.rmtree(repo) + def test_remove(self): + """ + Testing remove + :return: + """ with svn.test_support.temp_repo() as (_, rc): with svn.test_support.temp_checkout(): svn.test_support.populate_bigger_file_changes1() From 2a2e729ef3e7778ae1920ad48b75c4c964cc7f80 Mon Sep 17 00:00:00 2001 From: Jean-Francois Thuong Date: Tue, 22 Dec 2020 19:49:54 +0800 Subject: [PATCH 2/8] Corrected typo and added cache in .gitignore --- .gitignore | 1 + tests/test_common.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d712718..d569ed1 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ venv* .tox /pip-selfcheck.json /man +.mypy_cache diff --git a/tests/test_common.py b/tests/test_common.py index b558257..2812476 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -158,7 +158,7 @@ def test_info(self): info['commit_revision'], 1) - info = cc.info(include_ext==True) + info = cc.info(include_ext=True) self.assertIsNotNone(info) # TODO: --include-externals not really tested From d6042dd107fda66b630e46c781fa8cfc314826b1 Mon Sep 17 00:00:00 2001 From: Jean-Francois Thuong Date: Wed, 23 Dec 2020 13:22:16 +0800 Subject: [PATCH 3/8] #96: Added depth and external to list and commit --- svn/common.py | 14 +++++++++----- svn/local.py | 10 ++++++++-- tests/test_common.py | 8 ++++++-- tests/test_local.py | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/svn/common.py b/svn/common.py index 353b971..f89e96c 100644 --- a/svn/common.py +++ b/svn/common.py @@ -301,15 +301,19 @@ def export(self, to_path, revision=None, force=False): self.run_command('export', cmd) - def list(self, extended=False, rel_path=None): + def list(self, extended=False, rel_path=None, depth=None, include_ext=False): full_url_or_path = self.__url_or_path if rel_path is not None: full_url_or_path += '/' + rel_path + cmd = [full_url_or_path] + if depth: + cmd += get_depth_options(depth, set_depth=False) + if include_ext: + cmd += ["--include-externals"] + if extended is False: - for line in self.run_command( - 'ls', - [full_url_or_path]): + for line in self.run_command('ls', cmd): line = line.strip() if line: yield line @@ -317,7 +321,7 @@ def list(self, extended=False, rel_path=None): else: raw = self.run_command( 'ls', - ['--xml', full_url_or_path], + ['--xml'] + cmd, do_combine=True) root = xml.etree.ElementTree.fromstring(raw) diff --git a/svn/local.py b/svn/local.py index f160a15..1c9116a 100644 --- a/svn/local.py +++ b/svn/local.py @@ -40,8 +40,14 @@ def add(self, rel_path, do_include_parents=False): args, wd=self.path) - def commit(self, message, rel_filepaths=[]): - args = ['-m', message] + rel_filepaths + def commit(self, message, rel_filepaths=None, depth=None, include_ext=False): + args = ['-m', message] + if depth: + args += svn.common.get_depth_options(depth, set_depth=False) + if include_ext: + args += ["--include-externals"] + if rel_filepaths: + args += rel_filepaths output = self.run_command( 'commit', diff --git a/tests/test_common.py b/tests/test_common.py index 2812476..56f9fc2 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -131,7 +131,11 @@ def test_list(self): 'new_file', ] - self.assertEqual(entries, expected) + self.assertListEqual(entries, expected) + + empty_entries = cc.list(depth="empty", include_ext=True) + self.assertListEqual(empty_entries, []) + # TODO: include_ext/--include-externals not really tested def test_info(self): with svn.test_support.temp_common() as (repo_path, _, cc): @@ -160,7 +164,7 @@ def test_info(self): info = cc.info(include_ext=True) self.assertIsNotNone(info) - # TODO: --include-externals not really tested + # TODO: include_ext/--include-externals not really tested def test_info_revision(self): with svn.test_support.temp_common() as (_, working_path, cc): diff --git a/tests/test_local.py b/tests/test_local.py index 445f4b8..5013c79 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -1,4 +1,5 @@ import os +from shutil import rmtree import unittest import svn.constants @@ -87,3 +88,43 @@ def test_cleanup(self): with svn.test_support.temp_repo(): with svn.test_support.temp_checkout() as (_, lc): lc.cleanup() + + def test_commit(self): + with svn.test_support.temp_repo(): + with svn.test_support.temp_checkout() as (_, lc): + svn.test_support.populate_bigger_file_changes1() + + rel_dirpath = "dir_to_be_added" + if os.path.isdir(rel_dirpath): + rmtree(rel_dirpath) + os.mkdir(rel_dirpath) + lc.add(rel_dirpath) + + rel_filepath_added = os.path.join(rel_dirpath, "added") + with open(rel_filepath_added, 'w') as f: + pass + lc.add(rel_filepath_added) + + rel_filepath_committed = "committed" + with open(rel_filepath_committed, 'w') as f: + pass + lc.add(rel_filepath_committed) + + lc.commit("empty commit", ["."], depth="empty") + info = lc.info(rel_filepath_committed) + info_in_dir = lc.info(rel_filepath_added) + self.assertEqual(info["wcinfo_schedule"], "add") + self.assertEqual(info_in_dir["wcinfo_schedule"], "add") + + lc.commit("commit files", depth="files") + info = lc.info(rel_filepath_committed) + info_in_dir = lc.info(rel_filepath_added) + self.assertEqual(info["wcinfo_schedule"], "normal") + self.assertEqual(info_in_dir["wcinfo_schedule"], "add") + + lc.commit("commit all", depth="infinity") + info_in_dir = lc.info(rel_filepath_added) + self.assertEqual(info_in_dir["wcinfo_schedule"], "normal") + + lc.commit("commit external", include_ext=True) + # TODO: include_ext/--include-externals not really tested From b29fb71f97eb760ebd774234db864069d01d38ff Mon Sep 17 00:00:00 2001 From: Jean-Francois Thuong Date: Wed, 23 Dec 2020 14:04:50 +0800 Subject: [PATCH 4/8] #169: Corrected bug for info on file just added - Corrected CommonClient.info for case when a file has been added but not committed - Improved Test Cases for info function --- svn/common.py | 29 +++++++++++++++++------------ tests/test_common.py | 35 +++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/svn/common.py b/svn/common.py index f89e96c..7d43a07 100644 --- a/svn/common.py +++ b/svn/common.py @@ -95,7 +95,8 @@ def info(self, rel_path=None, revision=None, include_ext=False): root = xml.etree.ElementTree.fromstring(result) entry_attr = root.find('entry').attrib - commit_attr = root.find('entry/commit').attrib + commit_tag = root.find('entry/commit') + commit_attr = commit_tag.attrib if commit_tag else None relative_url = root.find('entry/relative-url') author = root.find('entry/commit/author') @@ -113,20 +114,22 @@ def info(self, rel_path=None, revision=None, include_ext=False): 'entry#kind': entry_attr['kind'], 'entry#path': entry_attr['path'], - 'entry#revision': int(entry_attr['revision']), 'repository/root': root.find('entry/repository/root').text, 'repository/uuid': root.find('entry/repository/uuid').text, 'wc-info/wcroot-abspath': self.__element_text(wcroot_abspath), 'wc-info/schedule': self.__element_text(wcinfo_schedule), - 'wc-info/depth': self.__element_text(wcinfo_depth), - 'commit/author': self.__element_text(author), - - 'commit/date': dateutil.parser.parse( - root.find('entry/commit/date').text), - 'commit#revision': int(commit_attr['revision']), + 'wc-info/depth': self.__element_text(wcinfo_depth) } + if commit_attr: + info.update({ + 'entry#revision': int(entry_attr['revision']), + 'commit/author': self.__element_text(author), + 'commit/date': dateutil.parser.parse( + root.find('entry/commit/date').text), + 'commit#revision': int(commit_attr['revision']), + }) # Set some more intuitive keys, because no one likes dealing with # symbols. However, we retain the old ones to maintain backwards- @@ -137,15 +140,17 @@ def info(self, rel_path=None, revision=None, include_ext=False): info['entry_kind'] = info['entry#kind'] info['entry_path'] = info['entry#path'] - info['entry_revision'] = info['entry#revision'] info['repository_root'] = info['repository/root'] info['repository_uuid'] = info['repository/uuid'] info['wcinfo_wcroot_abspath'] = info['wc-info/wcroot-abspath'] info['wcinfo_schedule'] = info['wc-info/schedule'] info['wcinfo_depth'] = info['wc-info/depth'] - info['commit_author'] = info['commit/author'] - info['commit_date'] = info['commit/date'] - info['commit_revision'] = info['commit#revision'] + + if commit_attr: + info['entry_revision'] = info['entry#revision'] + info['commit_author'] = info['commit/author'] + info['commit_date'] = info['commit/date'] + info['commit_revision'] = info['commit#revision'] return info diff --git a/tests/test_common.py b/tests/test_common.py index 56f9fc2..dd0cf2e 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -143,24 +143,13 @@ def test_info(self): info = cc.info() - self.assertEqual( - info['entry_path'], - '.') - + self.assertEqual(info['entry_path'], '.') uri = 'file://{}'.format(repo_path) - - self.assertEqual( - info['repository_root'], - uri) - - self.assertEqual( - info['entry#kind'], - 'dir') + self.assertEqual(info['repository_root'], uri) + self.assertEqual(info['entry_kind'], 'dir') info = cc.info(revision=1) - self.assertEqual( - info['commit_revision'], - 1) + self.assertEqual(info['commit_revision'], 1) info = cc.info(include_ext=True) self.assertIsNotNone(info) @@ -181,9 +170,23 @@ def test_info_revision(self): info1 = cc.info(revision=1) self.assertEquals(info1['commit_revision'], 1) - info2 = cc.info(revision=2) + info2 = cc.info(".", revision=2) self.assertEquals(info2['commit_revision'], 2) + rel_filepath_not_committed = "to_be_added" + with open(rel_filepath_not_committed, 'w') as _: + pass + lc.add(rel_filepath_not_committed) + + # Get information on file not yet committed to SVN + info3 = cc.info(rel_filepath_not_committed) + self.assertEquals(info3['wcinfo_schedule'], "add") + self.assertEqual(info3['entry_kind'], 'file') + self.assertNotIn('entry_revision', info3) + for attr in {"date", "revision", "author"}: + self.assertNotIn('commit_' + attr, info3) + + def test_log(self): with svn.test_support.temp_common() as (_, _, cc): svn.test_support.populate_bigger_file_changes1() From 610ed95948d9e09c6fbd1c624084d41bed6c2d74 Mon Sep 17 00:00:00 2001 From: Jean-Francois Thuong Date: Wed, 23 Dec 2020 14:15:02 +0800 Subject: [PATCH 5/8] Corrected test for list with empty depth --- tests/test_common.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_common.py b/tests/test_common.py index dd0cf2e..e8442e1 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -121,9 +121,6 @@ def test_list(self): with svn.test_support.temp_common() as (_, _, cc): svn.test_support.populate_bigger_file_changes1() - entries = cc.list() - entries = sorted(entries) - expected = [ 'committed_changed', 'committed_deleted', @@ -131,10 +128,14 @@ def test_list(self): 'new_file', ] - self.assertListEqual(entries, expected) + entries = cc.list() + self.assertListEqual(sorted(entries), expected) + + empty_entries = cc.list(depth="empty") + self.assertListEqual(list(empty_entries), []) - empty_entries = cc.list(depth="empty", include_ext=True) - self.assertListEqual(empty_entries, []) + entries = cc.list(include_ext=True) + self.assertListEqual(sorted(entries), expected) # TODO: include_ext/--include-externals not really tested def test_info(self): From e16262466cb9de655ba5709e65e14d6231df2890 Mon Sep 17 00:00:00 2001 From: Jean-Francois Thuong Date: Tue, 22 Dec 2020 19:43:29 +0800 Subject: [PATCH 6/8] #169: Corrected bug for info on file just added - Corrected CommonClient.info for case when a file has been added but not committed - Improved Test Cases for info function #96: Added depth and external to several functions - Added depth, (set_depth), and "external" options for commands - Updated commands checkout, update, info, list, and commit --- svn/common.py | 61 +++++++++++++++++++++++++++++++------------- svn/local.py | 31 +++++++++++++++++++--- svn/remote.py | 8 +++++- tests/test_common.py | 56 ++++++++++++++++++++++++++++------------ tests/test_local.py | 41 +++++++++++++++++++++++++++++ tests/test_remote.py | 45 ++++++++++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 39 deletions(-) diff --git a/svn/common.py b/svn/common.py index a53e2f2..7d43a07 100644 --- a/svn/common.py +++ b/svn/common.py @@ -19,6 +19,20 @@ _HUNK_HEADER_LINE_NUMBERS_PREFIX = '@@ ' +def get_depth_options(depth, set_depth=False): + """Get options for depth and check (set_depth=True for --set-depth)""" + depth_values = {"empty", "files", "immediates", "infinity"} + if set_depth: + depth_values = depth_values.union({"exclude"}) + + if depth not in depth_values: + raise svn.exception.SvnException( + "Invalid depth '{d}' (values allowed: {v})!".format(d=depth, v=depth_values) + ) + + return ["--set-depth" if set_depth else "--depth", depth] + + class CommonClient(svn.common_base.CommonBase): def __init__(self, url_or_path, type_, username=None, password=None, svn_filepath='svn', trust_cert=None, env={}, *args, **kwargs): @@ -61,7 +75,7 @@ def __element_text(self, element): return None - def info(self, rel_path=None, revision=None): + def info(self, rel_path=None, revision=None, include_ext=False): cmd = [] if revision is not None: cmd += ['-r', str(revision)] @@ -70,6 +84,8 @@ def info(self, rel_path=None, revision=None): if rel_path is not None: full_url_or_path += '/' + rel_path cmd += ['--xml', full_url_or_path] + if include_ext: + cmd += ["--include-externals"] result = self.run_command( 'info', @@ -79,7 +95,8 @@ def info(self, rel_path=None, revision=None): root = xml.etree.ElementTree.fromstring(result) entry_attr = root.find('entry').attrib - commit_attr = root.find('entry/commit').attrib + commit_tag = root.find('entry/commit') + commit_attr = commit_tag.attrib if commit_tag else None relative_url = root.find('entry/relative-url') author = root.find('entry/commit/author') @@ -97,20 +114,22 @@ def info(self, rel_path=None, revision=None): 'entry#kind': entry_attr['kind'], 'entry#path': entry_attr['path'], - 'entry#revision': int(entry_attr['revision']), 'repository/root': root.find('entry/repository/root').text, 'repository/uuid': root.find('entry/repository/uuid').text, 'wc-info/wcroot-abspath': self.__element_text(wcroot_abspath), 'wc-info/schedule': self.__element_text(wcinfo_schedule), - 'wc-info/depth': self.__element_text(wcinfo_depth), - 'commit/author': self.__element_text(author), - - 'commit/date': dateutil.parser.parse( - root.find('entry/commit/date').text), - 'commit#revision': int(commit_attr['revision']), + 'wc-info/depth': self.__element_text(wcinfo_depth) } + if commit_attr: + info.update({ + 'entry#revision': int(entry_attr['revision']), + 'commit/author': self.__element_text(author), + 'commit/date': dateutil.parser.parse( + root.find('entry/commit/date').text), + 'commit#revision': int(commit_attr['revision']), + }) # Set some more intuitive keys, because no one likes dealing with # symbols. However, we retain the old ones to maintain backwards- @@ -121,15 +140,17 @@ def info(self, rel_path=None, revision=None): info['entry_kind'] = info['entry#kind'] info['entry_path'] = info['entry#path'] - info['entry_revision'] = info['entry#revision'] info['repository_root'] = info['repository/root'] info['repository_uuid'] = info['repository/uuid'] info['wcinfo_wcroot_abspath'] = info['wc-info/wcroot-abspath'] info['wcinfo_schedule'] = info['wc-info/schedule'] info['wcinfo_depth'] = info['wc-info/depth'] - info['commit_author'] = info['commit/author'] - info['commit_date'] = info['commit/date'] - info['commit_revision'] = info['commit#revision'] + + if commit_attr: + info['entry_revision'] = info['entry#revision'] + info['commit_author'] = info['commit/author'] + info['commit_date'] = info['commit/date'] + info['commit_revision'] = info['commit#revision'] return info @@ -285,15 +306,19 @@ def export(self, to_path, revision=None, force=False): self.run_command('export', cmd) - def list(self, extended=False, rel_path=None): + def list(self, extended=False, rel_path=None, depth=None, include_ext=False): full_url_or_path = self.__url_or_path if rel_path is not None: full_url_or_path += '/' + rel_path + cmd = [full_url_or_path] + if depth: + cmd += get_depth_options(depth, set_depth=False) + if include_ext: + cmd += ["--include-externals"] + if extended is False: - for line in self.run_command( - 'ls', - [full_url_or_path]): + for line in self.run_command('ls', cmd): line = line.strip() if line: yield line @@ -301,7 +326,7 @@ def list(self, extended=False, rel_path=None): else: raw = self.run_command( 'ls', - ['--xml', full_url_or_path], + ['--xml'] + cmd, do_combine=True) root = xml.etree.ElementTree.fromstring(raw) diff --git a/svn/local.py b/svn/local.py index 686063b..1c9116a 100644 --- a/svn/local.py +++ b/svn/local.py @@ -40,18 +40,41 @@ def add(self, rel_path, do_include_parents=False): args, wd=self.path) - def commit(self, message, rel_filepaths=[]): - args = ['-m', message] + rel_filepaths + def commit(self, message, rel_filepaths=None, depth=None, include_ext=False): + args = ['-m', message] + if depth: + args += svn.common.get_depth_options(depth, set_depth=False) + if include_ext: + args += ["--include-externals"] + if rel_filepaths: + args += rel_filepaths output = self.run_command( 'commit', args, wd=self.path) - def update(self, rel_filepaths=[], revision=None): + def update( + self, + rel_filepaths=[], + revision=None, + force=False, + depth=None, + set_depth=None, + ignore_ext=False, + ): cmd = [] if revision is not None: - cmd += ['-r', str(revision)] + cmd += ["-r", str(revision)] + if force: + cmd += ["--force"] + if depth: + cmd += svn.common.get_depth_options(depth, set_depth=False) + if set_depth: + cmd += svn.common.get_depth_options(set_depth, set_depth=True) + if ignore_ext: + cmd += ["--ignore-externals"] + cmd += rel_filepaths self.run_command( 'update', diff --git a/svn/remote.py b/svn/remote.py index c55bf96..3dadd20 100644 --- a/svn/remote.py +++ b/svn/remote.py @@ -10,10 +10,16 @@ def __init__(self, url, *args, **kwargs): svn.constants.LT_URL, *args, **kwargs) - def checkout(self, path, revision=None): + def checkout(self, path, revision=None, force=False, depth=None, ignore_ext=False): cmd = [] if revision is not None: cmd += ['-r', str(revision)] + if force: + cmd += ["--force"] + if depth: + cmd += svn.common.get_depth_options(depth, set_depth=False) + if ignore_ext: + cmd += ["--ignore-externals"] cmd += [self.url, path] diff --git a/tests/test_common.py b/tests/test_common.py index ba74bec..e8442e1 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -23,7 +23,14 @@ def test_update(self): lc.update() self.assertEqual(3, lc.info()['commit_revision']) - lc.update(revision=1) + lc.update(set_depth="files") + self.assertEqual("files", lc.info()['wcinfo_depth']) + + lc.update(depth="empty") # depth is not changed + self.assertEqual("files", lc.info()['wcinfo_depth']) + + lc.update(revision=1, ignore_ext=True) + # TODO: ignore_ext not really tested self.assertEqual(1, lc.info()['commit_revision']) def test_diff_summary(self): @@ -114,9 +121,6 @@ def test_list(self): with svn.test_support.temp_common() as (_, _, cc): svn.test_support.populate_bigger_file_changes1() - entries = cc.list() - entries = sorted(entries) - expected = [ 'committed_changed', 'committed_deleted', @@ -124,7 +128,15 @@ def test_list(self): 'new_file', ] - self.assertEqual(entries, expected) + entries = cc.list() + self.assertListEqual(sorted(entries), expected) + + empty_entries = cc.list(depth="empty") + self.assertListEqual(list(empty_entries), []) + + entries = cc.list(include_ext=True) + self.assertListEqual(sorted(entries), expected) + # TODO: include_ext/--include-externals not really tested def test_info(self): with svn.test_support.temp_common() as (repo_path, _, cc): @@ -132,19 +144,17 @@ def test_info(self): info = cc.info() - self.assertEqual( - info['entry_path'], - '.') - + self.assertEqual(info['entry_path'], '.') uri = 'file://{}'.format(repo_path) + self.assertEqual(info['repository_root'], uri) + self.assertEqual(info['entry_kind'], 'dir') - self.assertEqual( - info['repository_root'], - uri) + info = cc.info(revision=1) + self.assertEqual(info['commit_revision'], 1) - self.assertEqual( - info['entry#kind'], - 'dir') + info = cc.info(include_ext=True) + self.assertIsNotNone(info) + # TODO: include_ext/--include-externals not really tested def test_info_revision(self): with svn.test_support.temp_common() as (_, working_path, cc): @@ -161,9 +171,23 @@ def test_info_revision(self): info1 = cc.info(revision=1) self.assertEquals(info1['commit_revision'], 1) - info2 = cc.info(revision=2) + info2 = cc.info(".", revision=2) self.assertEquals(info2['commit_revision'], 2) + rel_filepath_not_committed = "to_be_added" + with open(rel_filepath_not_committed, 'w') as _: + pass + lc.add(rel_filepath_not_committed) + + # Get information on file not yet committed to SVN + info3 = cc.info(rel_filepath_not_committed) + self.assertEquals(info3['wcinfo_schedule'], "add") + self.assertEqual(info3['entry_kind'], 'file') + self.assertNotIn('entry_revision', info3) + for attr in {"date", "revision", "author"}: + self.assertNotIn('commit_' + attr, info3) + + def test_log(self): with svn.test_support.temp_common() as (_, _, cc): svn.test_support.populate_bigger_file_changes1() diff --git a/tests/test_local.py b/tests/test_local.py index 445f4b8..5013c79 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -1,4 +1,5 @@ import os +from shutil import rmtree import unittest import svn.constants @@ -87,3 +88,43 @@ def test_cleanup(self): with svn.test_support.temp_repo(): with svn.test_support.temp_checkout() as (_, lc): lc.cleanup() + + def test_commit(self): + with svn.test_support.temp_repo(): + with svn.test_support.temp_checkout() as (_, lc): + svn.test_support.populate_bigger_file_changes1() + + rel_dirpath = "dir_to_be_added" + if os.path.isdir(rel_dirpath): + rmtree(rel_dirpath) + os.mkdir(rel_dirpath) + lc.add(rel_dirpath) + + rel_filepath_added = os.path.join(rel_dirpath, "added") + with open(rel_filepath_added, 'w') as f: + pass + lc.add(rel_filepath_added) + + rel_filepath_committed = "committed" + with open(rel_filepath_committed, 'w') as f: + pass + lc.add(rel_filepath_committed) + + lc.commit("empty commit", ["."], depth="empty") + info = lc.info(rel_filepath_committed) + info_in_dir = lc.info(rel_filepath_added) + self.assertEqual(info["wcinfo_schedule"], "add") + self.assertEqual(info_in_dir["wcinfo_schedule"], "add") + + lc.commit("commit files", depth="files") + info = lc.info(rel_filepath_committed) + info_in_dir = lc.info(rel_filepath_added) + self.assertEqual(info["wcinfo_schedule"], "normal") + self.assertEqual(info_in_dir["wcinfo_schedule"], "add") + + lc.commit("commit all", depth="infinity") + info_in_dir = lc.info(rel_filepath_added) + self.assertEqual(info_in_dir["wcinfo_schedule"], "normal") + + lc.commit("commit external", include_ext=True) + # TODO: include_ext/--include-externals not really tested diff --git a/tests/test_remote.py b/tests/test_remote.py index 5b83c85..9d3eec9 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,3 +1,4 @@ +import glob import os import shutil import unittest @@ -27,6 +28,10 @@ def tearDown(self): shutil.rmtree('trial') def test_error_client_formation(self): + """ + Testing checkout of incorrect URL + :return: + """ try: svn.remote.RemoteClient(self.test_fake_url).checkout('.') except svn.exception.SvnException: @@ -42,7 +47,47 @@ def test_checkout(self): svn.remote.RemoteClient(self.test_svn_url).checkout('trial') self.assertTrue(os.path.exists('trial')) + def test_error_checkout_depth(self): + """ + Testing checkout with incorrect argument for option depth + :return: + """ + repo = "trial_checkout_opt_err" + try: + svn.remote.RemoteClient(self.test_svn_url).checkout(repo, depth="toto") + except svn.exception.SvnException: + self.assertFalse(os.path.exists(repo)) + else: + if os.path.exists(repo): + shutil.rmtree(repo) + raise Exception("Expected exception for bad URL.") + + def test_checkout_options(self): + """ + Testing options of checkout + :return: + """ + repo = "trial_checkout_opt" + rc = svn.remote.RemoteClient(self.test_svn_url) + + rc.checkout(repo, force=True) + self.assertTrue(os.path.exists(repo)) + shutil.rmtree(repo) + + rc.checkout(repo, depth="empty") + self.assertTrue(os.path.exists(repo)) + self.assertListEqual(glob.glob(repo + "/*.*"), []) + shutil.rmtree(repo) + + rc.checkout(repo, ignore_ext=True) + self.assertTrue(os.path.exists(repo)) + shutil.rmtree(repo) + def test_remove(self): + """ + Testing remove + :return: + """ with svn.test_support.temp_repo() as (_, rc): with svn.test_support.temp_checkout(): svn.test_support.populate_bigger_file_changes1() From 79d2b98ec20e9336fef1dda0942e4f1932f0549e Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 29 Dec 2020 21:56:30 +0800 Subject: [PATCH 7/8] Renamed parameter to get dept option --- .gitignore | 3 ++- svn/common.py | 10 +++++----- svn/local.py | 6 +++--- svn/remote.py | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index d569ed1..f496e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /dist/ /.DS_Store /build/ +/cover/ *.pyc venv* *.svn @@ -15,4 +16,4 @@ venv* .tox /pip-selfcheck.json /man -.mypy_cache +.mypy_cache \ No newline at end of file diff --git a/svn/common.py b/svn/common.py index 7d43a07..9c4f334 100644 --- a/svn/common.py +++ b/svn/common.py @@ -19,10 +19,10 @@ _HUNK_HEADER_LINE_NUMBERS_PREFIX = '@@ ' -def get_depth_options(depth, set_depth=False): - """Get options for depth and check (set_depth=True for --set-depth)""" +def get_depth_options(depth, is_set_depth=False): + """Get options for depth and check (is_set_depth=True for --set-depth)""" depth_values = {"empty", "files", "immediates", "infinity"} - if set_depth: + if is_set_depth: depth_values = depth_values.union({"exclude"}) if depth not in depth_values: @@ -30,7 +30,7 @@ def get_depth_options(depth, set_depth=False): "Invalid depth '{d}' (values allowed: {v})!".format(d=depth, v=depth_values) ) - return ["--set-depth" if set_depth else "--depth", depth] + return ["--set-depth" if is_set_depth else "--depth", depth] class CommonClient(svn.common_base.CommonBase): @@ -313,7 +313,7 @@ def list(self, extended=False, rel_path=None, depth=None, include_ext=False): cmd = [full_url_or_path] if depth: - cmd += get_depth_options(depth, set_depth=False) + cmd += get_depth_options(depth) if include_ext: cmd += ["--include-externals"] diff --git a/svn/local.py b/svn/local.py index 1c9116a..f7a5336 100644 --- a/svn/local.py +++ b/svn/local.py @@ -43,7 +43,7 @@ def add(self, rel_path, do_include_parents=False): def commit(self, message, rel_filepaths=None, depth=None, include_ext=False): args = ['-m', message] if depth: - args += svn.common.get_depth_options(depth, set_depth=False) + args += svn.common.get_depth_options(depth) if include_ext: args += ["--include-externals"] if rel_filepaths: @@ -69,9 +69,9 @@ def update( if force: cmd += ["--force"] if depth: - cmd += svn.common.get_depth_options(depth, set_depth=False) + cmd += svn.common.get_depth_options(depth) if set_depth: - cmd += svn.common.get_depth_options(set_depth, set_depth=True) + cmd += svn.common.get_depth_options(set_depth, is_set_depth=True) if ignore_ext: cmd += ["--ignore-externals"] diff --git a/svn/remote.py b/svn/remote.py index 3dadd20..f45e6df 100644 --- a/svn/remote.py +++ b/svn/remote.py @@ -17,7 +17,7 @@ def checkout(self, path, revision=None, force=False, depth=None, ignore_ext=Fals if force: cmd += ["--force"] if depth: - cmd += svn.common.get_depth_options(depth, set_depth=False) + cmd += svn.common.get_depth_options(depth) if ignore_ext: cmd += ["--ignore-externals"] From e6423b9b2b8054367fabc8718ba8693449ed4ce3 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 29 Dec 2020 23:29:51 +0800 Subject: [PATCH 8/8] Added depth and propert tests for "add" --- svn/local.py | 6 ++++-- tests/test_local.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/svn/local.py b/svn/local.py index f7a5336..c3bbfbe 100644 --- a/svn/local.py +++ b/svn/local.py @@ -29,11 +29,13 @@ def __init__(self, path_, *args, **kwargs): def __repr__(self): return '' % self.path - def add(self, rel_path, do_include_parents=False): + def add(self, rel_path, do_include_parents=False, depth=None): args = [rel_path] - if do_include_parents is True: + if do_include_parents: args.append('--parents') + if depth: + args += svn.common.get_depth_options(depth) self.run_command( 'add', diff --git a/tests/test_local.py b/tests/test_local.py index 5013c79..9df78cf 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -1,5 +1,6 @@ import os from shutil import rmtree +from svn.exception import SvnException import unittest import svn.constants @@ -89,6 +90,47 @@ def test_cleanup(self): with svn.test_support.temp_checkout() as (_, lc): lc.cleanup() + def test_add(self): + with svn.test_support.temp_repo(): + with svn.test_support.temp_checkout() as (_, lc): + dir1 = "d1" + dir12 = os.path.join(dir1, "d2") + dir123 = os.path.join(dir12, "d3") + dir124 = os.path.join(dir12, "d4") + for dir_ in [dir1, dir12, dir123, dir124]: + if os.path.isdir(dir_): + rmtree(dir_) + os.mkdir(dir_) + + added = "added" + f12 = os.path.join(dir12, "f12") + f123_1 = os.path.join(dir123, "f123.1") + f123_2 = os.path.join(dir123, "f123.2") + f124 = os.path.join(dir124, "f124") + for file in [added, f12, f123_1, f123_2, f124]: + with open(file, 'w') as f: + pass + + lc.add(dir1, depth="empty") + self.assertRaises(SvnException, lc.info, dir12) + lc.run_command("revert", [dir1]) + + lc.add(added) + info = lc.info(added) + self.assertEqual(info["wcinfo_schedule"], "add") + + lc.add(f124, do_include_parents=True) + for path in [f124, dir124]: + info = lc.info(path) + self.assertEqual(info["wcinfo_schedule"], "add") + + lc.add(dir123, depth="infinity") + for path in [f123_1, f123_2]: + info = lc.info(path) + self.assertEqual(info["wcinfo_schedule"], "add") + + + def test_commit(self): with svn.test_support.temp_repo(): with svn.test_support.temp_checkout() as (_, lc):