Package moap :: Package vcs :: Module vcs
[hide private]
[frames] | no frames]

Source Code for Module moap.vcs.vcs

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  """ 
  5  Version Control System functionality. 
  6  """ 
  7   
  8  import re 
  9  import os 
 10  import sys 
 11   
 12  from moap.util import util, log 
 13   
14 -def getNames():
15 """ 16 Returns a sorted list of VCS names. 17 """ 18 moduleNames = util.getPackageModules('moap.vcs', ignore=['vcs', ]) 19 modules = [util.namedModule('moap.vcs.%s' % s) for s in moduleNames] 20 names = [m.VCSClass.name for m in modules] 21 names.sort() 22 return names
23
24 -def detect(path=None):
25 """ 26 Detect which version control system is being used in the source tree. 27 28 @returns: an instance of a subclass of L{VCS}, or None. 29 """ 30 log.debug('vcs', 'detecting VCS in %s' % path) 31 if not path: 32 path = os.getcwd() 33 systems = util.getPackageModules('moap.vcs', ignore=['vcs', ]) 34 log.debug('vcs', 'trying vcs modules %r' % systems) 35 36 for s in systems: 37 m = util.namedModule('moap.vcs.%s' % s) 38 39 try: 40 ret = m.detect(path) 41 except AttributeError: 42 sys.stderr.write('moap.vcs.%s is missing detect()\n' % s) 43 continue 44 45 if ret: 46 try: 47 o = m.VCSClass(path) 48 except AttributeError: 49 sys.stderr.write('moap.vcs.%s is missing VCSClass()\n' % s) 50 continue 51 52 log.debug('vcs', 'detected VCS %s' % s) 53 54 return o 55 log.debug('vcs', 'did not find %s' % s) 56 57 return None
58 59 # FIXME: add stdout and stderr, so all spawned commands output there instead
60 -class VCS(log.Loggable):
61 """ 62 cvar path: the path to the top of the source tree 63 """ 64 name = 'Some Version Control System' 65 logCategory = 'VCS' 66
67 - def __init__(self, path=None):
68 self.path = path 69 if not path: 70 self.path = os.getcwd()
71
72 - def getNotIgnored(self):
73 """ 74 @return: list of paths unknown to the VCS, relative to the base path 75 """ 76 raise NotImplementedError
77
78 - def ignore(self, paths, commit=True):
79 """ 80 Make the VCS ignore the given list of paths. 81 82 @param paths: list of paths, relative to the checkout directory 83 @type paths: list of str 84 @param commit: if True, commit the ignore updates. 85 @type commit: boolean 86 """ 87 raise NotImplementedError
88
89 - def commit(self, paths, message):
90 """ 91 Commit the given list of paths, with the given message. 92 Note that depending on the VCS, parents that were just added 93 may need to be commited as well. 94 95 @type paths: list 96 @type message: str 97 98 @rtype: bool 99 """
100
101 - def createTree(self, paths):
102 """ 103 Given the list of paths, create a dict of parentPath -> [child, ...] 104 If the path is in the root of the repository, parentPath will be '' 105 106 @rtype: dict of str -> list of str 107 """ 108 result = {} 109 110 if not paths: 111 return result 112 113 for p in paths: 114 # os.path.basename('test/') returns '', so strip possibly trailing / 115 if p.endswith(os.path.sep): p = p[:-1] 116 base = os.path.basename(p) 117 dirname = os.path.dirname(p) 118 if not dirname in result.keys(): 119 result[dirname] = [] 120 result[dirname].append(base) 121 122 return result
123
124 - def diff(self, path):
125 """ 126 Return a diff for the given path. 127 128 @rtype: str 129 @returns: the diff 130 """ 131 raise NotImplementedError
132
133 - def getFileMatcher(self):
134 """ 135 Return an re matcher object that will expand to the file being 136 changed. 137 138 The default implementation works for CVS and SVN. 139 """ 140 return re.compile('^Index: (\S+)$')
141
142 - def getChanges(self, path, diff=None):
143 """ 144 Get a list of changes for the given path and subpaths. 145 146 @type diff: str 147 @param diff: the diff to use instead of a local vcs diff 148 (only useful for testing) 149 150 @returns: dict of path -> list of (oldLine, oldCount, newLine, newCount) 151 """ 152 if not diff: 153 self.debug('getting changes from diff in %s' % path) 154 diff = self.diff(path) 155 156 changes = {} 157 fileMatcher = self.getFileMatcher() 158 159 # cvs diff can put a function name after the final @@ pair 160 # svn diff on a one-line change in a one-line file looks like this: 161 # @@ -1 +1 @@ 162 changeMatcher = re.compile( 163 '^\@\@\s+' # start of line 164 '(-)(\d+),?(\d*)' # -x,y or -x 165 '\s+' 166 '(\+)(\d+),?(\d*)' 167 '\s+\@\@' # end of line 168 ) 169 # We rstrip so that we don't end up with a dangling '' line 170 lines = diff.rstrip('\n').split("\n") 171 self.debug('diff is %d lines' % len(lines)) 172 for i in range(len(lines)): 173 fm = fileMatcher.search(lines[i]) 174 if fm: 175 # found a file being diffed, now get changes 176 path = fm.expand('\\1') 177 self.debug('Found file %s with deltas on line %d' % ( 178 path, i + 1)) 179 changes[path] = [] 180 i += 1 181 while i < len(lines) and not fileMatcher.search(lines[i]): 182 self.log('Looking at line %d for file match' % (i + 1)) 183 m = changeMatcher.search(lines[i]) 184 if m: 185 self.debug('Found change on line %d' % (i + 1)) 186 oldLine = int(m.expand('\\2')) 187 # oldCount can be missing, which means it's 1 188 c = m.expand('\\3') 189 if not c: c = '1' 190 oldCount = int(c) 191 newLine = int(m.expand('\\5')) 192 c = m.expand('\\6') 193 if not c: c = '1' 194 newCount = int(c) 195 i += 1 196 197 # the diff has 3 lines of context by default 198 # if a line was added/removed at the beginning or end, 199 # that context is not always there 200 # so we need to parse each non-changeMatcher line 201 block = [] 202 while i < len(lines) \ 203 and not changeMatcher.search(lines[i]) \ 204 and not fileMatcher.search(lines[i]): 205 block.append(lines[i]) 206 i += 1 207 208 # now we have the whole block 209 self.log('Found change block of %d lines at line %d' % ( 210 len(block), i - len(block) + 1)) 211 212 for line in block: 213 # starting non-change lines add to Line and 214 # subtract from Count 215 if line[0] == ' ': 216 oldLine += 1 217 newLine += 1 218 oldCount -= 1 219 newCount -= 1 220 else: 221 break 222 223 block.reverse() 224 for line in block: 225 # trailing non-change lines subtract from Count 226 # line can be empty 227 if line and line[0] == ' ': 228 oldCount -= 1 229 newCount -= 1 230 else: 231 break 232 233 changes[path].append( 234 (oldLine, oldCount, newLine, newCount)) 235 236 # we're at a change line, so go back 237 i -= 1 238 239 i += 1 240 241 log.debug('vcs', '%d files changed' % len(changes.keys())) 242 return changes
243
244 - def getPropertyChanges(self, path):
245 """ 246 Get a list of property changes for the given path and subpaths. 247 These are metadata changes to files, not content changes. 248 249 @rtype: dict of str -> list of str 250 @returns: dict of path -> list of property names 251 """ 252 log.info('vcs', 253 "subclass %r should implement getPropertyChanges" % self.__class__)
254
255 - def getAdded(self, path):
256 """ 257 Get a list of paths newly added under the given path. 258 259 @rtype: list of str 260 @returns: list of paths 261 """ 262 log.info('vcs', 263 "subclass %r should implement getAdded" % self.__class__)
264
265 - def getDeleted(self, path):
266 """ 267 Get a list of paths deleted under the given path. 268 269 @rtype: list of str 270 @returns: list of paths 271 """ 272 log.info('vcs', 273 "subclass %r should implement getDeleted" % self.__class__)
274 275
276 - def update(self, path):
277 """ 278 Update the given path to the latest version. 279 """ 280 raise NotImplementedError
281
282 -class VCSException(Exception):
283 """ 284 Generic exception for a failed VCS operation. 285 """ 286 pass
287