Rev 5991: Merge trunk to resolve news conflict in file:///home/vila/src/bzr/bugs/688101-duplicate-ids/

Vincent Ladeuil v.ladeuil+lp at free.fr
Tue Jun 21 22:03:19 UTC 2011


At file:///home/vila/src/bzr/bugs/688101-duplicate-ids/

------------------------------------------------------------
revno: 5991 [merge]
revision-id: v.ladeuil+lp at free.fr-20110621220318-nex6n6els5y9x1hj
parent: v.ladeuil+lp at free.fr-20110621220233-fqpsdymzsoqhy064
parent: pqm at pqm.ubuntu.com-20110621172728-3203vaenqctilpd7
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: 688101-duplicate-ids
timestamp: Wed 2011-06-22 00:03:18 +0200
message:
  Merge trunk to resolve news conflict
modified:
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/config.py               config.py-20051011043216-070c74f4e9e338e8
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/graph.py                graph_walker.py-20070525030359-y852guab65d4wtn0-1
  bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
  bzrlib/tests/test_graph.py     test_graph_walker.py-20070525030405-enq4r60hhi9xrujc-1
  doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
-------------- next part --------------
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2011-06-01 13:43:12 +0000
+++ b/bzrlib/branch.py	2011-06-20 11:04:42 +0000
@@ -513,7 +513,7 @@
         rev_iter = iter(merge_sorted_revisions)
         if start_revision_id is not None:
             for node in rev_iter:
-                rev_id = node.key[-1]
+                rev_id = node.key
                 if rev_id != start_revision_id:
                     continue
                 else:
@@ -525,19 +525,19 @@
         if stop_revision_id is None:
             # Yield everything
             for node in rev_iter:
-                rev_id = node.key[-1]
+                rev_id = node.key
                 yield (rev_id, node.merge_depth, node.revno,
                        node.end_of_merge)
         elif stop_rule == 'exclude':
             for node in rev_iter:
-                rev_id = node.key[-1]
+                rev_id = node.key
                 if rev_id == stop_revision_id:
                     return
                 yield (rev_id, node.merge_depth, node.revno,
                        node.end_of_merge)
         elif stop_rule == 'include':
             for node in rev_iter:
-                rev_id = node.key[-1]
+                rev_id = node.key
                 yield (rev_id, node.merge_depth, node.revno,
                        node.end_of_merge)
                 if rev_id == stop_revision_id:
@@ -549,7 +549,7 @@
             ancestors = graph.find_unique_ancestors(start_revision_id,
                                                     [stop_revision_id])
             for node in rev_iter:
-                rev_id = node.key[-1]
+                rev_id = node.key
                 if rev_id not in ancestors:
                     continue
                 yield (rev_id, node.merge_depth, node.revno,
@@ -565,7 +565,7 @@
             reached_stop_revision_id = False
             revision_id_whitelist = []
             for node in rev_iter:
-                rev_id = node.key[-1]
+                rev_id = node.key
                 if rev_id == left_parent:
                     # reached the left parent after the stop_revision
                     return

=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2011-06-20 09:31:17 +0000
+++ b/bzrlib/config.py	2011-06-20 14:03:40 +0000
@@ -692,6 +692,8 @@
             self._parser = ConfigObj(co_input, encoding='utf-8')
         except configobj.ConfigObjError, e:
             raise errors.ParseConfigError(e.errors, e.config.filename)
+        except UnicodeDecodeError:
+            raise errors.ConfigContentError(self.file_name)
         # Make sure self.reload() will use the right file name
         self._parser.filename = self.file_name
         for hook in OldConfigHooks['load']:
@@ -1697,6 +1699,8 @@
             self._config = ConfigObj(self._input, encoding='utf-8')
         except configobj.ConfigObjError, e:
             raise errors.ParseConfigError(e.errors, e.config.filename)
+        except UnicodeError:
+            raise errors.ConfigContentError(self._filename)
         return self._config
 
     def _save(self):
@@ -2178,12 +2182,21 @@
         except errors.NoSuchFile:
             return StringIO()
 
+    def _external_url(self):
+        return urlutils.join(self._transport.external_url(), self._filename)
+
     def _get_configobj(self):
         f = self._get_config_file()
         try:
-            return ConfigObj(f, encoding='utf-8')
+            try:
+                conf = ConfigObj(f, encoding='utf-8')
+            except configobj.ConfigObjError, e:
+                raise errors.ParseConfigError(e.errors, self._external_url())
+            except UnicodeDecodeError:
+                raise errors.ConfigContentError(self._external_url())
         finally:
             f.close()
+        return conf
 
     def _set_configobj(self, configobj):
         out_file = StringIO()
@@ -2285,14 +2298,10 @@
         """Loads the Store from persistent storage."""
         raise NotImplementedError(self.load)
 
-    def _load_from_string(self, str_or_unicode):
+    def _load_from_string(self, bytes):
         """Create a store from a string in configobj syntax.
 
-        :param str_or_unicode: A string representing the file content. This will
-            be encoded to suit store needs internally.
-
-        This is for tests and should not be used in production unless a
-        convincing use case can be demonstrated :)
+        :param bytes: A string representing the file content.
         """
         raise NotImplementedError(self._load_from_string)
 
@@ -2370,21 +2379,22 @@
         for hook in ConfigHooks['load']:
             hook(self)
 
-    def _load_from_string(self, str_or_unicode):
+    def _load_from_string(self, bytes):
         """Create a config store from a string.
 
-        :param str_or_unicode: A string representing the file content. This will
-            be utf-8 encoded internally.
+        :param bytes: A string representing the file content.
         """
         if self.is_loaded():
             raise AssertionError('Already loaded: %r' % (self._config_obj,))
-        co_input = StringIO(str_or_unicode)
+        co_input = StringIO(bytes)
         try:
             # The config files are always stored utf8-encoded
             self._config_obj = ConfigObj(co_input, encoding='utf-8')
         except configobj.ConfigObjError, e:
             self._config_obj = None
             raise errors.ParseConfigError(e.errors, self.external_url())
+        except UnicodeDecodeError:
+            raise errors.ConfigContentError(self.external_url())
 
     def save(self):
         if not self.is_loaded():

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2011-06-02 16:57:09 +0000
+++ b/bzrlib/errors.py	2011-06-20 13:58:14 +0000
@@ -1770,6 +1770,15 @@
     _fmt = "Working tree has conflicts."
 
 
+class ConfigContentError(BzrError):
+
+    _fmt = "Config file %(filename)s is not UTF-8 encoded\n"
+
+    def __init__(self, filename):
+        BzrError.__init__(self)
+        self.filename = filename
+
+
 class ParseConfigError(BzrError):
 
     _fmt = "Error(s) parsing config file %(filename)s:\n%(errors)s"

=== modified file 'bzrlib/graph.py'
--- a/bzrlib/graph.py	2011-05-20 13:28:35 +0000
+++ b/bzrlib/graph.py	2011-06-20 11:03:53 +0000
@@ -1980,7 +1980,10 @@
         return set([h[0] for h in head_keys])
 
     def merge_sort(self, tip_revision):
-        return self._graph.merge_sort((tip_revision,))
+        nodes = self._graph.merge_sort((tip_revision,))
+        for node in nodes:
+            node.key = node.key[0]
+        return nodes
 
     def add_node(self, revision, parents):
         self._graph.add_node((revision,), [(p,) for p in parents])

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2011-06-20 09:31:17 +0000
+++ b/bzrlib/tests/test_config.py	2011-06-20 13:58:54 +0000
@@ -1871,9 +1871,34 @@
 
 class TestTransportConfig(tests.TestCaseWithTransport):
 
+    def test_load_utf8(self):
+        """Ensure we can load an utf8-encoded file."""
+        t = self.get_transport()
+        unicode_user = u'b\N{Euro Sign}ar'
+        unicode_content = u'user=%s' % (unicode_user,)
+        utf8_content = unicode_content.encode('utf8')
+        # Store the raw content in the config file
+        t.put_bytes('foo.conf', utf8_content)
+        conf = config.TransportConfig(t, 'foo.conf')
+        self.assertEquals(unicode_user, conf.get_option('user'))
+
+    def test_load_non_ascii(self):
+        """Ensure we display a proper error on non-ascii, non utf-8 content."""
+        t = self.get_transport()
+        t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
+        conf = config.TransportConfig(t, 'foo.conf')
+        self.assertRaises(errors.ConfigContentError, conf._get_configobj)
+
+    def test_load_erroneous_content(self):
+        """Ensure we display a proper error on content that can't be parsed."""
+        t = self.get_transport()
+        t.put_bytes('foo.conf', '[open_section\n')
+        conf = config.TransportConfig(t, 'foo.conf')
+        self.assertRaises(errors.ParseConfigError, conf._get_configobj)
+
     def test_get_value(self):
         """Test that retreiving a value from a section is possible"""
-        bzrdir_config = config.TransportConfig(transport.get_transport('.'),
+        bzrdir_config = config.TransportConfig(self.get_transport('.'),
                                                'control.conf')
         bzrdir_config.set_option('value', 'key', 'SECTION')
         bzrdir_config.set_option('value2', 'key2')
@@ -2328,12 +2353,22 @@
         self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
 
 
-class TestBug799212(TestStore):
-
-   def test_load_utf8(self):
+class TestIniFileStoreContent(tests.TestCaseWithTransport):
+    """Simulate loading a config store without content of various encodings.
+
+    All files produced by bzr are in utf8 content.
+
+    Users may modify them manually and end up with a file that can't be
+    loaded. We need to issue proper error messages in this case.
+    """
+
+    invalid_utf8_char = '\xff'
+
+    def test_load_utf8(self):
+        """Ensure we can load an utf8-encoded file."""
         t = self.get_transport()
         # From http://pad.lv/799212
-        unicode_user = u'Piotr O\u017carowski'
+        unicode_user = u'b\N{Euro Sign}ar'
         unicode_content = u'user=%s' % (unicode_user,)
         utf8_content = unicode_content.encode('utf8')
         # Store the raw content in the config file
@@ -2343,6 +2378,58 @@
         stack = config.Stack([store.get_sections], store)
         self.assertEquals(unicode_user, stack.get('user'))
 
+    def test_load_non_ascii(self):
+        """Ensure we display a proper error on non-ascii, non utf-8 content."""
+        t = self.get_transport()
+        t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
+        store = config.IniFileStore(t, 'foo.conf')
+        self.assertRaises(errors.ConfigContentError, store.load)
+
+    def test_load_erroneous_content(self):
+        """Ensure we display a proper error on content that can't be parsed."""
+        t = self.get_transport()
+        t.put_bytes('foo.conf', '[open_section\n')
+        store = config.IniFileStore(t, 'foo.conf')
+        self.assertRaises(errors.ParseConfigError, store.load)
+
+
+class TestIniConfigContent(tests.TestCaseWithTransport):
+    """Simulate loading a IniBasedConfig without content of various encodings.
+
+    All files produced by bzr are in utf8 content.
+
+    Users may modify them manually and end up with a file that can't be
+    loaded. We need to issue proper error messages in this case.
+    """
+
+    invalid_utf8_char = '\xff'
+
+    def test_load_utf8(self):
+        """Ensure we can load an utf8-encoded file."""
+        # From http://pad.lv/799212
+        unicode_user = u'b\N{Euro Sign}ar'
+        unicode_content = u'user=%s' % (unicode_user,)
+        utf8_content = unicode_content.encode('utf8')
+        # Store the raw content in the config file
+        with open('foo.conf', 'wb') as f:
+            f.write(utf8_content)
+        conf = config.IniBasedConfig(file_name='foo.conf')
+        self.assertEquals(unicode_user, conf.get_user_option('user'))
+
+    def test_load_badly_encoded_content(self):
+        """Ensure we display a proper error on non-ascii, non utf-8 content."""
+        with open('foo.conf', 'wb') as f:
+            f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
+        conf = config.IniBasedConfig(file_name='foo.conf')
+        self.assertRaises(errors.ConfigContentError, conf._get_parser)
+
+    def test_load_erroneous_content(self):
+        """Ensure we display a proper error on content that can't be parsed."""
+        with open('foo.conf', 'wb') as f:
+            f.write('[open_section\n')
+        conf = config.IniBasedConfig(file_name='foo.conf')
+        self.assertRaises(errors.ParseConfigError, conf._get_parser)
+
 
 class TestMutableStore(TestStore):
 
@@ -3052,6 +3139,11 @@
         self.assertEquals({}, conf._get_config())
         self._got_user_passwd(None, None, conf, 'http', 'foo.net')
 
+    def test_non_utf8_config(self):
+        conf = config.AuthenticationConfig(_file=StringIO(
+                'foo = bar\xff'))
+        self.assertRaises(errors.ConfigContentError, conf._get_config)
+        
     def test_missing_auth_section_header(self):
         conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
         self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')

=== modified file 'bzrlib/tests/test_graph.py'
--- a/bzrlib/tests/test_graph.py	2011-06-16 13:26:38 +0000
+++ b/bzrlib/tests/test_graph.py	2011-06-20 11:03:53 +0000
@@ -1646,6 +1646,15 @@
         self.assertEqual(['B', 'D'],
             sorted(graph_thunk.heads(['D', 'B', 'A'])))
 
+    def test_merge_sort(self):
+        d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
+        g = _mod_graph.KnownGraph(d)
+        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
+        graph_thunk.add_node("D", ["A", "C"])
+        self.assertEqual([('C', 0, (2,), False), ('A', 0, (1,), True)],
+            [(n.key, n.merge_depth, n.revno, n.end_of_merge)
+                 for n in graph_thunk.merge_sort('C')])
+
 
 class TestPendingAncestryResultGetKeys(TestCaseWithMemoryTransport):
     """Tests for bzrlib.graph.PendingAncestryResult."""

=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt	2011-06-21 15:19:18 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt	2011-06-21 22:03:18 +0000
@@ -29,12 +29,19 @@
 Bug Fixes
 *********
 
+* Display a proper error message when a config file content cannot be
+  decoded as UTF-8 or when it cannot be parsed.
+  (Vincent Ladeuil, #502060, #688677, #792246)
+
 * Generate a single conflict (instead of two) when merging a branch
   modifying and renaming a file in a branch that deleted it (or vice-versa).
   (Vincent Ladeuil, #688101)
 
 * Properly load utf8-encoded config files. (Vincent Ladeuil, #799212)
 
+* ``GraphThunkIdsToKeys.merge_sort`` now properly returns
+  keys rather than ids. (Jelmer Vernooij, #799677)
+
 .. Fixes for situations where bzr would previously crash or give incorrect
    or undesirable results.
 
@@ -93,7 +100,7 @@
 .. New commands, options, etc that users may wish to try out.
 
 * Hooks have been added for config stacks: ``get``, ``set`` and ``remove``
-  are called when an option is repsectively read, modified or deleted. Also
+  are called when an option is respectively read, modified or deleted. Also
   added ``load`` and ``save`` hooks for config stores, called when the
   stores are loaded or saved.  (Vincent Ladeuil)
 



More information about the bazaar-commits mailing list