Rev 5724: (jelmer) Fix some export bugs. (Jelmer Vernooij) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Tue Mar 15 00:38:30 UTC 2011


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5724 [merge]
revision-id: pqm at pqm.ubuntu.com-20110315003823-636nub1z70i2sc7p
parent: pqm at pqm.ubuntu.com-20110314164900-8v7s3nzvigrhoydq
parent: jelmer at samba.org-20110314231848-1lckp5aeoeobuiq3
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Tue 2011-03-15 00:38:23 +0000
message:
  (jelmer) Fix some export bugs. (Jelmer Vernooij)
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
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/blackbox/test_export.py test_export.py-20051229024010-e6c26658e460fb1c
  bzrlib/tests/features.py       features.py-20090820042958-jglgza3wrn03ha9e-1
  bzrlib/tests/test_export.py    test_export.py-20090220201010-tpbxssdnezsvu9pk-1
  doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
=== modified file 'bzrlib/export/__init__.py'
--- a/bzrlib/export/__init__.py	2010-09-21 03:20:09 +0000
+++ b/bzrlib/export/__init__.py	2011-03-14 12:18:38 +0000
@@ -20,9 +20,11 @@
 """
 
 import os
+import time
 from bzrlib import (
     errors,
     pyutils,
+    trace,
     )
 
 # Maps format name => export function
@@ -57,10 +59,10 @@
 
     When requesting a specific type of export, load the respective path.
     """
-    def _loader(tree, dest, root, subdir, filtered, per_file_timestamps):
+    def _loader(tree, dest, root, subdir, filtered, force_mtime):
         func = pyutils.get_named_object(module, funcname)
         return func(tree, dest, root, subdir, filtered=filtered,
-                    per_file_timestamps=per_file_timestamps)
+                    force_mtime=force_mtime)
     register_exporter(scheme, extensions, _loader)
 
 
@@ -103,10 +105,18 @@
 
     if format not in _exporters:
         raise errors.NoSuchExportFormat(format)
+
+    if not per_file_timestamps:
+        force_mtime = time.time()
+    else:
+        force_mtime = None
+
+    trace.mutter('export version %r', tree)
+
     tree.lock_read()
     try:
         return _exporters[format](tree, dest, root, subdir, filtered=filtered,
-                                  per_file_timestamps=per_file_timestamps)
+                                  force_mtime=force_mtime)
     finally:
         tree.unlock()
 
@@ -114,26 +124,11 @@
 def get_root_name(dest):
     """Get just the root name for an export.
 
-    >>> get_root_name('../mytest.tar')
-    'mytest'
-    >>> get_root_name('mytar.tar')
-    'mytar'
-    >>> get_root_name('mytar.tar.bz2')
-    'mytar'
-    >>> get_root_name('tar.tar.tar.tgz')
-    'tar.tar.tar'
-    >>> get_root_name('bzr-0.0.5.tar.gz')
-    'bzr-0.0.5'
-    >>> get_root_name('bzr-0.0.5.zip')
-    'bzr-0.0.5'
-    >>> get_root_name('bzr-0.0.5')
-    'bzr-0.0.5'
-    >>> get_root_name('a/long/path/mytar.tgz')
-    'mytar'
-    >>> get_root_name('../parent/../dir/other.tbz2')
-    'other'
     """
     global _exporter_extensions
+    if dest == '-':
+        # Exporting to -/foo doesn't make sense so use relative paths.
+        return ''
     dest = os.path.basename(dest)
     for ext in _exporter_extensions:
         if dest.endswith(ext):
@@ -141,11 +136,12 @@
     return dest
 
 
-def _export_iter_entries(tree, subdir):
+def _export_iter_entries(tree, subdir, skip_special=True):
     """Iter the entries for tree suitable for exporting.
 
     :param tree: A tree object.
     :param subdir: None or the path of an entry to start exporting from.
+    :param skip_special: Whether to skip .bzr files.
     """
     inv = tree.inventory
     if subdir is None:
@@ -167,7 +163,7 @@
     for entry in entries:
         # The .bzr* namespace is reserved for "magic" files like
         # .bzrignore and .bzrrules - do not export these
