Rev 4795: (abentley) add support for shelving with an editor. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu Nov 12 08:40:23 GMT 2009


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

------------------------------------------------------------
revno: 4795 [merge]
revision-id: pqm at pqm.ubuntu.com-20091112084021-z1abucfx1bwampnq
parent: pqm at pqm.ubuntu.com-20091112003735-0e7h1y9j2fo0kbnv
parent: aaron at aaronbentley.com-20091112075013-p9qy6y50so3x6bxk
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2009-11-12 08:40:21 +0000
message:
  (abentley) add support for shelving with an editor.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/config.py               config.py-20051011043216-070c74f4e9e338e8
  bzrlib/diff.py                 diff.py-20050309040759-26944fbbf2ebbf36
  bzrlib/shelf_ui.py             shelver.py-20081005210102-33worgzwrtdw0yrm-1
  bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
  bzrlib/tests/test_diff.py      testdiff.py-20050727164403-d1a3496ebb12e339
  bzrlib/tests/test_shelf_ui.py  test_shelf_ui.py-20081027155203-wtcuazg85wp9u4fv-1
=== modified file 'NEWS'
--- a/NEWS	2009-11-11 06:50:40 +0000
+++ b/NEWS	2009-11-12 07:50:13 +0000
@@ -18,6 +18,9 @@
 New Features
 ************
 
+* Users can define a shelve editor to provide shelf functionality at a
+  granularity finer than per-patch-hunk. (Aaron Bentley)
+
 Bug Fixes
 *********
 

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2009-11-04 22:32:13 +0000
+++ b/bzrlib/builtins.py	2009-11-12 07:47:19 +0000
@@ -5671,7 +5671,7 @@
             try:
                 shelver.run()
             finally:
-                shelver.work_tree.unlock()
+                shelver.finalize()
         except errors.UserAbort:
             return 0
 

=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2009-08-20 04:53:23 +0000
+++ b/bzrlib/config.py	2009-10-31 01:43:48 +0000
@@ -153,6 +153,15 @@
         """Get the users pop up editor."""
         raise NotImplementedError
 
+    def get_change_editor(self, old_tree, new_tree):
+        from bzrlib import diff
+        cmd = self._get_change_editor()
+        if cmd is None:
+            return None
+        return diff.DiffFromTool.from_string(cmd, old_tree, new_tree,
+                                             sys.stdout)
+
+
     def get_mail_client(self):
         """Get a mail client to use"""
         selected_client = self.get_user_option('mail_client')
@@ -346,6 +355,9 @@
         """Return the policy for the given (section, option_name) pair."""
         return POLICY_NONE
 
+    def _get_change_editor(self):
+        return self.get_user_option('change_editor')
+
     def _get_signature_checking(self):
         """See Config._get_signature_checking."""
         policy = self._get_user_option('check_signatures')
@@ -679,6 +691,9 @@
 
         return self._get_best_value('_get_user_id')
 
+    def _get_change_editor(self):
+        return self._get_best_value('_get_change_editor')
+
     def _get_signature_checking(self):
         """See Config._get_signature_checking."""
         return self._get_best_value('_get_signature_checking')

=== modified file 'bzrlib/diff.py'
--- a/bzrlib/diff.py	2009-10-13 13:05:16 +0000
+++ b/bzrlib/diff.py	2009-11-03 01:15:09 +0000
@@ -18,6 +18,7 @@
 import os
 import re
 import shutil
+import string
 import sys
 
 from bzrlib.lazy_import import lazy_import
@@ -46,6 +47,12 @@
 from bzrlib.trace import mutter, note, warning
 
 
+class AtTemplate(string.Template):
+    """Templating class that uses @ instead of $."""
+
+    delimiter = '@'
+
+
 # TODO: Rather than building a changeset object, we should probably
 # invoke callbacks on an object.  That object can either accumulate a
 # list, write them out directly, etc etc.
