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 |