Rev 5958: Exporting may now be done with a generator in http://bazaar.launchpad.net/~vila/bzr/integration/

Vincent Ladeuil v.ladeuil+lp at free.fr
Tue Jun 7 07:23:37 UTC 2011


At http://bazaar.launchpad.net/~vila/bzr/integration/

------------------------------------------------------------
revno: 5958 [merge]
revision-id: v.ladeuil+lp at free.fr-20110607072336-us9gklyqstjyq4mt
parent: pqm at pqm.ubuntu.com-20110606125209-j8r8jiltfjypii3i
parent: geoffreyfishing at gmail.com-20110606170837-prj4v9au7cfevwfk
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: trunk
timestamp: Tue 2011-06-07 09:23:36 +0200
message:
  Exporting may now be done with a generator
modified:
  bzrlib/export/__init__.py      __init__.py-20051114235828-1ba62cb4062304e6
  bzrlib/export/dir_exporter.py  dir_exporter.py-20051114235828-b51397f56bc7b117
  bzrlib/export/tar_exporter.py  tar_exporter.py-20051114235828-1f6349a2f090a5d0
  bzrlib/export/zip_exporter.py  zip_exporter.py-20051114235828-8f57f954fba6497e
  doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
  doc/en/whats-new/whats-new-in-2.4.txt whatsnewin2.4.txt-20110114044330-nipk1og7j729fy89-1
-------------- next part --------------
=== modified file 'bzrlib/export/__init__.py'
--- a/bzrlib/export/__init__.py	2011-04-28 06:45:01 +0000
+++ b/bzrlib/export/__init__.py	2011-06-06 09:31:59 +0000
@@ -59,40 +59,48 @@
 
     When requesting a specific type of export, load the respective path.
     """
-    def _loader(tree, dest, root, subdir, filtered, force_mtime):
+    def _loader(tree, dest, root, subdir, filtered, force_mtime, fileobj):
         func = pyutils.get_named_object(module, funcname)
-        return func(tree, dest, root, subdir, filtered=filtered,
-                    force_mtime=force_mtime)
+        return func(tree, dest, root, subdir, filtered=filtered, 
+                    force_mtime=force_mtime, fileobj=fileobj)
     register_exporter(scheme, extensions, _loader)
-
-
-def export(tree, dest, format=None, root=None, subdir=None, filtered=False,
-           per_file_timestamps=False):
-    """Export the given Tree to the specific destination.
+    
+def get_export_generator(tree, dest=None, format=None, root=None, subdir=None,
+                         filtered=False, per_file_timestamps=False,
+                         fileobj=None):
+    """Returns a generator that exports the given tree.
+    
+    The generator is expected to yield None while exporting the tree while the
+    actual export is written to ``fileobj``.
 
     :param tree: A Tree (such as RevisionTree) to export
-    :param dest: The destination where the files,etc should be put
+
+    :param dest: The destination where the files, etc should be put
+
     :param format: The format (dir, zip, etc), if None, it will check the
-                   extension on dest, looking for a match
-    :param root: The root location inside the format.
-                 It is common practise to have zipfiles and tarballs
-                 extract into a subdirectory, rather than into the
-                 current working directory.
-                 If root is None, the default root will be
-                 selected as the destination without its
-                 extension.
+        extension on dest, looking for a match
+
+    :param root: The root location inside the format.  It is common practise to
+        have zipfiles and tarballs extract into a subdirectory, rather than
+        into the current working directory.  If root is None, the default root
+        will be selected as the destination without its extension.
+
     :param subdir: A starting directory within the tree. None means to export
         the entire tree, and anything else should specify the relative path to
         a directory to start exporting from.
-    :param filtered: If True, content filtering is applied to the
-                     files exported.
-    :param per_file_timestamps: Whether to use the timestamp stored in the 
-        tree rather than now(). This will do a revision lookup 
-        for every file so will be significantly slower.
+
+    :param filtered: If True, content filtering is applied to the exported
+        files.
+
+    :param per_file_timestamps: Whether to use the timestamp stored in the tree
+        rather than now(). This will do a revision lookup for every file so
+        will be significantly slower.
+
+    :param fileobj: Optional file object to use
     """
     global _exporters, _exporter_extensions
 
-    if format is None:
+    if format is None and dest is not None:
         for ext in _exporter_extensions:
             if dest.endswith(ext):
                 format = _exporter_extensions[ext]
