[patch, rfc] speed up selective-file diff

Martin Pool mbp at canonical.com
Fri Dec 22 09:00:46 GMT 2006


These changes speed up `bzr diff FILE` with a specified file.  At the moment we
walk the whole inventory of both trees, which is unnecessary.

On the mozilla tree that cuts diff of a single file from 3.4s to 2.6s in ad-hoc
testing.

This passes all the tests but I would like to eliminate more of the redundancy
before it's merged in.

There is more that could be done here, both in cleaning up and for speed.  I'd
like to add a standard benchmark which measures specific-file and whole-tree
diff.

One of the problems for single-file diff is that we read the entire inventories
of both trees at the moment.  The right way around that is to bring in
Robert/John's work to split the inventory, and we should make that a
priority next year.

I think this code might be a bit happier returning objects rather than
tuples if the cost is not too high.

We can also make it cleaner by asking entries to compare themselves
rather than hardcoding a switch on their type in here.  That would take
us towards getting properties of wt files through their entries rather
than through the tree.

http://launchpad.net/people/mbp/+branch/bzr/diff-speed





# Bazaar revision bundle v0.8
#
# message:
#   add new InterTree._iter_changes_specific files
#   
#   This is substantially faster as it avoids walking all files
# committer: Martin Pool <mbp at sourcefrog.net>
# date: Fri 2006-12-22 19:39:40.292000055 +1100

=== modified file bzrlib/delta.py // last-changed:mbp at sourcefrog.net-2006122105
... 2637-rxk83ufpeloifxcc
--- bzrlib/delta.py
+++ bzrlib/delta.py
@@ -174,14 +174,14 @@
         include_root=False)
 
 
-def _compare_trees(old_tree, new_tree, want_unchanged, specific_file_ids,
-                   include_root):
+def _tree_delta_from_change_iter(change_iter, old_tree, include_root):
+    """Construct a TreeDelta from _iter_changes results"""
+
     delta = TreeDelta()
     # mutter('start compare_trees')
 
     for (file_id, path, content_change, versioned, parent_id, name, kind,
-         executable) in new_tree._iter_changes(old_tree, want_unchanged, 
-                                               specific_file_ids):
+         executable) in change_iter:
         if not include_root and (None, None) == parent_id:
             continue
         assert kind[0] == kind[1] or None in kind
@@ -224,3 +224,10 @@
     delta.unchanged.sort()
 
     return delta
+
+
+def _compare_trees(old_tree, new_tree, want_unchanged, specific_file_ids,
+                   include_root):
+    """Return TreeDelta describing changes"""
+    it = new_tree._iter_changes(old_tree, want_unchanged, specific_file_ids)
+    return _tree_delta_from_change_iter(it, old_tree, include_root)

=== modified file bzrlib/tests/blackbox/test_diff.py
--- bzrlib/tests/blackbox/test_diff.py
+++ bzrlib/tests/blackbox/test_diff.py
@@ -148,7 +148,7 @@
                                           'branch1'],
                                          retcode=1)
         self.assertEquals('', err)
