New svn_helper.py functionality for new_branch.py and related scripts.
authorTodd Larsen <tlarsen@google.com>
Fri, 06 Jun 2008 03:18:55 +0000
changeset 44 9d3a0f98df34
parent 43 fdb9a6d839ae
child 45 66c450a53786
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
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))