@@ -113,14 +121,48 @@
 
     trace.mutter('export version %r', tree)
 
-    tree.lock_read()
     try:
-        return _exporters[format](tree, dest, root, subdir, filtered=filtered,
-                                  force_mtime=force_mtime)
-    finally:
+        tree.lock_read()
+    
+        for _ in _exporters[format](tree, dest, root, subdir,
+                                    filtered=filtered, 
+                                    force_mtime=force_mtime, fileobj=fileobj):
+            
+            yield
+    finally:    
         tree.unlock()
 
 
+def export(tree, dest, format=None, root=None, subdir=None, filtered=False, 
+           per_file_timestamps=False, fileobj=None):
+    """Export the given Tree to the specific destination.
+
+    :param tree: A Tree (such as RevisionTree) to export
+    :param dest: The destination where the files,etc should be put
+    :param format: The format (dir, zip, etc), if None, it will check the
+                   extension on dest, looking for a match
+    :param root: The root location inside the format.
+                 It is common practise to have zipfiles and tarballs
+                 extract into a subdirectory, rather than into the
+                 current working directory.
+                 If root is None, the default root will be
+                 selected as the destination without its
+                 extension.
+    :param subdir: A starting directory within the tree. None means to export
+        the entire tree, and anything else should specify the relative path to
+        a directory to start exporting from.
+    :param filtered: If True, content filtering is applied to the
+                     files exported.
+    :param per_file_timestamps: Whether to use the timestamp stored in the 
+        tree rather than now(). This will do a revision lookup 
+        for every file so will be significantly slower.
+    :param fileobj: Optional file object to use
+    """
+    for _ in get_export_generator(tree, dest, format, root, subdir, filtered, 
+                                  per_file_timestamps, fileobj):
+        
+        pass
+
 def get_root_name(dest):
     """Get just the root name for an export.
 
@@ -167,15 +209,24 @@
             final_path = path
         if not tree.has_filename(path):
             continue
+        
         yield final_path, entry
 
 
-register_lazy_exporter(None, [], 'bzrlib.export.dir_exporter', 'dir_exporter')
-register_lazy_exporter('dir', [], 'bzrlib.export.dir_exporter', 'dir_exporter')
-register_lazy_exporter('tar', ['.tar'], 'bzrlib.export.tar_exporter', 'plain_tar_exporter')
-register_lazy_exporter('tgz', ['.tar.gz', '.tgz'], 'bzrlib.export.tar_exporter', 'tgz_exporter')
-register_lazy_exporter('tbz2', ['.tar.bz2', '.tbz2'], 'bzrlib.export.tar_exporter', 'tbz_exporter')
-register_lazy_exporter('tlzma', ['.tar.lzma'], 'bzrlib.export.tar_exporter', 'tar_lzma_exporter')
-register_lazy_exporter('txz', ['.tar.xz'], 'bzrlib.export.tar_exporter', 'tar_xz_exporter')
-register_lazy_exporter('zip', ['.zip'], 'bzrlib.export.zip_exporter', 'zip_exporter')
+register_lazy_exporter(None, [], 'bzrlib.export.dir_exporter',
+                       'dir_exporter_generator')
+register_lazy_exporter('dir', [], 'bzrlib.export.dir_exporter',
+                       'dir_exporter_generator')
+register_lazy_exporter('tar', ['.tar'], 'bzrlib.export.tar_exporter',
+                       'plain_tar_exporter_generator')
+register_lazy_exporter('tgz', ['.tar.gz', '.tgz'], 'bzrlib.export.tar_exporter',
+                       'tgz_exporter_generator')
+register_lazy_exporter('tbz2', ['.tar.bz2', '.tbz2'],
+                       'bzrlib.export.tar_exporter', 'tbz_exporter_generator')
+register_lazy_exporter('tlzma', ['.tar.lzma'], 'bzrlib.export.tar_exporter',
+                       'tar_lzma_exporter_generator')
+register_lazy_exporter('txz', ['.tar.xz'], 'bzrlib.export.tar_exporter',
+                       'tar_xz_exporte_generatorr')
+register_lazy_exporter('zip', ['.zip'], 'bzrlib.export.zip_exporter',
+                       'zip_exporter_generator')
 

=== modified file 'bzrlib/export/dir_exporter.py'
--- a/bzrlib/export/dir_exporter.py	2011-03-13 21:30:33 +0000
+++ b/bzrlib/export/dir_exporter.py	2011-06-06 09:31:59 +0000
@@ -27,11 +27,14 @@
     )
 
 
-def dir_exporter(tree, dest, root, subdir=None, filtered=False, force_mtime=None):
-    """Export this tree to a new directory.
+def dir_exporter_generator(tree, dest, root, subdir=None, filtered=False,
+                           force_mtime=None, fileobj=None):
+    """Return a generator that exports this tree to a new directory.
 
     `dest` should either not exist or should be empty. If it does not exist it
     will be created holding the contents of this tree.
+    
+    :param fileobj: Is not used in this exporter
 
     :note: If the export fails, the destination directory will be
            left in an incompletely exported state: export is not transactional.
@@ -42,7 +45,8 @@
         if e.errno == errno.EEXIST:
             # check if directory empty
             if os.listdir(dest) != []:
-                raise errors.BzrError("Can't export tree to non-empty directory.")
+                raise errors.BzrError(
+                    "Can't export tree to non-empty directory.")
         else:
             raise
     # Iterate everything, building up the files we will want to export, and
@@ -69,6 +73,8 @@
         else:
             raise errors.BzrError("don't know how to export {%s} of kind %r" %
                (ie.file_id, ie.kind))
+
+        yield
     # The data returned here can be in any order, but we've already created all
     # the directories
     flags = os.O_CREAT | os.O_TRUNC | os.O_WRONLY | getattr(os, 'O_BINARY', 0)
@@ -92,3 +98,12 @@
         else:
             mtime = tree.get_file_mtime(tree.path2id(relpath), relpath)
         os.utime(fullpath, (mtime, mtime))
+
+        yield
+        
+def dir_exporter(tree, dest, root, subdir=None, filtered=False, 
+                 force_mtime=None, fileobj=None):
+
+    for _ in dir_exporter_generator(tree, dest, root, subdir, filtered,
+                                    force_mtime, fileobj):
+        pass

=== modified file 'bzrlib/export/tar_exporter.py'
--- a/bzrlib/export/tar_exporter.py	2011-05-13 21:05:38 +0000
+++ b/bzrlib/export/tar_exporter.py	2011-06-06 09:31:59 +0000
@@ -14,8 +14,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
-"""Export a Tree to a non-versioned directory.
-"""
+"""Export a Tree to a non-versioned directory."""
 
 import os
 import StringIO
@@ -32,61 +31,96 @@
     filtered_output_bytes,
     )
 
-
-def export_tarball(tree, ball, root, subdir=None, filtered=False,
+def prepare_tarball_item(tree, root, final_path, entry, filtered=False,
+                         force_mtime=None):
+    """Prepare a tarball item for exporting
+        
+    :param tree: Tree to export
+
+    :param final_path: Final path to place item
+
+    :param entry: Entry to export
+
+    :param filtered: Whether to apply filters
+
+    :param force_mtime: Option mtime to force, instead of using tree timestamps.
+    
+    Returns a (tarinfo, fileobj) tuple
+    """
+    filename = osutils.pathjoin(root, final_path).encode('utf8')
+    item = tarfile.TarInfo(filename)
+    if force_mtime is not None:
+        item.mtime = force_mtime
+    else:
+        item.mtime = tree.get_file_mtime(entry.file_id, final_path)
+    if entry.kind == "file":
+        item.type = tarfile.REGTYPE
+        if tree.is_executable(entry.file_id):
+            item.mode = 0755
+        else:
+            item.mode = 0644
+        if filtered:
+            chunks = tree.get_file_lines(entry.file_id)
+            filters = tree._content_filter_stack(final_path)
+            context = ContentFilterContext(final_path, tree, entry)
+            contents = filtered_output_bytes(chunks, filters, context)
+            content = ''.join(contents)
+            item.size = len(content)
+            fileobj = StringIO.StringIO(content)
+        else:
+            item.size = tree.get_file_size(entry.file_id)
+            fileobj = tree.get_file(entry.file_id)
+    elif entry.kind == "directory":
+        item.type = tarfile.DIRTYPE
+        item.name += '/'
+        item.size = 0
+        item.mode = 0755
+        fileobj = None
+    elif entry.kind == "symlink":
+        item.type = tarfile.SYMTYPE
+        item.size = 0
+        item.mode = 0755
+        item.linkname = tree.get_symlink_target(entry.file_id)
+        fileobj = None
+    else:
+        raise errors.BzrError("don't know how to export {%s} of kind %r" 
+                              % (entry.file_id, entry.kind))
+    
+    return (item, fileobj)
+
+def export_tarball_generator(tree, ball, root, subdir=None, filtered=False,
                    force_mtime=None):
-    """Export tree contents to a tarball.
+    """Export tree contents to a tarball. This is a generator.
 
     :param tree: Tree to export
+
     :param ball: Tarball to export to
+
     :param filtered: Whether to apply filters
+
     :param subdir: Sub directory to export
-    :param force_mtime: Option mtime to force, instead of using
-        tree timestamps.
+
+    :param force_mtime: Option mtime to force, instead of using tree
+        timestamps.
     """