-        self.assertEquals("=== modified file 'file'\n"
+        self.assertEqualDiff("=== modified file 'file'\n"
                           "--- file\tYYYY-MM-DD HH:MM:SS +ZZZZ\n"
                           "+++ file\tYYYY-MM-DD HH:MM:SS +ZZZZ\n"
                           "@@ -1,1 +1,1 @@\n"

=== modified file bzrlib/tree.py
--- bzrlib/tree.py
+++ bzrlib/tree.py
@@ -90,8 +90,7 @@
     def _iter_changes(self, from_tree, include_unchanged=False, 
                      specific_file_ids=None, pb=None):
         intertree = InterTree.get(from_tree, self)
-        return intertree._iter_changes(from_tree, self, include_unchanged, 
-                                       specific_file_ids, pb)
+        return intertree._iter_changes(include_unchanged, specific_file_ids, pb)
     
     def conflicts(self):
         """Get a list of the conflicts in the tree.
@@ -243,6 +242,10 @@
         pred = self.inventory.has_filename
         return set((p for p in paths if not pred(p)))
 
+    def _get_entry(self, file_id):
+        """Return inventory entry"""
+        return self.inventory[file_id]
+
 
 class EmptyTree(Tree):
 
@@ -445,64 +448,143 @@
             a PathsNotVersionedError will be thrown.
         """
         # NB: show_status depends on being able to pass in non-versioned files and
-        # report them as unknown
-        trees = (self.source, self.target)
-        if extra_trees is not None:
-            trees = trees + tuple(extra_trees)
-        specific_file_ids = find_ids_across_trees(specific_files,
-            trees, require_versioned=require_versioned)
-        if specific_files and not specific_file_ids:
-            # All files are unversioned, so just return an empty delta
-            # _compare_trees would think we want a complete delta
-            return delta.TreeDelta()
-        return delta._compare_trees(self.source, self.target, want_unchanged,
-            specific_file_ids, include_root)
-
-    def _iter_changes(self, from_tree, to_tree, include_unchanged, 
-                      specific_file_ids, pb):
+        # report them as unknown; in this case require_versioned is False and
+        # we shouldn't complain about them
+        if specific_files:
+            trees = (self.source, self.target)
+            if extra_trees is not None:
+                trees = trees + tuple(extra_trees)
+            specific_file_ids = find_ids_across_trees(specific_files,
+                trees, require_versioned=require_versioned)
+            if not specific_file_ids:
+                # All files are unversioned, so just return an empty delta
+                # _compare_trees would think we want a complete delta
+                return delta.TreeDelta()
+            change_iter = self._iter_changes_specific_files(want_unchanged,
+                    specific_file_ids)
+        else:
+            change_iter = self._iter_changes(want_unchanged, None, None)
+        return delta._tree_delta_from_change_iter(change_iter, self.source, include_root)
+
+    def _iter_changes(self, include_unchanged, specific_file_ids, pb):
+        if specific_file_ids:
+            return self._iter_changes_specific_files(include_unchanged, specific_file_ids)
+        else:
+            return self._iter_changes_whole_tree(include_unchanged, pb)
+
+    def _iter_changes_specific_files(self, want_unchanged, specific_file_ids):
+        """List changes between specific file ids"""
+        # note that this is also called when we're given just the root of the
+        # branch or tree, because it's expanded to the ids of all versioned
+        # files
+        from_tree = self.source
+        to_tree = self.target
+        for file_id in specific_file_ids:
+            in_to = to_tree.has_id(file_id)
+            in_from = from_tree.has_id(file_id)
+            if in_from:
+                from_entry = from_tree._get_entry(file_id)
+                from_versioned = True
+                from_name = from_entry.name
+                from_parent = from_entry.parent_id
+                from_path = from_tree.id2path(file_id)
+                from_kind, from_executable, from_stat = \
+                    from_tree._comparison_data(from_entry, from_path)
+            else:
+                from_entry = None
+                from_versioned = False
+                from_kind = None
+                from_parent = None
+                from_name = None
+                from_executable = None
+                from_path = None
+                from_stat = None
+            if in_to:
+                to_entry = to_tree._get_entry(file_id)
+                to_path = to_tree.id2path(file_id)
+                to_versioned = True
+                to_kind, to_executable, to_stat = \
+                    to_tree._comparison_data(to_entry, to_path)
+                to_parent = to_entry.parent_id
+                to_name = to_entry.name
+                reported_path = to_path
+            else:
+                to_versioned = False
+                to_kind = to_executable = to_stat = None
+                to_parent = None
+                to_name = None
+                to_entry = None
+                to_path = None
+                reported_path = from_path
+            versioned = (from_versioned, to_versioned)
+            kind = (from_kind, to_kind)
+            parent = (from_parent, to_parent)
+            name = (from_name, to_name)
+            executable = (from_executable, to_executable)
+            if to_versioned and from_versioned:
+                changed_content = self._compare_entry_content(file_id,
+                        from_entry, to_entry,
+                        from_path, to_path,
+                        from_stat, to_stat)
+            else:
+                # if added or deleted, content must have changed
+                changed_content = True
+            if (want_unchanged
+                or changed_content is not False 
+                or versioned[0] != versioned[1]
+                or parent[0] != parent[1]
+                or name[0] != name[1]
+                or executable[0] != executable[1]):
+                yield (file_id, reported_path, changed_content, versioned, parent,
+                       name, kind, executable)
+
+    def _iter_changes_whole_tree(self, include_unchanged, pb):
         """Generate an iterator of changes between trees.
 
-        A tuple is returned:
-        (file_id, path, changed_content, versioned, parent, name, kind,
-         executable)
+        The changes are from self.source to self.target.
+
+        The iterator yields tuples of 
+
+          (file_id, path, changed_content, versioned, parent, name, kind,
+           executable)
 
         Path is relative to the to_tree.  changed_content is True if the file's
         content has changed.  This includes changes to its kind, and to
         a symlink's target.
 
+        As a special case, if the file does not exist in the destination tree, then 
+        the path from the source tree is given instead.
+
         versioned, parent, name, kind, executable are tuples of (from, to).
         If a file is missing in a tree, its kind is None.
 
         Iteration is done in parent-to-child order, relative to the to_tree.
         """
+        # TODO: perhaps generate objects rather than tuples, so that it's
+        # easier to see what fields are being accessed, and so that some
+        # expensive things like the path can be generated as needed.
         to_paths = {}
+        from_tree = self.source
+        to_tree = self.target
         from_entries_by_dir = list(from_tree.inventory.iter_entries_by_dir())
         from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
         to_entries_by_dir = list(to_tree.inventory.iter_entries_by_dir())
-        if specific_file_ids is not None:
-            specific_file_ids = set(specific_file_ids)
-            num_entries = len(specific_file_ids)
-        else:
-            num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
+        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
         entry_count = 0
         for to_path, to_entry in to_entries_by_dir:
             file_id = to_entry.file_id
             to_paths[file_id] = to_path
-            if (specific_file_ids is not None and 
-                file_id not in specific_file_ids):
-                continue
             entry_count += 1
             changed_content = False
             from_path, from_entry = from_data.get(file_id, (None, None))
             from_versioned = (from_entry is not None)
-            if from_entry is not None:
+            if from_versioned:
                 from_versioned = True
                 from_name = from_entry.name
                 from_parent = from_entry.parent_id
                 from_kind, from_executable, from_stat = \
                     from_tree._comparison_data(from_entry, from_path)
-                if specific_file_ids is None:
-                    entry_count += 1
+                entry_count += 1
             else:
                 from_versioned = False
                 from_kind = None
@@ -548,9 +630,6 @@
                 to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
                                            from_entry.name)
             to_paths[file_id] = to_path
-            if (specific_file_ids is not None and 
-                file_id not in specific_file_ids):
-                continue
             entry_count += 1
             if pb is not None:
                 pb.update('comparing files', entry_count, num_entries)
@@ -566,6 +645,36 @@
             yield(file_id, to_path, changed_content, versioned, parent,
                   name, kind, executable)
 
+    def _compare_entry_content(self, file_id,
+            from_entry, to_entry,
+            from_path, to_path,
+            from_stat, to_stat):
+        if from_entry is None and to_entry is None:
+            # neither one is versioned - not sure what we should do about
+            # this...
+            return False
+        if (from_entry is None) or (to_entry is None):
+            return True
+        if from_entry.kind != to_entry.kind:
+            return True
+        elif from_entry.kind == 'file':
+            # TODO: should possible fix workingtree entries so that they can
+            # answer these dynamically... mbp 20061221
+            from_size = self.source._file_size(from_entry, from_stat)
+            to_size = self.target._file_size(to_entry, to_stat)
+            if from_size != to_size:
+                return True
+            elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
+                self.target.get_file_sha1(file_id, to_path, to_stat)):
+                return True
+        elif from_entry.kind == 'symlink':
+            old_target = self.source.get_symlink_target(file_id)
+            if old_target != \
+                self.target.get_symlink_target(file_id):
+                return True
+        return False
+
+
 
 # This was deprecated before 0.12, but did not have an official warning
 @symbol_versioning.deprecated_function(symbol_versioning.zero_twelve)

