Rev 4540: Fix NEWS conflicts. in http://bazaar.launchpad.net/~lifeless/bzr/bug-395556

Robert Collins robertc at robertcollins.net
Thu Jul 16 01:52:55 BST 2009


At http://bazaar.launchpad.net/~lifeless/bzr/bug-395556

------------------------------------------------------------
revno: 4540 [merge]
revision-id: robertc at robertcollins.net-20090716005251-s8knpmg22hic3pvp
parent: robertc at robertcollins.net-20090716003813-aww7uc3s7zf67yym
parent: pqm at pqm.ubuntu.com-20090715231357-3abvg8h62kjh3zak
committer: Robert Collins <robertc at robertcollins.net>
branch nick: bug-395556
timestamp: Thu 2009-07-16 10:52:51 +1000
message:
  Fix NEWS conflicts.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/commit.py               commit.py-20050511101309-79ec1a0168e0e825
  bzrlib/config.py               config.py-20051011043216-070c74f4e9e338e8
  bzrlib/send.py                 send.py-20090521192735-j7cdb33ykmtmzx4w-1
  bzrlib/tests/per_workingtree/test_commit.py test_commit.py-20060421013633-1610ec2331c8190f
  bzrlib/tests/per_workingtree/test_unversion.py test_unversion.py-20060907074408-bygh2y28jz8u0cg7-1
  bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
  bzrlib/tests/test_transform.py test_transaction.py-20060105172520-b3ffb3946550e6c4
  bzrlib/tests/test_ui.py        test_ui.py-20051130162854-458e667a7414af09
  bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
  bzrlib/ui/__init__.py          ui.py-20050824083933-8cf663c763ba53a9
  bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
