Merge lp:~vila/bzr/376582-auth-prompt into lp:~bzr/bzr/trunk-old

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Ian Clatworthy
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~vila/bzr/376582-auth-prompt
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 277 lines
To merge this branch: bzr merge lp:~vila/bzr/376582-auth-prompt
Reviewer Review Type Date Requested Status
Ian Clatworthy Approve
Review via email: mp+6609@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

There is no good reason to use stdout for prompting the user. Bug #376582 raised the issue that doing that forbid the redirection of command outputs.

This patch use stderr instead and should address the issue.

Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

I suspect this change ought to be mentioned as a Compatibility break, not just a bug fix, in NEWS. When landing, make sure you tweak NEWS to put this in 1.16 (not 1.15) as well. :-)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-06-02 01:37:30 +0000
+++ NEWS 2009-06-02 14:35:20 +0000
@@ -12,6 +12,11 @@
12Compatibility Breaks12Compatibility Breaks
13********************13********************
1414
15* Display prompt on stderr (instead of stdout) when querying users so
16 that the output of commands can be safely redirected.
17 (Vincent Ladeuil, #376582)
18
19
15New Features20New Features
16************21************
1722
1823
=== modified file 'bzrlib/plugins/launchpad/test_register.py'
--- bzrlib/plugins/launchpad/test_register.py 2009-03-23 14:59:43 +0000
+++ bzrlib/plugins/launchpad/test_register.py 2009-06-02 14:35:20 +0000
@@ -335,12 +335,14 @@
335 g_conf = config.GlobalConfig()335 g_conf = config.GlobalConfig()
336 g_conf.set_user_option('email', 'Test User <test@user.com>')336 g_conf.set_user_option('email', 'Test User <test@user.com>')
337 stdout = tests.StringIOWrapper()337 stdout = tests.StringIOWrapper()
338 stderr = tests.StringIOWrapper()
338 ui.ui_factory = tests.TestUIFactory(stdin='userpass\n',339 ui.ui_factory = tests.TestUIFactory(stdin='userpass\n',
339 stdout=stdout)340 stdout=stdout, stderr=stderr)
340 self.assertIs(None, service.registrant_password)341 self.assertIs(None, service.registrant_password)
341 service.gather_user_credentials()342 service.gather_user_credentials()
342 self.assertEqual('test@user.com', service.registrant_email)343 self.assertEqual('test@user.com', service.registrant_email)
343 self.assertEqual('userpass', service.registrant_password)344 self.assertEqual('userpass', service.registrant_password)
344 self.assertContainsRe(stdout.getvalue(),345 self.assertEquals('', stdout.getvalue())
346 self.assertContainsRe(stderr.getvalue(),
345 'launchpad.net password for test@user\\.com')347 'launchpad.net password for test@user\\.com')
346348
347349
=== modified file 'bzrlib/tests/test_config.py'
--- bzrlib/tests/test_config.py 2009-04-27 16:10:10 +0000
+++ bzrlib/tests/test_config.py 2009-06-02 14:35:20 +0000
@@ -1461,7 +1461,8 @@
1461 """Test AuthenticationConfig behaviour"""1461 """Test AuthenticationConfig behaviour"""
14621462
1463 def _check_default_password_prompt(self, expected_prompt_format, scheme,1463 def _check_default_password_prompt(self, expected_prompt_format, scheme,
1464 host=None, port=None, realm=None, path=None):1464 host=None, port=None, realm=None,
1465 path=None):
1465 if host is None:1466 if host is None:
1466 host = 'bar.org'1467 host = 'bar.org'
1467 user, password = 'jim', 'precious'1468 user, password = 'jim', 'precious'
@@ -1470,17 +1471,20 @@
1470 'user': user, 'realm': realm}1471 'user': user, 'realm': realm}
14711472
1472 stdout = tests.StringIOWrapper()1473 stdout = tests.StringIOWrapper()
1474 stderr = tests.StringIOWrapper()
1473 ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',1475 ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
1474 stdout=stdout)1476 stdout=stdout, stderr=stderr)
1475 # We use an empty conf so that the user is always prompted1477 # We use an empty conf so that the user is always prompted
1476 conf = config.AuthenticationConfig()1478 conf = config.AuthenticationConfig()
1477 self.assertEquals(password,1479 self.assertEquals(password,
1478 conf.get_password(scheme, host, user, port=port,1480 conf.get_password(scheme, host, user, port=port,
1479 realm=realm, path=path))1481 realm=realm, path=path))
1480 self.assertEquals(stdout.getvalue(), expected_prompt)1482 self.assertEquals(expected_prompt, stderr.getvalue())
1483 self.assertEquals('', stdout.getvalue())
14811484
1482 def _check_default_username_prompt(self, expected_prompt_format, scheme,1485 def _check_default_username_prompt(self, expected_prompt_format, scheme,
1483 host=None, port=None, realm=None, path=None):1486 host=None, port=None, realm=None,
1487 path=None):
1484 if host is None:1488 if host is None:
1485 host = 'bar.org'1489 host = 'bar.org'
1486 username = 'jim'1490 username = 'jim'
@@ -1488,13 +1492,15 @@
1488 'scheme': scheme, 'host': host, 'port': port,1492 'scheme': scheme, 'host': host, 'port': port,
1489 'realm': realm}1493 'realm': realm}
1490 stdout = tests.StringIOWrapper()1494 stdout = tests.StringIOWrapper()
1495 stderr = tests.StringIOWrapper()
1491 ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',1496 ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
1492 stdout=stdout)1497 stdout=stdout, stderr=stderr)
1493 # We use an empty conf so that the user is always prompted1498 # We use an empty conf so that the user is always prompted
1494 conf = config.AuthenticationConfig()1499 conf = config.AuthenticationConfig()
1495 self.assertEquals(username, conf.get_user(scheme, host, port=port,1500 self.assertEquals(username, conf.get_user(scheme, host, port=port,
1496 realm=realm, path=path, ask=True))1501 realm=realm, path=path, ask=True))
1497 self.assertEquals(stdout.getvalue(), expected_prompt)1502 self.assertEquals(expected_prompt, stderr.getvalue())
1503 self.assertEquals('', stdout.getvalue())
14981504
1499 def test_username_defaults_prompts(self):1505 def test_username_defaults_prompts(self):
1500 # HTTP prompts can't be tested here, see test_http.py1506 # HTTP prompts can't be tested here, see test_http.py
15011507
=== modified file 'bzrlib/tests/test_http.py'
--- bzrlib/tests/test_http.py 2009-05-27 13:59:47 +0000
+++ bzrlib/tests/test_http.py 2009-06-02 14:35:20 +0000
@@ -1569,15 +1569,18 @@
1569 self.server.add_user('joe', 'foo')1569 self.server.add_user('joe', 'foo')
1570 t = self.get_user_transport(None, None)1570 t = self.get_user_transport(None, None)
1571 stdout = tests.StringIOWrapper()1571 stdout = tests.StringIOWrapper()
1572 ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n', stdout=stdout)1572 stderr = tests.StringIOWrapper()
1573 ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
1574 stdout=stdout, stderr=stderr)
1573 self.assertEqual('contents of a\n',t.get('a').read())1575 self.assertEqual('contents of a\n',t.get('a').read())
1574 # stdin should be empty1576 # stdin should be empty
1575 self.assertEqual('', ui.ui_factory.stdin.readline())1577 self.assertEqual('', ui.ui_factory.stdin.readline())
1576 stdout.seek(0)1578 stderr.seek(0)
1577 expected_prompt = self._expected_username_prompt(t._unqualified_scheme)1579 expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
1578 self.assertEquals(expected_prompt, stdout.read(len(expected_prompt)))1580 self.assertEquals(expected_prompt, stderr.read(len(expected_prompt)))
1581 self.assertEquals('', stdout.getvalue())
1579 self._check_password_prompt(t._unqualified_scheme, 'joe',1582 self._check_password_prompt(t._unqualified_scheme, 'joe',
1580 stdout.readline())1583 stderr.readline())
15811584
1582 def test_prompt_for_password(self):1585 def test_prompt_for_password(self):
1583 if self._testing_pycurl():1586 if self._testing_pycurl():
@@ -1588,12 +1591,15 @@
1588 self.server.add_user('joe', 'foo')1591 self.server.add_user('joe', 'foo')
1589 t = self.get_user_transport('joe', None)1592 t = self.get_user_transport('joe', None)
1590 stdout = tests.StringIOWrapper()1593 stdout = tests.StringIOWrapper()
1591 ui.ui_factory = tests.TestUIFactory(stdin='foo\n', stdout=stdout)1594 stderr = tests.StringIOWrapper()
1592 self.assertEqual('contents of a\n',t.get('a').read())1595 ui.ui_factory = tests.TestUIFactory(stdin='foo\n',
1596 stdout=stdout, stderr=stderr)
1597 self.assertEqual('contents of a\n', t.get('a').read())
1593 # stdin should be empty1598 # stdin should be empty
1594 self.assertEqual('', ui.ui_factory.stdin.readline())1599 self.assertEqual('', ui.ui_factory.stdin.readline())
1595 self._check_password_prompt(t._unqualified_scheme, 'joe',1600 self._check_password_prompt(t._unqualified_scheme, 'joe',
1596 stdout.getvalue())1601 stderr.getvalue())
1602 self.assertEquals('', stdout.getvalue())
1597 # And we shouldn't prompt again for a different request1603 # And we shouldn't prompt again for a different request
1598 # against the same transport.1604 # against the same transport.
1599 self.assertEqual('contents of b\n',t.get('b').read())1605 self.assertEqual('contents of b\n',t.get('b').read())
16001606
=== modified file 'bzrlib/tests/test_ui.py'
--- bzrlib/tests/test_ui.py 2009-04-24 13:30:48 +0000
+++ bzrlib/tests/test_ui.py 2009-06-02 14:35:20 +0000
@@ -67,15 +67,17 @@
67 self.assertEqual('', stdout.getvalue())67 self.assertEqual('', stdout.getvalue())
6868
69 def test_text_factory_ascii_password(self):69 def test_text_factory_ascii_password(self):
70 ui = TestUIFactory(stdin='secret\n', stdout=StringIOWrapper())70 ui = TestUIFactory(stdin='secret\n', stdout=StringIOWrapper(),
71 stderr=StringIOWrapper())
71 pb = ui.nested_progress_bar()72 pb = ui.nested_progress_bar()
72 try:73 try:
73 self.assertEqual('secret',74 self.assertEqual('secret',
74 self.apply_redirected(ui.stdin, ui.stdout,75 self.apply_redirected(ui.stdin, ui.stdout,
75 ui.stdout,76 ui.stderr,
76 ui.get_password))77 ui.get_password))
77 # ': ' is appended to prompt78 # ': ' is appended to prompt
78 self.assertEqual(': ', ui.stdout.getvalue())79 self.assertEqual(': ', ui.stderr.getvalue())
80 self.assertEqual('', ui.stdout.readline())
79 # stdin should be empty81 # stdin should be empty
80 self.assertEqual('', ui.stdin.readline())82 self.assertEqual('', ui.stdin.readline())
81 finally:83 finally:
@@ -88,21 +90,22 @@
88 it to utf8 to test that we transport the password correctly.90 it to utf8 to test that we transport the password correctly.
89 """91 """
90 ui = TestUIFactory(stdin=u'baz\u1234'.encode('utf8'),92 ui = TestUIFactory(stdin=u'baz\u1234'.encode('utf8'),
91 stdout=StringIOWrapper())93 stdout=StringIOWrapper(),
92 ui.stdin.encoding = 'utf8'94 stderr=StringIOWrapper())
93 ui.stdout.encoding = ui.stdin.encoding95 ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = 'utf8'
94 pb = ui.nested_progress_bar()96 pb = ui.nested_progress_bar()
95 try:97 try:
96 password = self.apply_redirected(ui.stdin, ui.stdout, ui.stdout,98 password = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
97 ui.get_password,99 ui.get_password,
98 u'Hello \u1234 %(user)s',100 u'Hello \u1234 %(user)s',
99 user=u'some\u1234')101 user=u'some\u1234')
100 # We use StringIO objects, we need to decode them102 # We use StringIO objects, we need to decode them
101 self.assertEqual(u'baz\u1234', password.decode('utf8'))103 self.assertEqual(u'baz\u1234', password.decode('utf8'))
102 self.assertEqual(u'Hello \u1234 some\u1234: ',104 self.assertEqual(u'Hello \u1234 some\u1234: ',
103 ui.stdout.getvalue().decode('utf8'))105 ui.stderr.getvalue().decode('utf8'))
104 # stdin should be empty106 # stdin and stdout should be empty
105 self.assertEqual('', ui.stdin.readline())107 self.assertEqual('', ui.stdin.readline())
108 self.assertEqual('', ui.stdout.readline())
106 finally:109 finally:
107 pb.finished()110 pb.finished()
108111
@@ -193,6 +196,7 @@
193 "yes\nn\nnot an answer\n"196 "yes\nn\nnot an answer\n"
194 "no\nfoo\n")197 "no\nfoo\n")
195 factory.stdout = StringIO()198 factory.stdout = StringIO()
199 factory.stderr = StringIO()
196 # there is no output from the base factory200 # there is no output from the base factory
197 self.assertEqual(True, factory.get_boolean(""))201 self.assertEqual(True, factory.get_boolean(""))
198 self.assertEqual(True, factory.get_boolean(""))202 self.assertEqual(True, factory.get_boolean(""))
@@ -223,8 +227,10 @@
223227
224 def test_text_factory_prompt(self):228 def test_text_factory_prompt(self):
225 # see <https://launchpad.net/bugs/365891>229 # see <https://launchpad.net/bugs/365891>
226 factory = TextUIFactory(None, StringIO(), StringIO())230 factory = TextUIFactory(None, StringIO(), StringIO(), StringIO())
227 factory.prompt('foo %2e')231 factory.prompt('foo %2e')
232 self.assertEqual('', factory.stdout.getvalue())
233 self.assertEqual('foo %2e', factory.stderr.getvalue())
228234
229 def test_text_factory_prompts_and_clears(self):235 def test_text_factory_prompts_and_clears(self):
230 # a get_boolean call should clear the pb before prompting236 # a get_boolean call should clear the pb before prompting
@@ -263,37 +269,41 @@
263 factory = SilentUIFactory()269 factory = SilentUIFactory()
264 factory.stdin = StringIO("someuser\n\n")270 factory.stdin = StringIO("someuser\n\n")
265 factory.stdout = StringIO()271 factory.stdout = StringIO()
266 self.assertEquals(None, 272 factory.stderr = StringIO()
273 self.assertEquals(None,
267 factory.get_username(u'Hello\u1234 %(host)s', host=u'some\u1234'))274 factory.get_username(u'Hello\u1234 %(host)s', host=u'some\u1234'))
268 self.assertEquals("", factory.stdout.getvalue())275 self.assertEquals("", factory.stdout.getvalue())
276 self.assertEquals("", factory.stderr.getvalue())
269 self.assertEquals("someuser\n\n", factory.stdin.getvalue())277 self.assertEquals("someuser\n\n", factory.stdin.getvalue())
270278
271 def test_text_ui_getusername(self):279 def test_text_ui_getusername(self):
272 factory = TextUIFactory(None, None, None)280 factory = TextUIFactory(None, None, None)
273 factory.stdin = StringIO("someuser\n\n")281 factory.stdin = StringIO("someuser\n\n")
274 factory.stdout = StringIO()282 factory.stdout = StringIO()
283 factory.stderr = StringIO()
275 factory.stdout.encoding = "utf8"284 factory.stdout.encoding = "utf8"
276 # there is no output from the base factory285 # there is no output from the base factory
277 self.assertEqual("someuser", 286 self.assertEqual("someuser",
278 factory.get_username('Hello %(host)s', host='some'))287 factory.get_username('Hello %(host)s', host='some'))
279 self.assertEquals("Hello some: ", factory.stdout.getvalue())288 self.assertEquals("Hello some: ", factory.stderr.getvalue())
289 self.assertEquals('', factory.stdout.getvalue())
280 self.assertEqual("", factory.get_username("Gebruiker"))290 self.assertEqual("", factory.get_username("Gebruiker"))
281 # stdin should be empty291 # stdin should be empty
282 self.assertEqual('', factory.stdin.readline())292 self.assertEqual('', factory.stdin.readline())
283293
284 def test_text_ui_getusername_utf8(self):294 def test_text_ui_getusername_utf8(self):
285 ui = TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),295 ui = TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
286 stdout=StringIOWrapper())296 stdout=StringIOWrapper(), stderr=StringIOWrapper())
287 ui.stdin.encoding = "utf8"297 ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
288 ui.stdout.encoding = ui.stdin.encoding
289 pb = ui.nested_progress_bar()298 pb = ui.nested_progress_bar()
290 try:299 try:
291 # there is no output from the base factory300 # there is no output from the base factory
292 username = self.apply_redirected(ui.stdin, ui.stdout, ui.stdout,301 username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
293 ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')302 ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
294 self.assertEquals(u"someuser\u1234", username.decode('utf8'))303 self.assertEquals(u"someuser\u1234", username.decode('utf8'))
295 self.assertEquals(u"Hello\u1234 some\u1234: ", 304 self.assertEquals(u"Hello\u1234 some\u1234: ",
296 ui.stdout.getvalue().decode("utf8"))305 ui.stderr.getvalue().decode("utf8"))
306 self.assertEquals('', ui.stdout.getvalue())
297 finally:307 finally:
298 pb.finished()308 pb.finished()
299309
300310
=== modified file 'bzrlib/ui/__init__.py'
--- bzrlib/ui/__init__.py 2009-04-24 15:49:33 +0000
+++ bzrlib/ui/__init__.py 2009-06-02 14:35:20 +0000
@@ -227,7 +227,7 @@
227 prompt = prompt % kwargs227 prompt = prompt % kwargs
228 prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')228 prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
229 self.clear_term()229 self.clear_term()
230 self.stdout.write(prompt)230 self.stderr.write(prompt)
231231
232 def note(self, msg):232 def note(self, msg):
233 """Write an already-formatted message."""233 """Write an already-formatted message."""