@@ -672,7 +679,8 @@
     def from_string(klass, command_string, old_tree, new_tree, to_file,
                     path_encoding='utf-8'):
         command_template = commands.shlex_split_unicode(command_string)
-        command_template.extend(['%(old_path)s', '%(new_path)s'])
+        if '@' not in command_string:
+            command_template.extend(['@old_path', '@new_path'])
         return klass(command_template, old_tree, new_tree, to_file,
                      path_encoding)
 
@@ -685,7 +693,8 @@
 
     def _get_command(self, old_path, new_path):
         my_map = {'old_path': old_path, 'new_path': new_path}
-        return [t % my_map for t in self.command_template]
+        return [AtTemplate(t).substitute(my_map) for t in
+                self.command_template]
 
     def _execute(self, old_path, new_path):
         command = self._get_command(old_path, new_path)
@@ -711,9 +720,10 @@
                 raise
         return True
 
-    def _write_file(self, file_id, tree, prefix, relpath):
+    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
+                    allow_write=False):
         full_path = osutils.pathjoin(self._root, prefix, relpath)
-        if self._try_symlink_root(tree, prefix):
+        if not force_temp and self._try_symlink_root(tree, prefix):
             return full_path
         parent_dir = osutils.dirname(full_path)
         try:
@@ -730,16 +740,19 @@
                 target.close()
         finally:
             source.close()
-        osutils.make_readonly(full_path)
+        if not allow_write:
+            osutils.make_readonly(full_path)
         mtime = tree.get_file_mtime(file_id)
         os.utime(full_path, (mtime, mtime))
         return full_path
 
-    def _prepare_files(self, file_id, old_path, new_path):
+    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
+                       allow_write_new=False):
         old_disk_path = self._write_file(file_id, self.old_tree, 'old',
-                                         old_path)
+                                         old_path, force_temp)
         new_disk_path = self._write_file(file_id, self.new_tree, 'new',
-                                         new_path)
+                                         new_path, force_temp,
+                                         allow_write=allow_write_new)
         return old_disk_path, new_disk_path
 
     def finish(self):
@@ -757,6 +770,29 @@
         self._execute(osutils.pathjoin('old', old_path),
                       osutils.pathjoin('new', new_path))
 
+    def edit_file(self, file_id):
+        """Use this tool to edit a file.
+
+        A temporary copy will be edited, and the new contents will be
+        returned.
+
+        :param file_id: The id of the file to edit.
+        :return: The new contents of the file.
+        """
+        old_path = self.old_tree.id2path(file_id)
+        new_path = self.new_tree.id2path(file_id)
+        new_abs_path = self._prepare_files(file_id, old_path, new_path,
+                                           allow_write_new=True,
+                                           force_temp=True)[1]
+        command = self._get_command(osutils.pathjoin('old', old_path),
+                                    osutils.pathjoin('new', new_path))
+        subprocess.call(command, cwd=self._root)
+        new_file = open(new_abs_path, 'r')
+        try:
+            return new_file.read()
+        finally:
+            new_file.close()
+
 
 class DiffTree(object):
     """Provides textual representations of the difference between two trees.

=== modified file 'bzrlib/shelf_ui.py'
--- a/bzrlib/shelf_ui.py	2009-09-11 07:55:48 +0000
+++ b/bzrlib/shelf_ui.py	2009-11-02 21:56:42 +0000
@@ -22,11 +22,13 @@
 
 from bzrlib import (
     builtins,
+    commands,
     delta,
     diff,
     errors,
     osutils,
     patches,
+    patiencediff,
     shelf,
     textfile,
     trace,
@@ -35,6 +37,10 @@
 )
 
 
+class UseEditor(Exception):
+    """Use an editor instead of selecting hunks."""
+
+
 class ShelfReporter(object):
 
     vocab = {'add file': 'Shelve adding file "%(path)s"?',
@@ -143,6 +149,9 @@
         if reporter is None:
             reporter = ShelfReporter()
         self.reporter = reporter
+        config = self.work_tree.branch.get_config()
+        self.change_editor = config.get_change_editor(target_tree, work_tree)
+        self.work_tree.lock_tree_write()
 
     @classmethod
     def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
@@ -168,11 +177,10 @@
             target_tree = builtins._get_one_revision_tree('shelf2', revision,
                 tree.branch, tree)
             files = builtins.safe_relpath_files(tree, file_list)
-        except:
+            return klass(tree, target_tree, diff_writer, all, all, files,
+                         message, destroy)
+        finally:
             tree.unlock()
-            raise
-        return klass(tree, target_tree, diff_writer, all, all, files, message,
-                     destroy)
 
     def run(self):
         """Interactively shelve the changes."""
@@ -211,6 +219,12 @@
             shutil.rmtree(self.tempdir)
             creator.finalize()
 
+    def finalize(self):
+        if self.change_editor is not None:
+            self.change_editor.finish()
+        self.work_tree.unlock()
+
+
     def get_parsed_patch(self, file_id, invert=False):
         """Return a parsed version of a file's patch.
 
