Skip to content
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/dist/
/.DS_Store
/build/
/cover/
*.pyc
venv*
*.svn
Expand All @@ -15,4 +16,5 @@ venv*
.tox
/pip-selfcheck.json
/man
.mypy_cache
.python-version
61 changes: 43 additions & 18 deletions svn/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@
_HUNK_HEADER_LINE_NUMBERS_PREFIX = '@@ '


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 is_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 is_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):
Expand Down Expand Up @@ -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)]
Expand All @@ -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',
Expand All @@ -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')
Expand All @@ -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-
Expand All @@ -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

Expand Down Expand Up @@ -285,23 +306,27 @@ 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)
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

else:
raw = self.run_command(
'ls',
['--xml', full_url_or_path],
['--xml'] + cmd,
do_combine=True)

root = xml.etree.ElementTree.fromstring(raw)
Expand Down
37 changes: 31 additions & 6 deletions svn/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,54 @@ def __init__(self, path_, *args, **kwargs):
def __repr__(self):
return '<SVN(LOCAL) %s>' % 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',
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)
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)
if set_depth:
cmd += svn.common.get_depth_options(set_depth, is_set_depth=True)
if ignore_ext:
cmd += ["--ignore-externals"]

cmd += rel_filepaths
self.run_command(
'update',
Expand Down
8 changes: 7 additions & 1 deletion svn/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
if ignore_ext:
cmd += ["--ignore-externals"]

cmd += [self.url, path]

Expand Down
56 changes: 40 additions & 16 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -114,37 +121,40 @@ 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',
'committed_unchanged',
'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):
svn.test_support.populate_bigger_file_changes1()

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):
Expand All @@ -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()
Expand Down
Loading