-    for dp, ie in _export_iter_entries(tree, subdir):
-        filename = osutils.pathjoin(root, dp).encode('utf8')
-        item = tarfile.TarInfo(filename)
-        if force_mtime is not None:
-            item.mtime = force_mtime
-        else:
-            item.mtime = tree.get_file_mtime(ie.file_id, dp)
-        if ie.kind == "file":
-            item.type = tarfile.REGTYPE
-            if tree.is_executable(ie.file_id):
-                item.mode = 0755
-            else:
-                item.mode = 0644
-            if filtered:
-                chunks = tree.get_file_lines(ie.file_id)
-                filters = tree._content_filter_stack(dp)
-                context = ContentFilterContext(dp, tree, ie)
-                contents = filtered_output_bytes(chunks, filters, context)
-                content = ''.join(contents)
-                item.size = len(content)
-                fileobj = StringIO.StringIO(content)
-            else:
-                item.size = tree.get_file_size(ie.file_id)
-                fileobj = tree.get_file(ie.file_id)
-        elif ie.kind == "directory":
-            item.type = tarfile.DIRTYPE
-            item.name += '/'
-            item.size = 0
-            item.mode = 0755
-            fileobj = None
-        elif ie.kind == "symlink":
-            item.type = tarfile.SYMTYPE
-            item.size = 0
-            item.mode = 0755
-            item.linkname = tree.get_symlink_target(ie.file_id)
-            fileobj = None
-        else:
-            raise errors.BzrError("don't know how to export {%s} of kind %r" %
-                           (ie.file_id, ie.kind))
+    for final_path, entry in _export_iter_entries(tree, subdir):
+
+        (item, fileobj) = prepare_tarball_item(tree, root, final_path,
+                                               entry, filtered, force_mtime)
         ball.addfile(item, fileobj)
 