@@ -245,7 +259,7 @@
         sys.stdout.flush()
         return char
 
-    def prompt_bool(self, question, long=False):
+    def prompt_bool(self, question, long=False, allow_editor=False):
         """Prompt the user with a yes/no question.
 
         This may be overridden by self.auto.  It may also *set* self.auto.  It
@@ -255,13 +269,20 @@
         """
         if self.auto:
             return True
+        editor_string = ''
         if long:
-            prompt = ' [(y)es, (N)o, (f)inish, or (q)uit]'
+            if allow_editor:
+                editor_string = '(E)dit manually, '
+            prompt = ' [(y)es, (N)o, %s(f)inish, or (q)uit]' % editor_string
         else:
-            prompt = ' [yNfq?]'
+            if allow_editor:
+                editor_string = 'e'
+            prompt = ' [yN%sfq?]' % editor_string
         char = self.prompt(question + prompt)
         if char == 'y':
             return True
+        elif char == 'e' and allow_editor:
+            raise UseEditor
         elif char == 'f':
             self.auto = True
             return True
@@ -273,6 +294,23 @@
             return False
 
     def handle_modify_text(self, creator, file_id):
+        """Handle modified text, by using hunk selection or file editing.
+
+        :param creator: A ShelfCreator.
+        :param file_id: The id of the file that was modified.
+        :return: The number of changes.
+        """
+        work_tree_lines = self.work_tree.get_file_lines(file_id)
+        try:
+            lines, change_count = self._select_hunks(creator, file_id,
+                                                     work_tree_lines)
+        except UseEditor:
+            lines, change_count = self._edit_file(file_id, work_tree_lines)
+        if change_count != 0:
+            creator.shelve_lines(file_id, lines)
+        return change_count
+
+    def _select_hunks(self, creator, file_id, work_tree_lines):
         """Provide diff hunk selection for modified text.
 
         If self.reporter.invert_diff is True, the diff is inverted so that
@@ -280,13 +318,14 @@
 
         :param creator: a ShelfCreator
         :param file_id: The id of the file to shelve.
+        :param work_tree_lines: Line contents of the file in the working tree.
         :return: number of shelved hunks.
         """
         if self.reporter.invert_diff:
-            target_lines = self.work_tree.get_file_lines(file_id)
+            target_lines = work_tree_lines
         else:
             target_lines = self.target_tree.get_file_lines(file_id)
-        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
+        textfile.check_text_lines(work_tree_lines)
         textfile.check_text_lines(target_lines)
         parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
         final_hunks = []
@@ -295,7 +334,9 @@
             self.diff_writer.write(parsed.get_header())
             for hunk in parsed.hunks:
                 self.diff_writer.write(str(hunk))
-                selected = self.prompt_bool(self.reporter.vocab['hunk'])
+                selected = self.prompt_bool(self.reporter.vocab['hunk'],
+                                            allow_editor=(self.change_editor
+                                                          is not None))
                 if not self.reporter.invert_diff:
                     selected = (not selected)
                 if selected:
@@ -304,16 +345,32 @@
                 else:
                     offset -= (hunk.mod_range - hunk.orig_range)
         sys.stdout.flush()
-        if not self.reporter.invert_diff and (
-            len(parsed.hunks) == len(final_hunks)):
-            return 0
-        if self.reporter.invert_diff and len(final_hunks) == 0:
-            return 0
-        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
-        creator.shelve_lines(file_id, list(patched))
         if self.reporter.invert_diff:
-            return len(final_hunks)
-        return len(parsed.hunks) - len(final_hunks)
+            change_count = len(final_hunks)
+        else:
+            change_count = len(parsed.hunks) - len(final_hunks)
+        patched = patches.iter_patched_from_hunks(target_lines,
+                                                  final_hunks)
+        lines = list(patched)
+        return lines, change_count
+
+    def _edit_file(self, file_id, work_tree_lines):
+        """
+        :param file_id: id of the file to edit.
+        :param work_tree_lines: Line contents of the file in the working tree.
+        :return: (lines, change_region_count), where lines is the new line
+            content of the file, and change_region_count is the number of
+            changed regions.
+        """
+        lines = osutils.split_lines(self.change_editor.edit_file(file_id))
+        return lines, self._count_changed_regions(work_tree_lines, lines)
+
+    @staticmethod
+    def _count_changed_regions(old_lines, new_lines):
+        matcher = patiencediff.PatienceSequenceMatcher(None, old_lines,
+                                                       new_lines)
+        blocks = matcher.get_matching_blocks()
+        return len(blocks) - 2
 
 
 class Unshelver(object):

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2009-08-20 04:53:23 +0000
+++ b/bzrlib/tests/test_config.py	2009-10-31 01:43:48 +0000
@@ -25,6 +25,7 @@
     branch,
     bzrdir,
     config,
+    diff,
     errors,
     osutils,
     mail_client,
@@ -42,6 +43,7 @@
 [DEFAULT]
 email=Erik B\u00e5gfors <erik at bagfors.nu>
 editor=vim
+change_editor=vimdiff -of @new_path @old_path
 gpg_signing_command=gnome-gpg
 log_format=short
 user_global_option=something
@@ -208,6 +210,10 @@
         self._calls.append('_get_signature_checking')
         return self._signatures
 
