Home | History | Annotate | Download | only in tools
      1 '''
      2 Copyright 2011 Google Inc.
      3 
      4 Use of this source code is governed by a BSD-style license that can be
      5 found in the LICENSE file.
      6 '''
      7 
      8 import fnmatch
      9 import os
     10 import re
     11 import subprocess
     12 import threading
     13 
     14 PROPERTY_MIMETYPE = 'svn:mime-type'
     15 
     16 # Status types for GetFilesWithStatus()
     17 STATUS_ADDED                 = 0x01
     18 STATUS_DELETED               = 0x02
     19 STATUS_MODIFIED              = 0x04
     20 STATUS_NOT_UNDER_SVN_CONTROL = 0x08
     21 
     22 
     23 if os.name == 'nt':
     24   SVN = 'svn.bat'
     25 else:
     26   SVN = 'svn'
     27 
     28 
     29 def Cat(svn_url):
     30     """Returns the contents of the file at the given svn_url. 
     31 
     32     @param svn_url URL of the file to read
     33     """
     34     proc = subprocess.Popen([SVN, 'cat', svn_url],
     35                             stdout=subprocess.PIPE,
     36                             stderr=subprocess.STDOUT)
     37     exitcode = proc.wait()
     38     if not exitcode == 0:
     39         raise Exception('Could not retrieve %s. Verify that the URL is valid '
     40                         'and check your connection.' % svn_url)
     41     return proc.communicate()[0]
     42 
     43 
     44 class Svn:
     45 
     46     def __init__(self, directory):
     47         """Set up to manipulate SVN control within the given directory.
     48 
     49         The resulting object is thread-safe: access to all methods is
     50         synchronized (if one thread is currently executing any of its methods,
     51         all other threads must wait before executing any of its methods).
     52 
     53         @param directory
     54         """
     55         self._directory = directory
     56         # This must be a reentrant lock, so that it can be held by both
     57         # _RunCommand() and (some of) the methods that call it.
     58         self._rlock = threading.RLock()
     59 
     60     def _RunCommand(self, args):
     61         """Run a command (from self._directory) and return stdout as a single
     62         string.
     63 
     64         @param args a list of arguments
     65         """
     66         with self._rlock:
     67             print 'RunCommand: %s' % args
     68             proc = subprocess.Popen(args, cwd=self._directory,
     69                                     stdout=subprocess.PIPE,
     70                                     stderr=subprocess.PIPE)
     71             (stdout, stderr) = proc.communicate()
     72             if proc.returncode is not 0:
     73               raise Exception('command "%s" failed in dir "%s": %s' %
     74                               (args, self._directory, stderr))
     75             return stdout
     76 
     77     def GetInfo(self):
     78         """Run "svn info" and return a dictionary containing its output.
     79         """
     80         output = self._RunCommand([SVN, 'info'])
     81         svn_info = {}
     82         for line in output.split('\n'):
     83           if ':' in line:
     84             (key, value) = line.split(':', 1)
     85             svn_info[key.strip()] = value.strip()
     86         return svn_info
     87 
     88     def Checkout(self, url, path):
     89         """Check out a working copy from a repository.
     90         Returns stdout as a single string.
     91 
     92         @param url URL from which to check out the working copy
     93         @param path path (within self._directory) where the local copy will be
     94         written
     95         """
     96         return self._RunCommand([SVN, 'checkout', url, path])
     97 
     98     def Update(self, path):
     99         """Update the working copy.
    100         Returns stdout as a single string.
    101 
    102         @param path path (within self._directory) within which to run
    103         "svn update"
    104         """
    105         return self._RunCommand([SVN, 'update', path])
    106 
    107     def ListSubdirs(self, url):
    108         """Returns a list of all subdirectories (not files) within a given SVN
    109         url.
    110 
    111         @param url remote directory to list subdirectories of
    112         """
    113         subdirs = []
    114         filenames = self._RunCommand([SVN, 'ls', url]).split('\n')
    115         for filename in filenames:
    116             if filename.endswith('/'):
    117                 subdirs.append(filename.strip('/'))
    118         return subdirs
    119 
    120     def GetNewFiles(self):
    121         """Return a list of files which are in this directory but NOT under
    122         SVN control.
    123         """
    124         return self.GetFilesWithStatus(STATUS_NOT_UNDER_SVN_CONTROL)
    125 
    126     def GetNewAndModifiedFiles(self):
    127         """Return a list of files in this dir which are newly added or modified,
    128         including those that are not (yet) under SVN control.
    129         """
    130         return self.GetFilesWithStatus(
    131             STATUS_ADDED | STATUS_MODIFIED | STATUS_NOT_UNDER_SVN_CONTROL)
    132 
    133     def GetFilesWithStatus(self, status):
    134         """Return a list of files in this dir with the given SVN status.
    135 
    136         @param status bitfield combining one or more STATUS_xxx values
    137         """
    138         status_types_string = ''
    139         if status & STATUS_ADDED:
    140             status_types_string += 'A'
    141         if status & STATUS_DELETED:
    142             status_types_string += 'D'
    143         if status & STATUS_MODIFIED:
    144             status_types_string += 'M'
    145         if status & STATUS_NOT_UNDER_SVN_CONTROL:
    146             status_types_string += '\?'
    147         status_regex_string = '^[%s].....\s+(.+)$' % status_types_string
    148         stdout = self._RunCommand([SVN, 'status']).replace('\r', '')
    149         status_regex = re.compile(status_regex_string, re.MULTILINE)
    150         files = status_regex.findall(stdout)
    151         return files
    152 
    153     def AddFiles(self, filenames):
    154         """Adds these files to SVN control.
    155 
    156         @param filenames files to add to SVN control
    157         """
    158         self._RunCommand([SVN, 'add'] + filenames)
    159 
    160     def SetProperty(self, filenames, property_name, property_value):
    161         """Sets a svn property for these files.
    162 
    163         @param filenames files to set property on
    164         @param property_name property_name to set for each file
    165         @param property_value what to set the property_name to
    166         """
    167         if filenames:
    168             self._RunCommand(
    169                 [SVN, 'propset', property_name, property_value] + filenames)
    170 
    171     def SetPropertyByFilenamePattern(self, filename_pattern,
    172                                      property_name, property_value):
    173         """Sets a svn property for all files matching filename_pattern.
    174 
    175         @param filename_pattern set the property for all files whose names match
    176                this Unix-style filename pattern (e.g., '*.jpg')
    177         @param property_name property_name to set for each file
    178         @param property_value what to set the property_name to
    179         """
    180         with self._rlock:
    181             all_files = os.listdir(self._directory)
    182             matching_files = sorted(fnmatch.filter(all_files, filename_pattern))
    183             self.SetProperty(matching_files, property_name, property_value)
    184 
    185     def ExportBaseVersionOfFile(self, file_within_repo, dest_path):
    186         """Retrieves a copy of the base version (what you would get if you ran
    187         'svn revert') of a file within the repository.
    188 
    189         @param file_within_repo path to the file within the repo whose base
    190                version you wish to obtain
    191         @param dest_path destination to which to write the base content
    192         """
    193         self._RunCommand([SVN, 'export', '--revision', 'BASE', '--force',
    194                           file_within_repo, dest_path])
    195