author | Pawel Solyga <Pawel.Solyga@gmail.com> |
Thu, 23 Oct 2008 11:42:22 +0000 | |
changeset 414 | 4877e9d83743 |
parent 180 | a1c6123f9d06 |
permissions | -rwxr-xr-x |
#!/usr/bin/python2.5 # # Copyright 2008 the Melange authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Helper functions that wrap the pysvn Python svn bindings. ls(): returns list of selected directory entries from an SVN repository lsDirs(): wrapper around ls() that only returns node_kind.dir entries lsFiles(): wrapper around ls() that only returns node_kind.files entries exists(): returns True if repo_path exists in the svn repository PYSVN_ALL_NODE_KINDS: all directory entry node_kinds supported by pysvn PYSVN_FILE_DIR_NODE_KINDS: actual file and directory node_kinds This module requires that the pysvn module be installed. """ __authors__ = [ # alphabetical order by last name, please '"Todd Larsen" <tlarsen@google.com>', ] import os import pysvn # top level script needs to use a relative import import settings #: all of the directory entry node_kinds supported py pysvn PYSVN_ALL_NODE_KINDS = set([pysvn.node_kind.none, pysvn.node_kind.dir, pysvn.node_kind.file, pysvn.node_kind.unknown]) #: actual file and directory node_kinds (includes dir and file, but excludes #: the "non-file" none and unknown) PYSVN_FILE_DIR_NODE_KINDS = set([pysvn.node_kind.dir, pysvn.node_kind.file]) # pysvn Client object initialized lazily the first time getPySvnClient() # is called. _client = None def getPySvnClient(): """Returns the module-global pysvn Client object (creating one if needed). Lazily initializes a global pysvn Client object, returning the same one once it exists. """ 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. Args: path: an SVN path (either working copy path or relative path, but not a full repository URL) that uses the canonical / separators """ 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: 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 **kwargs: keyword arguments passed on to Client.list(), including: recurse: indicates if return results should include entries from subdirectories of repo_path as well; default is False Returns: list of (Unicode, coming from pysvn) strings representing the entries of types indicated by keep_kinds; strings are altered to match the 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 = [] # Find shortest repos_path that is a 'dir' entry; will be prefix of all # other entries, since Client.list() always returns repo_path as one of # the entries. It is easier and more reliable to do this search than to # try to manipulate repo_path into the prefix (since the user could supply # any number of valid, but different, formats). shortest_path = raw_entries[0][0].repos_path for svn_list, _ in raw_entries: if svn_list.kind == pysvn.node_kind.dir: entry_path = svn_list.repos_path if len(entry_path) < len(shortest_path): shortest_path = entry_path # normalize the path name of entry_prefix to include a trailing separator entry_prefix = formatDirPath(shortest_path) for svn_list,_ in raw_entries: # only include requested node kinds (dir, file, etc.) if svn_list.kind not in keep_kinds: continue entry_path = svn_list.repos_path # omit the repo_path directory entry itself (simiilar to omitting '.' as # is done by the actual 'svn ls' command) if entry_path == shortest_path: continue # all entry_paths except for the shortest should start with that # shortest entry_prefix, so assert that and remove the prefix assert entry_path.startswith(entry_prefix) entry_path = entry_path[len(entry_prefix):] # normalize directory entry_paths to include a trailing separator if ((svn_list.kind == pysvn.node_kind.dir) and (not entry_path.endswith('/'))): entry_path = entry_path + '/' entries.append(entry_path) return entries def lsDirs(repo_path, **kwargs): """Wrapper around ls() that only returns node_kind.dir entries. """ return ls(repo_path, keep_kinds=(pysvn.node_kind.dir,), **kwargs) def lsFiles(repo_path, **kwargs): """Wrapper around ls() that only returns node_kind.files entries. """ return ls(repo_path, keep_kinds=(pysvn.node_kind.file,), **kwargs) 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))