=== modified file bzrlib/workingtree.py
--- bzrlib/workingtree.py
+++ bzrlib/workingtree.py
@@ -1325,6 +1325,9 @@
         return kind, executable, stat_value
 
     def _file_size(self, entry, stat_value):
+        if stat_value is None:
+            # eg missing
+            return None
         return stat_value.st_size
 
     def last_revision(self):

=== modified directory  // last-changed:mbp at sourcefrog.net-20061222083940-a8euw
... g2xk7cid3pa
# revision id: mbp at sourcefrog.net-20061222083940-a8euwg2xk7cid3pa
# sha1: 43b54384f0bd06700590d239e459e1f59530b2a7
# inventory sha1: 57a64de2e188c2fd842b27805448b094b1d9c7bb
# parent ids:
#   mbp at sourcefrog.net-20061222013644-4cujoexksder64dd
# base id: pqm at pqm.ubuntu.com-20061221041314-2991e63a023efcf1
# properties:
#   branch-nick: 56299-dash-c

# message:
#   InterTree._iter_changes should use the two trees implicit to the InterTree.
#   
# committer: Martin Pool <mbp at sourcefrog.net>
# date: Fri 2006-12-22 12:36:44.552999973 +1100

=== modified file bzrlib/tree.py // encoding:base64
LS0tIGJ6cmxpYi90cmVlLnB5CisrKyBienJsaWIvdHJlZS5weQpAQCAtOTAsOCArOTAsNyBAQAog
ICAgIGRlZiBfaXRlcl9jaGFuZ2VzKHNlbGYsIGZyb21fdHJlZSwgaW5jbHVkZV91bmNoYW5nZWQ9
RmFsc2UsIAogICAgICAgICAgICAgICAgICAgICAgc3BlY2lmaWNfZmlsZV9pZHM9Tm9uZSwgcGI9
Tm9uZSk6CiAgICAgICAgIGludGVydHJlZSA9IEludGVyVHJlZS5nZXQoZnJvbV90cmVlLCBzZWxm
KQotICAgICAgICByZXR1cm4gaW50ZXJ0cmVlLl9pdGVyX2NoYW5nZXMoZnJvbV90cmVlLCBzZWxm
LCBpbmNsdWRlX3VuY2hhbmdlZCwgCi0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICBzcGVjaWZpY19maWxlX2lkcywgcGIpCisgICAgICAgIHJldHVybiBpbnRlcnRyZWUuX2l0
ZXJfY2hhbmdlcyhpbmNsdWRlX3VuY2hhbmdlZCwgc3BlY2lmaWNfZmlsZV9pZHMsIHBiKQogICAg
IAogICAgIGRlZiBjb25mbGljdHMoc2VsZik6CiAgICAgICAgICIiIkdldCBhIGxpc3Qgb2YgdGhl
IGNvbmZsaWN0cyBpbiB0aGUgdHJlZS4KQEAgLTQ1NSwxMyArNDU0LDE1IEBACiAgICAgICAgICAg
ICAjIEFsbCBmaWxlcyBhcmUgdW52ZXJzaW9uZWQsIHNvIGp1c3QgcmV0dXJuIGFuIGVtcHR5IGRl
bHRhCiAgICAgICAgICAgICAjIF9jb21wYXJlX3RyZWVzIHdvdWxkIHRoaW5rIHdlIHdhbnQgYSBj
b21wbGV0ZSBkZWx0YQogICAgICAgICAgICAgcmV0dXJuIGRlbHRhLlRyZWVEZWx0YSgpCi0gICAg
ICAgIHJldHVybiBkZWx0YS5fY29tcGFyZV90cmVlcyhzZWxmLnNvdXJjZSwgc2VsZi50YXJnZXQs
IHdhbnRfdW5jaGFuZ2VkLAotICAgICAgICAgICAgc3BlY2lmaWNfZmlsZV9pZHMsIGluY2x1ZGVf
cm9vdCkKLQotICAgIGRlZiBfaXRlcl9jaGFuZ2VzKHNlbGYsIGZyb21fdHJlZSwgdG9fdHJlZSwg
aW5jbHVkZV91bmNoYW5nZWQsIAotICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZmljX2ZpbGVf
aWRzLCBwYik6CisgICAgICAgIGNoYW5nZV9pdGVyID0gc2VsZi5faXRlcl9jaGFuZ2VzKHdhbnRf
dW5jaGFuZ2VkLCBzcGVjaWZpY19maWxlX2lkcywgTm9uZSkKKyAgICAgICAgcmV0dXJuIGRlbHRh
Ll90cmVlX2RlbHRhX2Zyb21fY2hhbmdlX2l0ZXIoY2hhbmdlX2l0ZXIsIHNlbGYuc291cmNlLCBp
bmNsdWRlX3Jvb3QpCisKKworICAgIGRlZiBfaXRlcl9jaGFuZ2VzKHNlbGYsIGluY2x1ZGVfdW5j
aGFuZ2VkLCBzcGVjaWZpY19maWxlX2lkcywgcGIpOgogICAgICAgICAiIiJHZW5lcmF0ZSBhbiBp
dGVyYXRvciBvZiBjaGFuZ2VzIGJldHdlZW4gdHJlZXMuCiAKKyAgICAgICAgVGhlIGNoYW5nZXMg
YXJlIGZyb20gc2VsZi5zb3VyY2UgdG8gc2VsZi50YXJnZXQuCisKICAgICAgICAgVGhlIGl0ZXJh
dG9yIHlpZWxkcyB0dXBsZXMgb2YgCiAKICAgICAgICAgICAoZmlsZV9pZCwgcGF0aCwgY2hhbmdl
ZF9jb250ZW50LCB2ZXJzaW9uZWQsIHBhcmVudCwgbmFtZSwga2luZCwKQEAgLTQ3Nyw2ICs0Nzgs
OCBAQAogICAgICAgICBJdGVyYXRpb24gaXMgZG9uZSBpbiBwYXJlbnQtdG8tY2hpbGQgb3JkZXIs
IHJlbGF0aXZlIHRvIHRoZSB0b190cmVlLgogICAgICAgICAiIiIKICAgICAgICAgdG9fcGF0aHMg
PSB7fQorICAgICAgICBmcm9tX3RyZWUgPSBzZWxmLnNvdXJjZQorICAgICAgICB0b190cmVlID0g
c2VsZi50YXJnZXQKICAgICAgICAgZnJvbV9lbnRyaWVzX2J5X2RpciA9IGxpc3QoZnJvbV90cmVl
LmludmVudG9yeS5pdGVyX2VudHJpZXNfYnlfZGlyKCkpCiAgICAgICAgIGZyb21fZGF0YSA9IGRp
Y3QoKGUuZmlsZV9pZCwgKHAsIGUpKSBmb3IgcCwgZSBpbiBmcm9tX2VudHJpZXNfYnlfZGlyKQog
ICAgICAgICB0b19lbnRyaWVzX2J5X2RpciA9IGxpc3QodG9fdHJlZS5pbnZlbnRvcnkuaXRlcl9l
bnRyaWVzX2J5X2RpcigpKQoK

