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