changeset 948:e2090fabc1a9

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.
author Patrick Mezard <patrick@mezard.eu>
date Wed, 26 Sep 2012 22:18:31 +0200
parents e1cb98792cf4
children 297e2b4a6e2c
files hgsubversion/editor.py hgsubversion/svnwrap/common.py hgsubversion/verify.py
diffstat 3 files changed, 15 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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
 
--- 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: '