blob: 0c1ec4fd2b0e2040f5db0faaff86cc575c05ee9c [file] [log] [blame]
epoger@google.com27442af2011-12-29 21:13:08 +00001'''
2Copyright 2011 Google Inc.
3
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6'''
7
epoger@google.com20ad5ac2012-01-17 21:26:05 +00008import fnmatch
9import os
epoger@google.com27442af2011-12-29 21:13:08 +000010import re
11import subprocess
epoger@google.com591469b2013-11-20 19:58:06 +000012import threading
epoger@google.com27442af2011-12-29 21:13:08 +000013
14PROPERTY_MIMETYPE = 'svn:mime-type'
15
epoger@google.com6dbf6cd2012-05-29 21:28:12 +000016# Status types for GetFilesWithStatus()
17STATUS_ADDED = 0x01
18STATUS_DELETED = 0x02
19STATUS_MODIFIED = 0x04
20STATUS_NOT_UNDER_SVN_CONTROL = 0x08
21
borenet@google.coma74302d2013-03-18 18:18:26 +000022
23if os.name == 'nt':
24 SVN = 'svn.bat'
25else:
26 SVN = 'svn'
27
28
29def 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
epoger@google.com27442af2011-12-29 21:13:08 +000044class Svn:
45
46 def __init__(self, directory):
47 """Set up to manipulate SVN control within the given directory.
48
epoger@google.com591469b2013-11-20 19:58:06 +000049 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
epoger@google.com27442af2011-12-29 21:13:08 +000053 @param directory
54 """
55 self._directory = directory
epoger@google.com591469b2013-11-20 19:58:06 +000056 # 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()
epoger@google.com27442af2011-12-29 21:13:08 +000059
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 """
epoger@google.com591469b2013-11-20 19:58:06 +000066 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
epoger@google.com27442af2011-12-29 21:13:08 +000076
borenet@google.coma74302d2013-03-18 18:18:26 +000077 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
epoger@google.com20ad5ac2012-01-17 21:26:05 +000088 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 """
borenet@google.coma74302d2013-03-18 18:18:26 +000096 return self._RunCommand([SVN, 'checkout', url, path])
epoger@google.com20ad5ac2012-01-17 21:26:05 +000097
epoger@google.comf9d134d2013-09-27 15:02:44 +000098 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
epoger@google.comf5ad0772012-09-07 16:05:34 +0000107 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 = []
borenet@google.coma74302d2013-03-18 18:18:26 +0000114 filenames = self._RunCommand([SVN, 'ls', url]).split('\n')
epoger@google.comf5ad0772012-09-07 16:05:34 +0000115 for filename in filenames:
116 if filename.endswith('/'):
117 subdirs.append(filename.strip('/'))
118 return subdirs
119
epoger@google.com27442af2011-12-29 21:13:08 +0000120 def GetNewFiles(self):
121 """Return a list of files which are in this directory but NOT under
122 SVN control.
123 """
epoger@google.com6dbf6cd2012-05-29 21:28:12 +0000124 return self.GetFilesWithStatus(STATUS_NOT_UNDER_SVN_CONTROL)
epoger@google.comd6256552012-01-10 14:10:34 +0000125
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 """
epoger@google.com6dbf6cd2012-05-29 21:28:12 +0000130 return self.GetFilesWithStatus(
131 STATUS_ADDED | STATUS_MODIFIED | STATUS_NOT_UNDER_SVN_CONTROL)
epoger@google.com27442af2011-12-29 21:13:08 +0000132
epoger@google.com6dbf6cd2012-05-29 21:28:12 +0000133 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
epoger@google.com2e0a0612012-05-25 19:48:05 +0000137 """
epoger@google.com6dbf6cd2012-05-29 21:28:12 +0000138 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
bungeman@google.com3c8d9cb2013-10-07 19:57:35 +0000148 stdout = self._RunCommand([SVN, 'status']).replace('\r', '')
epoger@google.com6dbf6cd2012-05-29 21:28:12 +0000149 status_regex = re.compile(status_regex_string, re.MULTILINE)
150 files = status_regex.findall(stdout)
epoger@google.com2e0a0612012-05-25 19:48:05 +0000151 return files
152
epoger@google.com27442af2011-12-29 21:13:08 +0000153 def AddFiles(self, filenames):
154 """Adds these files to SVN control.
155
156 @param filenames files to add to SVN control
157 """
borenet@google.coma74302d2013-03-18 18:18:26 +0000158 self._RunCommand([SVN, 'add'] + filenames)
epoger@google.com27442af2011-12-29 21:13:08 +0000159
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 """
epoger@google.com20ad5ac2012-01-17 21:26:05 +0000167 if filenames:
168 self._RunCommand(
borenet@google.coma74302d2013-03-18 18:18:26 +0000169 [SVN, 'propset', property_name, property_value] + filenames)
epoger@google.com20ad5ac2012-01-17 21:26:05 +0000170
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 """
epoger@google.com591469b2013-11-20 19:58:06 +0000180 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)
epoger@google.com2e0a0612012-05-25 19:48:05 +0000184
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 """
bungeman@google.com3c8d9cb2013-10-07 19:57:35 +0000193 self._RunCommand([SVN, 'export', '--revision', 'BASE', '--force',
epoger@google.com2e0a0612012-05-25 19:48:05 +0000194 file_within_repo, dest_path])