--- a/scripts/svn_helper.py Fri Jun 06 03:15:26 2008 +0000
+++ b/scripts/svn_helper.py Fri Jun 06 03:18:55 2008 +0000
@@ -21,7 +21,6 @@
lsFiles(): wrapper around ls() that only returns node_kind.files entries
exists(): returns True if repo_path exists in the svn repository
-DEF_SVN_REPO: default HTTPS path to the SoC project SVN repo
PYSVN_ALL_NODE_KINDS: all directory entry node_kinds supported by pysvn
PYSVN_FILE_DIR_NODE_KINDS: actual file and directory node_kinds
"""
@@ -32,11 +31,11 @@
]
+import os
import pysvn
+from trunk.scripts import settings
-#: default HTTPS path to the SoC project SVN repo
-DEF_SVN_REPO = 'https://soc.googlecode.com/svn/'
#: all of the directory entry node_kinds supported py pysvn
PYSVN_ALL_NODE_KINDS = set([pysvn.node_kind.none, pysvn.node_kind.dir,
@@ -47,13 +46,109 @@
PYSVN_FILE_DIR_NODE_KINDS = set([pysvn.node_kind.dir, pysvn.node_kind.file])
-def ls(client, repo_path, keep_kinds=PYSVN_FILE_DIR_NODE_KINDS, **kwargs):
+_client = None
+
+
+def getPySvnClient():
+ """Returns the module-global pysvn Client object (creating one if needed).
+ """
+ global _client
+
+ if not _client:
+ _client = pysvn.Client()
+
+ return _client
+
+
+def formatDirPath(path):
+ """Appends trailing separator to non-empty path if it is missing.
+
+ Args:
+ path: path string, which may be with or without a trailing separator,
+ or even empty or None
+
+ Returns:
+ path unchanged if path evaluates to False or already ends with a trailing
+ separator; otherwise, a / separator is appended
+ """
+ if path and not path.endswith('/'):
+ path = path + '/'
+ return path
+
+
+def formatDirPaths(*args):
+ """Apply formatDirPath() to all supplied arguments, returning them in order.
+ """
+ return tuple([formatDirPath(arg) for arg in args])
+
+
+def getCanonicalSvnPath(path):
+ """Returns the supplied svn repo path *without* the trailing / character.
+
+ Some pysvn methods raise exceptions if svn directory URLs end with a
+ trailing / ("non-canonical form") and some do not. Go figure...
+ """
+ if path and path.endswith('/'):
+ path = path[:-1]
+ return path
+
+
+def useLocalOsSep(path):
+ """Return path with all / characters replaced with os.sep, to be OS-agnostic.
+ """
+ return path.replace('/', os.sep)
+
+
+def getExpandedWorkingCopyPath(path, wc_root=None):
+ """Returns expanded, local, native filesystem working copy path.
+
+ Args:
+ path: path to expand and convert to local filesystem directory separators
+ wc_root: if present, prepended to path first
+ """
+ path = useLocalOsSep(path)
+
+ if wc_root:
+ # prepend (Windows-compatible) working copy root if one was supplied
+ path = os.path.join(useLocalOsSep(wc_root), path)
+
+ path = settings.getExpandedPath(path)
+
+ if not path.endswith(os.sep):
+ path = path + os.sep
+
+ return path
+
+
+def encodeRevision(rev):
+ """Encode supplied revision into a pysvn.Revision instance.
+
+ This function is currently very simplistic and does not produce all possible
+ types of pysvn.Revision object. See below for current limitations.
+
+ Args:
+ rev: integer revision number or None
+
+ Returns:
+ HEAD pysvn.Revision object if rev is None,
+ otherwise a pysvn.opt_revision_kind.number pysvn.Revision object created
+ using the supplied integer revision number
+ """
+ if rev is None:
+ return pysvn.Revision(pysvn.opt_revision_kind.head)
+
+ return pysvn.Revision(pysvn.opt_revision_kind.number, int(rev))
+
+
+def ls(repo_path, client=None, keep_kinds=PYSVN_FILE_DIR_NODE_KINDS, **kwargs):
"""Returns a list of (possibly recursive) svn repo directory entries.
Args:
- client: pysvn Client instance
repo_path: absolute svn repository path URL, including the server and
directory path within the repo
+ client: pysvn Client instance; default is None, which will use the pysvn
+ Client created by first call to getPySvnClient() (or create one if
+ necessary)
keep_kinds: types of directory entries to keep in the returned list; a
collection of pysvn.node_kind objects; default is
PYSVN_FILE_DIR_NODE_KINDS
@@ -67,6 +162,9 @@
output of the actual 'svn ls' command: repo_path prefix is removed,
directories end with the / separator.
"""
+ if not client:
+ client = getPySvnClient()
+
raw_entries = client.list(repo_path, **kwargs)
entries = []
@@ -85,10 +183,7 @@
shortest_path = entry_path
# normalize the path name of entry_prefix to include a trailing separator
- entry_prefix = shortest_path
-
- if not entry_prefix.endswith('/'):
- entry_prefix = entry_prefix + '/'
+ entry_prefix = formatDirPath(shortest_path)
for svn_list,_ in raw_entries:
# only include requested node kinds (dir, file, etc.)
@@ -117,23 +212,134 @@
return entries
-def lsDirs(client, repo_path, **kwargs):
+def lsDirs(repo_path, **kwargs):
"""Wrapper around ls() that only returns node_kind.dir entries.
"""
- return ls(client, repo_path, keep_kinds=(pysvn.node_kind.dir,), **kwargs)
+ return ls(repo_path, keep_kinds=(pysvn.node_kind.dir,), **kwargs)
-def lsFiles(client, repo_path, **kwargs):
+def lsFiles(repo_path, **kwargs):
"""Wrapper around ls() that only returns node_kind.files entries.
"""
- return ls(client, repo_path, keep_kinds=(pysvn.node_kind.file,), **kwargs)
+ return ls(repo_path, keep_kinds=(pysvn.node_kind.file,), **kwargs)
-def exists(client, repo_path):
+def exists(repo_path, client=None):
"""Returns True if repo_path exists in the svn repository."""
+ if not client:
+ client = getPySvnClient()
+
try:
raw_entries = client.list(repo_path)
return True
except pysvn._pysvn.ClientError:
# Client.list() raises an exception if the path is not present in the repo
return False
+
+
+def branchItems(src, dest, items, rev=None, client=None):
+ """Branch a list of items (files and/or directories).
+
+ Using the supplied pysvn client object, a list of items (expected to be
+ present in the src directory) is branched from the absolute svn repo src
+ path URL to the relative working client dest directory.
+
+ Args:
+ src: absolute svn repository source path URL, including the server and
+ directory path within the repo
+ dest: relative svn repository destination path in the current working copy
+ items: list of relative paths of items in src/ to branch to dest/ (no item
+ should begin with the / separator)
+ client: pysvn Client instance; default is None, which will use the pysvn
+ Client created by first call to getPySvnClient() (or create one if
+ necessary)
+ """
+ if not client:
+ client = getPySvnClient()
+
+ src = formatDirPath(src)
+ dest = useLocalOsSep(formatDirPath(dest))
+
+ for item in items:
+ assert not item.startswith('/')
+ src_item = getCanonicalSvnPath(src + item)
+ # attempt to be compatible with Windows working copy paths
+ item = useLocalOsSep(item)
+ client.copy(src_item, dest + item, src_revision=encodeRevision(rev))
+
+
+def branchDir(src, dest, client=None, rev=None):
+ """Branch one directory to another.
+
+ Using the supplied pysvn client object, the absolute svn repo path URL src
+ directory is branched to the relative working client dest directory.
+
+ Args:
+ src: absolute svn repository source path URL, including the server and
+ directory path within the repo
+ dest: relative svn repository destination path in the current working copy
+ client: pysvn Client instance; default is None, which will use the pysvn
+ Client created by first call to getPySvnClient() (or create one if
+ necessary)
+ """
+ if not client:
+ client = getPySvnClient()
+
+ src = getCanonicalSvnPath(src)
+ dest = useLocalOsSep(formatDirPath(dest))
+
+ client.copy(src, dest, src_revision=encodeRevision(rev))
+
+
+def exportItems(src, dest, items, rev=None, client=None):
+ """Export a list of items (files and/or directories).
+
+ Using the supplied pysvn client object, a list of items (expected to be
+ present in the src directory) is exported from the absolute svn repo src
+ path URL to the local filesystem directory.
+
+ Args:
+ src: absolute svn repository source path URL, including the server and
+ directory path within the repo
+ dest: local filesystem destination path
+ items: list of relative paths of items in src/ to export to dest/ (no item
+ should begin with the / separator)
+ client: pysvn Client instance; default is None, which will use the pysvn
+ Client created by first call to getPySvnClient() (or create one if
+ necessary)
+ """
+ if not client:
+ client = getPySvnClient()
+
+ src = formatDirPath(src)
+ dest = useLocalOsSep(formatDirPath(dest))
+
+ for item in items:
+ assert not item.startswith('/')
+ src_item = getCanonicalSvnPath(src + item)
+ # attempt to be compatible with Windows local filesystem paths
+ dest_item = useLocalOsSep(getCanonicalSvnPath(dest + item))
+ client.export(src_item, dest_item, revision=encodeRevision(rev))
+
+
+def exportDir(src, dest, client=None, rev=None):
+ """Export one directory to another.
+
+ Using the supplied pysvn client object, the absolute svn repo path URL src
+ directory is exported to the the local filesystem directory.
+
+ Args:
+ src: absolute svn repository source path URL, including the server and
+ directory path within the repo
+ dest: local filesystem destination path
+ client: pysvn Client instance; default is None, which will use the pysvn
+ Client created by first call to getPySvnClient() (or create one if
+ necessary)
+ """
+ if not client:
+ client = getPySvnClient()
+
+ src = getCanonicalSvnPath(src)
+ dest = useLocalOsSep(getCanonicalSvnPath(dest))
+
+ client.export(src, dest, revision=encodeRevision(rev))