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