=== modified directory  // last-changed:mbp at sourcefrog.net-20061222013644-4cujo
... exksder64dd
# revision id: mbp at sourcefrog.net-20061222013644-4cujoexksder64dd
# sha1: 184499a03e26481057e1a14acd485f20612dc973
# inventory sha1: 952caa386e2304bd87ac21c2c1db763c47b83c71
# parent ids:
#   mbp at sourcefrog.net-20061221052637-rxk83ufpeloifxcc
# properties:
#   branch-nick: 56299-dash-c

# message:
#   Refactor _compare_trees into a separate function that makes a TreeDelta
#   from a change iterator.
#   
# committer: Martin Pool <mbp at sourcefrog.net>
# date: Thu 2006-12-21 16:26:37.496000051 +1100

=== modified file bzrlib/delta.py // encoding:base64
LS0tIGJ6cmxpYi9kZWx0YS5weQorKysgYnpybGliL2RlbHRhLnB5CkBAIC0xNzQsMTQgKzE3NCwx
NCBAQAogICAgICAgICBpbmNsdWRlX3Jvb3Q9RmFsc2UpCiAKIAotZGVmIF9jb21wYXJlX3RyZWVz
KG9sZF90cmVlLCBuZXdfdHJlZSwgd2FudF91bmNoYW5nZWQsIHNwZWNpZmljX2ZpbGVfaWRzLAot
ICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfcm9vdCk6CitkZWYgX3RyZWVfZGVsdGFfZnJvbV9j
aGFuZ2VfaXRlcihjaGFuZ2VfaXRlciwgb2xkX3RyZWUsIGluY2x1ZGVfcm9vdCk6CisgICAgIiIi
Q29uc3RydWN0IGEgVHJlZURlbHRhIGZyb20gX2l0ZXJfY2hhbmdlcyByZXN1bHRzIiIiCisKICAg
ICBkZWx0YSA9IFRyZWVEZWx0YSgpCiAgICAgIyBtdXR0ZXIoJ3N0YXJ0IGNvbXBhcmVfdHJlZXMn
KQogCiAgICAgZm9yIChmaWxlX2lkLCBwYXRoLCBjb250ZW50X2NoYW5nZSwgdmVyc2lvbmVkLCBw
YXJlbnRfaWQsIG5hbWUsIGtpbmQsCi0gICAgICAgICBleGVjdXRhYmxlKSBpbiBuZXdfdHJlZS5f
aXRlcl9jaGFuZ2VzKG9sZF90cmVlLCB3YW50X3VuY2hhbmdlZCwgCi0gICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZmljX2ZpbGVfaWRzKToKKyAgICAg
ICAgIGV4ZWN1dGFibGUpIGluIGNoYW5nZV9pdGVyOgogICAgICAgICBpZiBub3QgaW5jbHVkZV9y
b290IGFuZCAoTm9uZSwgTm9uZSkgPT0gcGFyZW50X2lkOgogICAgICAgICAgICAgY29udGludWUK
ICAgICAgICAgYXNzZXJ0IGtpbmRbMF0gPT0ga2luZFsxXSBvciBOb25lIGluIGtpbmQKQEAgLTIy
NCwzICsyMjQsMTAgQEAKICAgICBkZWx0YS51bmNoYW5nZWQuc29ydCgpCiAKICAgICByZXR1cm4g
ZGVsdGEKKworCitkZWYgX2NvbXBhcmVfdHJlZXMob2xkX3RyZWUsIG5ld190cmVlLCB3YW50X3Vu
Y2hhbmdlZCwgc3BlY2lmaWNfZmlsZV9pZHMsCisgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9y
b290KToKKyAgICAiIiJSZXR1cm4gVHJlZURlbHRhIGRlc2NyaWJpbmcgY2hhbmdlcyIiIgorICAg
IGl0ID0gbmV3X3RyZWUuX2l0ZXJfY2hhbmdlcyhvbGRfdHJlZSwgd2FudF91bmNoYW5nZWQsIHNw
ZWNpZmljX2ZpbGVfaWRzKQorICAgIHJldHVybiBfdHJlZV9kZWx0YV9mcm9tX2NoYW5nZV9pdGVy
KGl0LCBvbGRfdHJlZSwgaW5jbHVkZV9yb290KQoK

