Rev 4242: (andrew) Add bzrlib.inventory_delta module for in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Apr 3 03:06:06 BST 2009


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

------------------------------------------------------------
revno: 4242
revision-id: pqm at pqm.ubuntu.com-20090403020602-mzxupgrpl0tuao0i
parent: pqm at pqm.ubuntu.com-20090402163502-ryn8zr2giilw5bki
parent: andrew.bennetts at canonical.com-20090403011132-b2311vphwyvoqokp
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2009-04-03 03:06:02 +0100
message:
  (andrew) Add bzrlib.inventory_delta module for
  	serializing/deserializing deltas of inventories.
added:
  bzrlib/inventory_delta.py      journalled_inventory-20080103020931-0ht5n40kwc0p7fy1-1
  bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  doc/developers/inventory.txt   inventory.txt-20080103013957-opkrhxy6lmywmx4i-1
    ------------------------------------------------------------
    revno: 4205.5.10
    revision-id: andrew.bennetts at canonical.com-20090403011132-b2311vphwyvoqokp
    parent: andrew.bennetts at canonical.com-20090402120400-nont94frxftnm8zi
    parent: pqm at pqm.ubuntu.com-20090402163502-ryn8zr2giilw5bki
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Fri 2009-04-03 12:11:32 +1100
    message:
      Merge bzr.dev.
    removed:
      bzrlib/tests/blackbox/test_guess_renames.py test_guess_renames.p-20090312063936-bqdtxr0r3md3jc00-1
    added:
      bzrlib/filters/eol.py          eol.py-20090327060429-todzdjmqt3bpv5r8-1
      bzrlib/help_topics/en/eol.txt  eol.txt-20090327060429-todzdjmqt3bpv5r8-3
      bzrlib/tests/test_eol_filters.py test_eol_filters.py-20090327060429-todzdjmqt3bpv5r8-2
      bzrlib/tests/workingtree_implementations/test_eol_conversion.py test_eol_conversion.-20090327060429-todzdjmqt3bpv5r8-4
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/commit.py               commit.py-20050511101309-79ec1a0168e0e825
      bzrlib/config.py               config.py-20051011043216-070c74f4e9e338e8
      bzrlib/help_topics/__init__.py help_topics.py-20060920210027-rnim90q9e0bwxvy4-1
      bzrlib/help_topics/en/rules.txt rules.txt-20080516063844-ghr5l6pvvrhiycun-1
      bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
      bzrlib/rename_map.py           rename_map.py-20090312140439-xexkkmjlg2enbohc-1
      bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
      bzrlib/smart/branch.py         branch.py-20061124031907-mzh3pla28r83r97f-1
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/blackbox/__init__.py __init__.py-20051128053524-eba30d8255e08dc3
      bzrlib/tests/blackbox/test_branch.py test_branch.py-20060524161337-noms9gmcwqqrfi8y-1
      bzrlib/tests/blackbox/test_mv.py test_mv.py-20060705114902-33tkxz0o9cdshemo-1
      bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
      bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
      bzrlib/tests/test_rename_map.py test_rename_map.py-20090312140439-xexkkmjlg2enbohc-2
      bzrlib/tests/workingtree_implementations/__init__.py __init__.py-20060203003124-b2aa5aca21a8bfad
      bzrlib/xml_serializer.py       xml.py-20050309040759-57d51586fdec365d
      tools/win32/build_release.py   build_release.py-20081105204355-2ghh5cv01v1x4rzz-1
    ------------------------------------------------------------
    revno: 4205.5.9
    revision-id: andrew.bennetts at canonical.com-20090402120400-nont94frxftnm8zi
    parent: andrew.bennetts at canonical.com-20090402055739-fmuvfpfijub5tz4q
    parent: pqm at pqm.ubuntu.com-20090401151438-hqulqoazddtacbls
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 23:04:00 +1100
    message:
      Merge bzr.dev.
    modified:
      .bzrignore                     bzrignore-20050311232317-81f7b71efa2db11a
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/__init__.py             __init__.py-20050309040759-33e65acf91bbcd5d
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
      bzrlib/btree_index.py          index.py-20080624222253-p0x5f92uyh5hw734-7
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/bundle/serializer/v4.py v10.py-20070611062757-5ggj7k18s9dej0fr-1
      bzrlib/bzrdir.py               bzrdir.py-20060131065624-156dfea39c4387cb
      bzrlib/commit.py               commit.py-20050511101309-79ec1a0168e0e825
      bzrlib/diff.py                 diff.py-20050309040759-26944fbbf2ebbf36
      bzrlib/filters/__init__.py     __init__.py-20080416080515-mkxl29amuwrf6uir-2
      bzrlib/graph.py                graph_walker.py-20070525030359-y852guab65d4wtn0-1
      bzrlib/help_topics/__init__.py help_topics.py-20060920210027-rnim90q9e0bwxvy4-1
      bzrlib/log.py                  log.py-20050505065812-c40ce11702fe5fb1
      bzrlib/osutils.py              osutils.py-20050309040759-eeaff12fbf77ac86
      bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
      bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
      bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
      bzrlib/smart/request.py        request.py-20061108095550-gunadhxmzkdjfeek-1
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/blackbox/test_add.py test_add.py-20060518072250-857e4f86f54a30b2
      bzrlib/tests/blackbox/test_branch.py test_branch.py-20060524161337-noms9gmcwqqrfi8y-1
      bzrlib/tests/blackbox/test_commit.py test_commit.py-20060212094538-ae88fc861d969db0
      bzrlib/tests/blackbox/test_filtered_view_ops.py test_filtered_view_o-20081110012645-5t7ogtola0l33lkg-1
      bzrlib/tests/blackbox/test_log.py test_log.py-20060112090212-78f6ea560c868e24
      bzrlib/tests/blackbox/test_push.py test_push.py-20060329002750-929af230d5d22663
      bzrlib/tests/blackbox/test_view.py test_view.py-20080731135100-66o8o32heop7augi-1
      bzrlib/tests/per_repository/test_commit_builder.py test_commit_builder.py-20060606110838-76e3ra5slucqus81-1
      bzrlib/tests/test_commit.py    test_commit.py-20050914060732-279f057f8c295434
      bzrlib/tests/test_diff.py      testdiff.py-20050727164403-d1a3496ebb12e339
      bzrlib/tests/test_filters.py   test_filters.py-20080417120614-tc3zok0vvvprsc99-1
      bzrlib/tests/test_http.py      testhttp.py-20051018020158-b2eef6e867c514d9
      bzrlib/tests/test_info.py      test_info.py-20070320150933-m0xxm1g7xi9v6noe-1
      bzrlib/tests/test_log.py       testlog.py-20050728115707-1a514809d7d49309
      bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
      bzrlib/tests/test_smart_request.py test_smart_request.p-20090211070731-o38wayv3asm25d6a-1
      bzrlib/tests/test_upgrade.py   test_upgrade.py-20051004040251-555fe1d2bae1bc71
      bzrlib/tests/workingtree_implementations/test_commit.py test_commit.py-20060421013633-1610ec2331c8190f
      bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
      bzrlib/upgrade.py              history2weaves.py-20050818063535-e7d319791c19a8b2
      bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
      bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
      doc/en/user-guide/browsing_history.txt browsing_history.txt-20071121073725-0corxykv5irjal00-2
      doc/en/user-guide/filtered_views.txt filtered_views.txt-20090226100856-a16ba1v97v91ru58-1
    ------------------------------------------------------------
    revno: 4205.5.8
    revision-id: andrew.bennetts at canonical.com-20090402055739-fmuvfpfijub5tz4q
    parent: andrew.bennetts at canonical.com-20090402055312-h7mvgumvm7e620mj
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 16:57:39 +1100
    message:
      Add NEWS file.
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 4205.5.7
    revision-id: andrew.bennetts at canonical.com-20090402055312-h7mvgumvm7e620mj
    parent: andrew.bennetts at canonical.com-20090402042937-nyac8vi5s1z61e91
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 16:53:12 +1100
    message:
      Fix nits in spelling and naming.
    modified:
      bzrlib/inventory_delta.py      journalled_inventory-20080103020931-0ht5n40kwc0p7fy1-1
      bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
      doc/developers/inventory.txt   inventory.txt-20080103013957-opkrhxy6lmywmx4i-1
    ------------------------------------------------------------
    revno: 4205.5.6
    revision-id: andrew.bennetts at canonical.com-20090402042937-nyac8vi5s1z61e91
    parent: andrew.bennetts at canonical.com-20090402042127-8k1ndenrh95ofsij
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 15:29:37 +1100
    message:
      Garden module docstring a little, cleanup unused imports and inaccurate __all__.
    modified:
      bzrlib/inventory_delta.py      journalled_inventory-20080103020931-0ht5n40kwc0p7fy1-1
      bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
    ------------------------------------------------------------
    revno: 4205.5.5
    revision-id: andrew.bennetts at canonical.com-20090402042127-8k1ndenrh95ofsij
    parent: andrew.bennetts at canonical.com-20090402040724-4jzvyt8p482n7io4
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 15:21:27 +1100
    message:
      Reorganise tests a little.
    modified:
      bzrlib/inventory_delta.py      journalled_inventory-20080103020931-0ht5n40kwc0p7fy1-1
      bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
    ------------------------------------------------------------
    revno: 4205.5.4
    revision-id: andrew.bennetts at canonical.com-20090402040724-4jzvyt8p482n7io4
    parent: andrew.bennetts at canonical.com-20090402023057-06vju4tqe4ze3mrj
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 15:07:24 +1100
    message:
      Fix many tests in test_inventory_delta to actually test what they intended to test.
    modified:
      bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
    ------------------------------------------------------------
    revno: 4205.5.3
    revision-id: andrew.bennetts at canonical.com-20090402023057-06vju4tqe4ze3mrj
    parent: andrew.bennetts at canonical.com-20090402021314-fccuhkjiu1xxy8r9
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 13:30:57 +1100
    message:
      Include oldpath in the the serialised delta
    modified:
      bzrlib/inventory_delta.py      journalled_inventory-20080103020931-0ht5n40kwc0p7fy1-1
      bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
      doc/developers/inventory.txt   inventory.txt-20080103013957-opkrhxy6lmywmx4i-1
    ------------------------------------------------------------
    revno: 4205.5.2
    revision-id: andrew.bennetts at canonical.com-20090402021314-fccuhkjiu1xxy8r9
    parent: andrew.bennetts at canonical.com-20090402011143-3663j8z2aoqsd2hr
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 13:13:14 +1100
    message:
      Remove a bit of test cruft.
    modified:
      bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
    ------------------------------------------------------------
    revno: 4205.5.1
    revision-id: andrew.bennetts at canonical.com-20090402011143-3663j8z2aoqsd2hr
    parent: pqm at pqm.ubuntu.com-20090326001427-mnhqpak56tlqa5e7
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: inventory-delta
    timestamp: Thu 2009-04-02 12:11:43 +1100
    message:
      Initial stab at adapting Robert's journalled_inventory serialisation into inventory_delta serialisation.
    added:
      bzrlib/inventory_delta.py      journalled_inventory-20080103020931-0ht5n40kwc0p7fy1-1
      bzrlib/tests/test_inventory_delta.py test_journalled_inv.-20080103012121-ny2w9slze5jgty8i-1
    modified:
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      doc/developers/inventory.txt   inventory.txt-20080103013957-opkrhxy6lmywmx4i-1
=== modified file 'NEWS'
--- a/NEWS	2009-04-02 16:35:02 +0000
+++ b/NEWS	2009-04-03 01:11:32 +0000
@@ -239,6 +239,10 @@
 Internals
 *********
 
