| OLD | NEW |
| (Empty) |
| 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, revision='HEAD'): | |
| 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 @param revision revision to update to | |
| 105 """ | |
| 106 return self._RunCommand([SVN, 'update', path, '--revision', revision]) | |
| 107 | |
| 108 def ListSubdirs(self, url): | |
| 109 """Returns a list of all subdirectories (not files) within a given SVN | |
| 110 url. | |
| 111 | |
| 112 @param url remote directory to list subdirectories of | |
| 113 """ | |
| 114 subdirs = [] | |
| 115 filenames = self._RunCommand([SVN, 'ls', url]).split('\n') | |
| 116 for filename in filenames: | |
| 117 if filename.endswith('/'): | |
| 118 subdirs.append(filename.strip('/')) | |
| 119 return subdirs | |
| 120 | |
| 121 def GetNewFiles(self): | |
| 122 """Return a list of files which are in this directory but NOT under | |
| 123 SVN control. | |
| 124 """ | |
| 125 return self.GetFilesWithStatus(STATUS_NOT_UNDER_SVN_CONTROL) | |
| 126 | |
| 127 def GetNewAndModifiedFiles(self): | |
| 128 """Return a list of files in this dir which are newly added or modified, | |
| 129 including those that are not (yet) under SVN control. | |
| 130 """ | |
| 131 return self.GetFilesWithStatus( | |
| 132 STATUS_ADDED | STATUS_MODIFIED | STATUS_NOT_UNDER_SVN_CONTROL) | |
| 133 | |
| 134 def GetFilesWithStatus(self, status): | |
| 135 """Return a list of files in this dir with the given SVN status. | |
| 136 | |
| 137 @param status bitfield combining one or more STATUS_xxx values | |
| 138 """ | |
| 139 status_types_string = '' | |
| 140 if status & STATUS_ADDED: | |
| 141 status_types_string += 'A' | |
| 142 if status & STATUS_DELETED: | |
| 143 status_types_string += 'D' | |
| 144 if status & STATUS_MODIFIED: | |
| 145 status_types_string += 'M' | |
| 146 if status & STATUS_NOT_UNDER_SVN_CONTROL: | |
| 147 status_types_string += '\?' | |
| 148 status_regex_string = '^[%s].....\s+(.+)$' % status_types_string | |
| 149 stdout = self._RunCommand([SVN, 'status']).replace('\r', '') | |
| 150 status_regex = re.compile(status_regex_string, re.MULTILINE) | |
| 151 files = status_regex.findall(stdout) | |
| 152 return files | |
| 153 | |
| 154 def AddFiles(self, filenames): | |
| 155 """Adds these files to SVN control. | |
| 156 | |
| 157 @param filenames files to add to SVN control | |
| 158 """ | |
| 159 self._RunCommand([SVN, 'add'] + filenames) | |
| 160 | |
| 161 def SetProperty(self, filenames, property_name, property_value): | |
| 162 """Sets a svn property for these files. | |
| 163 | |
| 164 @param filenames files to set property on | |
| 165 @param property_name property_name to set for each file | |
| 166 @param property_value what to set the property_name to | |
| 167 """ | |
| 168 if filenames: | |
| 169 self._RunCommand( | |
| 170 [SVN, 'propset', property_name, property_value] + filenames) | |
| 171 | |
| 172 def SetPropertyByFilenamePattern(self, filename_pattern, | |
| 173 property_name, property_value): | |
| 174 """Sets a svn property for all files matching filename_pattern. | |
| 175 | |
| 176 @param filename_pattern set the property for all files whose names match | |
| 177 this Unix-style filename pattern (e.g., '*.jpg') | |
| 178 @param property_name property_name to set for each file | |
| 179 @param property_value what to set the property_name to | |
| 180 """ | |
| 181 with self._rlock: | |
| 182 all_files = os.listdir(self._directory) | |
| 183 matching_files = sorted(fnmatch.filter(all_files, filename_pattern)) | |
| 184 self.SetProperty(matching_files, property_name, property_value) | |
| 185 | |
| 186 def ExportBaseVersionOfFile(self, file_within_repo, dest_path): | |
| 187 """Retrieves a copy of the base version (what you would get if you ran | |
| 188 'svn revert') of a file within the repository. | |
| 189 | |
| 190 @param file_within_repo path to the file within the repo whose base | |
| 191 version you wish to obtain | |
| 192 @param dest_path destination to which to write the base content | |
| 193 """ | |
| 194 self._RunCommand([SVN, 'export', '--revision', 'BASE', '--force', | |
| 195 file_within_repo, dest_path]) | |
| OLD | NEW |