##// 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 b''
15
15
16 import pprint
16 import pprint
17 import locale
17 import locale
18
18 import os
19 import cPickle as pickle
19 from mercurial import util
20 from mercurial import util
20
21
21 # Subversion stuff. Works best with very recent Python SVN bindings
22 # Subversion stuff. Works best with very recent Python SVN bindings
@@ -38,6 +39,12 b' except ImportError:'
38
39
39 class CompatibilityException(Exception): pass
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 # SVN conversion code stolen from bzr-svn and tailor
48 # SVN conversion code stolen from bzr-svn and tailor
42 class convert_svn(converter_source):
49 class convert_svn(converter_source):
43 def __init__(self, ui, url, rev=None):
50 def __init__(self, ui, url, rev=None):
@@ -71,9 +78,9 b' class convert_svn(converter_source):'
71 self.url = url
78 self.url = url
72 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
79 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
73 try:
80 try:
74 self.transport = transport.SvnRaTransport(url = url)
81 self.transport = transport.SvnRaTransport(url=url)
75 self.ra = self.transport.ra
82 self.ra = self.transport.ra
76 self.ctx = svn.client.create_context()
83 self.ctx = self.transport.client
77 self.base = svn.ra.get_repos_root(self.ra)
84 self.base = svn.ra.get_repos_root(self.ra)
78 self.module = self.url[len(self.base):]
85 self.module = self.url[len(self.base):]
79 self.modulemap = {} # revision, module
86 self.modulemap = {} # revision, module
@@ -174,10 +181,65 b' class convert_svn(converter_source):'
174 del self.commits[rev]
181 del self.commits[rev]
175 return commit
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 def gettags(self):
239 def gettags(self):
178 tags = {}
240 tags = {}
179 def parselogentry(*arg, **args):
241 for entry in self.get_log(['/tags'], 0, self.revnum(self.head)):
180 orig_paths, revnum, author, date, message, pool = arg
242 orig_paths, revnum, author, date, message = entry
181 for path in orig_paths:
243 for path in orig_paths:
182 if not path.startswith('/tags/'):
244 if not path.startswith('/tags/'):
183 continue
245 continue
@@ -186,15 +248,7 b' class convert_svn(converter_source):'
186 rev = ent.copyfrom_rev
248 rev = ent.copyfrom_rev
187 tag = path.split('/', 2)[2]
249 tag = path.split('/', 2)[2]
188 tags[tag] = self.revid(rev, module=source)
250 tags[tag] = self.revid(rev, module=source)
189
251 return tags
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 {}
198
252
199 # -- helper functions --
253 # -- helper functions --
200
254
@@ -276,23 +330,10 b' class convert_svn(converter_source):'
276 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
330 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
277 return None
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 self.child_cset = None
333 self.child_cset = None
294 def parselogentry((revnum, orig_paths, author, date, message)):
334 def parselogentry(orig_paths, revnum, author, date, message):
295 self.ui.debug("parsing revision %d\n" % revnum)
335 self.ui.debug("parsing revision %d (%d changes)\n" %
336 (revnum, len(orig_paths)))
296
337
297 if revnum in self.modulemap:
338 if revnum in self.modulemap:
298 new_module = self.modulemap[revnum]
339 new_module = self.modulemap[revnum]
@@ -318,6 +359,7 b' class convert_svn(converter_source):'
318 except IndexError:
359 except IndexError:
319 branch = None
360 branch = None
320
361
362 orig_paths = orig_paths.items()
321 orig_paths.sort()
363 orig_paths.sort()
322 for path, ent in orig_paths:
364 for path, ent in orig_paths:
323 # self.ui.write("path %s\n" % path)
365 # self.ui.write("path %s\n" % path)
@@ -527,13 +569,15 b' class convert_svn(converter_source):'
527 try:
569 try:
528 discover_changed_paths = True
570 discover_changed_paths = True
529 strict_node_history = False
571 strict_node_history = False
530 svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
572 for entry in self.get_log([self.module], from_revnum, to_revnum):
531 discover_changed_paths, strict_node_history,
573 orig_paths, revnum, author, date, message = entry
532 receivelog)
574 if self.is_blacklisted(revnum):
533 self.ui.note('parsing %d log entries for "%s"\n' %
575 self.ui.note('skipping blacklisted revision %d\n' % revnum)
534 (len(received), self.module))
576 continue
535 for entry in received:
577 if orig_paths is None:
536 parselogentry(entry)
578 self.ui.debug('revision %d has no entries\n' % revnum)
579 continue
580 parselogentry(orig_paths, revnum, author, date, message)
537 except SubversionException, (_, num):
581 except SubversionException, (_, num):
538 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
582 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
539 raise NoSuchRevision(branch=self,
583 raise NoSuchRevision(branch=self,
General Comments 0
You need to be logged in to leave comments. Login now