+* Added ``bzrlib.inventory_delta`` module.  This will be used for
+  serializing and deserializing inventory deltas for more efficient
+  streaming on the the network.  (Robert Collins, Andrew Bennetts)
+
 * ``Branch._get_config`` has been added, which splits out access to the
   specific config file from the branch. This is used to let RemoteBranch
   avoid constructing real branch objects to access configuration settings.

=== added file 'bzrlib/inventory_delta.py'
--- a/bzrlib/inventory_delta.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/inventory_delta.py	2009-04-02 05:53:12 +0000
@@ -0,0 +1,299 @@
+# Copyright (C) 2008, 2009 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Inventory delta serialisation.
+
+See doc/developers/inventory.txt for the description of the format.
+
+In this module the interesting classes are:
+ - InventoryDeltaSerializer - object to read/write inventory deltas.
+"""
+
+__all__ = ['InventoryDeltaSerializer']
+
+from bzrlib import errors
+from bzrlib.osutils import basename
+from bzrlib import inventory
+from bzrlib.revision import NULL_REVISION
+
+
+def _directory_content(entry):
+    """Serialize the content component of entry which is a directory.
+    
+    :param entry: An InventoryDirectory.
+    """
+    return "dir"
+
+
+def _file_content(entry):
+    """Serialize the content component of entry which is a file.
+    
+    :param entry: An InventoryFile.
+    """
+    if entry.executable:
+        exec_bytes = 'Y'
+    else:
+        exec_bytes = ''
+    size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
+    if None in size_exec_sha:
+        raise errors.BzrError('Missing size or sha for %s' % entry.file_id)
+    return "file\x00%d\x00%s\x00%s" % size_exec_sha
+
+
+def _link_content(entry):
+    """Serialize the content component of entry which is a symlink.
+    
+    :param entry: An InventoryLink.
+    """
+    target = entry.symlink_target
+    if target is None:
+        raise errors.BzrError('Missing target for %s' % entry.file_id)
+    return "link\x00%s" % target.encode('utf8')
+
+
+def _reference_content(entry):
+    """Serialize the content component of entry which is a tree-reference.
+    
+    :param entry: A TreeReference.
+    """
+    tree_revision = entry.reference_revision
+    if tree_revision is None:
+        raise errors.BzrError('Missing reference revision for %s' % entry.file_id)
+    return "tree\x00%s" % tree_revision
+
+
+def _dir_to_entry(content, name, parent_id, file_id, last_modified,
+    _type=inventory.InventoryDirectory):
+    """Convert a dir content record to an InventoryDirectory."""
+    result = _type(file_id, name, parent_id)
+    result.revision = last_modified
+    return result
+
+
+def _file_to_entry(content, name, parent_id, file_id, last_modified,
+    _type=inventory.InventoryFile):
+    """Convert a dir content record to an InventoryFile."""
+    result = _type(file_id, name, parent_id)
+    result.revision = last_modified
+    result.text_size = int(content[1])
+    result.text_sha1 = content[3]
+    if content[2]:
+        result.executable = True
+    else:
+        result.executable = False
+    return result
+
+
+def _link_to_entry(content, name, parent_id, file_id, last_modified,
+    _type=inventory.InventoryLink):
+    """Convert a link content record to an InventoryLink."""
+    result = _type(file_id, name, parent_id)
+    result.revision = last_modified
+    result.symlink_target = content[1].decode('utf8')
+    return result
+
+
+def _tree_to_entry(content, name, parent_id, file_id, last_modified,
+    _type=inventory.TreeReference):
+    """Convert a tree content record to a TreeReference."""
+    result = _type(file_id, name, parent_id)
+    result.revision = last_modified
+    result.reference_revision = content[1]
+    return result
+
+
+
+class InventoryDeltaSerializer(object):
+    """Serialize and deserialize inventory deltas."""
+
+    FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
+
+    def __init__(self, versioned_root, tree_references):
+        """Create an InventoryDeltaSerializer.
+
+        :param versioned_root: If True, any root entry that is seen is expected
+            to be versioned, and root entries can have any fileid.
+        :param tree_references: If True support tree-reference entries.
+        """
+        self._versioned_root = versioned_root
+        self._tree_references = tree_references
+        self._entry_to_content = {
+            'directory': _directory_content,
+            'file': _file_content,
+            'symlink': _link_content,
+        }
+        if tree_references:
+            self._entry_to_content['tree-reference'] = _reference_content
+
+    def delta_to_lines(self, old_name, new_name, delta_to_new):
+        """Return a line sequence for delta_to_new.
+
+        :param old_name: A UTF8 revision id for the old inventory.  May be
+            NULL_REVISION if there is no older inventory and delta_to_new
+            includes the entire inventory contents.
+        :param new_name: The version name of the inventory we create with this
+            delta.
+        :param delta_to_new: An inventory delta such as Inventory.apply_delta
+            takes.
+        :return: The serialized delta as lines.
+        """
+        lines = ['', '', '', '', '']
+        to_line = self._delta_item_to_line
+        for delta_item in delta_to_new:
+            lines.append(to_line(delta_item))
+            if lines[-1].__class__ != str:
+                raise errors.BzrError(
+                    'to_line generated non-str output %r' % lines[-1])
+        lines.sort()
+        lines[0] = "format: %s\n" % InventoryDeltaSerializer.FORMAT_1
+        lines[1] = "parent: %s\n" % old_name
+        lines[2] = "version: %s\n" % new_name
+        lines[3] = "versioned_root: %s\n" % self._serialize_bool(
+            self._versioned_root)
+        lines[4] = "tree_references: %s\n" % self._serialize_bool(
+            self._tree_references)
+        return lines
+
+    def _serialize_bool(self, value):
+        if value:
+            return "true"
+        else:
+            return "false"
+
+    def _delta_item_to_line(self, delta_item):
+        """Convert delta_item to a line."""
+        oldpath, newpath, file_id, entry = delta_item
+        if newpath is None:
+            # delete
+            oldpath_utf8 = '/' + oldpath.encode('utf8')
+            newpath_utf8 = 'None'
+            parent_id = ''
+            last_modified = NULL_REVISION
+            content = 'deleted\x00\x00'
+        else:
+            if oldpath is None:
+                oldpath_utf8 = 'None'
+            else:
+                oldpath_utf8 = '/' + oldpath.encode('utf8')
+            # TODO: Test real-world utf8 cache hit rate. It may be a win.
+            newpath_utf8 = '/' + newpath.encode('utf8')
+            # Serialize None as ''
+            parent_id = entry.parent_id or ''
+            # Serialize unknown revisions as NULL_REVISION
+            last_modified = entry.revision
+            # special cases for /
+            if newpath_utf8 == '/' and not self._versioned_root:
+                if file_id != 'TREE_ROOT':
+                    raise errors.BzrError(
+                        'file_id %s is not TREE_ROOT for /' % file_id)
+                if last_modified is not None:
+                    raise errors.BzrError(
+                        'Version present for / in %s' % file_id)
+                last_modified = NULL_REVISION
+            if last_modified is None:
+                raise errors.BzrError("no version for fileid %s" % file_id)
+            content = self._entry_to_content[entry.kind](entry)
+        return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
+            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
+                content))
+
+    def _deserialize_bool(self, value):
+        if value == "true":
+            return True
+        elif value == "false":
+            return False
+        else:
+            raise errors.BzrError("value %r is not a bool" % (value,))
+
+    def parse_text_bytes(self, bytes):
+        """Parse the text bytes of a serialized inventory delta.
+
+        :param bytes: The bytes to parse. This can be obtained by calling
+            delta_to_lines and then doing ''.join(delta_lines).
+        :return: (parent_id, new_id, inventory_delta)
+        """
+        lines = bytes.split('\n')[:-1] # discard the last empty line
+        if not lines or lines[0] != 'format: %s' % InventoryDeltaSerializer.FORMAT_1:
+            raise errors.BzrError('unknown format %r' % lines[0:1])
+        if len(lines) < 2 or not lines[1].startswith('parent: '):
+            raise errors.BzrError('missing parent: marker')
+        delta_parent_id = lines[1][8:]
+        if len(lines) < 3 or not lines[2].startswith('version: '):
+            raise errors.BzrError('missing version: marker')
+        delta_version_id = lines[2][9:]
+        if len(lines) < 4 or not lines[3].startswith('versioned_root: '):
+            raise errors.BzrError('missing versioned_root: marker')
+        delta_versioned_root = self._deserialize_bool(lines[3][16:])
+        if len(lines) < 5 or not lines[4].startswith('tree_references: '):
+            raise errors.BzrError('missing tree_references: marker')
+        delta_tree_references = self._deserialize_bool(lines[4][17:])
+        if delta_versioned_root != self._versioned_root:
+            raise errors.BzrError(
+                "serialized versioned_root flag is wrong: %s" %
+                (delta_versioned_root,))
+        if delta_tree_references != self._tree_references:
+            raise errors.BzrError(
+                "serialized tree_references flag is wrong: %s" %
+                (delta_tree_references,))
+        result = []
+        seen_ids = set()
+        line_iter = iter(lines)
+        for i in range(5):
+            line_iter.next()
+        for line in line_iter:
+            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
+                content) = line.split('\x00', 5)
+            parent_id = parent_id or None
+            if file_id in seen_ids:
+                raise errors.BzrError(
+                    "duplicate file id in inventory delta %r" % lines)
+            seen_ids.add(file_id)
+            if newpath_utf8 == '/' and not delta_versioned_root and (
+                last_modified != 'null:' or file_id != 'TREE_ROOT'):
+                    raise errors.BzrError("Versioned root found: %r" % line)
+            elif last_modified[-1] == ':':
+                    raise errors.BzrError('special revisionid found: %r' % line)
+            if not delta_tree_references and content.startswith('tree\x00'):
+                raise errors.BzrError("Tree reference found: %r" % line)
+            content_tuple = tuple(content.split('\x00'))
+            entry = _parse_entry(
+                newpath_utf8, file_id, parent_id, last_modified, content_tuple)
+            if oldpath_utf8 == 'None':
+                oldpath = None
+            else:
+                oldpath = oldpath_utf8.decode('utf8')
+            if newpath_utf8 == 'None':
+                newpath = None
+            else:
+                newpath = newpath_utf8.decode('utf8')
+            delta_item = (oldpath, newpath, file_id, entry)
+            result.append(delta_item)
+        return delta_parent_id, delta_version_id, result
+
+
+def _parse_entry(utf8_path, file_id, parent_id, last_modified, content):
+    entry_factory = {
+        'dir': _dir_to_entry,
+        'file': _file_to_entry,
+        'link': _link_to_entry,
+        'tree': _tree_to_entry,
+    }
+    kind = content[0]
+    path = utf8_path[1:].decode('utf8')
+    name = basename(path)
+    return entry_factory[content[0]](
+            content, name, parent_id, file_id, last_modified)
+

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2009-04-02 08:28:30 +0000
+++ b/bzrlib/tests/__init__.py	2009-04-03 01:11:32 +0000
@@ -3345,6 +3345,7 @@
                    'bzrlib.tests.test_index',
                    'bzrlib.tests.test_info',
                    'bzrlib.tests.test_inv',
+                   'bzrlib.tests.test_inventory_delta',
                    'bzrlib.tests.test_knit',
                    'bzrlib.tests.test_lazy_import',
                    'bzrlib.tests.test_lazy_regex',

=== added file 'bzrlib/tests/test_inventory_delta.py'
--- a/bzrlib/tests/test_inventory_delta.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/test_inventory_delta.py	2009-04-02 05:53:12 +0000
@@ -0,0 +1,533 @@
+# Copyright (C) 2008, 2009 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# 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.inventory_delta.
+
+See doc/developer/inventory.txt for more information.
+"""
+
+from cStringIO import StringIO
+
+from bzrlib import (
+    errors,
+    inventory,
+    inventory_delta,
+    )
+from bzrlib.inventory import Inventory
+from bzrlib.revision import NULL_REVISION
+from bzrlib.tests import TestCase
+
+### DO NOT REFLOW THESE TEXTS. NEW LINES ARE SIGNIFICANT. ###
+empty_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: null:
+versioned_root: true
+tree_references: true
+"""
+
+root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: entry-version
+versioned_root: true
+tree_references: true
+None\x00/\x00an-id\x00\x00a at e\xc3\xa5ample.com--2004\x00dir
+"""
+
+
+root_change_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: entry-version
+version: changed-root
+versioned_root: true
+tree_references: true
+/\x00an-id\x00\x00different-version\x00dir
+"""
+
+corrupt_parent_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: entry-version
+version: changed-root
+versioned_root: false
+tree_references: false
+/\x00an-id\x00\x00different-version\x00dir
+"""
+
+root_only_unversioned = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: entry-version
+versioned_root: false
+tree_references: false
+None\x00/\x00TREE_ROOT\x00\x00null:\x00dir
+"""
+
+reference_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: entry-version
+versioned_root: true
+tree_references: true
+None\x00/\x00TREE_ROOT\x00\x00a at e\xc3\xa5ample.com--2004\x00dir
+None\x00/foo\x00id\x00TREE_ROOT\x00changed\x00tree\x00subtree-version
+"""
+
+change_tree_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: entry-version
+version: change-tree
+versioned_root: false
+tree_references: false
+/foo\x00id\x00TREE_ROOT\x00changed-twice\x00tree\x00subtree-version2
+"""
+
+
+class TestDeserialization(TestCase):
+    """Test InventoryDeltaSerializer.parse_text_bytes."""
+
+    def test_parse_no_bytes(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        err = self.assertRaises(
+            errors.BzrError, serializer.parse_text_bytes, '')
+        self.assertContainsRe(str(err), 'unknown format')
+
+    def test_parse_bad_format(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes, 'format: foo\n')
+        self.assertContainsRe(str(err), 'unknown format')
+
+    def test_parse_no_parent(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes,
+            'format: bzr inventory delta v1 (bzr 1.14)\n')
+        self.assertContainsRe(str(err), 'missing parent: marker')
+
+    def test_parse_no_version(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes,
+            'format: bzr inventory delta v1 (bzr 1.14)\n'
+            'parent: null:\n')
+        self.assertContainsRe(str(err), 'missing version: marker')
+            
+    def test_parse_duplicate_key_errors(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        double_root_lines = \
+"""format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: null:
+versioned_root: true
+tree_references: true
+None\x00/\x00an-id\x00\x00a at e\xc3\xa5ample.com--2004\x00dir\x00\x00
+None\x00/\x00an-id\x00\x00a at e\xc3\xa5ample.com--2004\x00dir\x00\x00
+"""
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes, double_root_lines)
+        self.assertContainsRe(str(err), 'duplicate file id')
+
+    def test_parse_versioned_root_only(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        parse_result = serializer.parse_text_bytes(root_only_lines)
+        expected_entry = inventory.make_entry(
+            'directory', u'', None, 'an-id')
+        expected_entry.revision = 'a at e\xc3\xa5ample.com--2004'
+        self.assertEqual(
+            ('null:', 'entry-version', [(None, '/', 'an-id', expected_entry)]),
+            parse_result)
+
+    def test_parse_special_revid_not_valid_last_mod(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=False, tree_references=True)
+        root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: null:
+versioned_root: false
+tree_references: true
+None\x00/\x00TREE_ROOT\x00\x00null:\x00dir\x00\x00
+"""
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes, root_only_lines)
+        self.assertContainsRe(str(err), 'special revisionid found')
+
+    def test_parse_versioned_root_versioned_disabled(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=False, tree_references=True)
+        root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: null:
+versioned_root: false
+tree_references: true
+None\x00/\x00TREE_ROOT\x00\x00a at e\xc3\xa5ample.com--2004\x00dir\x00\x00
+"""
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes, root_only_lines)
+        self.assertContainsRe(str(err), 'Versioned root found')
+
+    def test_parse_unique_root_id_root_versioned_disabled(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=False, tree_references=True)
+        root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: null:
+versioned_root: false
+tree_references: true
+None\x00/\x00an-id\x00\x00null:\x00dir\x00\x00
+"""
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes, root_only_lines)
+        self.assertContainsRe(str(err), 'Versioned root found')
+
+    def test_parse_unversioned_root_versioning_enabled(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes, root_only_unversioned)
+        self.assertContainsRe(
+            str(err), 'serialized versioned_root flag is wrong: False')
+
+    def test_parse_tree_when_disabled(self):
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=False)
+        err = self.assertRaises(errors.BzrError,
+            serializer.parse_text_bytes, reference_lines)
+        self.assertContainsRe(
+            str(err), 'serialized tree_references flag is wrong: True')
+
+
+class TestSerialization(TestCase):
+    """Tests for InventoryDeltaSerializer.delta_to_lines."""
+
+    def test_empty_delta_to_lines(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        self.assertEqual(StringIO(empty_lines).readlines(),
+            serializer.delta_to_lines(NULL_REVISION, NULL_REVISION, delta))
+
+    def test_root_only_to_lines(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'an-id')
+        root.revision = 'a at e\xc3\xa5ample.com--2004'
+        new_inv.add(root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        self.assertEqual(StringIO(root_only_lines).readlines(),
+            serializer.delta_to_lines(NULL_REVISION, 'entry-version', delta))
+
+    def test_unversioned_root(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
+        new_inv.add(root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=False, tree_references=False)
+        self.assertEqual(StringIO(root_only_unversioned).readlines(),
+            serializer.delta_to_lines(NULL_REVISION, 'entry-version', delta))
+
+    def test_unversioned_non_root_errors(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
+        root.revision = 'a at e\xc3\xa5ample.com--2004'
+        new_inv.add(root)
+        non_root = new_inv.make_entry('directory', 'foo', root.file_id, 'id')
+        new_inv.add(non_root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
+        self.assertEqual(str(err), 'no version for fileid id')
+
+    def test_richroot_unversioned_root_errors(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
+        new_inv.add(root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
+        self.assertEqual(str(err), 'no version for fileid TREE_ROOT')
+
+    def test_nonrichroot_versioned_root_errors(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
+        root.revision = 'a at e\xc3\xa5ample.com--2004'
+        new_inv.add(root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=False, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
+        self.assertEqual(str(err), 'Version present for / in TREE_ROOT')
+
+    def test_nonrichroot_non_TREE_ROOT_id_errors(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'my-rich-root-id')
+        new_inv.add(root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=False, tree_references=True)
+        err = self.assertRaises(errors.BzrError,
+            serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
+        self.assertEqual(
+            str(err), 'file_id my-rich-root-id is not TREE_ROOT for /')
+
+    def test_unknown_kind_errors(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'my-rich-root-id')
+        root.revision = 'changed'
+        new_inv.add(root)
+        non_root = new_inv.make_entry('directory', 'foo', root.file_id, 'id')
+        non_root.revision = 'changed'
+        non_root.kind = 'strangelove'
+        new_inv.add(non_root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        # we expect keyerror because there is little value wrapping this.
+        # This test aims to prove that it errors more than how it errors.
+        err = self.assertRaises(KeyError,
+            serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
+        self.assertEqual(('strangelove',), err.args)
+
+    def test_tree_reference_disabled(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
+        root.revision = 'a at e\xc3\xa5ample.com--2004'
+        new_inv.add(root)
+        non_root = new_inv.make_entry(
+            'tree-reference', 'foo', root.file_id, 'id')
+        non_root.revision = 'changed'
+        non_root.reference_revision = 'subtree-version'
+        new_inv.add(non_root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=False)
+        # we expect keyerror because there is little value wrapping this.
+        # This test aims to prove that it errors more than how it errors.
+        err = self.assertRaises(KeyError,
+            serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
+        self.assertEqual(('tree-reference',), err.args)
+
+    def test_tree_reference_enabled(self):
+        old_inv = Inventory(None)
+        new_inv = Inventory(None)
+        root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
+        root.revision = 'a at e\xc3\xa5ample.com--2004'
+        new_inv.add(root)
+        non_root = new_inv.make_entry(
+            'tree-reference', 'foo', root.file_id, 'id')
+        non_root.revision = 'changed'
+        non_root.reference_revision = 'subtree-version'
+        new_inv.add(non_root)
+        delta = new_inv._make_delta(old_inv)
+        serializer = inventory_delta.InventoryDeltaSerializer(
+            versioned_root=True, tree_references=True)
+        self.assertEqual(StringIO(reference_lines).readlines(),
+            serializer.delta_to_lines(NULL_REVISION, 'entry-version', delta))
+
+    def test_to_inventory_root_id_versioned_not_permitted(self):
+        delta = [(None, '/', 'TREE_ROOT', inventory.make_entry(
+            'directory', '', None, 'TREE_ROOT'))]
+        serializer = inventory_delta.InventoryDeltaSerializer(False, True)
+        self.assertRaises(
+            errors.BzrError, serializer.delta_to_lines, 'old-version',
+            'new-version', delta)
+
+    def test_to_inventory_root_id_not_versioned(self):
+        delta = [(None, '/', 'an-id', inventory.make_entry(
+            'directory', '', None, 'an-id'))]
+        serializer = inventory_delta.InventoryDeltaSerializer(True, True)
+        self.assertRaises(
+            errors.BzrError, serializer.delta_to_lines, 'old-version',
+            'new-version', delta)
+
+    def test_to_inventory_has_tree_not_meant_to(self):
+        make_entry = inventory.make_entry
+        tree_ref = make_entry('tree-reference', 'foo', 'changed-in', 'ref-id')
+        tree_ref.reference_revision = 'ref-revision'
+        delta = [
+            (None, '/', 'an-id',
+             make_entry('directory', '', 'changed-in', 'an-id')),
+            (None, '/foo', 'ref-id', tree_ref)
+            # a file that followed the root move
+            ]
+        serializer = inventory_delta.InventoryDeltaSerializer(True, True)
+        self.assertRaises(errors.BzrError, serializer.delta_to_lines,
+            'old-version', 'new-version', delta)
+
+    def test_to_inventory_torture(self):
+        def make_entry(kind, name, parent_id, file_id, **attrs):
+            entry = inventory.make_entry(kind, name, parent_id, file_id)
+            for name, value in attrs.items():
+                setattr(entry, name, value)
+            return entry
+        # this delta is crafted to have all the following:
+        # - deletes
+        # - renamed roots
+        # - deep dirs
+        # - files moved after parent dir was renamed
+        # - files with and without exec bit
+        delta = [
+            # new root:
+            (None, '', 'new-root-id',
+                make_entry('directory', '', None, 'new-root-id',
+                    revision='changed-in')),
+            # an old root:
+            ('', 'old-root', 'TREE_ROOT',
+                make_entry('directory', 'subdir-now', 'new-root-id',
+                'TREE_ROOT', revision='moved-root')),
+            # a file that followed the root move
+            ('under-old-root', 'old-root/under-old-root', 'moved-id',
+                make_entry('file', 'under-old-root', 'TREE_ROOT', 'moved-id',
+                   revision='old-rev', executable=False, text_size=30,
+                   text_sha1='some-sha')),
+            # a deleted path
+            ('old-file', None, 'deleted-id', None),
+            # a tree reference moved to the new root
+            ('ref', 'ref', 'ref-id',
+                make_entry('tree-reference', 'ref', 'new-root-id', 'ref-id',
+                    reference_revision='tree-reference-id',
+                    revision='new-rev')),
+            # a symlink now in a deep dir
+            ('dir/link', 'old-root/dir/link', 'link-id',
+                make_entry('symlink', 'link', 'deep-id', 'link-id',
+                   symlink_target='target', revision='new-rev')),
+            # a deep dir
+            ('dir', 'old-root/dir', 'deep-id',
+                make_entry('directory', 'dir', 'TREE_ROOT', 'deep-id',
+                    revision='new-rev')),
+            # a file with an exec bit set
+            (None, 'configure', 'exec-id',
+                make_entry('file', 'configure', 'new-root-id', 'exec-id',
+                   executable=True, text_size=30, text_sha1='some-sha',
+                   revision='old-rev')),
+            ]
+        serializer = inventory_delta.InventoryDeltaSerializer(True, True)
+        lines = serializer.delta_to_lines(NULL_REVISION, 'something', delta)
+        expected = """format: bzr inventory delta v1 (bzr 1.14)
+parent: null:
+version: something
+versioned_root: true
+tree_references: true
+/\x00/old-root\x00TREE_ROOT\x00new-root-id\x00moved-root\x00dir
+/dir\x00/old-root/dir\x00deep-id\x00TREE_ROOT\x00new-rev\x00dir
+/dir/link\x00/old-root/dir/link\x00link-id\x00deep-id\x00new-rev\x00link\x00target
+/old-file\x00None\x00deleted-id\x00\x00null:\x00deleted\x00\x00
+/ref\x00/ref\x00ref-id\x00new-root-id\x00new-rev\x00tree\x00tree-reference-id
+/under-old-root\x00/old-root/under-old-root\x00moved-id\x00TREE_ROOT\x00old-rev\x00file\x0030\x00\x00some-sha
+None\x00/\x00new-root-id\x00\x00changed-in\x00dir
+None\x00/configure\x00exec-id\x00new-root-id\x00old-rev\x00file\x0030\x00Y\x00some-sha
+"""
+        serialized = ''.join(lines)
+        self.assertIsInstance(serialized, str)
+        self.assertEqual(expected, serialized)
+
+
+class TestContent(TestCase):
+    """Test serialization of the content part of a line."""
+
+    def test_dir(self):
+        entry = inventory.make_entry('directory', 'a dir', None)
+        self.assertEqual('dir', inventory_delta._directory_content(entry))
+
+    def test_file_0_short_sha(self):
+        file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
+        file_entry.text_sha1 = ''
+        file_entry.text_size = 0
+        self.assertEqual('file\x000\x00\x00',
+            inventory_delta._file_content(file_entry))
+
+    def test_file_10_foo(self):
+        file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
+        file_entry.text_sha1 = 'foo'
+        file_entry.text_size = 10
+        self.assertEqual('file\x0010\x00\x00foo',
+            inventory_delta._file_content(file_entry))
+
+    def test_file_executable(self):
+        file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
+        file_entry.executable = True
+        file_entry.text_sha1 = 'foo'
+        file_entry.text_size = 10
+        self.assertEqual('file\x0010\x00Y\x00foo',
+            inventory_delta._file_content(file_entry))
+
+    def test_file_without_size(self):
+        file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
+        file_entry.text_sha1 = 'foo'
+        self.assertRaises(errors.BzrError,
+            inventory_delta._file_content, file_entry)
+
+    def test_file_without_sha1(self):
+        file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
+        file_entry.text_size = 10
+        self.assertRaises(errors.BzrError,
+            inventory_delta._file_content, file_entry)
+
+    def test_link_empty_target(self):
+        entry = inventory.make_entry('symlink', 'a link', None)
+        entry.symlink_target = ''
+        self.assertEqual('link\x00',
+            inventory_delta._link_content(entry))
+
+    def test_link_unicode_target(self):
+        entry = inventory.make_entry('symlink', 'a link', None)
+        entry.symlink_target = ' \xc3\xa5'.decode('utf8')
+        self.assertEqual('link\x00 \xc3\xa5',
+            inventory_delta._link_content(entry))
+
+    def test_link_space_target(self):
+        entry = inventory.make_entry('symlink', 'a link', None)
+        entry.symlink_target = ' '
+        self.assertEqual('link\x00 ',
+            inventory_delta._link_content(entry))
+
+    def test_link_no_target(self):
+        entry = inventory.make_entry('symlink', 'a link', None)
+        self.assertRaises(errors.BzrError,
+            inventory_delta._link_content, entry)
+
+    def test_reference_null(self):
+        entry = inventory.make_entry('tree-reference', 'a tree', None)
+        entry.reference_revision = NULL_REVISION
+        self.assertEqual('tree\x00null:',
+            inventory_delta._reference_content(entry))
+
+    def test_reference_revision(self):
+        entry = inventory.make_entry('tree-reference', 'a tree', None)
+        entry.reference_revision = 'foo@\xc3\xa5b-lah'
+        self.assertEqual('tree\x00foo@\xc3\xa5b-lah',
+            inventory_delta._reference_content(entry))
+
+    def test_reference_no_reference(self):
+        entry = inventory.make_entry('tree-reference', 'a tree', None)
+        self.assertRaises(errors.BzrError,
+            inventory_delta._reference_content, entry)

=== modified file 'doc/developers/inventory.txt'
--- a/doc/developers/inventory.txt	2008-09-24 07:33:56 +0000
+++ b/doc/developers/inventory.txt	2009-04-02 05:53:12 +0000
@@ -451,3 +451,50 @@
 
 Multiple versions of nodes for the same PREFIX and internal prefix width should
 compress well for the same tree.
+
+
+Inventory deltas
+================
+
+An inventory is a serialization of the in-memory inventory delta.  To serialize
+an inventory delta, one takes an existing inventory delta and the revision_id
+of the revision it was created it against and the revision id of the inventory
+which should result by applying the delta to the parent.  We then serialize
+every item in the delta in a simple format:
+
+'format: bzr inventory delta v1 (1.14)' NL
+'parent:' SP BASIS_INVENTORY NL
+'version:' SP NULL_OR_REVISION NL
+'versioned_root:' SP BOOL NL
+'tree_references:' SP BOOL NL
+DELTA_LINES
+
+DELTA_LINES ::= (DELTA_LINE NL)*
+DELTA_LINE ::= OLDPATH NULL NEWPATH NULL file-id NULL PARENT_ID NULL LAST_MODIFIED NULL CONTENT
+SP ::= ' '
+BOOL ::= 'true' | 'false'
+NULL ::= \x00
+OLDPATH ::= NONE | PATH
+NEWPATH ::= NONE | PATH
+NONE ::= 'None'
+PATH ::= path
+PARENT_ID ::= FILE_ID | ''
+CONTENT ::= DELETED_CONTENT | FILE_CONTENT | DIR_CONTENT | TREE_CONTENT | LINK_CONTENT
+DELETED_CONTENT ::= 'deleted'
+FILE_CONTENT ::= 'file' NULL text_size NULL EXEC NULL text_sha1
+DIR_CONTENT ::= 'dir'
+TREE_CONTENT ::= 'tree' NULL tree-revision
+LINK_CONTENT ::= 'link' NULL link-target
+BASIS_INVENTORY ::= NULL_OR_REVISION
+LAST_MODIFIED ::= NULL_OR_REVISION
+NULL_OR_REVISION ::= 'null:' | REVISION
+REVISION ::= revision-id-in-utf8-no-whitespace
+EXEC ::= '' | 'Y'
+
+DELTA_LINES is lexicographically sorted.
+
+Some explanation is in order. When NEWPATH is 'None' a delete has been
+recorded, and because this inventory delta is not attempting to be a reversible
+delta, the only other valid fields are OLDPATH and 'file-id'. PARENT_ID is ''
+when a delete has been recorded or when recording a new root entry.
+




More information about the bazaar-commits mailing list