-        if entry[0].startswith(".bzr"):
+        if skip_special and entry[0].startswith(".bzr"):
             continue
         if subdir is None:
             if not tree.has_filename(entry[0]):
@@ -180,8 +176,10 @@
 
 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', 'tar_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')
 

=== modified file 'bzrlib/export/dir_exporter.py'
--- a/bzrlib/export/dir_exporter.py	2010-05-25 17:27:52 +0000
+++ b/bzrlib/export/dir_exporter.py	2011-03-13 21:30:33 +0000
@@ -18,7 +18,6 @@
 
 import errno
 import os
-import time
 
 from bzrlib import errors, osutils
 from bzrlib.export import _export_iter_entries
@@ -26,11 +25,9 @@
     ContentFilterContext,
     filtered_output_bytes,
     )
-from bzrlib.trace import mutter
-
-
-def dir_exporter(tree, dest, root, subdir, filtered=False,
-                 per_file_timestamps=False):
+
+
+def dir_exporter(tree, dest, root, subdir=None, filtered=False, force_mtime=None):
     """Export this tree to a new directory.
 
     `dest` should either not exist or should be empty. If it does not exist it
@@ -39,7 +36,6 @@
     :note: If the export fails, the destination directory will be
            left in an incompletely exported state: export is not transactional.
     """
-    mutter('export version %r', tree)
     try:
         os.mkdir(dest)
     except OSError, e:
@@ -76,7 +72,6 @@
     # 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)
-    now = time.time()
     for (relpath, executable), chunks in tree.iter_files_bytes(to_fetch):
         if filtered:
             filters = tree._content_filter_stack(relpath)
@@ -92,8 +87,8 @@
             out.writelines(chunks)
         finally:
             out.close()
-        if per_file_timestamps:
+        if force_mtime is not None:
+            mtime = force_mtime
+        else:
             mtime = tree.get_file_mtime(tree.path2id(relpath), relpath)
-        else:
-            mtime = now
         os.utime(fullpath, (mtime, mtime))

=== modified file 'bzrlib/export/tar_exporter.py'
--- a/bzrlib/export/tar_exporter.py	2011-03-13 18:18:10 +0000
+++ b/bzrlib/export/tar_exporter.py	2011-03-14 14:11:06 +0000
@@ -17,14 +17,13 @@
 """Export a Tree to a non-versioned directory.
 """
 
+import os
 import StringIO
 import sys
 import tarfile
-import time
 
 from bzrlib import (
     errors,
-    export,
     osutils,
     )
 from bzrlib.export import _export_iter_entries
@@ -32,41 +31,26 @@
     ContentFilterContext,
     filtered_output_bytes,
     )
