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