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