Merge lp:~vila/bzr/856261-unshelve-line-based into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 6175
Proposed branch: lp:~vila/bzr/856261-unshelve-line-based
Merge into: lp:bzr
Diff against target: 182 lines (+97/-7)
4 files modified
bzrlib/shelf_ui.py (+26/-4)
bzrlib/tests/script.py (+8/-0)
bzrlib/tests/test_script.py (+56/-3)
doc/en/release-notes/bzr-2.5.txt (+7/-0)
To merge this branch: bzr merge lp:~vila/bzr/856261-unshelve-line-based
Reviewer Review Type Date Requested Status
Martin Packman (community) Needs Fixing
Vincent Ladeuil Needs Information
Review via email: mp+77195@code.launchpad.net

Commit message

Allows bzr shelve to be used in test scripts and under emacs shells

Description of the change

I've been deprived from using shelve under emacs for years.

But when I ran into bug #856261 trying to reproduce bug #850594, I thought
it may be worth to have another go at it.

The fix is simple: since osutils.getchar() requires a true term (which
neither emacs not the test scripts provide), I handle the input as a
line-based one which is exactly what emacs does and also exactly what the
test script interface provides :)

I'd like feedback on how we want to configure this feature, knowing that an
environment variable may be more appropriate here (not everybody use
terminals under emacs only and the test script need a way to force the
behavior). Or should I just consider that the fix for #491196 is around the
corner and name the config variable... shelve.line_based_input ?
shelve.line_ui ? both boolean defaulting to False or shelve.input
(defaulting to 'char' accepting 'line' ?)

To post a comment you must log in.
Revision history for this message
Benoit Pierre (benoit.pierre) wrote :

Excerpts from Vincent Ladeuil's message of Tue Sep 27 16:37:19 UTC 2011:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/856261-unshelve-line-based into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> For more details, see:
> https://code.launchpad.net/~vila/bzr/856261-unshelve-line-based/+merge/77195
>
> I've been deprived from using shelve under emacs for years.
>
> But when I ran into bug #856261 trying to reproduce bug #850594, I thought
> it may be worth to have another go at it.
>
> The fix is simple: since osutils.getchar() requires a true term (which
> neither emacs not the test scripts provide), I handle the input as a
> line-based one which is exactly what emacs does and also exactly what the
> test script interface provides :)
>
> I'd like feedback on how we want to configure this feature, knowing that an
> environment variable may be more appropriate here (not everybody use
> terminals under emacs only and the test script need a way to force the
> behavior). Or should I just consider that the fix for #491196 is around the
> corner and name the config variable... shelve.line_based_input ?
> shelve.line_ui ? both boolean defaulting to False or shelve.input
> (defaulting to 'char' accepting 'line' ?)

I also ran into problems using shelve with my VIM plugin. Other commands
use ui.get_boolean, but there is no corresponding method for handling
more complex choices. So even after providing my own UI class
implementation, I cannot use shelve.

In VIM there is a confirm function:

confirm({msg} [, {choices} [, {default} [, {type}]]])

Which can be used like this:

confirm("Save changes?", "&Yes\n&No\n&Cancel")

Where the letter after the '&' is the shortcut key for that choice.

So I have been working on a branch to implement something just like
this (see attached patch).

N.B.: your patch removes a call to gettext.
--
A: Because it destroys the flow of conversation.
Q: Why is top posting dumb?

# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: benoit.pierre@gmail.com-20110927192241-jfqqjzm2e6ojjfq0
# target_branch: lp:bzr
# testament_sha1: b3590b8caa798f8691f0e18ee303b9dbd983fd09
# timestamp: 2011-09-27 21:23:43 +0200
# base_revision_id: pqm@pqm.ubuntu.com-20110610161957-hh5ni839m7r3wsan
#
# Begin patch
=== modified file 'bzrlib/shelf_ui.py'
--- bzrlib/shelf_ui.py 2010-11-04 17:48:47 +0000
+++ bzrlib/shelf_ui.py 2011-09-27 19:22:41 +0000
@@ -251,23 +251,8 @@
251 diff_file.seek(0)251 diff_file.seek(0)
252 return patches.parse_patch(diff_file)252 return patches.parse_patch(diff_file)
253253
254 def prompt(self, message):254 def prompt(self, message, choices, default):
255 """Prompt the user for a character.255 return ui.ui_factory.confirm(message, choices, default=default)
256
257 :param message: The message to prompt a user with.
258 :return: A character.
259 """
260 if not sys.stdin.isatty():
261 # Since there is no controlling terminal we will hang when trying
262 # to prompt the user, better abort now. See
263 # https://code.launchpad.net/~bialix/bzr/shelve-no-tty/+merge/14905
264 # for more context.
265 raise errors.BzrError("You need a controlling terminal.")
266 sys.stdout.write(message)
267 char = osutils.getchar()
268 sys.stdout.write("\r" + ' ' * len(message) + '\r')
269 sys.stdout.flush()
270 return char
271256
272 def prompt_bool(self, question, long=False, allow_editor=False):257 def prompt_bool(self, question, long=False, allow_editor=False):
273 """Prompt the user with a yes/no question.258 """Prompt the user with a yes/no question.
@@ -279,16 +264,11 @@
279 """264 """
280 if self.auto:265 if self.auto:
281 return True266 return True
282 editor_string = ''267 choices = '&yes\n&No'
283 if long:268 if allow_editor:
284 if allow_editor:269 choices += '\n&edit manually'
285 editor_string = '(E)dit manually, '270 choices += '\n&finish\n&quit'
286 prompt = ' [(y)es, (N)o, %s(f)inish, or (q)uit]' % editor_string271 char = self.prompt(question, choices, 'n')
287 else:
288 if allow_editor:
289 editor_string = 'e'
290 prompt = ' [yN%sfq?]' % editor_string
291 char = self.prompt(question + prompt)
292 if char == 'y':272 if char == 'y':
293 return True273 return True
294 elif char == 'e' and allow_editor:274 elif char == 'e' and allow_editor:
295275
=== modified file 'bzrlib/tests/blackbox/test_break_lock.py'
--- bzrlib/tests/blackbox/test_break_lock.py 2011-03-10 13:29:54 +0000
+++ bzrlib/tests/blackbox/test_break_lock.py 2011-09-27 19:22:41 +0000
@@ -91,7 +91,7 @@
91 self.master_branch.lock_write()91 self.master_branch.lock_write()
92 # run the break-lock92 # run the break-lock
93 # we need 5 yes's - wt, branch, repo, bound branch, bound repo.93 # we need 5 yes's - wt, branch, repo, bound branch, bound repo.
94 self.run_bzr('break-lock checkout', stdin="y\ny\ny\ny\n")94 self.run_bzr('break-lock checkout', stdin="yyyy")
95 # a new tree instance should be lockable95 # a new tree instance should be lockable
96 br = branch.Branch.open('checkout')96 br = branch.Branch.open('checkout')
97 br.lock_write()97 br.lock_write()
9898
=== modified file 'bzrlib/tests/blackbox/test_uncommit.py'
--- bzrlib/tests/blackbox/test_uncommit.py 2010-09-15 09:35:42 +0000
+++ bzrlib/tests/blackbox/test_uncommit.py 2011-09-27 19:22:41 +0000
@@ -72,7 +72,7 @@
72 $ bzr uncommit72 $ bzr uncommit
73 ...73 ...
74 The above revision(s) will be removed.74 The above revision(s) will be removed.
75 2>Uncommit these revisions? [y/n]: 75 2>Uncommit these revisions? ([y]es, [n]o): n
76 <n76 <n
77 Canceled77 Canceled
78 """)78 """)
7979
=== modified file 'bzrlib/tests/test_script.py'
--- bzrlib/tests/test_script.py 2011-05-16 13:39:39 +0000
+++ bzrlib/tests/test_script.py 2011-09-27 19:22:41 +0000
@@ -580,12 +580,12 @@
580 self.addCleanup(commands.builtin_command_registry.remove, 'test-confirm')580 self.addCleanup(commands.builtin_command_registry.remove, 'test-confirm')
581 self.run_script("""581 self.run_script("""
582 $ bzr test-confirm582 $ bzr test-confirm
583 2>Really do it? [y/n]: 583 2>Really do it? ([y]es, [n]o): y
584 <yes584 <y
585 Do it!585 Do it!
586 $ bzr test-confirm586 $ bzr test-confirm
587 2>Really do it? [y/n]: 587 2>Really do it? ([y]es, [n]o): n
588 <no588 <n
589 ok, no589 ok, no
590 """)590 """)
591591
592592
=== modified file 'bzrlib/tests/test_shelf_ui.py'
--- bzrlib/tests/test_shelf_ui.py 2011-06-09 14:40:22 +0000
+++ bzrlib/tests/test_shelf_ui.py 2011-09-27 19:22:41 +0000
@@ -41,16 +41,18 @@
41 self.expected = []41 self.expected = []
42 self.diff_writer = StringIO()42 self.diff_writer = StringIO()
4343
44 def expect(self, prompt, response):44 def expect(self, message, choices, response):
45 self.expected.append((prompt, response))45 self.expected.append((message, choices, response))
4646
47 def prompt(self, message):47 def prompt(self, message, choices, default):
48 try:48 try:
49 prompt, response = self.expected.pop(0)49 expected_message, expected_choices, response = self.expected.pop(0)
50 except IndexError:50 except IndexError:
51 raise AssertionError('Unexpected prompt: %s' % message)51 raise AssertionError('Unexpected prompt: %s' % message)
52 if prompt != message:52 if message != expected_message:
53 raise AssertionError('Wrong prompt: %s' % message)53 raise AssertionError('Wrong prompt: %s' % message)
54 if choices != expected_choices:
55 raise AssertionError('Wrong choices: %s' % choices)
54 return response56 return response
5557
5658
@@ -83,7 +85,7 @@
83 shelver = ExpectShelver(tree, tree.basis_tree())85 shelver = ExpectShelver(tree, tree.basis_tree())
84 self.addCleanup(shelver.finalize)86 self.addCleanup(shelver.finalize)
85 e = self.assertRaises(AssertionError, shelver.run)87 e = self.assertRaises(AssertionError, shelver.run)
86 self.assertEqual('Unexpected prompt: Shelve? [yNfq?]', str(e))88 self.assertEqual('Unexpected prompt: Shelve?', str(e))
8789
88 def test_wrong_prompt_failure(self):90 def test_wrong_prompt_failure(self):
89 tree = self.create_shelvable_tree()91 tree = self.create_shelvable_tree()
@@ -91,9 +93,9 @@
91 self.addCleanup(tree.unlock)93 self.addCleanup(tree.unlock)
92 shelver = ExpectShelver(tree, tree.basis_tree())94 shelver = ExpectShelver(tree, tree.basis_tree())
93 self.addCleanup(shelver.finalize)95 self.addCleanup(shelver.finalize)
94 shelver.expect('foo', 'y')96 shelver.expect('foo', 'bar', 'y')
95 e = self.assertRaises(AssertionError, shelver.run)97 e = self.assertRaises(AssertionError, shelver.run)
96 self.assertEqual('Wrong prompt: Shelve? [yNfq?]', str(e))98 self.assertEqual('Wrong prompt: Shelve?', str(e))
9799
98 def test_shelve_not_diff(self):100 def test_shelve_not_diff(self):
99 tree = self.create_shelvable_tree()101 tree = self.create_shelvable_tree()
@@ -101,8 +103,8 @@
101 self.addCleanup(tree.unlock)103 self.addCleanup(tree.unlock)
102 shelver = ExpectShelver(tree, tree.basis_tree())104 shelver = ExpectShelver(tree, tree.basis_tree())
103 self.addCleanup(shelver.finalize)105 self.addCleanup(shelver.finalize)
104 shelver.expect('Shelve? [yNfq?]', 'n')106 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'n')
105 shelver.expect('Shelve? [yNfq?]', 'n')107 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'n')
106 # No final shelving prompt because no changes were selected108 # No final shelving prompt because no changes were selected
107 shelver.run()109 shelver.run()
108 self.assertFileEqual(LINES_ZY, 'tree/foo')110 self.assertFileEqual(LINES_ZY, 'tree/foo')
@@ -113,9 +115,9 @@
113 self.addCleanup(tree.unlock)115 self.addCleanup(tree.unlock)
114 shelver = ExpectShelver(tree, tree.basis_tree())116 shelver = ExpectShelver(tree, tree.basis_tree())
115 self.addCleanup(shelver.finalize)117 self.addCleanup(shelver.finalize)
116 shelver.expect('Shelve? [yNfq?]', 'y')118 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
117 shelver.expect('Shelve? [yNfq?]', 'y')119 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
118 shelver.expect('Shelve 2 change(s)? [yNfq?]', 'n')120 shelver.expect('Shelve 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'n')
119 shelver.run()121 shelver.run()
120 self.assertFileEqual(LINES_ZY, 'tree/foo')122 self.assertFileEqual(LINES_ZY, 'tree/foo')
121123
@@ -125,9 +127,9 @@
125 self.addCleanup(tree.unlock)127 self.addCleanup(tree.unlock)
126 shelver = ExpectShelver(tree, tree.basis_tree())128 shelver = ExpectShelver(tree, tree.basis_tree())
127 self.addCleanup(shelver.finalize)129 self.addCleanup(shelver.finalize)
128 shelver.expect('Shelve? [yNfq?]', 'y')130 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
129 shelver.expect('Shelve? [yNfq?]', 'y')131 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
130 shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')132 shelver.expect('Shelve 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
131 shelver.run()133 shelver.run()
132 self.assertFileEqual(LINES_AJ, 'tree/foo')134 self.assertFileEqual(LINES_AJ, 'tree/foo')
133135
@@ -137,9 +139,9 @@
137 self.addCleanup(tree.unlock)139 self.addCleanup(tree.unlock)
138 shelver = ExpectShelver(tree, tree.basis_tree())140 shelver = ExpectShelver(tree, tree.basis_tree())
139 self.addCleanup(shelver.finalize)141 self.addCleanup(shelver.finalize)
140 shelver.expect('Shelve? [yNfq?]', 'y')142 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
141 shelver.expect('Shelve? [yNfq?]', 'n')143 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'n')
142 shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')144 shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
143 shelver.run()145 shelver.run()
144 self.assertFileEqual(LINES_AY, 'tree/foo')146 self.assertFileEqual(LINES_AY, 'tree/foo')
145147
@@ -150,8 +152,8 @@
150 self.addCleanup(tree.unlock)152 self.addCleanup(tree.unlock)
151 shelver = ExpectShelver(tree, tree.basis_tree())153 shelver = ExpectShelver(tree, tree.basis_tree())
152 self.addCleanup(shelver.finalize)154 self.addCleanup(shelver.finalize)
153 shelver.expect('Shelve binary changes? [yNfq?]', 'y')155 shelver.expect('Shelve binary changes?', '&yes\n&No\n&finish\n&quit', 'y')
154 shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')156 shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
155 shelver.run()157 shelver.run()
156 self.assertFileEqual(LINES_AJ, 'tree/foo')158 self.assertFileEqual(LINES_AJ, 'tree/foo')
157159
@@ -162,10 +164,10 @@
162 self.addCleanup(tree.unlock)164 self.addCleanup(tree.unlock)
163 shelver = ExpectShelver(tree, tree.basis_tree())165 shelver = ExpectShelver(tree, tree.basis_tree())
164 self.addCleanup(shelver.finalize)166 self.addCleanup(shelver.finalize)
165 shelver.expect('Shelve renaming "foo" => "bar"? [yNfq?]', 'y')167 shelver.expect('Shelve renaming "foo" => "bar"?', '&yes\n&No\n&finish\n&quit', 'y')
166 shelver.expect('Shelve? [yNfq?]', 'y')168 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
167 shelver.expect('Shelve? [yNfq?]', 'y')169 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
168 shelver.expect('Shelve 3 change(s)? [yNfq?]', 'y')170 shelver.expect('Shelve 3 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
169 shelver.run()171 shelver.run()
170 self.assertFileEqual(LINES_AJ, 'tree/foo')172 self.assertFileEqual(LINES_AJ, 'tree/foo')
171173
@@ -176,8 +178,8 @@
176 self.addCleanup(tree.unlock)178 self.addCleanup(tree.unlock)
177 shelver = ExpectShelver(tree, tree.basis_tree())179 shelver = ExpectShelver(tree, tree.basis_tree())
178 self.addCleanup(shelver.finalize)180 self.addCleanup(shelver.finalize)
179 shelver.expect('Shelve removing file "foo"? [yNfq?]', 'y')181 shelver.expect('Shelve removing file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
180 shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')182 shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
181 shelver.run()183 shelver.run()
182 self.assertFileEqual(LINES_AJ, 'tree/foo')184 self.assertFileEqual(LINES_AJ, 'tree/foo')
183185
@@ -190,8 +192,8 @@
190 self.addCleanup(tree.unlock)192 self.addCleanup(tree.unlock)
191 shelver = ExpectShelver(tree, tree.basis_tree())193 shelver = ExpectShelver(tree, tree.basis_tree())
192 self.addCleanup(shelver.finalize)194 self.addCleanup(shelver.finalize)
193 shelver.expect('Shelve adding file "foo"? [yNfq?]', 'y')195 shelver.expect('Shelve adding file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
194 shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')196 shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
195 shelver.run()197 shelver.run()
196 self.assertPathDoesNotExist('tree/foo')198 self.assertPathDoesNotExist('tree/foo')
197199
@@ -203,9 +205,9 @@
203 self.addCleanup(tree.unlock)205 self.addCleanup(tree.unlock)
204 shelver = ExpectShelver(tree, tree.basis_tree())206 shelver = ExpectShelver(tree, tree.basis_tree())
205 self.addCleanup(shelver.finalize)207 self.addCleanup(shelver.finalize)
206 shelver.expect('Shelve changing "foo" from file to directory? [yNfq?]',208 shelver.expect('Shelve changing "foo" from file to directory?', '&yes\n&No\n&finish\n&quit',
207 'y')209 'y')
208 shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')210 shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
209211
210 def test_shelve_modify_target(self):212 def test_shelve_modify_target(self):
211 self.requireFeature(tests.SymlinkFeature)213 self.requireFeature(tests.SymlinkFeature)
@@ -220,8 +222,8 @@
220 shelver = ExpectShelver(tree, tree.basis_tree())222 shelver = ExpectShelver(tree, tree.basis_tree())
221 self.addCleanup(shelver.finalize)223 self.addCleanup(shelver.finalize)
222 shelver.expect('Shelve changing target of "baz" from "bar" to '224 shelver.expect('Shelve changing target of "baz" from "bar" to '
223 '"vax"? [yNfq?]', 'y')225 '"vax"?', '&yes\n&No\n&finish\n&quit', 'y')
224 shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')226 shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
225 shelver.run()227 shelver.run()
226 self.assertEqual('bar', os.readlink('tree/baz'))228 self.assertEqual('bar', os.readlink('tree/baz'))
227229
@@ -231,8 +233,8 @@
231 self.addCleanup(tree.unlock)233 self.addCleanup(tree.unlock)
232 shelver = ExpectShelver(tree, tree.basis_tree())234 shelver = ExpectShelver(tree, tree.basis_tree())
233 self.addCleanup(shelver.finalize)235 self.addCleanup(shelver.finalize)
234 shelver.expect('Shelve? [yNfq?]', 'f')236 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'f')
235 shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')237 shelver.expect('Shelve 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
236 shelver.run()238 shelver.run()
237 self.assertFileEqual(LINES_AJ, 'tree/foo')239 self.assertFileEqual(LINES_AJ, 'tree/foo')
238240
@@ -242,7 +244,7 @@
242 self.addCleanup(tree.unlock)244 self.addCleanup(tree.unlock)
243 shelver = ExpectShelver(tree, tree.basis_tree())245 shelver = ExpectShelver(tree, tree.basis_tree())
244 self.addCleanup(shelver.finalize)246 self.addCleanup(shelver.finalize)
245 shelver.expect('Shelve? [yNfq?]', 'q')247 shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'q')
246 self.assertRaises(errors.UserAbort, shelver.run)248 self.assertRaises(errors.UserAbort, shelver.run)
247 self.assertFileEqual(LINES_ZY, 'tree/foo')249 self.assertFileEqual(LINES_ZY, 'tree/foo')
248250
@@ -264,19 +266,8 @@
264 self.addCleanup(tree.unlock)266 self.addCleanup(tree.unlock)
265 shelver = ExpectShelver(tree, tree.basis_tree(), file_list=['bar'])267 shelver = ExpectShelver(tree, tree.basis_tree(), file_list=['bar'])
266 self.addCleanup(shelver.finalize)268 self.addCleanup(shelver.finalize)
267 shelver.expect('Shelve adding file "bar"? [yNfq?]', 'y')269 shelver.expect('Shelve adding file "bar"?', '&yes\n&No\n&finish\n&quit', 'y')
268 shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')270 shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
269 shelver.run()
270
271 def test_shelve_help(self):
272 tree = self.create_shelvable_tree()
273 tree.lock_tree_write()
274 self.addCleanup(tree.unlock)
275 shelver = ExpectShelver(tree, tree.basis_tree())
276 self.addCleanup(shelver.finalize)
277 shelver.expect('Shelve? [yNfq?]', '?')
278 shelver.expect('Shelve? [(y)es, (N)o, (f)inish, or (q)uit]', 'f')
279 shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')
280 shelver.run()271 shelver.run()
281272
282 def test_shelve_destroy(self):273 def test_shelve_destroy(self):
@@ -339,8 +330,8 @@
339 shelver = ExpectShelver(tree, tree.basis_tree(),330 shelver = ExpectShelver(tree, tree.basis_tree(),
340 reporter=shelf_ui.ApplyReporter())331 reporter=shelf_ui.ApplyReporter())
341 self.addCleanup(shelver.finalize)332 self.addCleanup(shelver.finalize)
342 shelver.expect('Apply change? [yNfq?]', 'n')333 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'n')
343 shelver.expect('Apply change? [yNfq?]', 'n')334 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'n')
344 # No final shelving prompt because no changes were selected335 # No final shelving prompt because no changes were selected
345 shelver.run()336 shelver.run()
346 self.assertFileEqual(LINES_ZY, 'tree/foo')337 self.assertFileEqual(LINES_ZY, 'tree/foo')
@@ -352,9 +343,9 @@
352 shelver = ExpectShelver(tree, tree.basis_tree(),343 shelver = ExpectShelver(tree, tree.basis_tree(),
353 reporter=shelf_ui.ApplyReporter())344 reporter=shelf_ui.ApplyReporter())
354 self.addCleanup(shelver.finalize)345 self.addCleanup(shelver.finalize)
355 shelver.expect('Apply change? [yNfq?]', 'y')346 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
356 shelver.expect('Apply change? [yNfq?]', 'y')347 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
357 shelver.expect('Apply 2 change(s)? [yNfq?]', 'n')348 shelver.expect('Apply 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'n')
358 shelver.run()349 shelver.run()
359 self.assertFileEqual(LINES_ZY, 'tree/foo')350 self.assertFileEqual(LINES_ZY, 'tree/foo')
360351
@@ -365,9 +356,9 @@
365 shelver = ExpectShelver(tree, tree.basis_tree(),356 shelver = ExpectShelver(tree, tree.basis_tree(),
366 reporter=shelf_ui.ApplyReporter())357 reporter=shelf_ui.ApplyReporter())
367 self.addCleanup(shelver.finalize)358 self.addCleanup(shelver.finalize)
368 shelver.expect('Apply change? [yNfq?]', 'y')359 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
369 shelver.expect('Apply change? [yNfq?]', 'y')360 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
370 shelver.expect('Apply 2 change(s)? [yNfq?]', 'y')361 shelver.expect('Apply 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
371 shelver.run()362 shelver.run()
372 self.assertFileEqual(LINES_AJ, 'tree/foo')363 self.assertFileEqual(LINES_AJ, 'tree/foo')
373364
@@ -379,8 +370,8 @@
379 shelver = ExpectShelver(tree, tree.basis_tree(),370 shelver = ExpectShelver(tree, tree.basis_tree(),
380 reporter=shelf_ui.ApplyReporter())371 reporter=shelf_ui.ApplyReporter())
381 self.addCleanup(shelver.finalize)372 self.addCleanup(shelver.finalize)
382 shelver.expect('Apply binary changes? [yNfq?]', 'y')373 shelver.expect('Apply binary changes?', '&yes\n&No\n&finish\n&quit', 'y')
383 shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')374 shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
384 shelver.run()375 shelver.run()
385 self.assertFileEqual(LINES_AJ, 'tree/foo')376 self.assertFileEqual(LINES_AJ, 'tree/foo')
386377
@@ -392,10 +383,10 @@
392 shelver = ExpectShelver(tree, tree.basis_tree(),383 shelver = ExpectShelver(tree, tree.basis_tree(),
393 reporter=shelf_ui.ApplyReporter())384 reporter=shelf_ui.ApplyReporter())
394 self.addCleanup(shelver.finalize)385 self.addCleanup(shelver.finalize)
395 shelver.expect('Rename "bar" => "foo"? [yNfq?]', 'y')386 shelver.expect('Rename "bar" => "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
396 shelver.expect('Apply change? [yNfq?]', 'y')387 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
397 shelver.expect('Apply change? [yNfq?]', 'y')388 shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
398 shelver.expect('Apply 3 change(s)? [yNfq?]', 'y')389 shelver.expect('Apply 3 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
399 shelver.run()390 shelver.run()
400 self.assertFileEqual(LINES_AJ, 'tree/foo')391 self.assertFileEqual(LINES_AJ, 'tree/foo')
401392
@@ -407,8 +398,8 @@
407 shelver = ExpectShelver(tree, tree.basis_tree(),398 shelver = ExpectShelver(tree, tree.basis_tree(),
408 reporter=shelf_ui.ApplyReporter())399 reporter=shelf_ui.ApplyReporter())
409 self.addCleanup(shelver.finalize)400 self.addCleanup(shelver.finalize)
410 shelver.expect('Add file "foo"? [yNfq?]', 'y')401 shelver.expect('Add file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
411 shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')402 shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
412 shelver.run()403 shelver.run()
413 self.assertFileEqual(LINES_AJ, 'tree/foo')404 self.assertFileEqual(LINES_AJ, 'tree/foo')
414405
@@ -422,8 +413,8 @@
422 shelver = ExpectShelver(tree, tree.basis_tree(),413 shelver = ExpectShelver(tree, tree.basis_tree(),
423 reporter=shelf_ui.ApplyReporter())414 reporter=shelf_ui.ApplyReporter())
424 self.addCleanup(shelver.finalize)415 self.addCleanup(shelver.finalize)
425 shelver.expect('Delete file "foo"? [yNfq?]', 'y')416 shelver.expect('Delete file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
426 shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')417 shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
427 shelver.run()418 shelver.run()
428 self.assertPathDoesNotExist('tree/foo')419 self.assertPathDoesNotExist('tree/foo')
429420
@@ -436,8 +427,8 @@
436 shelver = ExpectShelver(tree, tree.basis_tree(),427 shelver = ExpectShelver(tree, tree.basis_tree(),
437 reporter=shelf_ui.ApplyReporter())428 reporter=shelf_ui.ApplyReporter())
438 self.addCleanup(shelver.finalize)429 self.addCleanup(shelver.finalize)
439 shelver.expect('Change "foo" from directory to a file? [yNfq?]', 'y')430 shelver.expect('Change "foo" from directory to a file?', '&yes\n&No\n&finish\n&quit', 'y')
440 shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')431 shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
441432
442 def test_shelve_modify_target(self):433 def test_shelve_modify_target(self):
443 self.requireFeature(tests.SymlinkFeature)434 self.requireFeature(tests.SymlinkFeature)
@@ -452,9 +443,9 @@
452 shelver = ExpectShelver(tree, tree.basis_tree(),443 shelver = ExpectShelver(tree, tree.basis_tree(),
453 reporter=shelf_ui.ApplyReporter())444 reporter=shelf_ui.ApplyReporter())
454 self.addCleanup(shelver.finalize)445 self.addCleanup(shelver.finalize)
455 shelver.expect('Change target of "baz" from "vax" to "bar"? [yNfq?]',446 shelver.expect('Change target of "baz" from "vax" to "bar"?', '&yes\n&No\n&finish\n&quit',
456 'y')447 'y')
457 shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')448 shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
458 shelver.run()449 shelver.run()
459 self.assertEqual('bar', os.readlink('tree/baz'))450 self.assertEqual('bar', os.readlink('tree/baz'))
460451
461452
=== modified file 'bzrlib/tests/test_ui.py'
--- bzrlib/tests/test_ui.py 2011-05-16 13:39:39 +0000
+++ bzrlib/tests/test_ui.py 2011-09-27 19:22:41 +0000
@@ -109,13 +109,14 @@
109 pb.finished()109 pb.finished()
110110
111 def test_text_ui_get_boolean(self):111 def test_text_ui_get_boolean(self):
112 stdin = tests.StringIOWrapper("y\n" # True112 stdin = tests.StringIOWrapper("y" # True
113 "n\n" # False113 "n" # False
114 "yes with garbage\nY\n" # True114 "Y" # True
115 "not an answer\nno\n" # False115 "N" # False
116 "I'm sure!\nyes\n" # True116 "\r\ngarbagey" # True
117 "NO\n" # False117 "\r\ngarbagen" # False
118 "foo\n")118 "foo\n"
119 )
119 stdout = tests.StringIOWrapper()120 stdout = tests.StringIOWrapper()
120 stderr = tests.StringIOWrapper()121 stderr = tests.StringIOWrapper()
121 factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)122 factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
@@ -154,7 +155,7 @@
154 out = test_progress._TTYStringIO()155 out = test_progress._TTYStringIO()
155 self.overrideEnv('TERM', 'xterm')156 self.overrideEnv('TERM', 'xterm')
156 factory = _mod_ui_text.TextUIFactory(157 factory = _mod_ui_text.TextUIFactory(
157 stdin=tests.StringIOWrapper("yada\ny\n"),158 stdin=tests.StringIOWrapper("bar\ny"),
158 stdout=out, stderr=out)159 stdout=out, stderr=out)
159 factory._avail_width = lambda: 79160 factory._avail_width = lambda: 79
160 pb = factory.nested_progress_bar()161 pb = factory.nested_progress_bar()
@@ -171,7 +172,7 @@
171 self.assertContainsRe(output,172 self.assertContainsRe(output,
172 "| foo *\r\r *\r*")173 "| foo *\r\r *\r*")
173 self.assertContainsRe(output,174 self.assertContainsRe(output,
174 r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")175 r"what do you want\? \(\[y\]es, \[n\]o\): y")
175 # stdin should have been totally consumed176 # stdin should have been totally consumed
176 self.assertEqual('', factory.stdin.readline())177 self.assertEqual('', factory.stdin.readline())
177178
178179
=== modified file 'bzrlib/ui/__init__.py'
--- bzrlib/ui/__init__.py 2011-05-27 05:16:48 +0000
+++ bzrlib/ui/__init__.py 2011-09-27 19:22:41 +0000
@@ -315,6 +315,9 @@
315 warnings.warn(fail) # so tests will fail etc315 warnings.warn(fail) # so tests will fail etc
316 return fail316 return fail
317317
318 def confirm(self, msg, choices, default=None):
319 raise NotImplementedError(self.confirm)
320
318 def get_boolean(self, prompt):321 def get_boolean(self, prompt):
319 """Get a boolean question answered from the user.322 """Get a boolean question answered from the user.
320323
@@ -322,7 +325,8 @@
322 line without terminating \\n.325 line without terminating \\n.
323 :return: True or False for y/yes or n/no.326 :return: True or False for y/yes or n/no.
324 """327 """
325 raise NotImplementedError(self.get_boolean)328 char = self.confirm(prompt + '?', '&yes\n&no', default=None)
329 return 'y' == char
326330
327 def get_integer(self, prompt):331 def get_integer(self, prompt):
328 """Get an integer from the user.332 """Get an integer from the user.
@@ -477,6 +481,9 @@
477 def confirm_action(self, prompt, confirmation_id, args):481 def confirm_action(self, prompt, confirmation_id, args):
478 return self.get_boolean(prompt % args)482 return self.get_boolean(prompt % args)
479483
484 def confirm(self, msg, choices, default=None):
485 return self.responses.pop(0)
486
480 def get_boolean(self, prompt):487 def get_boolean(self, prompt):
481 return self.responses.pop(0)488 return self.responses.pop(0)
482489
483490
=== modified file 'bzrlib/ui/text.py'
--- bzrlib/ui/text.py 2011-05-16 13:39:39 +0000
+++ bzrlib/ui/text.py 2011-09-27 19:22:41 +0000
@@ -30,6 +30,7 @@
3030
31from bzrlib import (31from bzrlib import (
32 debug,32 debug,
33 errors,
33 progress,34 progress,
34 osutils,35 osutils,
35 trace,36 trace,
@@ -61,6 +62,40 @@
61 # paints progress, network activity, etc62 # paints progress, network activity, etc
62 self._progress_view = self.make_progress_view()63 self._progress_view = self.make_progress_view()
6364
65 def confirm(self, msg, choices, default=None):
66 if self.stdin == sys.stdin and self.stdin.isatty():
67 def getchar():
68 return osutils.getchar()
69 else:
70 def getchar():
71 return self.stdin.read(1)
72
73 shortcuts = ''
74 alternatives = []
75 for c in choices.split('\n'):
76 shortcut = c.find('&')
77 if -1 != shortcut and (shortcut + 1) < len(c):
78 help = c[:shortcut]
79 help += '[' + c[shortcut + 1] + ']'
80 help += c[(shortcut + 2):]
81 shortcut = c[shortcut + 1]
82 else:
83 help = c.strip('&')
84 shortcut = c[0]
85 shortcuts += shortcut.lower()
86 alternatives.append(help)
87
88 prompt = u'%s (%s): ' % (msg, ', '.join(alternatives))
89 self.prompt(prompt)
90
91 while True:
92 char = getchar().lower()
93 if '\r' == char and default is not None:
94 char = default
95 if -1 != shortcuts.find(char):
96 self.stderr.write(char + '\n')
97 return char
98
64 def be_quiet(self, state):99 def be_quiet(self, state):
65 if state and not self._quiet:100 if state and not self._quiet:
66 self.clear_term()101 self.clear_term()
@@ -78,18 +113,6 @@
78 # to clear it. We might need to separately check for the case of113 # to clear it. We might need to separately check for the case of
79 self._progress_view.clear()114 self._progress_view.clear()
80115
81 def get_boolean(self, prompt):
82 while True:
83 self.prompt(prompt + "? [y/n]: ")
84 line = self.stdin.readline().lower()
85 if line in ('y\n', 'yes\n'):
86 return True
87 elif line in ('n\n', 'no\n'):
88 return False
89 elif line in ('', None):
90 # end-of-file; possibly should raise an error here instead
91 return None
92
93 def get_integer(self, prompt):116 def get_integer(self, prompt):
94 while True:117 while True:
95 self.prompt(prompt)118 self.prompt(prompt)
96119
# Begin bundle
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
Revision history for this message
Vincent Ladeuil (vila) wrote :

> I also ran into problems using shelve with my VIM plugin.

WOW ! (And that's coming from die-hard emacs user ;-D)

> Other commands
> use ui.get_boolean, but there is no corresponding method for handling
> more complex choices. So even after providing my own UI class
> implementation, I cannot use shelve.
>
> In VIM there is a confirm function:
>
> confirm({msg} [, {choices} [, {default} [, {type}]]])
>
> Which can be used like this:
>
> confirm("Save changes?", "&Yes\n&No\n&Cancel")
>
> Where the letter after the '&' is the shortcut key for that choice.
>
> So I have been working on a branch to implement something just like
> this (see attached patch).

This sounds *seriously* interesting and I urge you to pusblish your changes
in a branch and propose it for merge so we can properly review it (I'm
pleased tht launchpad displays your patch inline ! Wow too there) but I
really want your patch to be discussed as it seems we have perfect case of
triangulation here ;)

>
> N.B.: your patch removes a call to gettext.

Ouch, yeah, I initially wrote the patch on a 2.4-based branch where the
gettext call wasn't there >-/ I'll push a revision fixing that asap. Thanks
catching it !

Revision history for this message
Martin Packman (gz) wrote :

The problem with the shelf_ui code is it does something totally different to all the other bazaar user interactions, hence the issues with qbzr and test scripts and editors apparently. Making it use bzrlib.ui should be the goal, but doesn't need to happen in this branch.

Benoit, that bundle is really exciting and apart from some nitpicking I think should basically be merged. Please do propose it as Vincent said.

Notes just on the changes in this branch:

+ char_based = not(os.environ.get('INSIDE_EMACS', None) is not None)
+ if char_based and not sys.stdin.isatty():

Space after 'not' otherwise it looks like a function. :)

Having modules one by one need to learn about magic emacs variables is a good argument for this logic living elsewhere.

+ # XXX: Warn if more than one char is typed ?

To get similar logic to the current and get_boolean behaviour, you'd take the first character that matched the set, which you can't do at this level without remembering the last line and only prompting for a new one when there are no more characters. That's pretty unimportant though.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> The problem with the shelf_ui code is it does something totally different to
> all the other bazaar user interactions, hence the issues with qbzr and test
> scripts and editors apparently. Making it use bzrlib.ui should be the goal,
> but doesn't need to happen in this branch.

/me nods

>
> Benoit, that bundle is really exciting and apart from some nitpicking I think
> should basically be merged. Please do propose it as Vincent said.

/me .. err, sorry :)

>
> Notes just on the changes in this branch:
>
> + char_based = not(os.environ.get('INSIDE_EMACS', None) is not None)
> + if char_based and not sys.stdin.isatty():
>
> Space after 'not' otherwise it looks like a function. :)

Yeah, thanks. But the plan is not to mention emacs at all there but instead
let user configure that themselves (INSIDE_EMACS is a far too big hammer and
was just a quick way to test but it's not reliable nor appropriate for all
cases), any feedback on the proposed config variables ?

>
> Having modules one by one need to learn about magic emacs variables is a good
> argument for this logic living elsewhere.
>
> + # XXX: Warn if more than one char is typed ?
>
> To get similar logic to the current and get_boolean behaviour, you'd take the
> first character that matched the set, which you can't do at this level without
> remembering the last line and only prompting for a new one when there are no
> more characters. That's pretty unimportant though.

And would keep the same UI which can drive any user nuts, it's really
intended to be line-based with one asnwer by *line* ending with CR, no
buffering of answers there.

What made shelve unsuable for me is that I needed to prepare the answers for
all the questions *before* seeing the questions, keeping this logic just
don't work ;)

So my question may be clearer as:

- given that we expect only a single char (followed by a \n), what should we
  do if we get more ? Warn ? Swallow silently ? Fail ?

Also, I need to add tests and that will probably remove the weird

  char = '\n'

just-in-case hack.

review: Needs Information
Revision history for this message
Martin Packman (gz) wrote :

So, to be clear, I think this branch may as well land as an improvement to the current isatty() hack, with the thought that Benoit's change will be the basis for a longer term solution.

> > + char_based = not(os.environ.get('INSIDE_EMACS', None) is not None)

...or just `is None` for that matter.

> Yeah, thanks. But the plan is not to mention emacs at all there but instead
> let user configure that themselves (INSIDE_EMACS is a far too big hammer and
> was just a quick way to test but it's not reliable nor appropriate for all
> cases), any feedback on the proposed config variables ?

For now it seems an appropriate hammer, just hang it elsewhere in a little private function. After this is all moved to bzrlib.ui then a config value to enable/disable character based input seems reasonable, but I wouldn't bother for now.

> - given that we expect only a single char (followed by a \n), what should we
> do if we get more ? Warn ? Swallow silently ? Fail ?

Swallow (and maybe warn) or fail and reprompt would all be fine.

> Also, I need to add tests and that will probably remove the weird
>
> char = '\n'
>
> just-in-case hack.

Right, you probably want a test for the EOF case, it needs to make sure it gets out of the shelf loop somehow.

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/shelf_ui.py'
--- bzrlib/shelf_ui.py 2011-09-16 15:39:47 +0000
+++ bzrlib/shelf_ui.py 2011-09-28 15:10:33 +0000
@@ -16,6 +16,7 @@
1616
1717
18from cStringIO import StringIO18from cStringIO import StringIO
19import os
19import shutil20import shutil
20import sys21import sys
21import tempfile22import tempfile
@@ -251,20 +252,41 @@
251 diff_file.seek(0)252 diff_file.seek(0)
252 return patches.parse_patch(diff_file)253 return patches.parse_patch(diff_file)
253254
255 def _char_based(self):
256 # FIXME: A bit hackish to use INSIDE_EMACS here, but there is another
257 # work in progress moving this method (and more importantly prompt()
258 # below) into the ui area and address the issue in better ways.
259 # -- vila 2011-09-28
260 return os.environ.get('INSIDE_EMACS', None) is None
261
254 def prompt(self, message):262 def prompt(self, message):
255 """Prompt the user for a character.263 """Prompt the user for a character.
256264
257 :param message: The message to prompt a user with.265 :param message: The message to prompt a user with.
258 :return: A character.266 :return: A character.
259 """267 """
260 if not sys.stdin.isatty():268 char_based = self._char_based()
261 # Since there is no controlling terminal we will hang when trying269 if char_based and not sys.stdin.isatty():
262 # to prompt the user, better abort now. See270 # Since there is no controlling terminal we will hang when
271 # trying to prompt the user, better abort now. See
263 # https://code.launchpad.net/~bialix/bzr/shelve-no-tty/+merge/14905272 # https://code.launchpad.net/~bialix/bzr/shelve-no-tty/+merge/14905
264 # for more context.273 # for more context.
265 raise errors.BzrError(gettext("You need a controlling terminal."))274 raise errors.BzrError(gettext("You need a controlling terminal."))
266 sys.stdout.write(message)275 sys.stdout.write(message)
267 char = osutils.getchar()276 if char_based:
277 # We peek one char at a time which requires a real term here
278 char = osutils.getchar()
279 else:
280 # While running tests (or under emacs) the input is line buffered
281 # so we must not use osutils.getchar(). Instead we switch to a mode
282 # where each line is terminated by a new line
283 line = sys.stdin.readline()
284 if line:
285 # XXX: Warn if more than one char is typed ?
286 char = line[0]
287 else:
288 # Empty input, callers handle it as enter
289 char = ''
268 sys.stdout.write("\r" + ' ' * len(message) + '\r')290 sys.stdout.write("\r" + ' ' * len(message) + '\r')
269 sys.stdout.flush()291 sys.stdout.flush()
270 return char292 return char
271293
=== modified file 'bzrlib/tests/script.py'
--- bzrlib/tests/script.py 2011-01-12 01:01:53 +0000
+++ bzrlib/tests/script.py 2011-09-28 15:10:33 +0000
@@ -481,6 +481,10 @@
481 def setUp(self):481 def setUp(self):
482 super(TestCaseWithMemoryTransportAndScript, self).setUp()482 super(TestCaseWithMemoryTransportAndScript, self).setUp()
483 self.script_runner = ScriptRunner()483 self.script_runner = ScriptRunner()
484 # FIXME: See shelf_ui.Shelver._char_based. This allow using shelve in
485 # scripts while providing a line-based input (better solution in
486 # progress). -- vila 2011-09-28
487 self.overrideEnv('INSIDE_EMACS', '1')
484488
485 def run_script(self, script, null_output_matches_anything=False):489 def run_script(self, script, null_output_matches_anything=False):
486 return self.script_runner.run_script(self, script, 490 return self.script_runner.run_script(self, script,
@@ -511,6 +515,10 @@
511 def setUp(self):515 def setUp(self):
512 super(TestCaseWithTransportAndScript, self).setUp()516 super(TestCaseWithTransportAndScript, self).setUp()
513 self.script_runner = ScriptRunner()517 self.script_runner = ScriptRunner()
518 # FIXME: See shelf_ui.Shelver._char_based. This allow using shelve in
519 # scripts while providing a line-based input (better solution in
520 # progress). -- vila 2011-09-28
521 self.overrideEnv('INSIDE_EMACS', '1')
514522
515 def run_script(self, script, null_output_matches_anything=False):523 def run_script(self, script, null_output_matches_anything=False):
516 return self.script_runner.run_script(self, script,524 return self.script_runner.run_script(self, script,
517525
=== modified file 'bzrlib/tests/test_script.py'
--- bzrlib/tests/test_script.py 2011-05-16 13:39:39 +0000
+++ bzrlib/tests/test_script.py 2011-09-28 15:10:33 +0000
@@ -572,9 +572,9 @@
572 def test_confirm_action(self):572 def test_confirm_action(self):
573 """You can write tests that demonstrate user confirmation.573 """You can write tests that demonstrate user confirmation.
574 574
575 Specifically, ScriptRunner does't care if the output line for the prompt575 Specifically, ScriptRunner does't care if the output line for the
576 isn't terminated by a newline from the program; it's implicitly terminated 576 prompt isn't terminated by a newline from the program; it's implicitly
577 by the input.577 terminated by the input.
578 """578 """
579 commands.builtin_command_registry.register(cmd_test_confirm)579 commands.builtin_command_registry.register(cmd_test_confirm)
580 self.addCleanup(commands.builtin_command_registry.remove, 'test-confirm')580 self.addCleanup(commands.builtin_command_registry.remove, 'test-confirm')
@@ -589,3 +589,56 @@
589 ok, no589 ok, no
590 """)590 """)
591591
592class TestShelve(script.TestCaseWithTransportAndScript):
593
594 def setUp(self):
595 super(TestShelve, self).setUp()
596 self.run_script("""
597 $ bzr init test
598 Created a standalone tree (format: 2a)
599 $ cd test
600 $ echo foo > file
601 $ bzr add
602 adding file
603 $ bzr commit -m 'file added'
604 2>Committing to:...test/
605 2>added file
606 2>Committed revision 1.
607 $ echo bar > file
608 """)
609
610 def test_shelve(self):
611 self.run_script("""
612 $ bzr shelve -m 'shelve bar'
613 # Shelve? [yNfq?]
614 <y
615 # Shelve 1 change(s)? [yNfq?]
616 <y
617 2>Selected changes:
618 2> M file
619 2>Changes shelved with id "1".
620 """,
621 # shelve uses \r that can't be represented in the
622 # script ?
623 null_output_matches_anything=True)
624 self.run_script("""
625 $ bzr shelve --list
626 1: shelve bar
627 """)
628
629 def test_dont_shelve(self):
630 # We intentionally provide no input here to test EOF
631 self.run_script("""
632 $ bzr shelve -m 'shelve bar'
633 # Shelve? [yNfq?]
634 # Shelve 1 change(s)? [yNfq?]
635 2>No changes to shelve.
636 """,
637 # shelve uses \r that can't be represented in the
638 # script ?
639 null_output_matches_anything=True)
640 self.run_script("""
641 $ bzr st
642 modified:
643 file
644 """)
592645
=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- doc/en/release-notes/bzr-2.5.txt 2011-09-27 10:17:36 +0000
+++ doc/en/release-notes/bzr-2.5.txt 2011-09-28 15:10:33 +0000
@@ -45,6 +45,10 @@
45.. Fixes for situations where bzr would previously crash or give incorrect45.. Fixes for situations where bzr would previously crash or give incorrect
46 or undesirable results.46 or undesirable results.
4747
48* ``bzr shelve`` can now be used in emacs shells as the input handling is
49 turned into a line-basde one when ``INSIDE_EMACS`` is set (which is the
50 case for all recent emacs versions). (Vincent Ladeuil, #856261)
51
48* Redirects between http and https no longer discard path information52* Redirects between http and https no longer discard path information
49 in some cases. (Jelmer Vernooij, #853765)53 in some cases. (Jelmer Vernooij, #853765)
5054
@@ -87,6 +91,9 @@
87Testing91Testing
88*******92*******
8993
94* Test scripts can now use ``bzr shelve`` and provide their input as
95 complete lines. (Vincent Ladeuil, #856261)
96
90.. Fixes and changes that are only relevant to bzr's test framework and 97.. Fixes and changes that are only relevant to bzr's test framework and
91 suite. This can include new facilities for writing tests, fixes to 98 suite. This can include new facilities for writing tests, fixes to
92 spurious test failures and changes to the way things should be tested.99 spurious test failures and changes to the way things should be tested.