=== modified file bzrlib/tree.py // encoding:base64
LS0tIGJ6cmxpYi90cmVlLnB5CisrKyBienJsaWIvdHJlZS5weQpAQCAtNDYyLDkgKzQ2MiwxMCBA
QAogICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZmljX2ZpbGVfaWRzLCBwYik6CiAgICAgICAg
ICIiIkdlbmVyYXRlIGFuIGl0ZXJhdG9yIG9mIGNoYW5nZXMgYmV0d2VlbiB0cmVlcy4KIAotICAg
ICAgICBBIHR1cGxlIGlzIHJldHVybmVkOgotICAgICAgICAoZmlsZV9pZCwgcGF0aCwgY2hhbmdl
ZF9jb250ZW50LCB2ZXJzaW9uZWQsIHBhcmVudCwgbmFtZSwga2luZCwKLSAgICAgICAgIGV4ZWN1
dGFibGUpCisgICAgICAgIFRoZSBpdGVyYXRvciB5aWVsZHMgdHVwbGVzIG9mIAorCisgICAgICAg
ICAgKGZpbGVfaWQsIHBhdGgsIGNoYW5nZWRfY29udGVudCwgdmVyc2lvbmVkLCBwYXJlbnQsIG5h
bWUsIGtpbmQsCisgICAgICAgICAgIGV4ZWN1dGFibGUpCiAKICAgICAgICAgUGF0aCBpcyByZWxh
dGl2ZSB0byB0aGUgdG9fdHJlZS4gIGNoYW5nZWRfY29udGVudCBpcyBUcnVlIGlmIHRoZSBmaWxl
J3MKICAgICAgICAgY29udGVudCBoYXMgY2hhbmdlZC4gIFRoaXMgaW5jbHVkZXMgY2hhbmdlcyB0
byBpdHMga2luZCwgYW5kIHRvCgo=

=== modified directory  // last-changed:mbp at sourcefrog.net-20061221052637-rxk83
... ufpeloifxcc
# revision id: mbp at sourcefrog.net-20061221052637-rxk83ufpeloifxcc
# sha1: 0ae007cba5aa75ea330b975c931d3a697ea0cd76
# inventory sha1: 31bbd6ac7097de92b7262d1c7858c77ff65a36b5
# parent ids:
#   pqm at pqm.ubuntu.com-20061221041314-2991e63a023efcf1
# properties:
#   branch-nick: 56299-dash-c

-- 
Martin




More information about the bazaar mailing list