##// 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
@@ -15,7 +15,8
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
@@ -38,6 +39,12 except ImportError:
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):
@@ -73,7 +80,7 class convert_svn(converter_source):
73 80 try:
74 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
@@ -174,10 +181,65 class convert_svn(converter_source):
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
@@ -186,15 +248,7 class convert_svn(converter_source):
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 251 return tags
195 except SubversionException:
196 self.ui.note('no tags found at revision %d\n' % start)
197 return {}
198 252
199 253 # -- helper functions --
200 254
@@ -276,23 +330,10 class convert_svn(converter_source):
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]
@@ -318,6 +359,7 class convert_svn(converter_source):
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)
@@ -527,13 +569,15 class convert_svn(converter_source):
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,
General Comments 0
You need to be logged in to leave comments. Login now