-from bzrlib.trace import mutter
-
-
-def tar_exporter(tree, dest, root, subdir, compression=None, filtered=False,
-                 per_file_timestamps=False):
-    """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".
+
+
+def export_tarball(tree, ball, root, subdir=None, filtered=False,
+                   force_mtime=None):
+    """Export tree contents to a tarball.
+
+    :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.
     """
-    mutter('export version %r', tree)
-    now = time.time()
-    compression = str(compression or '')
-    if dest == '-':
-        # XXX: If no root is given, the output tarball will contain files
-        # named '-/foo'; perhaps this is the most reasonable thing.
-        ball = tarfile.open(None, 'w|' + compression, sys.stdout)
-    else:
-        if root is None:
-            root = export.get_root_name(dest)
-
-        # tarfile.open goes on to do 'os.getcwd() + dest' for opening
-        # the tar file. With dest being unicode, this throws UnicodeDecodeError
-        # unless we encode dest before passing it on. This works around
-        # 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:' + compression)
-
     for dp, ie in _export_iter_entries(tree, subdir):
         filename = osutils.pathjoin(root, dp).encode('utf8')
         item = tarfile.TarInfo(filename)
-        if per_file_timestamps:
+        if force_mtime is not None:
+            item.mtime = force_mtime
+        else:
             item.mtime = tree.get_file_mtime(ie.file_id, dp)
-        else:
-            item.mtime = now
         if ie.kind == "file":
             item.type = tarfile.REGTYPE
             if tree.is_executable(ie.file_id):
@@ -82,7 +66,7 @@
                 item.size = len(content)
                 fileobj = StringIO.StringIO(content)
             else:
-                item.size = ie.text_size
+                item.size = tree.get_file_size(ie.file_id)
                 fileobj = tree.get_file(ie.file_id)
         elif ie.kind == "directory":
             item.type = tarfile.DIRTYPE
@@ -94,22 +78,113 @@
             item.type = tarfile.SYMTYPE
             item.size = 0
             item.mode = 0755
-            item.linkname = ie.symlink_target
+            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))
         ball.addfile(item, fileobj)
-    ball.close()
-
-
-def tgz_exporter(tree, dest, root, subdir, filtered=False,
-                 per_file_timestamps=False):
-    tar_exporter(tree, dest, root, subdir, compression='gz',
-                 filtered=filtered, per_file_timestamps=per_file_timestamps)
-
-
-def tbz_exporter(tree, dest, root, subdir, filtered=False,
-                 per_file_timestamps=False):
-    tar_exporter(tree, dest, root, subdir, compression='bz2',
-                 filtered=filtered, per_file_timestamps=per_file_timestamps)
+
+
+def tgz_exporter(tree, dest, root, subdir, 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".
+    """
+    import gzip
+    if force_mtime is not None:
+        root_mtime = force_mtime
+    elif (getattr(tree, "repository", None) and
+          getattr(tree, "get_revision_id", None)):
+        # If this is a revision tree, use the revisions' timestamp
+        rev = tree.repository.get_revision(tree.get_revision_id())
+        root_mtime = rev.timestamp
+    elif tree.get_root_id() is not None:
+        root_mtime = tree.get_file_mtime(tree.get_root_id())
+    else:
+        root_mtime = None
+    if dest == '-':
+        basename = None
+        stream = sys.stdout
+    else:
+        stream = open(dest.encode(osutils._fs_enc), 'w')
+        # gzip file is used with an explicit fileobj so that
+        # the basename can be stored in the gzip file rather than
+        # dest. (bug 102234)
+        basename = os.path.basename(dest)
+    try:
+        stream = gzip.GzipFile(basename, 'w', fileobj=stream, mtime=root_mtime)
+    except TypeError:
+        # Python < 2.7 doesn't support the mtime argument
+        stream = gzip.GzipFile(basename, 'w', fileobj=stream)
+    ball = tarfile.open(None, 'w|', fileobj=stream)
+    export_tarball(tree, ball, root, subdir, filtered=filtered,
+                   force_mtime=force_mtime)
+    ball.close()
+
+
+def tbz_exporter(tree, dest, root, subdir, 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 == '-':
+        ball = tarfile.open(None, 'w|bz2', sys.stdout)
+    else:
+        # tarfile.open goes on to do 'os.getcwd() + dest' for opening
+        # the tar file. With dest being unicode, this throws UnicodeDecodeError
+        # unless we encode dest before passing it on. This works around
+        # 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()
+
+
+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.encode(osutils._fs_enc), 'w')
+    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"):
+    """Export this tree to a new .tar.lzma 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 == '-':
+        raise errors.BzrError("Writing to stdout not supported for .tar.lzma")
+
+    try:
+        import lzma
+    except ImportError, e:
+        raise errors.DependencyNotPresent('lzma', e)
+
+    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)
+    ball.close()
+

=== modified file 'bzrlib/export/zip_exporter.py'
--- a/bzrlib/export/zip_exporter.py	2011-02-16 17:20:10 +0000
+++ b/bzrlib/export/zip_exporter.py	2011-03-13 21:30:33 +0000
@@ -19,6 +19,7 @@
 
 import os
 import stat
+import sys
 import time
 import zipfile
 
@@ -43,20 +44,17 @@
 _DIR_ATTR = stat.S_IFDIR | ZIP_DIRECTORY_BIT | DIR_PERMISSIONS
 
 
-def zip_exporter(tree, dest, root, subdir, filtered=False,
-                 per_file_timestamps=False):
+def zip_exporter(tree, dest, root, subdir=None, filtered=False, force_mtime=None):
     """ Export this tree to a new zip file.
 
     `dest` will be created holding the contents of this tree; if it
     already exists, it will be overwritten".
     """
-    mutter('export version %r', tree)
-
-    now = time.localtime()[:6]
 
     compression = zipfile.ZIP_DEFLATED
+    if dest == "-":
+        dest = sys.stdout
     zipf = zipfile.ZipFile(dest, "w", compression)
-
     try:
         for dp, ie in _export_iter_entries(tree, subdir):
             file_id = ie.file_id
@@ -64,15 +62,16 @@
 
             # zipfile.ZipFile switches all paths to forward
             # slashes anyway, so just stick with that.
-            if per_file_timestamps:
+            if force_mtime is not None:
+                mtime = force_mtime
+            else:
                 mtime = tree.get_file_mtime(ie.file_id, dp)
-            else:
-                mtime = now
+            date_time = time.localtime(mtime)[:6]
             filename = osutils.pathjoin(root, dp).encode('utf8')
             if ie.kind == "file":
                 zinfo = zipfile.ZipInfo(
                             filename=filename,
-                            date_time=mtime)
+                            date_time=date_time)
                 zinfo.compress_type = compression
                 zinfo.external_attr = _FILE_ATTR
                 if filtered:
@@ -90,14 +89,14 @@
                 # not just empty files.
                 zinfo = zipfile.ZipInfo(
                             filename=filename + '/',
-                            date_time=mtime)
+                            date_time=date_time)
                 zinfo.compress_type = compression
                 zinfo.external_attr = _DIR_ATTR
                 zipf.writestr(zinfo,'')
             elif ie.kind == "symlink":
                 zinfo = zipfile.ZipInfo(
                             filename=(filename + '.lnk'),
-                            date_time=mtime)
+                            date_time=date_time)
                 zinfo.compress_type = compression
                 zinfo.external_attr = _FILE_ATTR
                 zipf.writestr(zinfo, ie.symlink_target)

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2011-03-10 13:29:54 +0000
+++ b/bzrlib/tests/__init__.py	2011-03-14 12:18:38 +0000
@@ -3903,7 +3903,6 @@
         'bzrlib',
         'bzrlib.branchbuilder',
         'bzrlib.decorators',
-        'bzrlib.export',
         'bzrlib.inventory',
         'bzrlib.iterablefile',
         'bzrlib.lockdir',

=== modified file 'bzrlib/tests/blackbox/test_export.py'
--- a/bzrlib/tests/blackbox/test_export.py	2011-02-16 17:20:10 +0000
+++ b/bzrlib/tests/blackbox/test_export.py	2011-03-14 23:18:48 +0000
@@ -23,6 +23,7 @@
 import stat
 import sys
 import tarfile
+import time
 import zipfile
 
 
@@ -117,6 +118,36 @@
         # '.bzrignore'.
         self.assertEqual(['test/a'], sorted(zfile.namelist()))
 
+    def test_zip_export_stdout(self):
+        tree = self.make_branch_and_tree('zip')
+        self.build_tree(['zip/a'])
+        tree.add('a')
+        tree.commit('1')
+        os.chdir('zip')
+        contents = self.run_bzr('export --format=zip -')[0]
+        zfile = zipfile.ZipFile(StringIO(contents))
+        self.assertEqual(['a'], sorted(zfile.namelist()))
+
+    def test_tgz_export_stdout(self):
+        tree = self.make_branch_and_tree('z')
+        self.build_tree(['z/a'])
+        tree.add('a')
+        tree.commit('1')
+        os.chdir('z')
+        contents = self.run_bzr('export --format=tgz -')[0]
+        ball = tarfile.open(mode='r|gz', fileobj=StringIO(contents))
+        self.assertEqual(['a'], ball.getnames())
+
+    def test_tbz2_export_stdout(self):
+        tree = self.make_branch_and_tree('z')
+        self.build_tree(['z/a'])
+        tree.add('a')
+        tree.commit('1')
+        os.chdir('z')
+        contents = self.run_bzr('export --format=tbz2 -')[0]
+        ball = tarfile.open(mode='r|bz2', fileobj=StringIO(contents))
+        self.assertEqual(['a'], ball.getnames())
+
     def test_zip_export_unicode(self):
         self.requireFeature(tests.UnicodeFilenameFeature)
         tree = self.make_branch_and_tree('zip')
@@ -317,3 +348,15 @@
         self.run_bzr(['export', '--directory=branch', 'latest'])
         self.assertEqual(['goodbye', 'hello'], sorted(os.listdir('latest')))
         self.check_file_contents('latest/goodbye', 'baz')
+
+    def test_zip_export_per_file_timestamps(self):
+        tree = self.example_branch()
+        self.build_tree_contents([('branch/har', 'foo')])
+        tree.add('har')
+        # Earliest allowable date on FAT32 filesystems is 1980-01-01
+        timestamp = 347151600
+        tree.commit('setup', timestamp=timestamp)
+        self.run_bzr('export --per-file-timestamps test.zip branch')
+        zfile = zipfile.ZipFile('test.zip')
+        info = zfile.getinfo("test/har")
+        self.assertEquals(time.localtime(timestamp)[:6], info.date_time)

=== modified file 'bzrlib/tests/features.py'
--- a/bzrlib/tests/features.py	2011-01-16 01:12:01 +0000
+++ b/bzrlib/tests/features.py	2011-03-14 12:43:19 +0000
@@ -42,6 +42,7 @@
 not_running_as_root = _NotRunningAsRoot()
 
 apport = tests.ModuleAvailableFeature('apport')
+lzma = tests.ModuleAvailableFeature('lzma')
 meliae = tests.ModuleAvailableFeature('meliae')
 paramiko = tests.ModuleAvailableFeature('paramiko')
 pycurl = tests.ModuleAvailableFeature('pycurl')

=== modified file 'bzrlib/tests/test_export.py'
--- a/bzrlib/tests/test_export.py	2010-04-14 00:11:32 +0000
+++ b/bzrlib/tests/test_export.py	2011-03-14 23:18:48 +0000
@@ -14,19 +14,27 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
+"""Tests for bzrlib.export."""
+
+from cStringIO import StringIO
 import os
+import tarfile
 import time
+import zipfile
 
 from bzrlib import (
     errors,
     export,
     tests,
     )
-
-
-class TestExport(tests.TestCaseWithTransport):
-
-    def test_dir_export_missing_file(self):
+from bzrlib.export import get_root_name
+from bzrlib.export.tar_exporter import export_tarball
+from bzrlib.tests import features
+
+
+class TestDirExport(tests.TestCaseWithTransport):
+
+    def test_missing_file(self):
         self.build_tree(['a/', 'a/b', 'a/c'])
         wt = self.make_branch_and_tree('.')
         wt.add(['a', 'a/b', 'a/c'])
@@ -35,7 +43,12 @@
         self.failUnlessExists('target/a/b')
         self.failIfExists('target/a/c')
 
-    def test_dir_export_symlink(self):
+    def test_empty(self):
+        wt = self.make_branch_and_tree('.')
+        export.export(wt, 'target', format="dir")
+        self.assertEquals([], os.listdir("target"))
+
+    def test_symlink(self):
         self.requireFeature(tests.SymlinkFeature)
         wt = self.make_branch_and_tree('.')
         os.symlink('source', 'link')
@@ -43,7 +56,7 @@
         export.export(wt, 'target', format="dir")
         self.failUnlessExists('target/link')
 
-    def test_dir_export_to_existing_empty_dir_success(self):
+    def test_to_existing_empty_dir_success(self):
         self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
         wt = self.make_branch_and_tree('source')
         wt.add(['a', 'b', 'b/c'])
@@ -54,7 +67,7 @@
         self.failUnlessExists('target/b')
         self.failUnlessExists('target/b/c')
 
-    def test_dir_export_to_existing_nonempty_dir_fail(self):
+    def test_to_existing_nonempty_dir_fail(self):
         self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
         wt = self.make_branch_and_tree('source')
         wt.add(['a', 'b', 'b/c'])
@@ -62,7 +75,7 @@
         self.build_tree(['target/', 'target/foo'])
         self.assertRaises(errors.BzrError, export.export, wt, 'target', format="dir")
 
-    def test_dir_export_existing_single_file(self):
+    def test_existing_single_file(self):
         self.build_tree(['dir1/', 'dir1/dir2/', 'dir1/first', 'dir1/dir2/second'])
         wtree = self.make_branch_and_tree('dir1')
         wtree.add(['dir2', 'first', 'dir2/second'])
@@ -71,8 +84,8 @@
         self.failUnlessExists('target1/first')
         export.export(wtree, 'target2', format='dir', subdir='dir2/second')
         self.failUnlessExists('target2/second')
-        
-    def test_dir_export_files_same_timestamp(self):
+
+    def test_files_same_timestamp(self):
         builder = self.make_branch_builder('source')
         builder.start_series()
         builder.build_snapshot(None, None, [
@@ -99,7 +112,7 @@
         # All files must be given the same mtime.
         self.assertEqual(st_a.st_mtime, st_b.st_mtime)
 
-    def test_dir_export_files_per_file_timestamps(self):
+    def test_files_per_file_timestamps(self):
         builder = self.make_branch_builder('source')
         builder.start_series()
         # Earliest allowable date on FAT32 filesystems is 1980-01-01
@@ -121,3 +134,124 @@
         t = self.get_transport('target')
         self.assertEqual(a_time, t.stat('a').st_mtime)
         self.assertEqual(b_time, t.stat('b').st_mtime)
+
+
+class TarExporterTests(tests.TestCaseWithTransport):
+
+    def test_xz(self):
+        self.requireFeature(features.lzma)
+        import lzma
+        wt = self.make_branch_and_tree('.')
+        self.build_tree(['a'])
+        wt.add(["a"])
+        wt.commit("1")
+        export.export(wt, 'target.tar.xz', format="txz")
+        tf = tarfile.open(fileobj=lzma.LZMAFile('target.tar.xz'))
+        self.assertEquals(["target/a"], tf.getnames())
+
+    def test_lzma(self):
+        self.requireFeature(features.lzma)
+        import lzma
+        wt = self.make_branch_and_tree('.')
+        self.build_tree(['a'])
+        wt.add(["a"])
+        wt.commit("1")
+        export.export(wt, 'target.tar.lzma', format="tlzma")
+        tf = tarfile.open(fileobj=lzma.LZMAFile('target.tar.lzma'))
+        self.assertEquals(["target/a"], tf.getnames())
+
+    def test_tgz(self):
+        wt = self.make_branch_and_tree('.')
+        self.build_tree(['a'])
+        wt.add(["a"])
+        wt.commit("1")
+        export.export(wt, 'target.tar.gz', format="tgz")
+        tf = tarfile.open('target.tar.gz')
+        self.assertEquals(["target/a"], tf.getnames())
+
+    def test_tgz_ignores_dest_path(self):
+        # The target path should not be a part of the target file.
+        # (bug #102234)
+        wt = self.make_branch_and_tree('.')
+        self.build_tree(['a'])
+        wt.add(["a"])
+        wt.commit("1")
+        os.mkdir("testdir1")
+        os.mkdir("testdir2")
+        export.export(wt, 'testdir1/target.tar.gz', format="tgz",
+            per_file_timestamps=True)
+        export.export(wt, 'testdir2/target.tar.gz', format="tgz",
+            per_file_timestamps=True)
+        file1 = open('testdir1/target.tar.gz', 'r')
+        self.addCleanup(file1.close)
+        file2 = open('testdir1/target.tar.gz', 'r')
+        self.addCleanup(file2.close)
+        content1 = file1.read()
+        content2 = file2.read()
+        self.assertEqualDiff(content1, content2)
+        # the gzip module doesn't have a way to read back to the original
+        # filename, but it's stored as-is in the tarfile.
+        self.assertFalse("testdir1" in content1)
+        self.assertFalse("target.tar.gz" in content1)
+        self.assertTrue("target.tar" in content1)
+
+    def test_tbz2(self):
+        wt = self.make_branch_and_tree('.')
+        self.build_tree(['a'])
+        wt.add(["a"])
+        wt.commit("1")
+        export.export(wt, 'target.tar.bz2', format="tbz2")
+        tf = tarfile.open('target.tar.bz2')
+        self.assertEquals(["target/a"], tf.getnames())
+
+    def test_xz_stdout(self):
+        wt = self.make_branch_and_tree('.')
+        self.assertRaises(errors.BzrError, export.export, wt, '-',
+            format="txz")
+
+    def test_export_tarball(self):
+        wt = self.make_branch_and_tree('.')
+        self.build_tree(['a'])
+        wt.add(["a"])
+        wt.commit("1", timestamp=42)
+        target = StringIO()
+        ball = tarfile.open(None, "w|", target)
+        wt.lock_read()
+        try:
+            export_tarball(wt, ball, "bar")
+        finally:
+            wt.unlock()
+        self.assertEquals(["bar/a"], ball.getnames())
+        ball.close()
+
+
+class ZipExporterTests(tests.TestCaseWithTransport):
+
+    def test_per_file_timestamps(self):
+        tree = self.make_branch_and_tree('.')
+        self.build_tree_contents([('har', 'foo')])
+        tree.add('har')
+        # Earliest allowable date on FAT32 filesystems is 1980-01-01
+        timestamp = 347151600
+        tree.commit('setup', timestamp=timestamp)
+        export.export(tree.basis_tree(), 'test.zip', format='zip',
+            per_file_timestamps=True)
+        zfile = zipfile.ZipFile('test.zip')
+        info = zfile.getinfo("test/har")
+        self.assertEquals(time.localtime(timestamp)[:6], info.date_time)
+
+
+class RootNameTests(tests.TestCase):
+
+    def test_root_name(self):
+        self.assertEquals('mytest', get_root_name('../mytest.tar'))
+        self.assertEquals('mytar', get_root_name('mytar.tar'))
+        self.assertEquals('mytar', get_root_name('mytar.tar.bz2'))
+        self.assertEquals('tar.tar.tar', get_root_name('tar.tar.tar.tgz'))
+        self.assertEquals('bzr-0.0.5', get_root_name('bzr-0.0.5.tar.gz'))
+        self.assertEquals('bzr-0.0.5', get_root_name('bzr-0.0.5.zip'))
+        self.assertEquals('bzr-0.0.5', get_root_name('bzr-0.0.5'))
+        self.assertEquals('mytar', get_root_name('a/long/path/mytar.tgz'))
+        self.assertEquals('other',
+            get_root_name('../parent/../dir/other.tbz2'))
+        self.assertEquals('', get_root_name('-'))

=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt	2011-03-11 15:36:12 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt	2011-03-14 14:21:29 +0000
@@ -50,8 +50,22 @@
 
 * Branching, merging and pulling a branch now copies revisions named in
   tags, not just the tag metadata.  (Andrew Bennetts, #309682)
-  
-* ``bzr cat-revision`` no longer requires a working tree. (Jelmer Vernooij, #704405)
+
+* ``bzr cat-revision`` no longer requires a working tree.
+  (Jelmer Vernooij, #704405)
+
+* ``bzr export --per-file-timestamps`` for .tar.gz files will now
+  override the mtime for trees exported on Python 2.7 and later, which
+  expose the 'mtime' field in gzip files. This makes the output of
+  ``bzr export --per-file-timestamps`` for a particular tree
+  deterministic.  (Jelmer Vernooij, #711226)
+
+* ``bzr export --format=zip`` can now export to standard output,
+  like the other exporters can. (Jelmer Vernooij, #513752)
+
+* ``bzr export`` can now create ``.tar.xz`` and ``.tar.lzma`` files.
+  (Jelmer Vernooij, #551714)
+
 
 Bug Fixes
 *********
@@ -69,6 +83,9 @@
 * ``bzr export`` to zip files will now set a mode on directories.
   (Jelmer Vernooij, #207253)
 
+* ``bzr export`` to tgz files will only write out the basename of the
+  tarfile to the gzip file. (Jelmer Vernooij, #102234)
+
 * ``bzr push --overwrite`` with an older revision specified will now correctly
   roll back the target branch. (Jelmer Vernooij, #386576)
 




More information about the bazaar-commits mailing list