-
-def tgz_exporter(tree, dest, root, subdir, filtered=False, force_mtime=None):
+        yield
+
+
+def export_tarball(tree, ball, root, subdir=None, filtered=False,
+                   force_mtime=None):
+
+    for _ in export_tarball_generator(tree, ball, root, subdir, filtered,
+                                      force_mtime):
+        pass
+
+def tgz_exporter_generator(tree, dest, root, subdir, filtered=False,
+                           force_mtime=None, fileobj=None):
     """Export this tree to a new tar file.
 
     `dest` will be created holding the contents of this tree; if it
@@ -106,7 +140,9 @@
         root_mtime = None
 
     is_stdout = False
-    if dest == '-':
+    if fileobj is not None:
+        stream = fileobj
+    elif dest == '-':
         basename = None
         stream = sys.stdout
         is_stdout = True
@@ -117,26 +153,41 @@
         # dest. (bug 102234)
         basename = os.path.basename(dest)
     try:
-        zipstream = gzip.GzipFile(basename, 'w', fileobj=stream, mtime=root_mtime)
+        zipstream = gzip.GzipFile(basename, 'w', fileobj=stream,
+                                  mtime=root_mtime)
     except TypeError:
         # Python < 2.7 doesn't support the mtime argument
         zipstream = gzip.GzipFile(basename, 'w', fileobj=stream)
     ball = tarfile.open(None, 'w|', fileobj=zipstream)
-    export_tarball(tree, ball, root, subdir, filtered=filtered,
-                   force_mtime=force_mtime)
-    ball.close()
+
+    for _ in export_tarball_generator(tree, ball, root, subdir, filtered,
+                                      force_mtime):
+
+        yield
+
     zipstream.close()
     if not is_stdout:
         stream.close()
 
 
-def tbz_exporter(tree, dest, root, subdir, filtered=False, force_mtime=None):
+def tgz_exporter(tree, dest, root, subdir, filtered=False, force_mtime=None,
+                 fileobj=None):
+
+    for _ in tgz_exporter_generator(tree, dest, root, subdir, filtered,
+                                    force_mtime, fileobj):
+        pass
+
+
+def tbz_exporter_generator(tree, dest, root, subdir, filtered=False,
+                           force_mtime=None, fileobj=None):
     """Export this tree to a new tar file.
 
     `dest` will be created holding the contents of this tree; if it
     already exists, it will be clobbered, like with "tar -c".
     """
-    if dest == '-':
+    if fileobj is not None:
+        ball = tarfile.open(None, 'w|bz2', fileobj)
+    elif dest == '-':
         ball = tarfile.open(None, 'w|bz2', sys.stdout)
     else:
         # tarfile.open goes on to do 'os.getcwd() + dest' for opening
@@ -145,35 +196,70 @@
         # upstream python bug http://bugs.python.org/issue8396
         # (fixed in Python 2.6.5 and 2.7b1)
         ball = tarfile.open(dest.encode(osutils._fs_enc), 'w:bz2')
-    export_tarball(tree, ball, root, subdir, filtered=filtered,
-                   force_mtime=force_mtime)
-    ball.close()
-
+
+    for _ in export_tarball_generator(tree, ball, root, subdir, filtered,
+                                      force_mtime):
+        yield
+
+    ball.close()
+
+
+def tbz_exporter(tree, dest, root, subdir, filtered=False, force_mtime=None,
+                 fileobj=None):
+
+    for _ in tbz_exporter_generator(tree, dest, root, subdir, filtered,
+                                    force_mtime, fileobj):
+        pass
+
+
+def plain_tar_exporter_generator(tree, dest, root, subdir, compression=None,
+                                 filtered=False, force_mtime=None,
+                                 fileobj=None):
+    """Export this tree to a new tar file.
+
+    `dest` will be created holding the contents of this tree; if it
+    already exists, it will be clobbered, like with "tar -c".
+    """
+    if fileobj is not None:
+        stream = fileobj
+    elif dest == '-':
+        stream = sys.stdout
+    else:
+        stream = open(dest, 'wb')
+    ball = tarfile.open(None, 'w|', stream)
+
+    for _ in export_tarball_generator(tree, ball, root, subdir, filtered,
+                                      force_mtime):
+
+        yield
+
+    ball.close()
 
 def plain_tar_exporter(tree, dest, root, subdir, compression=None,
-                       filtered=False, force_mtime=None):
-    """Export this tree to a new tar file.
-
-    `dest` will be created holding the contents of this tree; if it
-    already exists, it will be clobbered, like with "tar -c".
-    """
-    if dest == '-':
-        stream = sys.stdout
-    else:
-        stream = open(dest, 'wb')
-    ball = tarfile.open(None, 'w|', stream)
-    export_tarball(tree, ball, root, subdir, filtered=filtered,
-                   force_mtime=force_mtime)
-    ball.close()
-
-
-def tar_xz_exporter(tree, dest, root, subdir, filtered=False,
-                    force_mtime=None):
-    return tar_lzma_exporter(tree, dest, root, subdir, filtered=filtered,
-        force_mtime=force_mtime, compression_format="xz")
-
-
-def tar_lzma_exporter(tree, dest, root, subdir, filtered=False, force_mtime=None, compression_format="alone"):
+                       filtered=False, force_mtime=None, fileobj=None):
+
+    for _ in plain_tar_exporter_generator(
+        tree, dest, root, subdir, compression, filtered, force_mtime, fileobj):
+        pass
+
+
+def tar_xz_exporter_generator(tree, dest, root, subdir, filtered=False,
+                              force_mtime=None, fileobj=None):
+
+    return tar_lzma_exporter_generator(tree, dest, root, subdir, filtered,
+                                       force_mtime, fileobj, "xz")
+
+
+def tar_xz_exporter(tree, dest, root, subdir, filtered=False, force_mtime=None,
+                     fileobj=None):
+    for _ in tar_xz_exporter_generator(tree, dest, root, subdir, filtered,
+                                       force_mtime, fileobj):
+        pass
+
+
+def tar_lzma_exporter_generator(tree, dest, root, subdir, filtered=False,
+                      force_mtime=None, fileobj=None,
+                                compression_format="alone"):
     """Export this tree to a new .tar.lzma file.
 
     `dest` will be created holding the contents of this tree; if it
