# HG changeset patch # User Todd Larsen # Date 1212722335 0 # Node ID 9d3a0f98df34e8f3d84e6e702cebda5b823d6ca2 # Parent fdb9a6d839ae34f4c1b39af44b2123b1f57b5408 New svn_helper.py functionality for new_branch.py and related scripts. Patch by: Todd Larsen TO BE REVIEWED Review issue: 301 Review URL: http://codereviews.googleopensourceprograms.com/301 diff -r fdb9a6d839ae -r 9d3a0f98df34 scripts/svn_helper.py --- 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))