=== modified file 'NEWS'
--- a/NEWS	2009-07-16 00:38:13 +0000
+++ b/NEWS	2009-07-16 00:52:51 +0000
@@ -25,10 +25,19 @@
 * BranchBuilder now accepts timezone to avoid test failures in countries far
   from GMT. (Vincent Ladeuil, #397716)
 
+* ``bzr commit`` no longer saves the unversioning of missing files until
+  the commit has completed on the branch. This means that aborting a
+  commit that found a missing file will leave the tree unedited.
+  (Robert Collins, #282402)
+
 * Renames to lexographically lower basenames in trees that have never been
   committed to will no longer corrupt the dirstate. This was caused by an
   bug in the dirstate update_minimal method. (Robert Collins, #395556)
 
+* ``WorkingTree4.unversion`` will no longer fail to unversion ids which
+  were present in a parent tree but renamed in the working tree.
+  (Robert Collins, #187207)
+
 Improvements
 ************
 
@@ -40,6 +49,9 @@
 API Changes
 ***********
 
+* New TransformPreview.commit() allows committing without a working tree.
+  (Aaron Bentley)
+
 Internals
 *********
 

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2009-07-14 16:27:40 +0000
+++ b/bzrlib/builtins.py	2009-07-15 07:32:26 +0000
@@ -1096,26 +1096,17 @@
         (tree, br_from,
          _unused) = bzrdir.BzrDir.open_containing_tree_or_branch(directory)
         if strict is None:
-            strict = br_from.get_config().get_user_option('push_strict')
-            if strict is not None:
-                # FIXME: This should be better supported by config
-                # -- vila 20090611
-                bools = dict(yes=True, no=False, on=True, off=False,
-                             true=True, false=False)
-                try:
-                    strict = bools[strict.lower()]
-                except KeyError:
-                    strict = None
+            strict = br_from.get_config().get_user_option_as_bool('push_strict')
+        if strict is None: strict = True # default value
         # Get the tip's revision_id
         revision = _get_one_revision('push', revision)
         if revision is not None:
             revision_id = revision.in_history(br_from).rev_id
         else:
             revision_id = None
-        if (tree is not None and revision_id is None
-            and (strict is None or strict)): # Default to True:
+        if strict and tree is not None and revision_id is None:
             if (tree.has_changes(tree.basis_tree())
-                 or len(tree.get_parent_ids()) > 1):
+                or len(tree.get_parent_ids()) > 1):
                 raise errors.UncommittedChanges(
                     tree, more='Use --no-strict to force the push.')
             if tree.last_revision() != tree.branch.last_revision():

=== modified file 'bzrlib/commit.py'
--- a/bzrlib/commit.py	2009-06-15 19:04:38 +0000
+++ b/bzrlib/commit.py	2009-07-15 05:54:37 +0000
@@ -204,6 +204,7 @@
         """Commit working copy as a new revision.
 
         :param message: the commit message (it or message_callback is required)
+        :param message_callback: A callback: message = message_callback(cmt_obj)
 
         :param timestamp: if not None, seconds-since-epoch for a
             postdated/predated commit.
@@ -392,7 +393,10 @@
             # and now do the commit locally.
             self.branch.set_last_revision_info(new_revno, self.rev_id)
 
-            # Make the working tree up to date with the branch
+            # Make the working tree be up to date with the branch. This
+            # includes automatic changes scheduled to be made to the tree, such
+            # as updating its basis and unversioning paths that were missing.
+            self.work_tree.unversion(self.deleted_ids)
             self._set_progress_stage("Updating the working tree")
             self.work_tree.update_basis_by_delta(self.rev_id,
                  self.builder.get_basis_delta())
@@ -679,7 +683,7 @@
                             reporter.snapshot_change('modified', new_path)
             self._next_progress_entry()
         # Unversion IDs that were found to be deleted
-        self.work_tree.unversion(deleted_ids)
+        self.deleted_ids = deleted_ids
 
     def _record_unselected(self):
         # If specific files are selected, then all un-selected files must be
@@ -842,7 +846,7 @@
                 content_summary)
 
         # Unversion IDs that were found to be deleted
-        self.work_tree.unversion(deleted_ids)
+        self.deleted_ids = deleted_ids
 
     def _commit_nested_tree(self, file_id, path):
         "Commit a nested tree."

=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2009-06-11 06:49:21 +0000
+++ b/bzrlib/config.py	2009-07-02 08:59:16 +0000
@@ -146,6 +146,9 @@
 class Config(object):
     """A configuration policy - what username, editor, gpg needs etc."""
 
+    def __init__(self):
+        super(Config, self).__init__()
+
     def get_editor(self):
         """Get the users pop up editor."""
         raise NotImplementedError
@@ -174,6 +177,15 @@
         """Get a generic option - no special process, no default."""
         return self._get_user_option(option_name)
 
+    def get_user_option_as_bool(self, option_name):
+        """Get a generic option as a boolean - no special process, no default.
+
+        :return None if the option doesn't exist or its value can't be
+            interpreted as a boolean. Returns True or False ortherwise.
+        """
+        s = self._get_user_option(option_name)
+        return ui.bool_from_string(s)
+
     def gpg_signing_command(self):
         """What program should be used to sign signatures?"""
         result = self._gpg_signing_command()
@@ -196,9 +208,6 @@
         """See log_format()."""
         return None
 
-    def __init__(self):
-        super(Config, self).__init__()
-
     def post_commit(self):
         """An ordered list of python functions to call.
 
@@ -299,6 +308,11 @@
 class IniBasedConfig(Config):
     """A configuration policy that draws from ini files."""
 
+    def __init__(self, get_filename):
+        super(IniBasedConfig, self).__init__()
+        self._get_filename = get_filename
+        self._parser = None
+
     def _get_parser(self, file=None):
         if self._parser is not None:
             return self._parser
@@ -381,11 +395,6 @@
         """See Config.log_format."""
         return self._get_user_option('log_format')
 
-    def __init__(self, get_filename):
-        super(IniBasedConfig, self).__init__()
-        self._get_filename = get_filename
-        self._parser = None
-
     def _post_commit(self):
         """See Config.post_commit."""
         return self._get_user_option('post_commit')

=== modified file 'bzrlib/send.py'
--- a/bzrlib/send.py	2009-07-02 13:07:14 +0000
+++ b/bzrlib/send.py	2009-07-15 07:32:26 +0000
@@ -108,17 +108,10 @@
                 base_revision_id = revision[0].as_revision_id(branch)
         if revision_id is None:
             if strict is None:
-                strict = branch.get_config().get_user_option('send_strict')
-                if strict is not None:
-                    # FIXME: This should be better supported by config
-                    # -- vila 20090626
-                    bools = dict(yes=True, no=False, on=True, off=False,
-                                 true=True, false=False)
-                    try:
-                        strict = bools[strict.lower()]
-                    except KeyError:
-                        strict = None
-            if tree is not None and (strict is None or strict):
+                strict = branch.get_config(
+                    ).get_user_option_as_bool('send_strict')
+            if strict is None: strict = True # default value
+            if strict and tree is not None:
                 if (tree.has_changes(tree.basis_tree())
                     or len(tree.get_parent_ids()) > 1):
                     raise errors.UncommittedChanges(

=== modified file 'bzrlib/tests/per_workingtree/test_commit.py'
--- a/bzrlib/tests/per_workingtree/test_commit.py	2009-07-15 01:13:20 +0000
+++ b/bzrlib/tests/per_workingtree/test_commit.py	2009-07-16 00:52:51 +0000
@@ -280,6 +280,15 @@
         wt2.merge_from_branch(wt.branch)
         wt2.commit('merged kind change')
 
+    def test_commit_aborted_does_not_apply_automatic_changes_bug_282402(self):
+        wt = self.make_branch_and_tree('.')
+        wt.add(['a'], ['a-id'], ['file'])
+        def fail_message(obj):
+            raise errors.BzrCommandError("empty commit message")
+        self.assertRaises(errors.BzrCommandError, wt.commit,
+            message_callback=fail_message)
+        self.assertEqual('a', wt.id2path('a-id'))
+
     def test_local_commit_ignores_master(self):
         # a --local commit does not require access to the master branch
         # at all, or even for it to exist.

=== modified file 'bzrlib/tests/per_workingtree/test_unversion.py'
--- a/bzrlib/tests/per_workingtree/test_unversion.py	2009-07-10 07:14:02 +0000
+++ b/bzrlib/tests/per_workingtree/test_unversion.py	2009-07-15 04:33:14 +0000
@@ -37,6 +37,20 @@
         tree = self.make_branch_and_tree('.')
         self.assertRaises(errors.NoSuchId, tree.unversion, ['missing-id'])
 
+    def test_unversion_parent_and_child_renamed_bug_187207(self):
+        # When unversioning dirstate trees show a bug in dealing with
+        # unversioning children of reparented children of unversioned
+        # paths when relocation entries are present and the relocation
+        # points later into the dirstate.
+        tree = self.make_branch_and_tree(['.'])
+        self.build_tree(['del/', 'del/sub/', 'del/sub/b'])
+        tree.add(['del', 'del/sub', 'del/sub/b'], ['del', 'sub', 'b'])
+        tree.commit('setup')
+        tree.rename_one('del/sub', 'sub')
+        self.assertEqual('sub/b', tree.id2path('b'))
+        tree.unversion(['del', 'b'])
+        self.assertRaises(errors.NoSuchId, tree.id2path, 'b')
+
     def test_unversion_several_files(self):
         """After unversioning several files, they should not be versioned."""
         tree = self.make_branch_and_tree('.')

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2009-06-29 12:14:09 +0000
+++ b/bzrlib/tests/test_config.py	2009-07-02 08:59:16 +0000
@@ -367,6 +367,20 @@
         parser = my_config._get_parser(file=config_file)
         self.failUnless(my_config._get_parser() is parser)
 
+    def test_get_user_option_as_bool(self):
+        config_file = StringIO("""
+a_true_bool = true
+a_false_bool = 0
+an_invalid_bool = maybe
+a_list = hmm, who knows ? # This interpreted as a list !
+""".encode('utf-8'))
+        my_config = config.IniBasedConfig(None)
+        parser = my_config._get_parser(file=config_file)
+        get_option = my_config.get_user_option_as_bool
+        self.assertEqual(True, get_option('a_true_bool'))
+        self.assertEqual(False, get_option('a_false_bool'))
+        self.assertIs(None, get_option('an_invalid_bool'))
+        self.assertIs(None, get_option('not_defined_in_this_config'))
 
 class TestGetConfig(tests.TestCase):
 

=== modified file 'bzrlib/tests/test_transform.py'
--- a/bzrlib/tests/test_transform.py	2009-06-10 03:56:49 +0000
+++ b/bzrlib/tests/test_transform.py	2009-07-15 13:45:28 +0000
@@ -1873,6 +1873,106 @@
         self.assertEqual('FILE', target.id2path('upper-id'))
 
 
+class TestCommitTransform(tests.TestCaseWithTransport):
+
+    def get_branch(self):
+        tree = self.make_branch_and_tree('tree')
+        tree.lock_write()
+        self.addCleanup(tree.unlock)
+        tree.commit('empty commit')
+        return tree.branch
+
+    def get_branch_and_transform(self):
+        branch = self.get_branch()
+        tt = TransformPreview(branch.basis_tree())
+        self.addCleanup(tt.finalize)
+        return branch, tt
+
+    def test_commit_wrong_basis(self):
+        branch = self.get_branch()
+        basis = branch.repository.revision_tree(
+            _mod_revision.NULL_REVISION)
+        tt = TransformPreview(basis)
+        self.addCleanup(tt.finalize)
+        e = self.assertRaises(ValueError, tt.commit, branch, '')
+        self.assertEqual('TreeTransform not based on branch basis: null:',
+                         str(e))
+
+    def test_empy_commit(self):
+        branch, tt = self.get_branch_and_transform()
+        rev = tt.commit(branch, 'my message')
+        self.assertEqual(2, branch.revno())
+        repo = branch.repository
+        self.assertEqual('my message', repo.get_revision(rev).message)
+
+    def test_merge_parents(self):
+        branch, tt = self.get_branch_and_transform()
+        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
+        self.assertEqual(['rev1b', 'rev1c'],
+                         branch.basis_tree().get_parent_ids()[1:])
+
+    def test_first_commit(self):
+        branch = self.make_branch('branch')
+        branch.lock_write()
+        self.addCleanup(branch.unlock)
+        tt = TransformPreview(branch.basis_tree())
+        rev = tt.commit(branch, 'my message')
+        self.assertEqual([], branch.basis_tree().get_parent_ids())
+        self.assertNotEqual(_mod_revision.NULL_REVISION,
+                            branch.last_revision())
+
+    def test_first_commit_with_merge_parents(self):
+        branch = self.make_branch('branch')
+        branch.lock_write()
+        self.addCleanup(branch.unlock)
+        tt = TransformPreview(branch.basis_tree())
+        e = self.assertRaises(ValueError, tt.commit, branch,
+                          'my message', ['rev1b-id'])
+        self.assertEqual('Cannot supply merge parents for first commit.',
+                         str(e))
+        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
+
+    def test_add_files(self):
+        branch, tt = self.get_branch_and_transform()
+        tt.new_file('file', tt.root, 'contents', 'file-id')
+        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
+        tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
+        rev = tt.commit(branch, 'message')
+        tree = branch.basis_tree()
+        self.assertEqual('file', tree.id2path('file-id'))
+        self.assertEqual('contents', tree.get_file_text('file-id'))
+        self.assertEqual('dir', tree.id2path('dir-id'))
+        self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
+        self.assertEqual('target', tree.get_symlink_target('symlink-id'))
+
+    def test_add_unversioned(self):
+        branch, tt = self.get_branch_and_transform()
+        tt.new_file('file', tt.root, 'contents')
+        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
+                          'message', strict=True)
+
+    def test_modify_strict(self):
+        branch, tt = self.get_branch_and_transform()
+        tt.new_file('file', tt.root, 'contents', 'file-id')
+        tt.commit(branch, 'message', strict=True)
+        tt = TransformPreview(branch.basis_tree())
+        trans_id = tt.trans_id_file_id('file-id')
+        tt.delete_contents(trans_id)
+        tt.create_file('contents', trans_id)
+        tt.commit(branch, 'message', strict=True)
+
+    def test_commit_malformed(self):
+        """Committing a malformed transform should raise an exception.
+
+        In this case, we are adding a file without adding its parent.
+        """
+        branch, tt = self.get_branch_and_transform()
+        parent_id = tt.trans_id_file_id('parent-id')
+        tt.new_file('file', parent_id, 'contents', 'file-id')
+        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
+                          'message')
+
+
 class MockTransform(object):
 
     def has_named_child(self, by_parent, parent_id, name):

=== modified file 'bzrlib/tests/test_ui.py'
--- a/bzrlib/tests/test_ui.py	2009-06-30 05:34:47 +0000
+++ b/bzrlib/tests/test_ui.py	2009-07-02 09:09:35 +0000
@@ -174,21 +174,22 @@
         pb1.finished()
 
     def assert_get_bool_acceptance_of_user_input(self, factory):
-        factory.stdin = StringIO("y\nyes with garbage\n"
-                                 "yes\nn\nnot an answer\n"
-                                 "no\n"
-                                 "N\nY\n"
-                                 "foo\n"
-                                )
+        factory.stdin = StringIO("y\n" # True
+                                 "n\n" # False
+                                 "yes with garbage\nY\n" # True
+                                 "not an answer\nno\n" # False
+                                 "I'm sure!\nyes\n" # True
+                                 "NO\n" # False
+                                 "foo\n")
         factory.stdout = StringIO()
         factory.stderr = StringIO()
         # there is no output from the base factory
         self.assertEqual(True, factory.get_boolean(""))
-        self.assertEqual(True, factory.get_boolean(""))
-        self.assertEqual(False, factory.get_boolean(""))
-        self.assertEqual(False, factory.get_boolean(""))
-        self.assertEqual(False, factory.get_boolean(""))
-        self.assertEqual(True, factory.get_boolean(""))
+        self.assertEqual(False, factory.get_boolean(""))
+        self.assertEqual(True, factory.get_boolean(""))
+        self.assertEqual(False, factory.get_boolean(""))
+        self.assertEqual(True, factory.get_boolean(""))
+        self.assertEqual(False, factory.get_boolean(""))
         self.assertEqual("foo\n", factory.stdin.read())
         # stdin should be empty
         self.assertEqual('', factory.stdin.readline())
@@ -355,3 +356,58 @@
             r'[####|               ] a:b:c 1/2'
             , uif._progress_view._render_line())
 
+
+class TestBoolFromString(tests.TestCase):
+
+    def assertIsTrue(self, s, accepted_values=None):
+        res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
+        self.assertEquals(True, res)
+
+    def assertIsFalse(self, s, accepted_values=None):
+        res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
+        self.assertEquals(False, res)
+
+    def assertIsNone(self, s, accepted_values=None):
+        res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
+        self.assertIs(None, res)
+
+    def test_know_valid_values(self):
+        self.assertIsTrue('true')
+        self.assertIsFalse('false')
+        self.assertIsTrue('1')
+        self.assertIsFalse('0')
+        self.assertIsTrue('on')
+        self.assertIsFalse('off')
+        self.assertIsTrue('yes')
+        self.assertIsFalse('no')
+        self.assertIsTrue('y')
+        self.assertIsFalse('n')
+        # Also try some case variations
+        self.assertIsTrue('True')
+        self.assertIsFalse('False')
+        self.assertIsTrue('On')
+        self.assertIsFalse('Off')
+        self.assertIsTrue('ON')
+        self.assertIsFalse('OFF')
+        self.assertIsTrue('oN')
+        self.assertIsFalse('oFf')
+
+    def test_invalid_values(self):
+        self.assertIsNone(None)
+        self.assertIsNone('doubt')
+        self.assertIsNone('frue')
+        self.assertIsNone('talse')
+        self.assertIsNone('42')
+
+    def test_provided_values(self):
+        av = dict(y=True, n=False, yes=True, no=False)
+        self.assertIsTrue('y', av)
+        self.assertIsTrue('Y', av)
+        self.assertIsTrue('Yes', av)
+        self.assertIsFalse('n', av)
+        self.assertIsFalse('N', av)
+        self.assertIsFalse('No', av)
+        self.assertIsNone('1', av)
+        self.assertIsNone('0', av)
+        self.assertIsNone('on', av)
+        self.assertIsNone('off', av)

=== modified file 'bzrlib/transform.py'
--- a/bzrlib/transform.py	2009-07-06 21:22:45 +0000
+++ b/bzrlib/transform.py	2009-07-15 13:45:28 +0000
@@ -442,6 +442,11 @@
         conflicts.extend(self._overwrite_conflicts())
         return conflicts
 
+    def _check_malformed(self):
+        conflicts = self.find_conflicts()
+        if len(conflicts) != 0:
+            raise MalformedTransform(conflicts=conflicts)
+
     def _add_tree_children(self):
         """Add all the children of all active parents to the known paths.
 
@@ -859,6 +864,43 @@
         """
         return _PreviewTree(self)
 
+    def commit(self, branch, message, merge_parents=None, strict=False):
+        """Commit the result of this TreeTransform to a branch.
+
+        :param branch: The branch to commit to.
+        :param message: The message to attach to the commit.
+        :param merge_parents: Additional parents specified by pending merges.
+        :return: The revision_id of the revision committed.
+        """
+        self._check_malformed()
+        if strict:
+            unversioned = set(self._new_contents).difference(set(self._new_id))
+            for trans_id in unversioned:
+                if self.final_file_id(trans_id) is None:
+                    raise errors.StrictCommitFailed()
+
+        revno, last_rev_id = branch.last_revision_info()
+        if last_rev_id == _mod_revision.NULL_REVISION:
+            if merge_parents is not None:
+                raise ValueError('Cannot supply merge parents for first'
+                                 ' commit.')
+            parent_ids = []
+        else:
+            parent_ids = [last_rev_id]
+            if merge_parents is not None:
+                parent_ids.extend(merge_parents)
+        if self._tree.get_revision_id() != last_rev_id:
+            raise ValueError('TreeTransform not based on branch basis: %s' %
+                             self._tree.get_revision_id())
+        builder = branch.get_commit_builder(parent_ids)
+        preview = self.get_preview_tree()
+        list(builder.record_iter_changes(preview, last_rev_id,
+                                         self.iter_changes()))
+        builder.finish_inventory()
+        revision_id = builder.commit(message)
+        branch.set_last_revision_info(revno + 1, revision_id)
+        return revision_id
+
     def _text_parent(self, trans_id):
         file_id = self.tree_file_id(trans_id)
         try:
@@ -1354,7 +1396,6 @@
                 continue
             yield self.trans_id_tree_path(childpath)
 
-
     def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
         """Apply all changes to the inventory and filesystem.
 
@@ -1370,9 +1411,7 @@
         :param _mover: Supply an alternate FileMover, for testing
         """
         if not no_conflicts:
-            conflicts = self.find_conflicts()
-            if len(conflicts) != 0:
-                raise MalformedTransform(conflicts=conflicts)
+            self._check_malformed()
         child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
         try:
             if precomputed_delta is None:

=== modified file 'bzrlib/ui/__init__.py'
--- a/bzrlib/ui/__init__.py	2009-06-30 05:34:47 +0000
+++ b/bzrlib/ui/__init__.py	2009-07-02 09:09:35 +0000
@@ -43,6 +43,42 @@
 """)
 
 
+_valid_boolean_strings = dict(yes=True, no=False,
+                              y=True, n=False,
+                              on=True, off=False,
+                              true=True, false=False)
+_valid_boolean_strings['1'] = True
+_valid_boolean_strings['0'] = False
+
+
+def bool_from_string(s, accepted_values=None):
+    """Returns a boolean if the string can be interpreted as such.
+
+    Interpret case insensitive strings as booleans. The default values
+    includes: 'yes', 'no, 'y', 'n', 'true', 'false', '0', '1', 'on',
+    'off'. Alternative values can be provided with the 'accepted_values'
+    parameter.
+
+    :param s: A string that should be interpreted as a boolean. It should be of
+        type string or unicode.
+
+    :param accepted_values: An optional dict with accepted strings as keys and
+        True/False as values. The strings will be tested against a lowered
+        version of 's'.
+
+    :return: True or False for accepted strings, None otherwise.
+    """
+    if accepted_values is None:
+        accepted_values = _valid_boolean_strings
+    val = None
+    if type(s) in (str, unicode):
+        try:
+            val = accepted_values[s.lower()]
+        except KeyError:
+            pass
+    return val
+
+
 class UIFactory(object):
     """UI abstraction.
 
@@ -156,15 +192,16 @@
         self.stdout = stdout or sys.stdout
         self.stderr = stderr or sys.stderr
 
+    _accepted_boolean_strings = dict(y=True, n=False, yes=True, no=False)
+
     def get_boolean(self, prompt):
         while True:
             self.prompt(prompt + "? [y/n]: ")
             line = self.stdin.readline()
-            line = line.lower()
-            if line in ('y\n', 'yes\n'):
-                return True
-            if line in ('n\n', 'no\n'):
-                return False
+            line = line.rstrip('\n')
+            val = bool_from_string(line, self._accepted_boolean_strings)
+            if val is not None:
+                return val
 
     def get_non_echoed_password(self):
         isatty = getattr(self.stdin, 'isatty', None)

=== modified file 'bzrlib/workingtree_4.py'
--- a/bzrlib/workingtree_4.py	2009-07-06 07:51:29 +0000
+++ b/bzrlib/workingtree_4.py	2009-07-15 04:33:14 +0000
@@ -1195,13 +1195,16 @@
                 # just forget the whole block.
                 entry_index = 0
                 while entry_index < len(block[1]):
-                    # Mark this file id as having been removed
                     entry = block[1][entry_index]
-                    ids_to_unversion.discard(entry[0][2])
-                    if (entry[1][0][0] in 'ar' # don't remove absent or renamed
-                                               # entries
-                        or not state._make_absent(entry)):
+                    if entry[1][0][0] in 'ar':
+                        # don't remove absent or renamed entries
                         entry_index += 1
+                    else:
+                        # Mark this file id as having been removed
+                        ids_to_unversion.discard(entry[0][2])
+                        if not state._make_absent(entry):
+                            # The block has not shrunk.
+                            entry_index += 1
                 # go to the next block. (At the moment we dont delete empty
                 # dirblocks)
                 block_index += 1




More information about the bazaar-commits mailing list