@@ -182,6 +268,9 @@
     if dest == '-':
         raise errors.BzrError("Writing to stdout not supported for .tar.lzma")
 
+    if fileobj is not None:
+        raise errors.BzrError(
+            "Writing to fileobject not supported for .tar.lzma")
     try:
         import lzma
     except ImportError, e:
@@ -190,7 +279,18 @@
     stream = lzma.LZMAFile(dest.encode(osutils._fs_enc), 'w',
             options={"format": compression_format})
     ball = tarfile.open(None, 'w:', fileobj=stream)
-    export_tarball(tree, ball, root, subdir, filtered=filtered,
-                   force_mtime=force_mtime)
+
+    for _ in export_tarball_generator(
+        tree, ball, root, subdir, filtered=filtered, force_mtime=force_mtime):
+        yield
+
     ball.close()
 
+
+def tar_lzma_exporter(tree, dest, root, subdir, filtered=False,
+                      force_mtime=None, fileobj=None,
+                      compression_format="alone"):
+    for _ in tar_lzma_exporter_generator(tree, dest, root, subdir, filtered,
+                                         force_mtime, fileobj,
+                                         compression_format):
+        pass

=== modified file 'bzrlib/export/zip_exporter.py'
--- a/bzrlib/export/zip_exporter.py	2011-04-30 00:08:00 +0000
+++ b/bzrlib/export/zip_exporter.py	2011-06-03 17:40:45 +0000
@@ -44,7 +44,8 @@
 _DIR_ATTR = stat.S_IFDIR | ZIP_DIRECTORY_BIT | DIR_PERMISSIONS
 
 
