##// END OF EJS Templates
convert: urlify svn repos if necessary....
Brendan Cully -
r5008:b6c3abdb default
parent child Browse files
Show More
@@ -1,667 +1,672 b''
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4 #
5 5 # Configuration options:
6 6 #
7 7 # convert.svn.trunk
8 8 # Relative path to the trunk (default: "trunk")
9 9 # convert.svn.branches
10 10 # Relative path to tree of branches (default: "branches")
11 11 #
12 12 # Set these in a hgrc, or on the command line as follows:
13 13 #
14 14 # hg convert --config convert.svn.trunk=wackoname [...]
15 15
16 16 import locale
17 17 import os
18 18 import cPickle as pickle
19 19 from mercurial import util
20 20
21 21 # Subversion stuff. Works best with very recent Python SVN bindings
22 22 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
23 23 # these bindings.
24 24
25 25 from cStringIO import StringIO
26 26
27 27 from common import NoRepo, commit, converter_source
28 28
29 29 try:
30 30 from svn.core import SubversionException, Pool
31 31 import svn.core
32 32 import svn.ra
33 33 import svn.delta
34 34 import svn
35 35 import transport
36 36 except ImportError:
37 37 pass
38 38
39 def geturl(path):
40 if os.path.isdir(path):
41 return 'file://%s' % os.path.normpath(os.path.abspath(path))
42 return path
43
39 44 class CompatibilityException(Exception): pass
40 45
41 46 class changedpath(object):
42 47 def __init__(self, p):
43 48 self.copyfrom_path = p.copyfrom_path
44 49 self.copyfrom_rev = p.copyfrom_rev
45 50 self.action = p.action
46 51
47 52 # SVN conversion code stolen from bzr-svn and tailor
48 53 class convert_svn(converter_source):
49 54 def __init__(self, ui, url, rev=None):
50 55 super(convert_svn, self).__init__(ui, url, rev=rev)
51 56
52 57 try:
53 58 SubversionException
54 59 except NameError:
55 60 msg = 'subversion python bindings could not be loaded\n'
56 61 ui.warn(msg)
57 62 raise NoRepo(msg)
58 63
59 64 self.encoding = locale.getpreferredencoding()
60 65 self.lastrevs = {}
61 66
62 67 latest = None
63 68 if rev:
64 69 try:
65 70 latest = int(rev)
66 71 except ValueError:
67 72 raise util.Abort('svn: revision %s is not an integer' % rev)
68 73 try:
69 74 # Support file://path@rev syntax. Useful e.g. to convert
70 75 # deleted branches.
71 76 at = url.rfind('@')
72 77 if at >= 0:
73 78 latest = int(url[at+1:])
74 79 url = url[:at]
75 80 except ValueError, e:
76 81 pass
77 self.url = url
82 self.url = geturl(url)
78 83 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
79 84 try:
80 self.transport = transport.SvnRaTransport(url=url)
85 self.transport = transport.SvnRaTransport(url=self.url)
81 86 self.ra = self.transport.ra
82 87 self.ctx = self.transport.client
83 88 self.base = svn.ra.get_repos_root(self.ra)
84 89 self.module = self.url[len(self.base):]
85 90 self.modulemap = {} # revision, module
86 91 self.commits = {}
87 92 self.files = {}
88 93 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
89 94 except SubversionException, e:
90 95 raise NoRepo("couldn't open SVN repo %s" % url)
91 96
92 97 try:
93 98 self.get_blacklist()
94 99 except IOError, e:
95 100 pass
96 101
97 102 self.last_changed = self.latest(self.module, latest)
98 103
99 104 self.head = self.revid(self.last_changed)
100 105
101 106 def setrevmap(self, revmap):
102 107 lastrevs = {}
103 108 for revid in revmap.keys():
104 109 uuid, module, revnum = self.revsplit(revid)
105 110 lastrevnum = lastrevs.setdefault(module, revnum)
106 111 if revnum > lastrevnum:
107 112 lastrevs[module] = revnum
108 113 self.lastrevs = lastrevs
109 114
110 115 def exists(self, path, optrev):
111 116 try:
112 117 return svn.client.ls(self.url.rstrip('/') + '/' + path,
113 118 optrev, False, self.ctx)
114 119 except SubversionException, err:
115 120 return []
116 121
117 122 def getheads(self):
118 123 # detect standard /branches, /tags, /trunk layout
119 124 optrev = svn.core.svn_opt_revision_t()
120 125 optrev.kind = svn.core.svn_opt_revision_number
121 126 optrev.value.number = self.last_changed
122 127 rpath = self.url.strip('/')
123 128 cfgtrunk = self.ui.config('convert', 'svn.trunk')
124 129 cfgbranches = self.ui.config('convert', 'svn.branches')
125 130 trunk = (cfgtrunk or 'trunk').strip('/')
126 131 branches = (cfgbranches or 'branches').strip('/')
127 132 if self.exists(trunk, optrev) and self.exists(branches, optrev):
128 133 self.ui.note('found trunk at %r and branches at %r\n' %
129 134 (trunk, branches))
130 135 oldmodule = self.module
131 136 self.module += '/' + trunk
132 137 lt = self.latest(self.module, self.last_changed)
133 138 self.head = self.revid(lt)
134 139 self.heads = [self.head]
135 140 branchnames = svn.client.ls(rpath + '/' + branches, optrev, False,
136 141 self.ctx)
137 142 for branch in branchnames.keys():
138 143 if oldmodule:
139 144 module = '/' + oldmodule + '/' + branches + '/' + branch
140 145 else:
141 146 module = '/' + branches + '/' + branch
142 147 brevnum = self.latest(module, self.last_changed)
143 148 brev = self.revid(brevnum, module)
144 149 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
145 150 self.heads.append(brev)
146 151 elif cfgtrunk or cfgbranches:
147 152 raise util.Abort(_('trunk/branch layout expected, '
148 153 'but not found'))
149 154 else:
150 155 self.ui.note('working with one branch\n')
151 156 self.heads = [self.head]
152 157 return self.heads
153 158
154 159 def getfile(self, file, rev):
155 160 data, mode = self._getfile(file, rev)
156 161 self.modecache[(file, rev)] = mode
157 162 return data
158 163
159 164 def getmode(self, file, rev):
160 165 return self.modecache[(file, rev)]
161 166
162 167 def getchanges(self, rev):
163 168 self.modecache = {}
164 169 files = self.files[rev]
165 170 cl = files
166 171 cl.sort()
167 172 # caller caches the result, so free it here to release memory
168 173 del self.files[rev]
169 174 return cl
170 175
171 176 def getcommit(self, rev):
172 177 if rev not in self.commits:
173 178 uuid, module, revnum = self.revsplit(rev)
174 179 self.module = module
175 180 self.reparent(module)
176 181 stop = self.lastrevs.get(module, 0)
177 182 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
178 183 commit = self.commits[rev]
179 184 # caller caches the result, so free it here to release memory
180 185 del self.commits[rev]
181 186 return commit
182 187
183 188 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
184 189 strict_node_history=False):
185 190 '''wrapper for svn.ra.get_log.
186 191 on a large repository, svn.ra.get_log pins huge amounts of
187 192 memory that cannot be recovered. work around it by forking
188 193 and writing results over a pipe.'''
189 194
190 195 def child(fp):
191 196 protocol = -1
192 197 def receiver(orig_paths, revnum, author, date, message, pool):
193 198 if orig_paths is not None:
194 199 for k, v in orig_paths.iteritems():
195 200 orig_paths[k] = changedpath(v)
196 201 pickle.dump((orig_paths, revnum, author, date, message),
197 202 fp, protocol)
198 203
199 204 try:
200 205 # Use an ra of our own so that our parent can consume
201 206 # our results without confusing the server.
202 207 t = transport.SvnRaTransport(url=self.url)
203 208 svn.ra.get_log(t.ra, paths, start, end, limit,
204 209 discover_changed_paths,
205 210 strict_node_history,
206 211 receiver)
207 212 except SubversionException, (_, num):
208 213 self.ui.print_exc()
209 214 pickle.dump(num, fp, protocol)
210 215 else:
211 216 pickle.dump(None, fp, protocol)
212 217 fp.close()
213 218
214 219 def parent(fp):
215 220 while True:
216 221 entry = pickle.load(fp)
217 222 try:
218 223 orig_paths, revnum, author, date, message = entry
219 224 except:
220 225 if entry is None:
221 226 break
222 227 raise SubversionException("child raised exception", entry)
223 228 yield entry
224 229
225 230 rfd, wfd = os.pipe()
226 231 pid = os.fork()
227 232 if pid:
228 233 os.close(wfd)
229 234 for p in parent(os.fdopen(rfd, 'rb')):
230 235 yield p
231 236 ret = os.waitpid(pid, 0)[1]
232 237 if ret:
233 238 raise util.Abort(_('get_log %s') % util.explain_exit(ret))
234 239 else:
235 240 os.close(rfd)
236 241 child(os.fdopen(wfd, 'wb'))
237 242 os._exit(0)
238 243
239 244 def gettags(self):
240 245 tags = {}
241 246 start = self.revnum(self.head)
242 247 try:
243 248 for entry in self.get_log(['/tags'], 0, start):
244 249 orig_paths, revnum, author, date, message = entry
245 250 for path in orig_paths:
246 251 if not path.startswith('/tags/'):
247 252 continue
248 253 ent = orig_paths[path]
249 254 source = ent.copyfrom_path
250 255 rev = ent.copyfrom_rev
251 256 tag = path.split('/', 2)[2]
252 257 tags[tag] = self.revid(rev, module=source)
253 258 except SubversionException, (_, num):
254 259 self.ui.note('no tags found at revision %d\n' % start)
255 260 return tags
256 261
257 262 # -- helper functions --
258 263
259 264 def revid(self, revnum, module=None):
260 265 if not module:
261 266 module = self.module
262 267 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
263 268
264 269 def revnum(self, rev):
265 270 return int(rev.split('@')[-1])
266 271
267 272 def revsplit(self, rev):
268 273 url, revnum = rev.encode(self.encoding).split('@', 1)
269 274 revnum = int(revnum)
270 275 parts = url.split('/', 1)
271 276 uuid = parts.pop(0)[4:]
272 277 mod = ''
273 278 if parts:
274 279 mod = '/' + parts[0]
275 280 return uuid, mod, revnum
276 281
277 282 def latest(self, path, stop=0):
278 283 'find the latest revision affecting path, up to stop'
279 284 if not stop:
280 285 stop = svn.ra.get_latest_revnum(self.ra)
281 286 try:
282 287 self.reparent('')
283 288 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
284 289 self.reparent(self.module)
285 290 except SubversionException:
286 291 dirent = None
287 292 if not dirent:
288 293 print self.base, path
289 294 raise util.Abort('%s not found up to revision %d' % (path, stop))
290 295
291 296 return dirent.created_rev
292 297
293 298 def get_blacklist(self):
294 299 """Avoid certain revision numbers.
295 300 It is not uncommon for two nearby revisions to cancel each other
296 301 out, e.g. 'I copied trunk into a subdirectory of itself instead
297 302 of making a branch'. The converted repository is significantly
298 303 smaller if we ignore such revisions."""
299 304 self.blacklist = set()
300 305 blacklist = self.blacklist
301 306 for line in file("blacklist.txt", "r"):
302 307 if not line.startswith("#"):
303 308 try:
304 309 svn_rev = int(line.strip())
305 310 blacklist.add(svn_rev)
306 311 except ValueError, e:
307 312 pass # not an integer or a comment
308 313
309 314 def is_blacklisted(self, svn_rev):
310 315 return svn_rev in self.blacklist
311 316
312 317 def reparent(self, module):
313 318 svn_url = self.base + module
314 319 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
315 320 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
316 321
317 322 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
318 323 def get_entry_from_path(path, module=self.module):
319 324 # Given the repository url of this wc, say
320 325 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
321 326 # extract the "entry" portion (a relative path) from what
322 327 # svn log --xml says, ie
323 328 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
324 329 # that is to say "tests/PloneTestCase.py"
325 330
326 331 if path.startswith(module):
327 332 relative = path[len(module):]
328 333 if relative.startswith('/'):
329 334 return relative[1:]
330 335 else:
331 336 return relative
332 337
333 338 # The path is outside our tracked tree...
334 339 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
335 340 return None
336 341
337 342 self.child_cset = None
338 343 def parselogentry(orig_paths, revnum, author, date, message):
339 344 self.ui.debug("parsing revision %d (%d changes)\n" %
340 345 (revnum, len(orig_paths)))
341 346
342 347 if revnum in self.modulemap:
343 348 new_module = self.modulemap[revnum]
344 349 if new_module != self.module:
345 350 self.module = new_module
346 351 self.reparent(self.module)
347 352
348 353 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
349 354 copies = {}
350 355 entries = []
351 356 rev = self.revid(revnum)
352 357 parents = []
353 358
354 359 # branch log might return entries for a parent we already have
355 360 if (rev in self.commits or
356 361 (revnum < self.lastrevs.get(self.module, 0))):
357 362 return
358 363
359 364 try:
360 365 branch = self.module.split("/")[-1]
361 366 if branch == 'trunk':
362 367 branch = ''
363 368 except IndexError:
364 369 branch = None
365 370
366 371 orig_paths = orig_paths.items()
367 372 orig_paths.sort()
368 373 for path, ent in orig_paths:
369 374 # self.ui.write("path %s\n" % path)
370 375 if path == self.module: # Follow branching back in history
371 376 if ent:
372 377 if ent.copyfrom_path:
373 378 # ent.copyfrom_rev may not be the actual last revision
374 379 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
375 380 self.modulemap[prev] = ent.copyfrom_path
376 381 parents = [self.revid(prev, ent.copyfrom_path)]
377 382 self.ui.note('found parent of branch %s at %d: %s\n' % \
378 383 (self.module, prev, ent.copyfrom_path))
379 384 else:
380 385 self.ui.debug("No copyfrom path, don't know what to do.\n")
381 386 # Maybe it was added and there is no more history.
382 387 entrypath = get_entry_from_path(path, module=self.module)
383 388 # self.ui.write("entrypath %s\n" % entrypath)
384 389 if entrypath is None:
385 390 # Outside our area of interest
386 391 self.ui.debug("boring@%s: %s\n" % (revnum, path))
387 392 continue
388 393 entry = entrypath.decode(self.encoding)
389 394
390 395 kind = svn.ra.check_path(self.ra, entrypath, revnum)
391 396 if kind == svn.core.svn_node_file:
392 397 if ent.copyfrom_path:
393 398 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
394 399 if copyfrom_path:
395 400 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
396 401 # It's probably important for hg that the source
397 402 # exists in the revision's parent, not just the
398 403 # ent.copyfrom_rev
399 404 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
400 405 if fromkind != 0:
401 406 copies[self.recode(entry)] = self.recode(copyfrom_path)
402 407 entries.append(self.recode(entry))
403 408 elif kind == 0: # gone, but had better be a deleted *file*
404 409 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
405 410
406 411 # if a branch is created but entries are removed in the same
407 412 # changeset, get the right fromrev
408 413 if parents:
409 414 uuid, old_module, fromrev = self.revsplit(parents[0])
410 415 else:
411 416 fromrev = revnum - 1
412 417 # might always need to be revnum - 1 in these 3 lines?
413 418 old_module = self.modulemap.get(fromrev, self.module)
414 419
415 420 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
416 421 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
417 422
418 423 def lookup_parts(p):
419 424 rc = None
420 425 parts = p.split("/")
421 426 for i in range(len(parts)):
422 427 part = "/".join(parts[:i])
423 428 info = part, copyfrom.get(part, None)
424 429 if info[1] is not None:
425 430 self.ui.debug("Found parent directory %s\n" % info[1])
426 431 rc = info
427 432 return rc
428 433
429 434 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
430 435
431 436 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
432 437
433 438 # need to remove fragment from lookup_parts and replace with copyfrom_path
434 439 if frompath is not None:
435 440 self.ui.debug("munge-o-matic\n")
436 441 self.ui.debug(entrypath + '\n')
437 442 self.ui.debug(entrypath[len(frompath):] + '\n')
438 443 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
439 444 fromrev = froment.copyfrom_rev
440 445 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
441 446
442 447 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
443 448 if fromkind == svn.core.svn_node_file: # a deleted file
444 449 entries.append(self.recode(entry))
445 450 elif fromkind == svn.core.svn_node_dir:
446 451 # print "Deleted/moved non-file:", revnum, path, ent
447 452 # children = self._find_children(path, revnum - 1)
448 453 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
449 454 # Sometimes this is tricky. For example: in
450 455 # The Subversion Repository revision 6940 a dir
451 456 # was copied and one of its files was deleted
452 457 # from the new location in the same commit. This
453 458 # code can't deal with that yet.
454 459 if ent.action == 'C':
455 460 children = self._find_children(path, fromrev)
456 461 else:
457 462 oroot = entrypath.strip('/')
458 463 nroot = path.strip('/')
459 464 children = self._find_children(oroot, fromrev)
460 465 children = [s.replace(oroot,nroot) for s in children]
461 466 # Mark all [files, not directories] as deleted.
462 467 for child in children:
463 468 # Can we move a child directory and its
464 469 # parent in the same commit? (probably can). Could
465 470 # cause problems if instead of revnum -1,
466 471 # we have to look in (copyfrom_path, revnum - 1)
467 472 entrypath = get_entry_from_path("/" + child, module=old_module)
468 473 if entrypath:
469 474 entry = self.recode(entrypath.decode(self.encoding))
470 475 if entry in copies:
471 476 # deleted file within a copy
472 477 del copies[entry]
473 478 else:
474 479 entries.append(entry)
475 480 else:
476 481 self.ui.debug('unknown path in revision %d: %s\n' % \
477 482 (revnum, path))
478 483 elif kind == svn.core.svn_node_dir:
479 484 # Should probably synthesize normal file entries
480 485 # and handle as above to clean up copy/rename handling.
481 486
482 487 # If the directory just had a prop change,
483 488 # then we shouldn't need to look for its children.
484 489 # Also this could create duplicate entries. Not sure
485 490 # whether this will matter. Maybe should make entries a set.
486 491 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
487 492 # This will fail if a directory was copied
488 493 # from another branch and then some of its files
489 494 # were deleted in the same transaction.
490 495 children = self._find_children(path, revnum)
491 496 children.sort()
492 497 for child in children:
493 498 # Can we move a child directory and its
494 499 # parent in the same commit? (probably can). Could
495 500 # cause problems if instead of revnum -1,
496 501 # we have to look in (copyfrom_path, revnum - 1)
497 502 entrypath = get_entry_from_path("/" + child, module=self.module)
498 503 # print child, self.module, entrypath
499 504 if entrypath:
500 505 # Need to filter out directories here...
501 506 kind = svn.ra.check_path(self.ra, entrypath, revnum)
502 507 if kind != svn.core.svn_node_dir:
503 508 entries.append(self.recode(entrypath))
504 509
505 510 # Copies here (must copy all from source)
506 511 # Probably not a real problem for us if
507 512 # source does not exist
508 513
509 514 # Can do this with the copy command "hg copy"
510 515 # if ent.copyfrom_path:
511 516 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
512 517 # module=self.module)
513 518 # copyto_entry = entrypath
514 519 #
515 520 # print "copy directory", copyfrom_entry, 'to', copyto_entry
516 521 #
517 522 # copies.append((copyfrom_entry, copyto_entry))
518 523
519 524 if ent.copyfrom_path:
520 525 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
521 526 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
522 527 if copyfrom_entry:
523 528 copyfrom[path] = ent
524 529 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
525 530
526 531 # Good, /probably/ a regular copy. Really should check
527 532 # to see whether the parent revision actually contains
528 533 # the directory in question.
529 534 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
530 535 children.sort()
531 536 for child in children:
532 537 entrypath = get_entry_from_path("/" + child, module=self.module)
533 538 if entrypath:
534 539 entry = entrypath.decode(self.encoding)
535 540 # print "COPY COPY From", copyfrom_entry, entry
536 541 copyto_path = path + entry[len(copyfrom_entry):]
537 542 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
538 543 # print "COPY", entry, "COPY To", copyto_entry
539 544 copies[self.recode(copyto_entry)] = self.recode(entry)
540 545 # copy from quux splort/quuxfile
541 546
542 547 self.modulemap[revnum] = self.module # track backwards in time
543 548 # a list of (filename, id) where id lets us retrieve the file.
544 549 # eg in git, id is the object hash. for svn it'll be the
545 550 self.files[rev] = zip(entries, [rev] * len(entries))
546 551 if not entries:
547 552 return
548 553
549 554 # Example SVN datetime. Includes microseconds.
550 555 # ISO-8601 conformant
551 556 # '2007-01-04T17:35:00.902377Z'
552 557 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
553 558
554 559 log = message and self.recode(message)
555 560 author = author and self.recode(author) or ''
556 561
557 562 cset = commit(author=author,
558 563 date=util.datestr(date),
559 564 desc=log,
560 565 parents=parents,
561 566 copies=copies,
562 567 branch=branch,
563 568 rev=rev.encode('utf-8'))
564 569
565 570 self.commits[rev] = cset
566 571 if self.child_cset and not self.child_cset.parents:
567 572 self.child_cset.parents = [rev]
568 573 self.child_cset = cset
569 574
570 575 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
571 576 (self.module, from_revnum, to_revnum))
572 577
573 578 try:
574 579 discover_changed_paths = True
575 580 strict_node_history = False
576 581 for entry in self.get_log([self.module], from_revnum, to_revnum):
577 582 orig_paths, revnum, author, date, message = entry
578 583 if self.is_blacklisted(revnum):
579 584 self.ui.note('skipping blacklisted revision %d\n' % revnum)
580 585 continue
581 586 if orig_paths is None:
582 587 self.ui.debug('revision %d has no entries\n' % revnum)
583 588 continue
584 589 parselogentry(orig_paths, revnum, author, date, message)
585 590 except SubversionException, (_, num):
586 591 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
587 592 raise NoSuchRevision(branch=self,
588 593 revision="Revision number %d" % to_revnum)
589 594 raise
590 595
591 596 def _getfile(self, file, rev):
592 597 io = StringIO()
593 598 # TODO: ra.get_file transmits the whole file instead of diffs.
594 599 mode = ''
595 600 try:
596 601 revnum = self.revnum(rev)
597 602 if self.module != self.modulemap[revnum]:
598 603 self.module = self.modulemap[revnum]
599 604 self.reparent(self.module)
600 605 info = svn.ra.get_file(self.ra, file, revnum, io)
601 606 if isinstance(info, list):
602 607 info = info[-1]
603 608 mode = ("svn:executable" in info) and 'x' or ''
604 609 mode = ("svn:special" in info) and 'l' or mode
605 610 except SubversionException, e:
606 611 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
607 612 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
608 613 if e.apr_err in notfound: # File not found
609 614 raise IOError()
610 615 raise
611 616 data = io.getvalue()
612 617 if mode == 'l':
613 618 link_prefix = "link "
614 619 if data.startswith(link_prefix):
615 620 data = data[len(link_prefix):]
616 621 return data, mode
617 622
618 623 def _find_children(self, path, revnum):
619 624 path = path.strip("/")
620 625
621 626 def _find_children_fallback(path, revnum):
622 627 # SWIG python bindings for getdir are broken up to at least 1.4.3
623 628 pool = Pool()
624 629 optrev = svn.core.svn_opt_revision_t()
625 630 optrev.kind = svn.core.svn_opt_revision_number
626 631 optrev.value.number = revnum
627 632 rpath = '/'.join([self.base, path]).strip('/')
628 633 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
629 634
630 635 if hasattr(self, '_find_children_fallback'):
631 636 return _find_children_fallback(path, revnum)
632 637
633 638 self.reparent("/" + path)
634 639 pool = Pool()
635 640
636 641 children = []
637 642 def find_children_inner(children, path, revnum = revnum):
638 643 if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
639 644 fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
640 645 getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
641 646 else:
642 647 getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
643 648 if type(getdir) == dict:
644 649 # python binding for getdir is broken up to at least 1.4.3
645 650 raise CompatibilityException()
646 651 dirents = getdir[0]
647 652 if type(dirents) == int:
648 653 # got here once due to infinite recursion bug
649 654 return
650 655 c = dirents.keys()
651 656 c.sort()
652 657 for child in c:
653 658 dirent = dirents[child]
654 659 if dirent.kind == svn.core.svn_node_dir:
655 660 find_children_inner(children, (path + "/" + child).strip("/"))
656 661 else:
657 662 children.append((path + "/" + child).strip("/"))
658 663
659 664 try:
660 665 find_children_inner(children, "")
661 666 except CompatibilityException:
662 667 self._find_children_fallback = True
663 668 self.reparent(self.module)
664 669 return _find_children_fallback(path, revnum)
665 670
666 671 self.reparent(self.module)
667 672 return [path + "/" + c for c in children]
General Comments 0
You need to be logged in to leave comments. Login now