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