-def zip_exporter(tree, dest, root, subdir=None, filtered=False, force_mtime=None):
+def zip_exporter_generator(tree, dest, root, subdir=None, filtered=False, 
+                 force_mtime=None, fileobj=None):
     """ Export this tree to a new zip file.
 
     `dest` will be created holding the contents of this tree; if it
@@ -52,7 +53,9 @@
     """
 
     compression = zipfile.ZIP_DEFLATED
-    if dest == "-":
+    if fileobj is not None:
+        dest = fileobj
+    elif dest == "-":
         dest = sys.stdout
     zipf = zipfile.ZipFile(dest, "w", compression)
     try:
@@ -100,6 +103,8 @@
                 zinfo.compress_type = compression
                 zinfo.external_attr = _FILE_ATTR
                 zipf.writestr(zinfo, tree.get_symlink_target(file_id))
+            
+            yield
 
         zipf.close()
 
@@ -108,3 +113,10 @@
         os.remove(dest)
         from bzrlib.errors import BzrError
         raise BzrError("Can't export non-ascii filenames to zip")
+    
+def zip_exporter(tree, dest, root, subdir=None, filtered=False, 
+                 force_mtime=None, fileobj=None):
+    
+    for _ in zip_exporter_generator(tree, dest, root, subdir, filtered,
+                                    force_mtime, fileobj):
+        pass

=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt	2011-06-06 11:13:41 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt	2011-06-07 07:23:36 +0000
@@ -780,6 +780,9 @@
   (``bzrlib.working_tree.format_registry``) rather than using the class
   methods on ``WorkingTreeFormat``. (Jelmer Vernooij, #714730)
 
+* Exporting may now be done with a generator
+  (``bzrlib.export.get_export_generator``) (Geoff/xaav, #791005)
+
 Internals
 *********
 

=== modified file 'doc/en/whats-new/whats-new-in-2.4.txt'
--- a/doc/en/whats-new/whats-new-in-2.4.txt	2011-05-27 15:02:17 +0000
+++ b/doc/en/whats-new/whats-new-in-2.4.txt	2011-06-06 17:08:37 +0000
@@ -101,6 +101,12 @@
 network roundtrips.  Other operations where a local branch is stacked on a
 branch hosted on a smart server will also benefit.
 
+More export control
+*******************
+
+When exporting a tree, you may now use get_export_generator() to 
+do an operation after each file is exported. 
+
 Testing
 *******
 



More information about the bazaar-commits mailing list