+    def _get_change_editor(self):
+        self._calls.append('_get_change_editor')
+        return 'vimdiff -fo @new_path @old_path'
+
 
 bool_config = """[DEFAULT]
 active = true
@@ -314,6 +320,14 @@
         my_config = config.Config()
         self.assertEqual('long', my_config.log_format())
 
+    def test_get_change_editor(self):
+        my_config = InstrumentedConfig()
+        change_editor = my_config.get_change_editor('old_tree', 'new_tree')
+        self.assertEqual(['_get_change_editor'], my_config._calls)
+        self.assertIs(diff.DiffFromTool, change_editor.__class__)
+        self.assertEqual(['vimdiff', '-fo', '@new_path', '@old_path'],
+                         change_editor.command_template)
+
 
 class TestConfigPath(tests.TestCase):
 
@@ -625,6 +639,18 @@
         my_config = self._get_sample_config()
         self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
 
+    def test_get_change_editor(self):
+        my_config = self._get_sample_config()
+        change_editor = my_config.get_change_editor('old', 'new')
+        self.assertIs(diff.DiffFromTool, change_editor.__class__)
+        self.assertEqual('vimdiff -of @new_path @old_path',
+                         ' '.join(change_editor.command_template))
+
+    def test_get_no_change_editor(self):
+        my_config = self._get_empty_config()
+        change_editor = my_config.get_change_editor('old', 'new')
+        self.assertIs(None, change_editor)
+
 
 class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
 

=== modified file 'bzrlib/tests/test_diff.py'
--- a/bzrlib/tests/test_diff.py	2009-10-08 16:32:43 +0000
+++ b/bzrlib/tests/test_diff.py	2009-10-31 01:16:23 +0000
@@ -1299,13 +1299,13 @@
     def test_from_string(self):
         diff_obj = DiffFromTool.from_string('diff', None, None, None)
         self.addCleanup(diff_obj.finish)
-        self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
+        self.assertEqual(['diff', '@old_path', '@new_path'],
             diff_obj.command_template)
 
     def test_from_string_u5(self):
         diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
         self.addCleanup(diff_obj.finish)
-        self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
+        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
                          diff_obj.command_template)
         self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
                          diff_obj._get_command('old-path', 'new-path'))
@@ -1313,7 +1313,7 @@
     def test_execute(self):
         output = StringIO()
         diff_obj = DiffFromTool(['python', '-c',
-                                 'print "%(old_path)s %(new_path)s"'],
+                                 'print "@old_path @new_path"'],
                                 None, None, output)
         self.addCleanup(diff_obj.finish)
         diff_obj._execute('old', 'new')
@@ -1338,7 +1338,7 @@
         tree.lock_read()
         self.addCleanup(tree.unlock)
         diff_obj = DiffFromTool(['python', '-c',
-                                 'print "%(old_path)s %(new_path)s"'],
+                                 'print "@old_path @new_path"'],
                                 tree, tree, output)
         diff_obj._prepare_files('file-id', 'file', 'file')
         self.assertReadableByAttrib(diff_obj._root, 'old\\file', r'old\\file')
@@ -1370,7 +1370,7 @@
         tree.lock_read()
         self.addCleanup(tree.unlock)
         diff_obj = DiffFromTool(['python', '-c',
-                                 'print "%(old_path)s %(new_path)s"'],
+                                 'print "@old_path @new_path"'],
                                 old_tree, tree, output)
         self.addCleanup(diff_obj.finish)
         self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')

=== modified file 'bzrlib/tests/test_shelf_ui.py'
--- a/bzrlib/tests/test_shelf_ui.py	2009-09-11 07:55:48 +0000
+++ b/bzrlib/tests/test_shelf_ui.py	2009-10-23 03:35:32 +0000
@@ -76,6 +76,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         e = self.assertRaises(AssertionError, shelver.run)
         self.assertEqual('Unexpected prompt: Shelve? [yNfq?]', str(e))
 
@@ -84,6 +85,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('foo', 'y')
         e = self.assertRaises(AssertionError, shelver.run)
         self.assertEqual('Wrong prompt: Shelve? [yNfq?]', str(e))
@@ -93,6 +95,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve? [yNfq?]', 'n')
         shelver.expect('Shelve? [yNfq?]', 'n')
         # No final shelving prompt because no changes were selected
@@ -104,6 +107,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve? [yNfq?]', 'y')
         shelver.expect('Shelve? [yNfq?]', 'y')
         shelver.expect('Shelve 2 change(s)? [yNfq?]', 'n')
@@ -115,6 +119,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve? [yNfq?]', 'y')
         shelver.expect('Shelve? [yNfq?]', 'y')
         shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')
@@ -126,6 +131,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve? [yNfq?]', 'y')
         shelver.expect('Shelve? [yNfq?]', 'n')
         shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
@@ -138,6 +144,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve binary changes? [yNfq?]', 'y')
         shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -149,6 +156,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve renaming "foo" => "bar"? [yNfq?]', 'y')
         shelver.expect('Shelve? [yNfq?]', 'y')
         shelver.expect('Shelve? [yNfq?]', 'y')
@@ -162,6 +170,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve removing file "foo"? [yNfq?]', 'y')
         shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -175,6 +184,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve adding file "foo"? [yNfq?]', 'y')
         shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -187,6 +197,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve changing "foo" from file to directory? [yNfq?]',
                        'y')
         shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
@@ -202,6 +213,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve changing target of "baz" from "bar" to '
                 '"vax"? [yNfq?]', 'y')
         shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
@@ -213,6 +225,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve? [yNfq?]', 'f')
         shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -223,6 +236,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve? [yNfq?]', 'q')
         self.assertRaises(errors.UserAbort, shelver.run)
         self.assertFileEqual(LINES_ZY, 'tree/foo')
@@ -234,7 +248,7 @@
         try:
             shelver.run()
         finally:
-            shelver.work_tree.unlock()
+            shelver.finalize()
         self.assertFileEqual(LINES_AJ, 'tree/foo')
 
     def test_shelve_filename(self):
@@ -244,6 +258,7 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(), file_list=['bar'])
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve adding file "bar"? [yNfq?]', 'y')
         shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -253,19 +268,18 @@
         tree.lock_tree_write()
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Shelve? [yNfq?]', '?')
         shelver.expect('Shelve? [(y)es, (N)o, (f)inish, or (q)uit]', 'f')
         shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')
         shelver.run()
 
-    def test_shelve_distroy(self):
+    def test_shelve_destroy(self):
         tree = self.create_shelvable_tree()
         shelver = shelf_ui.Shelver.from_args(sys.stdout, all=True,
                                              directory='tree', destroy=True)
-        try:
-            shelver.run()
-        finally:
-            shelver.work_tree.unlock()
+        self.addCleanup(shelver.finalize)
+        shelver.run()
         self.assertIs(None, tree.get_shelf_manager().last_shelf())
         self.assertFileEqual(LINES_AJ, 'tree/foo')
 
@@ -276,7 +290,10 @@
             target = tree.branch.repository.revision_tree(target_revision_id)
             shelver = shelf_ui.Shelver(tree, target, auto=True,
                                        auto_apply=True)
-            shelver.run()
+            try:
+                shelver.run()
+            finally:
+                shelver.finalize()
         finally:
             tree.unlock()
 
@@ -316,6 +333,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Apply change? [yNfq?]', 'n')
         shelver.expect('Apply change? [yNfq?]', 'n')
         # No final shelving prompt because no changes were selected
@@ -328,6 +346,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Apply change? [yNfq?]', 'y')
         shelver.expect('Apply change? [yNfq?]', 'y')
         shelver.expect('Apply 2 change(s)? [yNfq?]', 'n')
@@ -340,6 +359,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Apply change? [yNfq?]', 'y')
         shelver.expect('Apply change? [yNfq?]', 'y')
         shelver.expect('Apply 2 change(s)? [yNfq?]', 'y')
@@ -353,6 +373,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Apply binary changes? [yNfq?]', 'y')
         shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -365,6 +386,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Rename "bar" => "foo"? [yNfq?]', 'y')
         shelver.expect('Apply change? [yNfq?]', 'y')
         shelver.expect('Apply change? [yNfq?]', 'y')
@@ -379,6 +401,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Add file "foo"? [yNfq?]', 'y')
         shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -393,6 +416,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Delete file "foo"? [yNfq?]', 'y')
         shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
         shelver.run()
@@ -406,6 +430,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Change "foo" from directory to a file? [yNfq?]', 'y')
         shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
 
@@ -421,6 +446,7 @@
         self.addCleanup(tree.unlock)
         shelver = ExpectShelver(tree, tree.basis_tree(),
                                 reporter=shelf_ui.ApplyReporter())
+        self.addCleanup(shelver.finalize)
         shelver.expect('Change target of "baz" from "vax" to "bar"? [yNfq?]',
                        'y')
         shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
@@ -438,8 +464,12 @@
             tree.add('foo', 'foo-id')
             tree.commit('added foo')
             self.build_tree_contents([('tree/foo', LINES_ZY)])
-            shelf_ui.Shelver(tree, tree.basis_tree(), auto_apply=True,
-                             auto=True).run()
+            shelver = shelf_ui.Shelver(tree, tree.basis_tree(),
+                                       auto_apply=True, auto=True)
+            try:
+                shelver.run()
+            finally:
+                shelver.finalize()
         finally:
             tree.unlock()
         return tree




More information about the bazaar-commits mailing list