# HG changeset patch # User Patrick Mezard # Date 1348690711 -7200 # Node ID e2090fabc1a9ed182449072e2b7826c41320b918 # Parent e1cb98792cf4b674c501fadbd1b967f0acc2f050 editor: use SimpleStringIO in apply_text() The design is a little ugly as the data stored in _openfiles will be a string or a SimpleStringIO depending on the file having been edited or not but this is a simple way to avoid allocating large blocks of data. This is also a bet the output stream passed to apply_text() is only being written and never seeked or read. diff --git a/hgsubversion/editor.py b/hgsubversion/editor.py --- a/hgsubversion/editor.py +++ b/hgsubversion/editor.py @@ -1,5 +1,4 @@ import errno -import cStringIO import sys import tempfile import shutil @@ -13,18 +12,6 @@ import svnwrap import util import svnexternals -class NeverClosingStringIO(object): - def __init__(self): - self._fp = cStringIO.StringIO() - - def __getattr__(self, name): - return getattr(self._fp, name) - - def close(self): - # svn 1.7 apply_delta driver now calls close() on passed file - # object which prevent us from calling getvalue() afterwards. - pass - class EditingError(Exception): pass @@ -234,6 +221,8 @@ class HgEditor(svnwrap.Editor): # A mapping of svn paths to CopiedFile entries self._svncopies = {} # A mapping of batons to (path, data, isexec, islink, copypath) tuples + # data is a SimpleStringIO if the file was edited, a string + # otherwise. self._openfiles = {} # A mapping of file paths to batons self._openpaths = {} @@ -385,6 +374,10 @@ class HgEditor(svnwrap.Editor): % file_baton) path, data, isexec, islink, copypath = self._openfiles.pop(file_baton) del self._openpaths[path] + if not isinstance(data, basestring): + # Files can be opened, properties changed and apply_text + # never called, in which case data is still a string. + data = data.getvalue() self.current.set(path, data, isexec, islink, copypath) @svnwrap.ieditor @@ -524,10 +517,12 @@ class HgEditor(svnwrap.Editor): if file_baton not in self._openfiles: raise EditingError('trying to patch a closed file %s' % file_baton) path, base, isexec, islink, copypath = self._openfiles[file_baton] + if not isinstance(base, basestring): + raise EditingError('trying to edit a file again: %s' % path) if not self.meta.is_path_valid(path): return lambda x: None - target = NeverClosingStringIO() + target = svnwrap.SimpleStringIO(closing=False) self.stream = target handler = svnwrap.apply_txdelta(base, target) @@ -554,7 +549,7 @@ class HgEditor(svnwrap.Editor): # window being None means commit this file if not window: self._openfiles[file_baton] = ( - path, target.getvalue(), isexec, islink, copypath) + path, target, isexec, islink, copypath) except svnwrap.SubversionException, e: # pragma: no cover if e.args[1] == svnwrap.ERR_INCOMPLETE_DATA: self.current.missing.add(path) diff --git a/hgsubversion/svnwrap/common.py b/hgsubversion/svnwrap/common.py --- a/hgsubversion/svnwrap/common.py +++ b/hgsubversion/svnwrap/common.py @@ -155,8 +155,9 @@ class SimpleStringIO(object): write in 16kB blocks (svn 1.7.5) which should be friendly to memory allocators. """ - def __init__(self): + def __init__(self, closing=True): self._blocks = [] + self._closing = closing def write(self, s): self._blocks.append(s) @@ -165,5 +166,6 @@ class SimpleStringIO(object): return ''.join(self._blocks) def close(self): - del self._blocks + if self._closing: + del self._blocks diff --git a/hgsubversion/verify.py b/hgsubversion/verify.py --- a/hgsubversion/verify.py +++ b/hgsubversion/verify.py @@ -124,7 +124,7 @@ def verify(ui, repo, args=None, **opts): raise NotImplementedError() def apply_textdelta(self, file_baton, base_checksum, pool=None): - stream = editor.NeverClosingStringIO() + stream = svnwrap.SimpleStringIO(closing=False) handler = svnwrap.apply_txdelta('', stream) if not callable(handler): raise hgutil.Abort('Error in Subversion bindings: '